diff --git a/.claude/backend.md b/.claude/backend.md new file mode 100644 index 0000000..729ab2e --- /dev/null +++ b/.claude/backend.md @@ -0,0 +1,217 @@ +# 02 — 后端规则 + +> **适用**:所有后端 AI 开发任务。 + +--- + +## 分层不变式 + +``` +Controller → 参数校验 + 调 Service + 返回统一格式 + 禁止:写业务逻辑、直接调 Mapper、处理事务 + +Service → 业务逻辑 + 事务管理 + 禁止:处理 HTTP 请求/响应、直接操作 Request/Response + +Mapper → 数据访问(MyBatis-Plus BaseMapper) + 禁止:写业务逻辑、调用其他 Mapper + +Entity → 数据映射(@TableName 对应表名) + 禁止:写业务方法、放非持久化字段(放 DTO/VO) +``` + +### 包结构 + +``` +com.ghb.base/ +├── system/ # 系统模块(不改) +│ ├── entity/ SysUser, SysRole, SysMenu... +│ ├── controller/ +│ └── service/ +└── business/ # 业务模块 ← 你的代码放这里 + ├── entity/ # 继承 JeecgBoot 的 BaseEntity + ├── controller/ # 继承 JeecgController + ├── service/ # impl/ 继承 ServiceImpl + ├── mapper/ # 继承 BaseMapper + │ └── xml/ # MyBatis XML(复杂查询才需要) + └── vo/ # 请求/响应 DTO (*VO, *Request, *ImportRow) +``` + +--- + +## 命名规则 + +| 元素 | 规则 | 示例(正确 → 错误) | +|------|------|---------------------| +| **类名** | 大驼峰,名词 | `TaskController` `OrderService` `UserMapper` | +| **方法名** | 小驼峰,动词开头 | `getById()` `saveTask()` `listByStatus()` | +| **变量** | 小驼峰,有意义 | `taskList` `userId` → ❌ `list` `id` `data` | +| **包名** | 全小写,点分隔 | `com.xxx.server.controller` | +| **Entity** | 大驼峰,对应表名 | 表 `task_apply` → 类 `TaskApply` | +| **DTO** | 大驼峰,后缀 DTO/VO/Request | `TaskCreateRequest` `UserListVO` | +| **常量** | 全大写,下划线分隔 | `MAX_RETRY_COUNT` `DEFAULT_PAGE_SIZE` | +| **SQL 表名** | 小写,下划线分隔 | `task_apply` `user_profile` | +| **SQL 字段** | 小写,下划线分隔 | `create_time` `task_id` `del_flag` | +| **URL 路径** | 小写,短横线或斜杠 | `/app/task/list` → ❌ `/app/Task/GetList` | +| **配置文件** | kebab-case 或小写 | `application-prod.yml` | + +### 长度控制 + +类名、方法名、表名不要过长。超过 3 个单词或 30 个字符时,用通用缩写。 + +**前缀也要缩**:模块前缀用一个单词,别堆多个词。 + +| 完整 | 缩写 | JeecgBoot 实际案例 | +|------|------|-------------------| +| Department | Dept | `SysDepartRolePermission` → `SysDeptRolePerm` | +| Permission | Perm | 同上 | +| Announcement | Notice | `SysAnnouncementSend` → `SysNoticeSend` | +| Enterprise | Ent | `WechatEnterprise` → `WxEnt` | +| Message | Msg | `SysMessageTemplate` → `SysMsgTemplate` | + +```java +// ❌ JeecgBoot 原版 — 太长 +SysDepartRolePermissionServiceImpl // 35 字符 +SysAnnouncementSendServiceImpl // 30 字符 +ThirdAppWechatEnterpriseServiceImpl // 35 字符 + +// ✅ 缩写后 +SysDeptRolePermServiceImpl // 26 字符 +SysNoticeSendServiceImpl // 25 字符 +ThirdAppWxEntServiceImpl // 27 字符 +``` + +**原则**:优先用全称,超长再缩写。缩写必须一眼能认出含义,不自造别人看不懂的缩写。 + +--- + +## 注释规则 + +| 位置 | 要求 | 示例 | +|------|------|------| +| **类/接口** | 必须有 Javadoc,说明职责 | `/** 任务管理 Controller,处理任务的增删改查 */` | +| **公共方法** | 必须有 Javadoc,说明入参/返回/异常 | `/** @param taskId 任务ID @return 任务详情 @throws 无 */` | +| **复杂逻辑** | 行内注释说明"为什么这么做" | `// 先查缓存再查库,避免击穿` | +| **常量** | 必须有注释说明含义 | `/** 最大重试次数 */` | +| **TODO/FIXME** | 必须有负责人和日期 | `// TODO(yaoshuli 2026-06) 后续改为配置项` | +| **禁止** | ❌ 注释写"做了什么"(代码本身已说明) | `// 查询任务列表` ← 删掉 | +| **禁止** | ❌ 注释掉的旧代码 | 直接删,Git 有历史 | +| **禁止** | ❌ 无意义注释 | `// 定义一个变量` `// 返回结果` | +| **禁止** | ❌ Controller 方法加 Javadoc 说"这是xxx接口" | 写业务含义,不写技术废话 | + +### 注释示例 + +```java +/** 任务服务,负责任务的创建、分配、状态流转 */ +@Service +public class TaskServiceImpl implements TaskService { + + /** 单次批量操作上限 */ + private static final int BATCH_LIMIT = 100; + + /** + * 根据状态和截止时间查询待处理任务。 + * 先查 Redis 缓存,未命中再查 MySQL。 + * + * @param status 任务状态,不能为 null + * @param deadline 截止时间,只查此时间之前的任务 + * @return 任务列表,可能为空 + */ + @Override + public List listPending(String status, LocalDateTime deadline) { + // 缓存 key: pending:{status} + String cacheKey = "pending:" + status; + List cached = cacheService.get(cacheKey); + if (cached != null) return cached; + + // 查库(这里用复合索引 idx_status_deadline) + List tasks = taskMapper.selectByStatusBefore(status, deadline); + cacheService.set(cacheKey, tasks, 300); + return tasks; + } +} +``` + +## 新增功能标准步骤 + +``` +1. 建表 SQL → [项目数据库脚本目录] +2. Entity → 表映射 +3. Mapper → 继承 BaseMapper +4. Service 接口+实现 → 业务逻辑 +5. Controller → 接口暴露 +6. 编译验证 → 构建通过 +``` + +--- + +## CRUD 模板(填空式) + +```java +// ===== 分页列表 ===== +@GetMapping("/list") +public Result> list(@RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size) { + Page p = new Page<>(page, size); + // [填空] 构建查询条件 + return Result.ok(service.page(p, wrapper)); +} + +// ===== 详情 ===== +@GetMapping("/{id}") +public Result detail(@PathVariable Long id) { + Xxx entity = service.getById(id); + if (entity == null) return Result.error("[资源]不存在"); + return Result.ok(entity); +} + +// ===== 新增 ===== +@PostMapping +public Result create(@RequestBody @Valid XxxDto dto) { + // [填空] DTO → Entity 转换 + service.save(entity); + return Result.ok(); +} + +// ===== 更新 ===== +@PutMapping("/{id}") +public Result update(@PathVariable Long id, @RequestBody @Valid XxxDto dto) { + Xxx entity = service.getById(id); + if (entity == null) return Result.error("[资源]不存在"); + // [填空] 字段更新 + service.updateById(entity); + return Result.ok(); +} +``` + +--- + +## 禁止清单 + +| 类别 | ❌ 禁止 | +|------|--------| +| **分层** | Controller 里写 if/else 业务判断 | +| **分层** | Controller 直接调 Mapper/Repository | +| **分层** | Entity 里写业务方法 | +| **数据** | SQL/查询条件字符串拼接 | +| **数据** | 查不到不判空,直接返回可能 NPE | +| **数据** | `selectOne` / `getOne` 不做唯一性约束 | +| **输入** | 入参不校验(该用 `@Valid` 的地方手撸 if) | +| **输出** | 返回裸对象、裸字符串、自造格式 | +| **值** | 代码里硬编码魔法数字、魔法字符串 | +| **事务** | 该加事务的地方漏加 | + +--- + +## 检查表 + +每完成一个后端任务,逐项自检: + +- [ ] 编译通过(`mvn compile` / `go build` / 对应构建命令) +- [ ] 所有返回值是统一格式,无裸返回 +- [ ] 分页接口格式正确(`list/total/pages/current/size`) +- [ ] Controller 方法体不超过 20 行(超过说明写了业务逻辑) +- [ ] 查不到/null 路径有明确的 `fail` 返回 +- [ ] 改了接口 → 已 grep 同步所有前端调用方 +- [ ] 新增/改字段 → 已更新数据库脚本 +- [ ] 无禁止清单中的违规项 diff --git a/.claude/contract.md b/.claude/contract.md new file mode 100644 index 0000000..a213462 --- /dev/null +++ b/.claude/contract.md @@ -0,0 +1,108 @@ +# 01 — 接口契约 + +> **这是前后端共享的唯一真相来源。** 修改本文件 = 修改协议,必须同步更新所有端。 + +--- + +## 契约生命周期(任务拆分时决定) + +契约由**任务拆分时先开工的那一端**负责起草。这是任务产出物之一,不是额外工作。 + +``` +任务拆分阶段: + ┌─ 任务A:后端-用户管理CRUD ──→ 产出:接口契约(后端起草) + │ + 后端代码 + │ + └─ 任务B:前端-用户管理页面 ──→ 拿到契约后对接 + +反过来: + ┌─ 任务A:前端-首页改版 ──→ 产出:接口契约(前端起草) + │ + 前端页面 + │ + └─ 任务B:后端-首页数据接口 ──→ 按契约实现 +``` + +**铁律**: +- 拆分任务时明确标注:**本任务包含接口契约起草** +- 契约随任务一起交付,不单独走审批 +- 后开工的一方拿到契约后,发现问题直接跟起草方沟通修改 +- 联调时对照本文件,不一致的以本文件为准 + +### 分仓库时契约放哪 + +``` +Monorepo(推荐): 分仓库: +项目根/ 后端仓库/ 前端仓库/ +└── rules/ ├── rules/ ├── rules/ + └── 01-接口契约.md │ └── 01-接口契约.md │ └── 01-接口契约.md + ← git submodule 引用同一份 → +``` + +--- + +## 返回格式(不变式) + +```json +// ✅ 唯一正确格式 +{ "code": 200, "message": "success", "data": { ... } } +{ "code": 500, "message": "错误原因", "data": null } + +// ❌ 永远不许出现 +return data; // 裸对象 +return "ok"; // 裸字符串 +{ "success": true, "result": {...} } // 自造格式 +``` + +--- + +## 分页格式(不变式) + +```json +{ "list": [...], "total": 100, "pages": 10, "current": 1, "size": 10 } +``` + +--- + +## 接口命名约定 + +| 前缀 | 用途 | 示例 | +|------|------|------| +| `/admin/**` | 管理后台 | `/admin/user/list` | +| `/app/**` | 移动端/H5/小程序 | `/app/task/list` | +| `/auth/**` | 登录注册(共享) | `/auth/login` | +| `/open/**` | 对外开放接口 | `/open/callback` | + +--- + +## 状态值映射表 + +**← 改这里 = 改协议,必须同步后端 + 所有前端。** + +| 含义 | 后端值 | 前端显示 | 前端 CSS 类 | +|------|--------|----------|------------| +| [状态1] | [值] | [文字] | [class] | +| [状态2] | [值] | [文字] | [class] | + +--- + +## 字段名桥接表 + +同一个概念在不同端字段名不同时,在此登记: + +| 概念 | 后端字段 | 前端A字段 | 前端B字段 | +|------|----------|-----------|-----------| +| | | | | + +--- + +## 契约同步铁律 + +``` +改后端接口 → grep 所有前端 → 逐个同步 → 两端都通过才算完成 + +步骤: +1. 改之前:grep -r "接口路径" 所有前端目录/ +2. 改后端 → 编译通过 +3. 逐个改前端 → 构建通过 +4. 更新本文件的映射表(如有新增状态值/字段差异) +``` diff --git a/.claude/database.md b/.claude/database.md new file mode 100644 index 0000000..e5aa79e --- /dev/null +++ b/.claude/database.md @@ -0,0 +1,175 @@ +# 04 — 数据库表设计规则 + +> **适用**:所有涉及建表、改表、索引设计的 AI 开发任务。 + +--- + +## 命名规则 + +| 元素 | 规则 | 示例(正确 → 错误) | +| -------- | ------------- | -------------------------------------------------------- | +| **表名** | 小写,下划线分隔,单数名词 | `task_apply` → ❌ `TaskApply` `task_applies` | +| **字段名** | 小写,下划线分隔 | `create_time` `task_id` → ❌ `createdAt` `taskId` | +| **主键** | 统一用 `id` | `id` → ❌ `task_id` `pk_id` | +| **外键** | `关联表名_id` | `task_id` `user_id` → ❌ `tid` `uid` | +| **布尔字段** | `del_flag` 或明确业务含义的 `is_` 前缀 | `del_flag` `is_active` → ❌ `deleted` `status` | +| **时间字段** | `_time` 后缀 | `create_time` `update_time` → ❌ `created_at` `updateTime` | +| **金额字段** | `_amount` 后缀 | `reward_amount` → ❌ `reward` `price` | +| **索引名** | `idx_表名_字段` | `idx_task_status` → ❌ `index1` `task_status_idx` | +| **唯一索引** | `uk_表名_字段` | `uk_user_phone` → ❌ `uq_phone` | +| **关联表** | 两表名用下划线连接 | `task_apply` `user_role` → ❌ `apply_task` | + +### 长度控制 + +表名超过 3 个单词或 30 个字符时缩写。前缀也要缩成一个单词。 + +| 完整 | 缩写 | JeecgBoot 实际案例 | +|------|------|-------------------| +| department | dept | `sys_depart_role_permission` → `sys_dept_role_perm` | +| permission | perm | 同上 | +| announcement | notice | `sys_announcement_send` → `sys_notice_send` | +| message | msg | `sys_message_template` → `sys_msg_template` | + +```sql +-- ❌ JeecgBoot 原版 — 太长 +sys_depart_role_permission -- 26 字符 +sys_permission_data_rule -- 24 字符 + +-- ✅ 缩写后 +sys_dept_role_perm -- 19 字符 +sys_perm_data_rule -- 20 字符 +``` + +--- + +## 字段类型规范 + +| 场景 | 类型 | 说明 | +|------|------|------| +| 主键 | `BIGINT` 自增 或 `VARCHAR(32)` | 推荐 BIGINT 自增;分布式用雪花ID | +| 短文本 | `VARCHAR(N)` | 姓名(50)、标题(200)、URL(500) | +| 长文本 | `TEXT` / `LONGTEXT` | 文章、JSON、描述 | +| 金额 | `DECIMAL(12,2)` | **禁止**用 FLOAT/DOUBLE | +| 状态/类型 | `VARCHAR(20)` 或 `TINYINT` | 枚举值,必须有注释说明 | +| 布尔 | `TINYINT(1)` | 0=否 1=是,注释必须写清含义 | +| 时间 | `DATETIME` | **禁止**用 TIMESTAMP(2038 问题) | +| 日期 | `DATE` | 生日、截止日期 | +| 数量 | `INT` 或 `BIGINT` | | + +--- + +## 每张表必须有的字段 + +```sql +CREATE TABLE xxx ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', + -- [业务字段] + create_by VARCHAR(32) NULL DEFAULT NULL COMMENT '创建人', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_by VARCHAR(32) NULL DEFAULT NULL COMMENT '更新人', + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + del_flag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除:0-正常 1-已删除', + INDEX idx_xxx_create_time (create_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='表说明'; +``` + +**强制要求**: +- `create_by` — 创建人,建议保留;系统自动填充时可为空 +- `create_time` — 创建时间,必须有 +- `update_by` — 更新人,建议保留;系统自动填充时可为空 +- `update_time` — 更新时间,必须有(`ON UPDATE CURRENT_TIMESTAMP` 自动更新) +- `del_flag` — 逻辑删除标记,除非明确不需要软删除 +- 每张表必须有 `COMMENT` +- 引擎统一 `InnoDB`,字符集 `utf8mb4` + +--- + +## 索引规则 + +| 规则 | 说明 | +|------|------| +| **主键** | 每张表必须有,推荐自增 BIGINT | +| **外键** | 必须加索引(MySQL 自动加,但显式声明更清晰) | +| **查询条件** | WHERE 条件字段必须加索引 | +| **排序字段** | ORDER BY 字段考虑加索引 | +| **联合索引** | 遵循最左前缀原则,区分度高的在前 | +| **唯一约束** | 业务唯一字段必须加唯一索引 | +| **禁止** | ❌ 不加索引的 WHERE / JOIN / ORDER BY | +| **禁止** | ❌ 在大字段(TEXT/BLOB)上建索引 | +| **禁止** | ❌ 过多索引(单表建议 ≤ 5 个) | + +### 索引命名 + +```sql +INDEX idx_表名_字段 -- 普通索引 +UNIQUE uk_表名_字段 -- 唯一索引 +INDEX idx_表名_字段1_字段2 -- 联合索引 +``` + +--- + +## 字段约束规则 + +| 规则 | 示例 | +|------|------| +| 主键 | `PRIMARY KEY` 或 `NOT NULL AUTO_INCREMENT` | +| 非空 | 业务必填字段 `NOT NULL` | +| 默认值 | 有默认值的字段 `DEFAULT xxx`,**禁止**依赖代码设默认值 | +| 唯一 | 业务唯一字段 `UNIQUE` | +| 外键 | 尽量用逻辑外键(代码维护),**不推荐**物理外键(`FOREIGN KEY`) | + +--- + +## 禁止清单 + +| ❌ 禁止 | 原因 | +|---------|------| +| 表名/字段名用大写或驼峰 | 跨平台兼容 | +| 金额用 FLOAT/DOUBLE | 精度丢失 | +| 时间用 TIMESTAMP | 2038 年溢出 | +| 用物理外键 | 分库分表/数据迁移困难 | +| 不加注释 | 无人知道字段含义 | +| 字符串代替布尔 | 空间浪费,索引效率低 | +| 大表无索引 | 性能灾难 | +| 字段用 NULL 代替默认值 | 查询需额外处理 `IS NULL` | +| 在代码里设默认值 | 数据一致性依赖应用层 | + +--- + +## 修改表规则 + +```sql +-- ✅ 正确:显式命名约束 +ALTER TABLE task ADD COLUMN priority TINYINT NOT NULL DEFAULT 0 COMMENT '优先级:0-普通 1-紧急'; +ALTER TABLE task ADD INDEX idx_task_priority (priority); + +-- ❌ 禁止:不写 COMMENT +ALTER TABLE task ADD COLUMN priority TINYINT; + +-- ❌ 禁止:不写默认值导致存量数据为 NULL +ALTER TABLE task ADD COLUMN priority TINYINT NOT NULL; +``` + +**修改表必须**: +- 新字段有 `COMMENT` +- 非空字段有 `DEFAULT` +- 考虑对存量数据的影响 +- 附带回滚 SQL + +--- + +## 检查表 + +每涉及建表/改表,逐项自检: + +- [ ] 表名、字段名全小写+下划线 +- [ ] 有 `create_by` `create_time` `update_by` `update_time` +- [ ] 软删除字段 `del_flag`(如需要) +- [ ] 每张表有 `COMMENT`,每个字段有 `COMMENT` +- [ ] 金额用 `DECIMAL`,时间用 `DATETIME` +- [ ] 主键 + 外键 + WHERE 条件字段有索引 +- [ ] 非空字段有 `NOT NULL DEFAULT` +- [ ] 无物理外键 +- [ ] 索引命名 `idx_表名_字段` / `uk_表名_字段` +- [ ] 附带回滚 SQL +- [ ] 引擎 InnoDB,字符集 utf8mb4 diff --git a/.claude/encoding.md b/.claude/encoding.md new file mode 100644 index 0000000..cdd62f7 --- /dev/null +++ b/.claude/encoding.md @@ -0,0 +1,44 @@ +# 编码规范(字符编码) + +> 本规则因一次事故而立:批量改包名时用了非 UTF-8 感知的脚本,把中文注释整成乱码(`鐗堟湰`=版本、`寰湇鍔`=微服务),且引入 BOM。以下为硬性约束。 + +## 不变式(违反即 Bug) + +1. **所有源码/配置/文本文件一律 UTF-8 编码**:`.java` `.xml` `.yml` `.yaml` `.properties` `.sql` `.md` `.vue` `.ts` `.js` `.json` 等。 +2. **禁止 BOM**:UTF-8 文件不得带 BOM(`EF BB BF`)。BOM 会让 XML/YAML 解析、shell 脚本、diff 出错。 +3. **禁止 GBK / GB2312 / GB18030 / Latin1 落盘**:任何环节(编辑器、脚本、终端重定向)都不得以非 UTF-8 写文件。 +4. **禁止用非 UTF-8 感知的工具批量改写文本**:如必须批量替换,工具必须显式按 UTF-8 读、UTF-8 写(Python 用 `io.open(p, encoding='utf-8')`;`sed`/`grep` 在 UTF-8 locale 下运行)。 +5. **换行**:统一 LF(`\n`),`.gitattributes` 固化,避免 CRLF 混入触发整文件 diff。 + +## 构建/工程配置(必须存在) + +- **Maven**:父 pom 设 + ```xml + + UTF-8 + UTF-8 + + ``` +- **`.editorconfig`**(后端仓库和前端仓库根目录各一个 `.editorconfig` 文件): + ``` + root = true + [*] + charset = utf-8 + end_of_line = lf + insert_final_newline = true + ``` +- **前端**:Vite/Node 默认 UTF-8,源文件保持 UTF-8 无 BOM 即可。 +- **Windows 终端**:PowerShell 默认输出可能是 GBK;**不要用 `>`/`>>` 重定向把含中文的内容写进源文件**(会按当前代码页编码)。要写文件用 UTF-8 感知的程序,不要靠 shell 重定向。 + +## 处理已损坏文件(mojibake 还原) + +经典双重编码(UTF-8 被当 GBK 再存 UTF-8)可逆转:`坏文本.encode('gbk').decode('utf-8')`。 +但若 mojibake 中已出现 `?`(0x3F),该字节已丢失、不可逆,只能按上下文重写注释。 + +## 检查表(提交前) + +- [ ] 新增/修改的文本文件是 UTF-8 无 BOM(`file *.xml` 或编辑器状态栏确认) +- [ ] 没有出现 `鐗堟湰`/`寰湇`/`锛` 这类乱码片段 +- [ ] 仓库根有 `.editorconfig`(charset=utf-8) +- [ ] 父 pom 有 `project.build.sourceEncoding=UTF-8` +- [ ] 批量文本替换用的是 UTF-8 感知工具,不是裸 `>` 重定向 diff --git a/.claude/frontend.md b/.claude/frontend.md new file mode 100644 index 0000000..05fdf58 --- /dev/null +++ b/.claude/frontend.md @@ -0,0 +1,199 @@ +# 03 — 前端规则 + +> **适用**:所有前端 AI 开发任务(Web / H5 / 小程序)。 + +--- + +## 样式铁律 + +**禁止在组件文件中硬编码**:颜色值(`#xxx` / `rgb()`)、字号(`px` / `rpx` / `rem`)、间距(`margin` / `padding` 数值)、圆角(`border-radius` 数值)。 + +所有样式必须来自: +1. **Design Token 文件** → 变量(`$color-primary`) +2. **全局 CSS 类** → 语义类(`.text-h1` `.card` `.gap-2`) +3. **UI 框架内置类** → Tailwind / Ant Design 等 + +```vue + + +``` + +--- + +## 禁止清单 + +| 类别 | ❌ 禁止 | +|------|--------| +| **样式** | 硬编码颜色/字号/间距/圆角 | +| **样式** | 在 scoped 中定义全局重置 | +| **样式** | scoped 超过 100 行 | +| **组件** | 自己写布局外壳(页头/侧栏/TabBar) | +| **组件** | 裸用 `` 代替图标组件 | +| **网络** | 直调 `fetch` / `axios` / `uni.request` | +| **网络** | 不处理错误态 | +| **状态** | 无加载态/空态 | + +--- + +## 检查表 + +每完成一个前端任务,逐项自检: + +- [ ] 使用项目统一布局组件 +- [ ] 无硬编码颜色/字号/间距/圆角 +- [ ] 图标全部走统一图标组件 +- [ ] API 调用走统一封装,无裸调 +- [ ] 页面/组件有加载态 + 空态 + 错误态 +- [ ] 关键操作有 loading 态(防重复点击) +- [ ] ` +
+
+ +
+ +
+
<%= title %>
+
+
+ + + + + + + diff --git a/jest.config.mjs b/jest.config.mjs new file mode 100644 index 0000000..162e72b --- /dev/null +++ b/jest.config.mjs @@ -0,0 +1,36 @@ +export default { + preset: 'ts-jest', + roots: ['/tests/'], + clearMocks: true, + moduleDirectories: ['node_modules', 'src'], + moduleFileExtensions: ['js', 'ts', 'vue', 'tsx', 'jsx', 'json', 'node'], + modulePaths: ['/src', '/node_modules'], + testMatch: [ + '**/tests/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[tj]s?(x)', + '(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts)$', + ], + testPathIgnorePatterns: [ + '/tests/server/', + '/tests/__mocks__/', + '/node_modules/', + ], + transform: { + '^.+\\.tsx?$': 'ts-jest', + }, + transformIgnorePatterns: ['/tests/__mocks__/', '/node_modules/'], + // A map from regular expressions to module names that allow to stub out resources with a single module + moduleNameMapper: { + '\\.(vs|fs|vert|frag|glsl|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': + '/tests/__mocks__/fileMock.ts', + '\\.(sass|s?css|less)$': '/tests/__mocks__/styleMock.ts', + '\\?worker$': '/tests/__mocks__/workerMock.ts', + '^/@/(.*)$': '/src/$1', + }, + testEnvironment: 'jsdom', + verbose: true, + collectCoverage: false, + coverageDirectory: 'coverage', + collectCoverageFrom: ['src/**/*.{js,ts,vue}'], + coveragePathIgnorePatterns: ['^.+\\.d\\.ts$'], +}; diff --git a/mock/_createProductionServer.ts b/mock/_createProductionServer.ts new file mode 100644 index 0000000..8f47c23 --- /dev/null +++ b/mock/_createProductionServer.ts @@ -0,0 +1,18 @@ +import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'; + +const modules = import.meta.glob('./**/*.ts', { eager: true }); + +const mockModules: any[] = []; +Object.keys(modules).forEach((key) => { + if (key.includes('/_')) { + return; + } + mockModules.push(...(modules as Recordable)[key].default); +}); + +/** + * Used in a production environment. Need to manually import all modules + */ +export function setupProdMockServer() { + createProdMockServer(mockModules); +} diff --git a/mock/_util.ts b/mock/_util.ts new file mode 100644 index 0000000..6a171d5 --- /dev/null +++ b/mock/_util.ts @@ -0,0 +1,63 @@ +// Interface data format used to return a unified format + +export function resultSuccess(result: T, { message = 'ok' } = {}) { + return { + code: 0, + result, + message, + type: 'success', + }; +} + +export function resultPageSuccess( + pageNo: number, + pageSize: number, + list: T[], + { message = 'ok' } = {} +) { + const pageData = pagination(pageNo, pageSize, list); + + return { + ...resultSuccess({ + records: pageData, + total: list.length, + }), + message, + }; +} + +export function resultError(message = 'Request failed', { code = -1, result = null } = {}) { + return { + code, + result, + message, + type: 'error', + }; +} + +export function pagination(pageNo: number, pageSize: number, array: T[]): T[] { + const offset = (pageNo - 1) * Number(pageSize); + const ret = + offset + Number(pageSize) >= array.length + ? array.slice(offset, array.length) + : array.slice(offset, offset + Number(pageSize)); + return ret; +} + +export interface requestParams { + method: string; + body: any; + headers?: { authorization?: string }; + query: any; +} + +/** + * @description 本函数用于从request数据中获取token,请根据项目的实际情况修改 + * + */ +export function getRequestToken({ headers }: requestParams): string | undefined { + return headers?.authorization; +} + +//TODO 接口父路径(写死不够灵活) +export const baseUrl = '/jeecgboot/mock'; diff --git a/mock/demo/account.ts b/mock/demo/account.ts new file mode 100644 index 0000000..a8a31c2 --- /dev/null +++ b/mock/demo/account.ts @@ -0,0 +1,70 @@ +import { MockMethod } from 'vite-plugin-mock'; +import { resultSuccess, resultError, baseUrl } from '../_util'; +import { ResultEnum } from '../../src/enums/httpEnum'; +const userInfo = { + name: 'Jeecg', + userid: '00000001', + email: 'test@gmail.com', + signature: '海纳百川,有容乃大', + introduction: '微笑着,努力着,欣赏着', + title: '交互专家', + group: '某某某事业群-某某平台部-某某技术部-UED', + tags: [ + { + key: '0', + label: '很有想法的', + }, + { + key: '1', + label: '专注设计', + }, + { + key: '2', + label: '辣~', + }, + { + key: '3', + label: '大长腿', + }, + { + key: '4', + label: '川妹子', + }, + { + key: '5', + label: '海纳百川', + }, + ], + notifyCount: 12, + unreadCount: 11, + country: 'China', + address: 'Xiamen City 77', + phone: '0592-268888888', +}; + +export default [ + { + url: `${baseUrl}/account/getAccountInfo`, + timeout: 1000, + method: 'get', + response: () => { + return resultSuccess(userInfo); + }, + }, + { + url: `${baseUrl}/user/sessionTimeout`, + method: 'post', + statusCode: 401, + response: () => { + return resultError(); + }, + }, + { + url: '/basic-api/user/tokenExpired', + method: 'post', + statusCode: 200, + response: () => { + return resultError('Token Expired!', { code: ResultEnum.TIMEOUT as number }); + }, + }, +] as MockMethod[]; diff --git a/mock/demo/select-demo.ts b/mock/demo/select-demo.ts new file mode 100644 index 0000000..cb77aec --- /dev/null +++ b/mock/demo/select-demo.ts @@ -0,0 +1,37 @@ +import { MockMethod } from 'vite-plugin-mock'; +import { resultSuccess, baseUrl } from '../_util'; + +const demoList = (keyword, count = 20) => { + const result = { + list: [] as any[], + }; + for (let index = 0; index < count; index++) { + //根据搜索关键词做一下匹配 + let name = `选项${index}`; + if(keyword && name.indexOf(keyword)!=-1){ + result.list.push({ + name: `选项${index}`, + id: `${index}`, + }); + }else if(!keyword){ + result.list.push({ + name: `选项${index}`, + id: `${index}`, + }); + } + } + return result; +}; + +export default [ + { + url: `${baseUrl}/select/getDemoOptions`, + timeout: 1000, + method: 'get', + response: ({ query }) => { + const { keyword,count} = query; + console.log("查询条件:", keyword); + return resultSuccess(demoList(keyword,count)); + }, + }, +] as MockMethod[]; diff --git a/mock/demo/system.ts b/mock/demo/system.ts new file mode 100644 index 0000000..940c04e --- /dev/null +++ b/mock/demo/system.ts @@ -0,0 +1,298 @@ +import { MockMethod } from 'vite-plugin-mock'; +import { resultError, resultPageSuccess, resultSuccess, baseUrl } from '../_util'; + +const accountList = (() => { + const result: any[] = []; + for (let index = 0; index < 20; index++) { + result.push({ + id: `${index}`, + account: '@first', + email: '@email', + nickname: '@cname()', + role: '@first', + createTime: '@datetime', + remark: '@cword(10,20)', + 'status|1': ['0', '1'], + }); + } + return result; +})(); + +const userList = (() => { + const result: any[] = []; + for (let index = 0; index < 20; index++) { + result.push({ + id: `${index}`, + username: '@first', + email: '@email', + realname: '@cname()', + createTime: '@datetime', + remark: '@cword(10,20)', + avatar: 'https://q1.qlogo.cn/g?b=qq&nk=190848757&s=640' + }); + } + return result; +})(); + +const roleList = (() => { + const result: any[] = []; + for (let index = 0; index < 4; index++) { + result.push({ + id: index + 1, + orderNo: `${index + 1}`, + roleName: ['超级管理员', '管理员', '文章管理员', '普通用户'][index], + roleValue: '@first', + createTime: '@datetime', + remark: '@cword(10,20)', + menu: [['0', '1', '2'], ['0', '1'], ['0', '2'], ['2']][index], + 'status|1': ['0', '1'], + }); + } + return result; +})(); + +const newRoleList = (() => { + const result: any[] = []; + for (let index = 0; index < 4; index++) { + result.push({ + id: index + 1, + orderNo: `${index + 1}`, + roleName: ['超级管理员', '管理员', '文章管理员', '普通用户'][index], + roleCode: '@first', + createTime: '@datetime', + remark: '@cword(10,20)' + }); + } + return result; +})(); + +const testList = (() => { + const result: any[] = []; + for (let index = 0; index < 4; index++) { + result.push({ + id: index + 1, + orderNo: `${index + 1}`, + testName: ['数据1', '数据2', '数据3', '数据4'][index], + testValue: '@first', + createTime: '@datetime' + }); + } + return result; +})(); + +const tableDemoList = (() => { + const result: any[] = []; + for (let index = 0; index < 4; index++) { + result.push({ + id: index + 1, + orderCode: '2008200' + `${index + 1}`, + orderMoney: '@natural(1000,3000)', + ctype: '@natural(1,2)', + content: '@cword(10,20)', + orderDate: '@datetime' + }); + } + return result; +})(); + +const deptList = (() => { + const result: any[] = []; + for (let index = 0; index < 3; index++) { + result.push({ + id: `${index}`, + deptName: ['华东分部', '华南分部', '西北分部'][index], + orderNo: index + 1, + createTime: '@datetime', + remark: '@cword(10,20)', + 'status|1': ['0', '0', '1'], + children: (() => { + const children: any[] = []; + for (let j = 0; j < 4; j++) { + children.push({ + id: `${index}-${j}`, + deptName: ['研发部', '市场部', '商务部', '财务部'][j], + orderNo: j + 1, + createTime: '@datetime', + remark: '@cword(10,20)', + 'status|1': ['0', '1'], + parentDept: `${index}`, + children: undefined, + }); + } + return children; + })(), + }); + } + return result; +})(); + +const menuList = (() => { + const result: any[] = []; + for (let index = 0; index < 3; index++) { + result.push({ + id: `${index}`, + icon: ['ion:layers-outline', 'ion:git-compare-outline', 'ion:tv-outline'][index], + component: 'LAYOUT', + type: '0', + menuName: ['Dashboard', '权限管理', '功能'][index], + permission: '', + orderNo: index + 1, + createTime: '@datetime', + 'status|1': ['0', '0', '1'], + children: (() => { + const children: any[] = []; + for (let j = 0; j < 4; j++) { + children.push({ + id: `${index}-${j}`, + type: '1', + menuName: ['菜单1', '菜单2', '菜单3', '菜单4'][j], + icon: 'ion:document', + permission: ['menu1:view', 'menu2:add', 'menu3:update', 'menu4:del'][index], + component: [ + '/dashboard/welcome/index', + '/dashboard/Analysis/index', + '/dashboard/workbench/index', + '/dashboard/test/index', + ][j], + orderNo: j + 1, + createTime: '@datetime', + 'status|1': ['0', '1'], + parentMenu: `${index}`, + children: (() => { + const children: any[] = []; + for (let k = 0; k < 4; k++) { + children.push({ + id: `${index}-${j}-${k}`, + type: '2', + menuName: '按钮' + (j + 1) + '-' + (k + 1), + icon: '', + permission: + ['menu1:view', 'menu2:add', 'menu3:update', 'menu4:del'][index] + + ':btn' + + (k + 1), + component: [ + '/dashboard/welcome/index', + '/dashboard/Analysis/index', + '/dashboard/workbench/index', + '/dashboard/test/index', + ][j], + orderNo: j + 1, + createTime: '@datetime', + 'status|1': ['0', '1'], + parentMenu: `${index}-${j}`, + children: undefined, + }); + } + return children; + })(), + }); + } + return children; + })(), + }); + } + return result; +})(); + +export default [ + { + url: `${baseUrl}/system/getAccountList`, + timeout: 100, + method: 'get', + response: ({ query }) => { + const { page = 1, pageSize = 20 } = query; + return resultPageSuccess(page, pageSize, accountList); + }, + }, + { + url: `${baseUrl}/sys/user/list`, + timeout: 100, + method: 'get', + response: ({ query }) => { + const { page = 1, pageSize = 20 } = query; + return resultPageSuccess(page, pageSize, userList); + }, + }, + { + url: `${baseUrl}/system/getRoleListByPage`, + timeout: 100, + method: 'get', + response: ({ query }) => { + const { page = 1, pageSize = 20 } = query; + return resultPageSuccess(page, pageSize, roleList); + }, + }, + { + url: `${baseUrl}/sys/role/list`, + timeout: 100, + method: 'get', + response: ({ query }) => { + const { page = 1, pageSize = 20 } = query; + return resultPageSuccess(page, pageSize, newRoleList); + }, + }, + { + url: `${baseUrl}/system/getTestListByPage`, + timeout: 100, + method: 'get', + response: ({ query }) => { + const { page = 1, pageSize = 20 } = query; + return resultPageSuccess(page, pageSize, testList); + }, + }, + { + url: `${baseUrl}/system/getDemoTableListByPage`, + timeout: 100, + method: 'get', + response: ({ query }) => { + const { page = 1, pageSize = 20 } = query; + return resultPageSuccess(page, pageSize, tableDemoList); + }, + }, + { + url: `${baseUrl}/system/setRoleStatus`, + timeout: 500, + method: 'post', + response: ({ query }) => { + const { id, status } = query; + return resultSuccess({ id, status }); + }, + }, + { + url: `${baseUrl}/system/getAllRoleList`, + timeout: 100, + method: 'get', + response: () => { + return resultSuccess(roleList); + }, + }, + { + url: `${baseUrl}/system/getDeptList`, + timeout: 100, + method: 'get', + response: () => { + return resultSuccess(deptList); + }, + }, + { + url: `${baseUrl}/system/getMenuList`, + timeout: 100, + method: 'get', + response: () => { + return resultSuccess(menuList); + }, + }, + { + url: `${baseUrl}/system/accountExist`, + timeout: 500, + method: 'post', + response: ({ body }) => { + const { account } = body || {}; + if (account && account.indexOf('admin') !== -1) { + return resultError('该字段不能包含admin'); + } else { + return resultSuccess(`${account} can use`); + } + }, + }, +] as MockMethod[]; diff --git a/mock/demo/table-demo.ts b/mock/demo/table-demo.ts new file mode 100644 index 0000000..699c9dc --- /dev/null +++ b/mock/demo/table-demo.ts @@ -0,0 +1,56 @@ +import { MockMethod } from 'vite-plugin-mock'; +import { Random } from 'mockjs'; +import { resultPageSuccess, baseUrl } from '../_util'; + +function getRandomPics(count = 10): string[] { + const arr: string[] = []; + for (let i = 0; i < count; i++) { + arr.push(Random.image('800x600', Random.color(), Random.color(), Random.title())); + } + return arr; +} + +const demoList = (() => { + const result: any[] = []; + for (let index = 0; index < 200; index++) { + result.push({ + id: `${index}`, + beginTime: '@datetime', + endTime: '@datetime', + address: '@city()', + name: '@cname()', + name1: '@cname()', + name2: '@cname()', + name3: '@cname()', + name4: '@cname()', + name5: '@cname()', + name6: '@cname()', + name7: '@cname()', + name8: '@cname()', + avatar: Random.image('400x400', Random.color(), Random.color(), Random.first()), + imgArr: getRandomPics(Math.ceil(Math.random() * 3) + 1), + imgs: getRandomPics(Math.ceil(Math.random() * 3) + 1), + age: Math.ceil(Math.random() * 30) + 1, + score: Math.ceil(Math.random() * 80) + 1, + date: `@date('yyyy-MM-dd')`, + time: `@time('HH:mm')`, + 'no|100000-10000000': 100000, + 'status|1': ['normal', 'enable', 'disable'], + }); + } + return result; +})(); + +export default [ + { + url: `${baseUrl}/table/getDemoList`, + timeout: 100, + method: 'get', + response: ({ query }) => { + const { page = 1, pageSize = 20 } = query; + // 代码逻辑说明: 【issues/6943】mock翻页之后数据id和图片没自动刷新 + const pageNo = +(query.pageNo ?? page); + return resultPageSuccess(pageNo, +pageSize, demoList); + }, + }, +] as MockMethod[]; diff --git a/mock/demo/tree-demo.ts b/mock/demo/tree-demo.ts new file mode 100644 index 0000000..388d913 --- /dev/null +++ b/mock/demo/tree-demo.ts @@ -0,0 +1,38 @@ +import { MockMethod } from 'vite-plugin-mock'; +import { resultSuccess, baseUrl } from '../_util'; + +const demoTreeList = (keyword) => { + const result = { + list: [] as Recordable[], + }; + for (let index = 0; index < 5; index++) { + const children: Recordable[] = []; + for (let j = 0; j < 3; j++) { + children.push({ + title: `${keyword ?? ''}选项${index}-${j}`, + value: `${index}-${j}`, + key: `${index}-${j}`, + }); + } + result.list.push({ + title: `${keyword ?? ''}选项${index}`, + value: `${index}`, + key: `${index}`, + children, + }); + } + return result; +}; + +export default [ + { + url: `${baseUrl}/tree/getDemoOptions`, + timeout: 1000, + method: 'get', + response: ({ query }) => { + const { keyword } = query; + console.log("查询条件:", keyword); + return resultSuccess(demoTreeList(keyword)); + }, + }, +] as MockMethod[]; diff --git a/mock/sys/menu.ts b/mock/sys/menu.ts new file mode 100644 index 0000000..5379cb5 --- /dev/null +++ b/mock/sys/menu.ts @@ -0,0 +1,271 @@ +import { resultSuccess, resultError, getRequestToken, requestParams,baseUrl} from '../_util'; +import { MockMethod } from 'vite-plugin-mock'; +import { createFakeUserList } from './user'; +import { PageEnum } from '/@/enums/pageEnum'; + +// single +const dashboardRoute = { + path: '/dashboard', + name: 'Dashboard', + component: 'LAYOUT', + redirect: PageEnum.BASE_HOME, + meta: { + title: 'routes.dashboard.dashboard', + hideChildrenInMenu: true, + icon: 'bx:bx-home', + }, + children: [ + { + path: 'analysis', + name: 'Analysis', + component: '/dashboard/Analysis/index', + meta: { + hideMenu: true, + hideBreadcrumb: true, + title: 'routes.dashboard.analysis', + currentActiveMenu: '/dashboard', + icon: 'bx:bx-home', + }, + }, + { + path: 'workbench', + name: 'Workbench', + component: '/dashboard/workbench/index', + meta: { + hideMenu: true, + hideBreadcrumb: true, + title: 'routes.dashboard.workbench', + currentActiveMenu: '/dashboard', + icon: 'bx:bx-home', + }, + }, + ], +}; + +const backRoute = { + path: 'back', + name: 'PermissionBackDemo', + meta: { + title: 'routes.demo.permission.back', + }, + + children: [ + { + path: 'page', + name: 'BackAuthPage', + component: '/demo/permission/back/index', + meta: { + title: 'routes.demo.permission.backPage', + }, + }, + { + path: 'btn', + name: 'BackAuthBtn', + component: '/demo/permission/back/Btn', + meta: { + title: 'routes.demo.permission.backBtn', + }, + }, + ], +}; + +const authRoute = { + path: '/permission', + name: 'Permission', + component: 'LAYOUT', + redirect: '/permission/front/page', + meta: { + icon: 'carbon:user-role', + title: 'routes.demo.permission.permission', + }, + children: [backRoute], +}; + +const levelRoute = { + path: '/level', + name: 'Level', + component: 'LAYOUT', + redirect: '/level/menu1/menu1-1', + meta: { + icon: 'carbon:user-role', + title: 'routes.demo.level.level', + }, + + children: [ + { + path: 'menu1', + name: 'Menu1Demo', + meta: { + title: 'Menu1', + }, + children: [ + { + path: 'menu1-1', + name: 'Menu11Demo', + meta: { + title: 'Menu1-1', + }, + children: [ + { + path: 'menu1-1-1', + name: 'Menu111Demo', + component: '/demo/level/Menu111', + meta: { + title: 'Menu111', + }, + }, + ], + }, + { + path: 'menu1-2', + name: 'Menu12Demo', + component: '/demo/level/Menu12', + meta: { + title: 'Menu1-2', + }, + }, + ], + }, + { + path: 'menu2', + name: 'Menu2Demo', + component: '/demo/level/Menu2', + meta: { + title: 'Menu2', + }, + }, + ], +}; + +const sysRoute = { + path: '/system', + name: 'System', + component: 'LAYOUT', + redirect: '/system/account', + meta: { + icon: 'ion:settings-outline', + title: 'routes.demo.system.moduleName', + }, + children: [ + { + path: 'account', + name: 'AccountManagement', + meta: { + title: 'routes.demo.system.account', + ignoreKeepAlive: true, + }, + component: '/demo/system/account/index', + }, + { + path: 'account_detail/:id', + name: 'AccountDetail', + meta: { + hideMenu: true, + title: 'routes.demo.system.account_detail', + ignoreKeepAlive: true, + showMenu: false, + currentActiveMenu: '/system/account', + }, + component: '/demo/system/account/AccountDetail', + }, + { + path: 'role', + name: 'RoleManagement', + meta: { + title: 'routes.demo.system.role', + ignoreKeepAlive: true, + }, + component: '/demo/system/role/index', + }, + + { + path: 'menu', + name: 'MenuManagement', + meta: { + title: 'routes.demo.system.menu', + ignoreKeepAlive: true, + }, + component: '/demo/system/menu/index', + }, + { + path: 'dept', + name: 'DeptManagement', + meta: { + title: 'routes.demo.system.dept', + ignoreKeepAlive: true, + }, + component: '/demo/system/dept/index', + }, + { + path: 'changePassword', + name: 'ChangePassword', + meta: { + title: 'routes.demo.system.password', + ignoreKeepAlive: true, + }, + component: '/demo/system/password/index', + }, + ], +}; + +const linkRoute = { + path: '/link', + name: 'Link', + component: 'LAYOUT', + meta: { + icon: 'ion:tv-outline', + title: 'routes.demo.iframe.frame', + }, + children: [ + { + path: 'doc', + name: 'Doc', + meta: { + title: 'routes.demo.iframe.doc', + frameSrc: 'https://vvbin.cn/doc-next/', + }, + }, + { + path: 'https://vvbin.cn/doc-next/', + name: 'DocExternal', + component: 'LAYOUT', + meta: { + title: 'routes.demo.iframe.docExternal', + }, + }, + ], +}; + +export default [ + { + url: `${baseUrl}/sys/permission/getUserPermissionByToken`, + timeout: 1000, + method: 'get', + response: (request: requestParams) => { + const token = getRequestToken(request); + if (!token) { + return resultError('Invalid token!'); + } + const checkUser = createFakeUserList().find((item) => item.token === token); + if (!checkUser) { + return resultError('Invalid user token!'); + } + const id = checkUser.userId; + let menu: Object[]; + switch (id) { + case '1': + dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[0].path; + menu = [dashboardRoute, authRoute, levelRoute, sysRoute, linkRoute]; + break; + case '2': + dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[1].path; + menu = [dashboardRoute, authRoute, levelRoute, linkRoute]; + break; + default: + menu = []; + } + + return resultSuccess(menu); + }, + }, +] as MockMethod[]; diff --git a/mock/sys/user.ts b/mock/sys/user.ts new file mode 100644 index 0000000..ba6f831 --- /dev/null +++ b/mock/sys/user.ts @@ -0,0 +1,125 @@ +import { MockMethod } from 'vite-plugin-mock'; +import { resultError, resultSuccess, getRequestToken, requestParams, baseUrl } from '../_util'; +import { PageEnum } from '/@/enums/pageEnum'; +export function createFakeUserList() { + return [ + { + userId: '1', + username: 'admin', + realname: '管理员', + avatar: 'https://q1.qlogo.cn/g?b=qq&nk=190848757&s=640', + desc: 'manager', + password: '123456', + token: 'fakeToken1', + homePath: PageEnum.BASE_HOME, + roles: [ + { + roleName: 'Super Admin', + value: 'super', + }, + ], + }, + { + userId: '2', + username: 'jeecg', + password: '123456', + realname: '测试用户', + avatar: 'https://q1.qlogo.cn/g?b=qq&nk=339449197&s=640', + desc: 'tester', + token: 'fakeToken2', + homePath: PageEnum.BASE_HOME, + roles: [ + { + roleName: 'Tester', + value: 'test', + }, + ], + }, + ]; +} + +const fakeCodeList: any = { + '1': ['1000', '3000', '5000'], + + '2': ['2000', '4000', '6000'], +}; + +export default [ + // mock user login + { + url: `${baseUrl}/sys/login`, + timeout: 200, + method: 'post', + response: ({ body }) => { + const { username, password } = body; + const checkUser = createFakeUserList().find( + (item) => item.username === username && password === item.password + ); + if (!checkUser) { + return resultError('Incorrect account or password!'); + } + const { userId, username: _username, token, realname, desc, roles } = checkUser; + return resultSuccess({ + roles, + userId, + username: _username, + token, + realname, + desc, + }); + }, + }, + { + url: `${baseUrl}/sys/user/getUserInfo`, + method: 'get', + response: (request: requestParams) => { + const token = getRequestToken(request); + if (!token) return resultError('Invalid token'); + const checkUser = createFakeUserList().find((item) => item.token === token); + if (!checkUser) { + return resultError('The corresponding user information was not obtained!'); + } + return resultSuccess(checkUser); + }, + }, + { + url: `${baseUrl}/sys/permission/getPermCode`, + timeout: 200, + method: 'get', + response: (request: requestParams) => { + const token = getRequestToken(request); + if (!token) return resultError('Invalid token'); + const checkUser = createFakeUserList().find((item) => item.token === token); + if (!checkUser) { + return resultError('Invalid token!'); + } + const codeList = fakeCodeList[checkUser.userId]; + + return resultSuccess(codeList); + }, + }, + { + url: `${baseUrl}/sys/logout`, + timeout: 200, + method: 'get', + response: (request: requestParams) => { + const token = getRequestToken(request); + if (!token) return resultError('Invalid token'); + const checkUser = createFakeUserList().find((item) => item.token === token); + if (!checkUser) { + return resultError('Invalid token!'); + } + return resultSuccess(undefined, { message: 'Token has been destroyed' }); + }, + }, + { + url: `${baseUrl}/sys/randomImage/1629428467008`, + timeout: 200, + method: 'get', + response: (request: requestParams) => { + const result = + 'data:image/jpg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAAjAGkDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3h/ME5lErCKNSHi8rO44yCp65/P060gmdbbIaKecxl0VDsEmOmMk8cgZ96dcypFGpkEm0uOUzxznJI6DjnPH54ryTWNW1+P436T4ftNev4LTU7Brhw9tbCSHiZxGrNCcKCijkMeuSSOAD1KJ7hrTZNK0d1Iz7egGVPRSV6HGRkE4z1xV5SWQEqVJGSpxke3FeYL4n1PSfi5pXhK7vxrFlf27XVveyQRrcQHa4KFo1CMhMLdFUjfy2F59NjZm3Bl2kMRxnGO3JA7Y/HI7UAVrjfbWt3NJLNKmCwVAoaNcc4PHTk8+n5rKs1tDGtsdw5X94GkOT90ls5xnr14PYDNeUfDzxvq3xCuNZvG8SNp1zBKHtdFgtYpFjhAADuWXfKpJwwRkIwcFd6geg+Dm12Tw9bN4hkX+0Y5bmKcLGVV9s7KjLkA7dijBPLAgnJ5oWgGrb36zBtqyPh9oKwuvG4DncByCeeexPsLHm7jH5amSNxnzFI2gY479/bNVrxYZpRD57Q3JRkikRFLxlgfmUspGQEbrkccg8V5l8PfEOt6h4p8YW+r+JJf7M8OXZhjWWC2jjMQaZSZWWNSMLGDkFQD1BHFAHq7usMcksjnYoLHI+6AOen0pqllZfNkUMWZVUdG5yOvOcD+dcclt4j1/XlvtM8T3dn4cIc7kgt2a4PGwwBoTiLqfMZm34+VQpVz0Ou2t/eWfk2GrXGmTNxHcW9ukxV8EAOrqwKZIJ+6fl+8M0AXjHcb5GWdcN9xWjyF6ehGe/5j05jmdri3cxPJGI3YOAOXABGARnGTjkcjpwenmvwt8Y61czeIdM8b6g0esaW3mTR3SwQJFAB99dijcOpZySoBjI+8TXR+EtK16SNdR1rWL64WUNPaWtxbxQGBXbKCVY0VjKq/f525dgBxuKA6i4ZfsSEyq0R275Wm8v5f7wZe/TjgHNWqiiSWNEV5vNwp3MygFj68cAdeMen4y0wKk6Mtysp3yQsvktEoyPmI+YgnGB9O57dPIfGML3H7SnhaGO+msXfS2C3EIjLqcXPTzFZeenIPXjnFeyiIJIzx4XzH3SZBO75ccc8dF/L3zXHah4D0K58US+ILuLUPtcMeI5l1G5WT5i+VjYSDYCW2hVIHLDaMg0AX9K8FaVZ+IJPEVxPc6rrTAxLf3rqzRJgKURUVUToeQoPzNk8mt0qRJF5uTMVdBNGuAucHoScdByeOPcAxRw3MdzOpuC3nZkQiEBY8EcMf4sggfRT0qYQSJBJGJWlGwKodirZxjlxzzxzjIOfYAA8Tn+HehfErRoPGvhDUP7J1uQCaWK3YmJLpQWZT0aN95X5xxgBgp3ZPb/AAi8U6r4q8BRajriv9qW5khFy6hBcrkEOoCqAAW2cZ5Q85yBdtPAfhSTSv7Og0mWxiWH7PcQ288lq86Y2/vjEy+cCM8sW6t6mukt9PttNsobLTbC0gtY34gjURIgzuJVVGM55xxz3oAnZf8ASI38vcQrLu3fdBwenccfy96+abbwjqHiu8+K1lpt/NFJBqYlW1MwSO5KzzECRmBJwA2ASBuKljxkfSF9ai7jeESzRtJGy5R3UAdCcoVIOGOCCD36qMc9oHgHw/oGrSalpMd1a3DuWnRb6Z1lYr/y2VnYOw3sQT03Z96AMn4W+PP+Et0xrLVZmi8TaerRX9pJH5Rba2BIE9egbGMMSCACtdxOHuYmXy3CCVRjIHmLkbs5HTrx3A9DXJN8LvCraj/adpa3SajtZBqC6rdGaMhdgw3mZOB8uMjgY9q39X0Sx1myuLLUknWwbLSLDdyQrKGUhgxRlOPvZUnac5OT0AseQfEHT7u71CP4keHrbTZJtFSGWQ/Zmk+3gMytMByDGm0BXwrbVdwwVYmPqXhvxDZeKdEj1bRL6AWM42NvT9/DLtVQkmWOXBx16jbjIINGl+GdPs9I/seG6nSAfIY7e/n3qqEBVSQuZI1A25RWABOOhO7P8NeA/DejRveaBpb2IuW2k/bJmDxjIDlJNyk4LYyOjHBGaAOwjeQogYxtIuBNtbhTjPH6cHsalqlbWMFrcSNuVnkkeZAwG5c/eI79wOO2O+SbtABSMiuMMoYZBwRnkHIP50UUALRRRQAUUUUAFFFFACBVUsVUAscsQOpxjn8AKWiigBFVUQIihVUYAAwAKY8EUkqSOgZ0BCk9uQf5qD+FFFAElFFFAH//2Q=='; + return resultSuccess(result); + }, + }, +] as MockMethod[]; diff --git a/npm b/npm new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json new file mode 100644 index 0000000..61ff9e9 --- /dev/null +++ b/package.json @@ -0,0 +1,201 @@ +{ + "name": "test-frontend", + "version": "3.9.2", + "author": { + "name": "北京国炬信息技术有限公司", + "email": "jeecgos@163.com", + "url": "https://www.jeecg.com" + }, + "scripts": { + "pinstall": "pnpm install", + "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite", + "dev": "vite", + "build": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 vite build && esno ./build/script/postBuild.ts && esno ./build/script/copyChat.ts", + "build:docker": "cross-env NODE_ENV=docker NODE_OPTIONS=--max-old-space-size=8192 vite build --mode docker && esno ./build/script/postBuild.ts && esno ./build/script/copyChat.ts", + "build:dockercloud": "cross-env NODE_ENV=dockercloud NODE_OPTIONS=--max-old-space-size=8192 vite build --mode dockercloud && esno ./build/script/postBuild.ts && esno ./build/script/copyChat.ts", + "build:report": "pnpm clean:cache && cross-env REPORT=true npm run build", + "preview": "npm run build && vite preview", + "reinstall": "rimraf pnpm-lock.yaml && rimraf yarn.lock && rimraf package.lock.json && rimraf node_modules && npm run install", + "clean:lib": "rimraf node_modules", + "gen:icon": "esno ./build/generate/icon/index.ts", + "lint": "eslint --ext .vue,.ts,.tsx src/", + "stylelint": "stylelint \"src/**/*.{vue,less,css}\"", + "batch:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"", + "upgrade:log": "conventional-changelog -p angular -i CHANGELOG.md -s", + "husky:install": "husky install" + }, + "dependencies": { + "@jeecg/aiflow": "3.9.2-beta", + "@logicflow/core": "^2.1.2", + "@logicflow/extension": "^2.1.4", + "@logicflow/vue-node-registry": "^1.1.3", + "@iconify/iconify": "^3.1.1", + "@ant-design/colors": "^7.2.1", + "@ant-design/icons-vue": "^7.0.1", + "@vue/shared": "^3.5.22", + "@vueuse/core": "^10.11.1", + "@tinymce/tinymce-vue": "4.0.7", + "@zxcvbn-ts/core": "^3.0.4", + "ant-design-vue": "^4.2.6", + "axios": "^1.12.2", + "@vant/area-data": "^1.5.2", + "clipboard": "^2.0.11", + "codemirror": "^5.65.20", + "cron-parser": "^4.9.0", + "cropperjs": "^1.6.2", + "crypto-js": "^4.2.0", + "dayjs": "^1.11.18", + "dom-align": "^1.12.4", + "echarts": "^5.6.0", + "emoji-mart-vue-fast": "^15.0.5", + "enquire.js": "^2.1.6", + "intro.js": "^7.2.0", + "lodash-es": "^4.17.21", + "lodash.get": "^4.4.2", + "markdown-it": "^14.1.0", + "markdown-it-link-attributes": "^4.0.1", + "event-source-polyfill": "^1.0.31", + "highlight.js": "^11.11.1", + "@traptitech/markdown-it-katex": "^3.6.0", + "md5": "^2.3.0", + "mockjs": "^1.1.0", + "nprogress": "^0.2.0", + "path-to-regexp": "^6.3.0", + "pinia": "2.1.7", + "print-js": "^1.6.0", + "pinyin-pro": "^3.27.0", + "qs": "^6.14.0", + "qrcode": "^1.5.4", + "resize-observer-polyfill": "^1.5.1", + "showdown": "^2.1.0", + "sortablejs": "^1.15.6", + "swagger-ui-dist": "^5.29.3", + "tinymce": "6.6.2", + "vditor": "^3.11.2", + "vue": "^3.5.22", + "vue-cropper": "^0.6.5", + "vue-cropperjs": "^5.0.0", + "vue-i18n": "^9.14.5", + "vue-infinite-scroll": "^2.0.2", + "vue-print-nb-jeecg": "^1.0.13", + "vue-router": "^4.5.1", + "vue-types": "^5.1.3", + "vuedraggable": "^4.1.0", + "vxe-table": "4.13.31", + "vxe-pc-ui": "4.6.12", + "vxe-table-plugin-antd": "4.0.8", + "xe-utils": "3.5.26", + "xss": "^1.0.15", + "vue-grid-layout-v3": "^3.1.2", + "lunar-javascript": "^1.7.5", + "perfect-scrollbar": "^1.5.6", + "vue-color": "^3.3.3" + }, + "devDependencies": { + "@commitlint/cli": "^18.6.1", + "@commitlint/config-conventional": "^18.6.3", + "@iconify/json": "^2.2.394", + "@purge-icons/generated": "^0.10.0", + "unplugin-icons": "^0.22.0", + "@types/codemirror": "^5.60.16", + "@types/crypto-js": "^4.2.2", + "@types/fs-extra": "^11.0.4", + "@types/inquirer": "^9.0.9", + "@types/intro.js": "^5.1.5", + "@types/jest": "^29.5.14", + "@types/lodash-es": "^4.17.12", + "@types/mockjs": "^1.0.10", + "@types/node": "^20.19.20", + "@types/nprogress": "^0.2.3", + "@types/qrcode": "^1.5.5", + "@types/qs": "^6.14.0", + "@types/pinyin": "^2.10.2", + "@types/showdown": "^2.0.6", + "@types/sortablejs": "^1.15.8", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "@vitejs/plugin-vue": "^6.0.6", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "@vue/compiler-sfc": "^3.5.22", + "@vue/test-utils": "^2.4.6", + "autoprefixer": "^10.4.21", + "commitizen": "^4.3.1", + "conventional-changelog-cli": "^4.1.0", + "cross-env": "^7.0.3", + "cz-git": "^1.12.0", + "czg": "^1.12.0", + "dotenv": "^16.6.1", + "eslint": "^8.57.1", + "eslint-config-prettier": "^9.1.2", + "eslint-define-config": "^2.1.0", + "eslint-plugin-jest": "^27.9.0", + "eslint-plugin-prettier": "^5.5.4", + "eslint-plugin-vue": "^9.33.0", + "esno": "^4.8.0", + "fs-extra": "^11.3.2", + "http-server": "^14.1.1", + "husky": "^8.0.3", + "inquirer": "^9.3.8", + "is-ci": "^3.0.1", + "jest": "^29.7.0", + "less": "^4.4.2", + "lint-staged": "15.2.2", + "npm-run-all": "^4.1.5", + "picocolors": "^1.1.1", + "postcss": "^8.5.6", + "postcss-html": "^1.8.0", + "postcss-less": "^6.0.0", + "prettier": "^3.6.2", + "pretty-quick": "^4.2.2", + "rimraf": "^5.0.10", + "rollup": "4.52.5", + "rollup-plugin-visualizer": "5.14.0", + "stylelint": "^16.25.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recommended": "^14.0.1", + "stylelint-config-recommended-vue": "^1.6.1", + "stylelint-config-standard": "^36.0.1", + "stylelint-order": "^6.0.4", + "ts-jest": "^29.4.4", + "ts-node": "^10.9.2", + "typescript": "^5.9.3", + "unplugin-vue-components": "~0.24.1", + "vite": "^7.3.3", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-html": "^3.2.2", + "vite-plugin-mkcert": "^1.17.9", + "vite-plugin-mock": "^2.9.8", + "vite-plugin-optimize-persist": "^0.1.2", + "vite-plugin-package-config": "^0.1.1", + "vite-plugin-purge-icons": "^0.10.0", + "vite-plugin-svg-icons": "^2.0.1", + "vite-plugin-pwa": "^1.1.0", + "workbox-window": "^7.3.0", + "vite-plugin-qiankun": "^1.0.15", + "vite-plugin-vue-setup-extend-plus": "^0.1.0", + "unocss": "^66.6.8", + "vue-eslint-parser": "^9.4.3", + "vue-tsc": "^1.8.27", + "dingtalk-jsapi": "^3.2.0", + "big.js": "^6.2.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/jeecgboot/JeecgBoot.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/jeecgboot/JeecgBoot/issues" + }, + "homepage": "https://www.jeecg.com", + "engines": { + "node": "^18 || >=20" + }, + "packageManager": "pnpm@9.15.4", + "lint-staged": { + "*.{vue,ts,tsx}": [ + "eslint --fix" + ], + "*.{js,json,scss,css,less,html,md}": "prettier --write" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..bd63d87 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,15376 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@ant-design/colors': + specifier: ^7.2.1 + version: 7.2.1 + '@ant-design/icons-vue': + specifier: ^7.0.1 + version: 7.0.1(vue@3.5.27(typescript@5.9.3)) + '@iconify/iconify': + specifier: ^3.1.1 + version: 3.1.1 + '@jeecg/aiflow': + specifier: 3.9.2-beta + version: 3.9.2-beta + '@logicflow/core': + specifier: ^2.1.2 + version: 2.1.9 + '@logicflow/extension': + specifier: ^2.1.4 + version: 2.1.11(@logicflow/core@2.1.9)(@logicflow/vue-node-registry@1.1.10(@logicflow/core@2.1.9)(vue@3.5.27(typescript@5.9.3))) + '@logicflow/vue-node-registry': + specifier: ^1.1.3 + version: 1.1.10(@logicflow/core@2.1.9)(vue@3.5.27(typescript@5.9.3)) + '@tinymce/tinymce-vue': + specifier: 4.0.7 + version: 4.0.7(vue@3.5.27(typescript@5.9.3)) + '@traptitech/markdown-it-katex': + specifier: ^3.6.0 + version: 3.6.0 + '@vant/area-data': + specifier: ^1.5.2 + version: 1.5.2 + '@vue/shared': + specifier: ^3.5.22 + version: 3.5.27 + '@vueuse/core': + specifier: ^10.11.1 + version: 10.11.1(vue@3.5.27(typescript@5.9.3)) + '@zxcvbn-ts/core': + specifier: ^3.0.4 + version: 3.0.4 + ant-design-vue: + specifier: ^4.2.6 + version: 4.2.6(vue@3.5.27(typescript@5.9.3)) + axios: + specifier: ^1.12.2 + version: 1.13.2(debug@4.4.3) + clipboard: + specifier: ^2.0.11 + version: 2.0.11 + codemirror: + specifier: ^5.65.20 + version: 5.65.20 + cron-parser: + specifier: ^4.9.0 + version: 4.9.0 + cropperjs: + specifier: ^1.6.2 + version: 1.6.2 + crypto-js: + specifier: ^4.2.0 + version: 4.2.0 + dayjs: + specifier: ^1.11.18 + version: 1.11.19 + dom-align: + specifier: ^1.12.4 + version: 1.12.4 + echarts: + specifier: ^5.6.0 + version: 5.6.0 + emoji-mart-vue-fast: + specifier: ^15.0.5 + version: 15.0.5(vue@3.5.27(typescript@5.9.3)) + enquire.js: + specifier: ^2.1.6 + version: 2.1.6 + event-source-polyfill: + specifier: ^1.0.31 + version: 1.0.31 + highlight.js: + specifier: ^11.11.1 + version: 11.11.1 + intro.js: + specifier: ^7.2.0 + version: 7.2.0 + lodash-es: + specifier: ^4.17.21 + version: 4.17.22 + lodash.get: + specifier: ^4.4.2 + version: 4.4.2 + lunar-javascript: + specifier: ^1.7.5 + version: 1.7.7 + markdown-it: + specifier: ^14.1.0 + version: 14.1.0 + markdown-it-link-attributes: + specifier: ^4.0.1 + version: 4.0.1 + md5: + specifier: ^2.3.0 + version: 2.3.0 + mockjs: + specifier: ^1.1.0 + version: 1.1.0 + nprogress: + specifier: ^0.2.0 + version: 0.2.0 + path-to-regexp: + specifier: ^6.3.0 + version: 6.3.0 + perfect-scrollbar: + specifier: ^1.5.6 + version: 1.5.6 + pinia: + specifier: 2.1.7 + version: 2.1.7(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)) + pinyin-pro: + specifier: ^3.27.0 + version: 3.28.0 + print-js: + specifier: ^1.6.0 + version: 1.6.0 + qrcode: + specifier: ^1.5.4 + version: 1.5.4 + qs: + specifier: ^6.14.0 + version: 6.14.1 + resize-observer-polyfill: + specifier: ^1.5.1 + version: 1.5.1 + showdown: + specifier: ^2.1.0 + version: 2.1.0 + sortablejs: + specifier: ^1.15.6 + version: 1.15.6 + swagger-ui-dist: + specifier: ^5.29.3 + version: 5.31.0 + tinymce: + specifier: 6.6.2 + version: 6.6.2 + vditor: + specifier: ^3.11.2 + version: 3.11.2 + vue: + specifier: ^3.5.22 + version: 3.5.27(typescript@5.9.3) + vue-color: + specifier: ^3.3.3 + version: 3.3.3(vue@3.5.27(typescript@5.9.3)) + vue-cropper: + specifier: ^0.6.5 + version: 0.6.5 + vue-cropperjs: + specifier: ^5.0.0 + version: 5.0.0(vue@3.5.27(typescript@5.9.3)) + vue-grid-layout-v3: + specifier: ^3.1.2 + version: 3.1.2(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)(typescript@5.9.3) + vue-i18n: + specifier: ^9.14.5 + version: 9.14.5(vue@3.5.27(typescript@5.9.3)) + vue-infinite-scroll: + specifier: ^2.0.2 + version: 2.0.2 + vue-print-nb-jeecg: + specifier: ^1.0.13 + version: 1.0.13 + vue-router: + specifier: ^4.5.1 + version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + vue-types: + specifier: ^5.1.3 + version: 5.1.3(vue@3.5.27(typescript@5.9.3)) + vuedraggable: + specifier: ^4.1.0 + version: 4.1.0(vue@3.5.27(typescript@5.9.3)) + vxe-pc-ui: + specifier: 4.6.12 + version: 4.6.12(vue@3.5.27(typescript@5.9.3)) + vxe-table: + specifier: 4.13.31 + version: 4.13.31(vue@3.5.27(typescript@5.9.3)) + vxe-table-plugin-antd: + specifier: 4.0.8 + version: 4.0.8(vxe-table@4.13.31(vue@3.5.27(typescript@5.9.3))) + xe-utils: + specifier: 3.5.26 + version: 3.5.26 + xss: + specifier: ^1.0.15 + version: 1.0.15 + devDependencies: + '@commitlint/cli': + specifier: ^18.6.1 + version: 18.6.1(@types/node@20.19.30)(typescript@5.9.3) + '@commitlint/config-conventional': + specifier: ^18.6.3 + version: 18.6.3 + '@iconify/json': + specifier: ^2.2.394 + version: 2.2.430 + '@purge-icons/generated': + specifier: ^0.10.0 + version: 0.10.0 + '@types/codemirror': + specifier: ^5.60.16 + version: 5.60.17 + '@types/crypto-js': + specifier: ^4.2.2 + version: 4.2.2 + '@types/fs-extra': + specifier: ^11.0.4 + version: 11.0.4 + '@types/inquirer': + specifier: ^9.0.9 + version: 9.0.9 + '@types/intro.js': + specifier: ^5.1.5 + version: 5.1.5 + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 + '@types/mockjs': + specifier: ^1.0.10 + version: 1.0.10 + '@types/node': + specifier: ^20.19.20 + version: 20.19.30 + '@types/nprogress': + specifier: ^0.2.3 + version: 0.2.3 + '@types/pinyin': + specifier: ^2.10.2 + version: 2.10.2 + '@types/qrcode': + specifier: ^1.5.5 + version: 1.5.6 + '@types/qs': + specifier: ^6.14.0 + version: 6.14.0 + '@types/showdown': + specifier: ^2.0.6 + version: 2.0.6 + '@types/sortablejs': + specifier: ^1.15.8 + version: 1.15.9 + '@typescript-eslint/eslint-plugin': + specifier: ^6.21.0 + version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^6.21.0 + version: 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@vitejs/plugin-vue': + specifier: ^6.0.6 + version: 6.0.7(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))(vue@3.5.27(typescript@5.9.3)) + '@vitejs/plugin-vue-jsx': + specifier: ^5.1.5 + version: 5.1.5(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))(vue@3.5.27(typescript@5.9.3)) + '@vue/compiler-sfc': + specifier: ^3.5.22 + version: 3.5.27 + '@vue/test-utils': + specifier: ^2.4.6 + version: 2.4.6 + autoprefixer: + specifier: ^10.4.21 + version: 10.4.23(postcss@8.5.6) + big.js: + specifier: ^6.2.2 + version: 6.2.2 + commitizen: + specifier: ^4.3.1 + version: 4.3.1(@types/node@20.19.30)(typescript@5.9.3) + conventional-changelog-cli: + specifier: ^4.1.0 + version: 4.1.0 + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + cz-git: + specifier: ^1.12.0 + version: 1.12.0 + czg: + specifier: ^1.12.0 + version: 1.12.0 + dingtalk-jsapi: + specifier: ^3.2.0 + version: 3.2.5 + dotenv: + specifier: ^16.6.1 + version: 16.6.1 + eslint: + specifier: ^8.57.1 + version: 8.57.1 + eslint-config-prettier: + specifier: ^9.1.2 + version: 9.1.2(eslint@8.57.1) + eslint-define-config: + specifier: ^2.1.0 + version: 2.1.0 + eslint-plugin-jest: + specifier: ^27.9.0 + version: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(jest@29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)))(typescript@5.9.3) + eslint-plugin-prettier: + specifier: ^5.5.4 + version: 5.5.5(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.8.0) + eslint-plugin-vue: + specifier: ^9.33.0 + version: 9.33.0(eslint@8.57.1) + esno: + specifier: ^4.8.0 + version: 4.8.0 + fs-extra: + specifier: ^11.3.2 + version: 11.3.3 + http-server: + specifier: ^14.1.1 + version: 14.1.1 + husky: + specifier: ^8.0.3 + version: 8.0.3 + inquirer: + specifier: ^9.3.8 + version: 9.3.8(@types/node@20.19.30) + is-ci: + specifier: ^3.0.1 + version: 3.0.1 + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)) + less: + specifier: ^4.4.2 + version: 4.5.1 + lint-staged: + specifier: 15.2.2 + version: 15.2.2 + npm-run-all: + specifier: ^4.1.5 + version: 4.1.5 + picocolors: + specifier: ^1.1.1 + version: 1.1.1 + postcss: + specifier: ^8.5.6 + version: 8.5.6 + postcss-html: + specifier: ^1.8.0 + version: 1.8.1 + postcss-less: + specifier: ^6.0.0 + version: 6.0.0(postcss@8.5.6) + prettier: + specifier: ^3.6.2 + version: 3.8.0 + pretty-quick: + specifier: ^4.2.2 + version: 4.2.2(prettier@3.8.0) + rimraf: + specifier: ^5.0.10 + version: 5.0.10 + rollup: + specifier: 4.52.5 + version: 4.52.5 + rollup-plugin-visualizer: + specifier: 5.14.0 + version: 5.14.0(rollup@4.52.5) + stylelint: + specifier: ^16.25.0 + version: 16.26.1(typescript@5.9.3) + stylelint-config-prettier: + specifier: ^9.0.5 + version: 9.0.5(stylelint@16.26.1(typescript@5.9.3)) + stylelint-config-recommended: + specifier: ^14.0.1 + version: 14.0.1(stylelint@16.26.1(typescript@5.9.3)) + stylelint-config-recommended-vue: + specifier: ^1.6.1 + version: 1.6.1(postcss-html@1.8.1)(stylelint@16.26.1(typescript@5.9.3)) + stylelint-config-standard: + specifier: ^36.0.1 + version: 36.0.1(stylelint@16.26.1(typescript@5.9.3)) + stylelint-order: + specifier: ^6.0.4 + version: 6.0.4(stylelint@16.26.1(typescript@5.9.3)) + ts-jest: + specifier: ^29.4.4 + version: 29.4.6(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)))(typescript@5.9.3) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.19.30)(typescript@5.9.3) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + unocss: + specifier: ^66.6.8 + version: 66.7.0(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)) + unplugin-icons: + specifier: ^0.22.0 + version: 0.22.0(@vue/compiler-sfc@3.5.27)(vue-template-compiler@2.7.16) + unplugin-vue-components: + specifier: ~0.24.1 + version: 0.24.1(@babel/parser@7.29.7)(rollup@4.52.5)(vue@3.5.27(typescript@5.9.3)) + vite: + specifier: ^7.3.3 + version: 7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0) + vite-plugin-compression: + specifier: ^0.5.1 + version: 0.5.1(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)) + vite-plugin-html: + specifier: ^3.2.2 + version: 3.2.2(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)) + vite-plugin-mkcert: + specifier: ^1.17.9 + version: 1.17.9(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)) + vite-plugin-mock: + specifier: ^2.9.8 + version: 2.9.8(mockjs@1.1.0)(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)) + vite-plugin-optimize-persist: + specifier: ^0.1.2 + version: 0.1.2(vite-plugin-package-config@0.1.1(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)))(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)) + vite-plugin-package-config: + specifier: ^0.1.1 + version: 0.1.1(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)) + vite-plugin-purge-icons: + specifier: ^0.10.0 + version: 0.10.0(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)) + vite-plugin-pwa: + specifier: ^1.1.0 + version: 1.2.0(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0) + vite-plugin-qiankun: + specifier: ^1.0.15 + version: 1.0.15(typescript@5.9.3)(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)) + vite-plugin-svg-icons: + specifier: ^2.0.1 + version: 2.0.1(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)) + vite-plugin-vue-setup-extend-plus: + specifier: ^0.1.0 + version: 0.1.0 + vue-eslint-parser: + specifier: ^9.4.3 + version: 9.4.3(eslint@8.57.1) + vue-tsc: + specifier: ^1.8.27 + version: 1.8.27(typescript@5.9.3) + workbox-window: + specifier: ^7.3.0 + version: 7.4.0 + +packages: + + '@ant-design/colors@6.0.0': + resolution: {integrity: sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==} + + '@ant-design/colors@7.2.1': + resolution: {integrity: sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==} + + '@ant-design/fast-color@2.0.6': + resolution: {integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==} + engines: {node: '>=8.x'} + + '@ant-design/icons-svg@4.4.2': + resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==} + + '@ant-design/icons-vue@7.0.1': + resolution: {integrity: sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==} + peerDependencies: + vue: '>=3.0.3' + + '@antfu/install-pkg@0.5.0': + resolution: {integrity: sha512-dKnk2xlAyC7rvTkpkHmu+Qy/2Zc3Vm/l8PtNyIOGDBtXPY3kThfU4ORNEp3V7SXw5XSOb+tOJaUYpfquPzL/Tg==} + + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + + '@antfu/utils@0.7.10': + resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} + + '@antfu/utils@8.1.1': + resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==} + + '@antv/hierarchy@0.6.14': + resolution: {integrity: sha512-V3uknf7bhynOqQDw2sg+9r9DwZ9pc6k/EcqyTFdfXB1+ydr7urisP0MipIuimucvQKN+Qkd+d6w601r1UIroqQ==} + + '@apideck/better-ajv-errors@0.3.6': + resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==} + engines: {node: '>=10'} + peerDependencies: + ajv: '>=8' + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.29.7': + resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.28.5': + resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.5': + resolution: {integrity: sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.29.7': + resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.29.7': + resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.27.1': + resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.29.7': + resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.28.6': + resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.6': + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': + resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1': + resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1': + resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': + resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6': + resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.28.6': + resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.29.7': + resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.27.1': + resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.28.6': + resolution: {integrity: sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.28.6': + resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.27.1': + resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.28.6': + resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.28.6': + resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.28.6': + resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.28.6': + resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.28.6': + resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.28.5': + resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.28.6': + resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.27.1': + resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.28.6': + resolution: {integrity: sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.27.1': + resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-explicit-resource-management@7.28.6': + resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.28.6': + resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.27.1': + resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.27.1': + resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.27.1': + resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.28.6': + resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.27.1': + resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.28.6': + resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.27.1': + resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.27.1': + resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.29.7': + resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.28.5': + resolution: {integrity: sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.27.1': + resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1': + resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.27.1': + resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': + resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.28.6': + resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.28.6': + resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.27.1': + resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.28.6': + resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.28.6': + resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.27.7': + resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.28.6': + resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.28.6': + resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.27.1': + resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.28.6': + resolution: {integrity: sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.28.6': + resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.27.1': + resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.27.1': + resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.28.6': + resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.27.1': + resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.27.1': + resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.27.1': + resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.29.7': + resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.27.1': + resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.28.6': + resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.27.1': + resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6': + resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.28.6': + resolution: {integrity: sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.6': + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@cacheable/memory@2.0.7': + resolution: {integrity: sha512-RbxnxAMf89Tp1dLhXMS7ceft/PGsDl1Ip7T20z5nZ+pwIAsQ1p2izPjVG69oCLv/jfQ7HDPHTWK0c9rcAWXN3A==} + + '@cacheable/utils@2.3.3': + resolution: {integrity: sha512-JsXDL70gQ+1Vc2W/KUFfkAJzgb4puKwwKehNLuB+HrNKWf91O736kGfxn4KujXCCSuh6mRRL4XEB0PkAFjWS0A==} + + '@commitlint/cli@18.6.1': + resolution: {integrity: sha512-5IDE0a+lWGdkOvKH892HHAZgbAjcj1mT5QrfA/SVbLJV/BbBMGyKN0W5mhgjekPJJwEQdVNvhl9PwUacY58Usw==} + engines: {node: '>=v18'} + hasBin: true + + '@commitlint/config-conventional@18.6.3': + resolution: {integrity: sha512-8ZrRHqF6je+TRaFoJVwszwnOXb/VeYrPmTwPhf0WxpzpGTcYy1p0SPyZ2eRn/sRi/obnWAcobtDAq6+gJQQNhQ==} + engines: {node: '>=v18'} + + '@commitlint/config-validator@18.6.1': + resolution: {integrity: sha512-05uiToBVfPhepcQWE1ZQBR/Io3+tb3gEotZjnI4tTzzPk16NffN6YABgwFQCLmzZefbDcmwWqJWc2XT47q7Znw==} + engines: {node: '>=v18'} + + '@commitlint/config-validator@20.3.1': + resolution: {integrity: sha512-ErVLC/IsHhcvxCyh+FXo7jy12/nkQySjWXYgCoQbZLkFp4hysov8KS6CdxBB0cWjbZWjvNOKBMNoUVqkmGmahw==} + engines: {node: '>=v18'} + + '@commitlint/ensure@18.6.1': + resolution: {integrity: sha512-BPm6+SspyxQ7ZTsZwXc7TRQL5kh5YWt3euKmEIBZnocMFkJevqs3fbLRb8+8I/cfbVcAo4mxRlpTPfz8zX7SnQ==} + engines: {node: '>=v18'} + + '@commitlint/execute-rule@18.6.1': + resolution: {integrity: sha512-7s37a+iWyJiGUeMFF6qBlyZciUkF8odSAnHijbD36YDctLhGKoYltdvuJ/AFfRm6cBLRtRk9cCVPdsEFtt/2rg==} + engines: {node: '>=v18'} + + '@commitlint/execute-rule@20.0.0': + resolution: {integrity: sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==} + engines: {node: '>=v18'} + + '@commitlint/format@18.6.1': + resolution: {integrity: sha512-K8mNcfU/JEFCharj2xVjxGSF+My+FbUHoqR+4GqPGrHNqXOGNio47ziiR4HQUPKtiNs05o8/WyLBoIpMVOP7wg==} + engines: {node: '>=v18'} + + '@commitlint/is-ignored@18.6.1': + resolution: {integrity: sha512-MOfJjkEJj/wOaPBw5jFjTtfnx72RGwqYIROABudOtJKW7isVjFe9j0t8xhceA02QebtYf4P/zea4HIwnXg8rvA==} + engines: {node: '>=v18'} + + '@commitlint/lint@18.6.1': + resolution: {integrity: sha512-8WwIFo3jAuU+h1PkYe5SfnIOzp+TtBHpFr4S8oJWhu44IWKuVx6GOPux3+9H1iHOan/rGBaiacicZkMZuluhfQ==} + engines: {node: '>=v18'} + + '@commitlint/load@18.6.1': + resolution: {integrity: sha512-p26x8734tSXUHoAw0ERIiHyW4RaI4Bj99D8YgUlVV9SedLf8hlWAfyIFhHRIhfPngLlCe0QYOdRKYFt8gy56TA==} + engines: {node: '>=v18'} + + '@commitlint/load@20.3.1': + resolution: {integrity: sha512-YDD9XA2XhgYgbjju8itZ/weIvOOobApDqwlPYCX5NLO/cPtw2UMO5Cmn44Ks8RQULUVI5fUT6roKvyxcoLbNmw==} + engines: {node: '>=v18'} + + '@commitlint/message@18.6.1': + resolution: {integrity: sha512-VKC10UTMLcpVjMIaHHsY1KwhuTQtdIKPkIdVEwWV+YuzKkzhlI3aNy6oo1eAN6b/D2LTtZkJe2enHmX0corYRw==} + engines: {node: '>=v18'} + + '@commitlint/parse@18.6.1': + resolution: {integrity: sha512-eS/3GREtvVJqGZrwAGRwR9Gdno3YcZ6Xvuaa+vUF8j++wsmxrA2En3n0ccfVO2qVOLJC41ni7jSZhQiJpMPGOQ==} + engines: {node: '>=v18'} + + '@commitlint/read@18.6.1': + resolution: {integrity: sha512-ia6ODaQFzXrVul07ffSgbZGFajpe8xhnDeLIprLeyfz3ivQU1dIoHp7yz0QIorZ6yuf4nlzg4ZUkluDrGN/J/w==} + engines: {node: '>=v18'} + + '@commitlint/resolve-extends@18.6.1': + resolution: {integrity: sha512-ifRAQtHwK+Gj3Bxj/5chhc4L2LIc3s30lpsyW67yyjsETR6ctHAHRu1FSpt0KqahK5xESqoJ92v6XxoDRtjwEQ==} + engines: {node: '>=v18'} + + '@commitlint/resolve-extends@20.3.1': + resolution: {integrity: sha512-iGTGeyaoDyHDEZNjD8rKeosjSNs8zYanmuowY4ful7kFI0dnY4b5QilVYaFQJ6IM27S57LAeH5sKSsOHy4bw5w==} + engines: {node: '>=v18'} + + '@commitlint/rules@18.6.1': + resolution: {integrity: sha512-kguM6HxZDtz60v/zQYOe0voAtTdGybWXefA1iidjWYmyUUspO1zBPQEmJZ05/plIAqCVyNUTAiRPWIBKLCrGew==} + engines: {node: '>=v18'} + + '@commitlint/to-lines@18.6.1': + resolution: {integrity: sha512-Gl+orGBxYSNphx1+83GYeNy5N0dQsHBQ9PJMriaLQDB51UQHCVLBT/HBdOx5VaYksivSf5Os55TLePbRLlW50Q==} + engines: {node: '>=v18'} + + '@commitlint/top-level@18.6.1': + resolution: {integrity: sha512-HyiHQZUTf0+r0goTCDs/bbVv/LiiQ7AVtz6KIar+8ZrseB9+YJAIo8HQ2IC2QT1y3N1lbW6OqVEsTHjbT6hGSw==} + engines: {node: '>=v18'} + + '@commitlint/types@18.6.1': + resolution: {integrity: sha512-gwRLBLra/Dozj2OywopeuHj2ac26gjGkz2cZ+86cTJOdtWfiRRr4+e77ZDAGc6MDWxaWheI+mAV5TLWWRwqrFg==} + engines: {node: '>=v18'} + + '@commitlint/types@20.3.1': + resolution: {integrity: sha512-VmIFV/JkBRhDRRv7N5B7zEUkNZIx9Mp+8Pe65erz0rKycXLsi8Epcw0XJ+btSeRXgTzE7DyOyA9bkJ9mn/yqVQ==} + engines: {node: '>=v18'} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.25': + resolution: {integrity: sha512-g0Kw9W3vjx5BEBAF8c5Fm2NcB/Fs8jJXh85aXqwEXiL+tqtOut07TWgyaGzAAfTM+gKckrrncyeGEZPcaRgm2Q==} + engines: {node: '>=18'} + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@csstools/media-query-list-parser@4.0.3': + resolution: {integrity: sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/selector-specificity@5.0.0': + resolution: {integrity: sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==} + engines: {node: '>=18'} + peerDependencies: + postcss-selector-parser: ^7.0.0 + + '@ctrl/tinycolor@3.6.1': + resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} + engines: {node: '>=10'} + + '@dual-bundle/import-meta-resolve@4.2.1': + resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/unitless@0.8.1': + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.14.54': + resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@hutson/parse-repository-url@5.0.0': + resolution: {integrity: sha512-e5+YUKENATs1JgYHMzTr2MW/NDcXGfYFAuOQU8gJgF/kEh4EqKgfGrfLI67bMD4tbhZVlkigz/9YYwWcbOFthg==} + engines: {node: '>=10.13.0'} + + '@iconify/iconify@2.1.2': + resolution: {integrity: sha512-QcUzFeEWkE/mW+BVtEGmcWATClcCOIJFiYUD/PiCWuTcdEA297o8D4oN6Ra44WrNOHu1wqNW4J0ioaDIiqaFOQ==} + deprecated: no longer maintained, switch to modern iconify-icon web component + + '@iconify/iconify@3.1.1': + resolution: {integrity: sha512-1nemfyD/OJzh9ALepH7YfuuP8BdEB24Skhd8DXWh0hzcOxImbb1ZizSZkpCzAwSZSGcJFmscIBaBQu+yLyWaxQ==} + deprecated: no longer maintained, switch to modern iconify-icon web component + + '@iconify/json@2.2.430': + resolution: {integrity: sha512-h6ZF1tqaY8qw+NzQSAxvZgrXkp0FvlVZbhaf4LIcwcia2ZpqqZWuwhrBlregXdpPUAqcn76Z0l1H0udGMycdsw==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@2.3.0': + resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} + + '@iconify/utils@3.1.3': + resolution: {integrity: sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==} + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@interactjs/actions@1.10.27': + resolution: {integrity: sha512-FCRg5KwB+stkPcAMx/Cn0fgGP6p4LyMX9S/Upcn/W+hpYme31bPi54PCqmOebzz6myTthN6zFf9jMyLOqtI/gg==} + peerDependencies: + '@interactjs/core': 1.10.27 + '@interactjs/utils': 1.10.27 + + '@interactjs/auto-scroll@1.10.27': + resolution: {integrity: sha512-zPg5TnVsZv+9Hnt4qnbxLvBMf+rIWHkoJVoSETEbLNaj90C8hIyr0pVwukSUySSgDhCgQ7np0f3pg4INLq9beQ==} + peerDependencies: + '@interactjs/utils': 1.10.27 + + '@interactjs/auto-start@1.10.27': + resolution: {integrity: sha512-ECLBO/nxmaF1knncJKIE5F7la3KKRgEkn0Cu2JTPOYj9xy/LpfYElo3wkRHsodgOqF651nR70GK2/IzPR2lO9A==} + peerDependencies: + '@interactjs/core': 1.10.27 + '@interactjs/utils': 1.10.27 + + '@interactjs/core@1.10.27': + resolution: {integrity: sha512-SliUr/3ZbLAdED8LokzYzWHWMdCB5Cq+UnpXuRy+BIod1j97m4IUFf/D1iIKUBBjBcucgXbz28z96WnenVCB7Q==} + peerDependencies: + '@interactjs/utils': 1.10.27 + + '@interactjs/dev-tools@1.10.27': + resolution: {integrity: sha512-YolmBwRaKH1gWbvyLeV3m5QSwtD38lOZnCBA87PCAlcd9PQAC2gb03fEPeEyD336bE20oLB8f0WZt4Wre+afiw==} + peerDependencies: + '@interactjs/modifiers': 1.10.27 + '@interactjs/utils': 1.10.27 + + '@interactjs/interact@1.10.27': + resolution: {integrity: sha512-XdH3A2UUzjEFGGJgFuJlhiz99tE8jB8xNh/DmnoMuL6uOQPxNA+sWRnzEVjG0+zY2P3/dbhEpi4Cn3FLPzydwA==} + + '@interactjs/modifiers@1.10.27': + resolution: {integrity: sha512-ei/qfoQ+9/8k6WzNzdNqHI6cWkIV576N4Ap16r5CoqOWwhA6Xzj3OMHf1g0t1O4eSq2HdJsVJn3eLNfw9HsbeQ==} + peerDependencies: + '@interactjs/core': 1.10.27 + '@interactjs/utils': 1.10.27 + + '@interactjs/snappers@1.10.27': + resolution: {integrity: sha512-HZLZ0XSi6HI08OmTv/HKG6AltQoaKAALLQ+KDW92utj3XSgw7oren0KsWUKPhaPg3Av7R1jFQd08s+uafqIlLw==} + peerDependencies: + '@interactjs/utils': 1.10.27 + + '@interactjs/utils@1.10.27': + resolution: {integrity: sha512-+qfLOio2OxQqg1cXSnRaCl+N8MQDQLDS9w+aOGxH8YLAhIMyt7Asxx/46//sT8orgsi16pmlBPtngPHT9s8zKw==} + + '@intlify/core-base@9.14.5': + resolution: {integrity: sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==} + engines: {node: '>= 16'} + + '@intlify/message-compiler@9.14.5': + resolution: {integrity: sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==} + engines: {node: '>= 16'} + + '@intlify/shared@9.14.5': + resolution: {integrity: sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==} + engines: {node: '>= 16'} + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jeecg/aiflow@3.9.2-beta': + resolution: {integrity: sha512-LN8uidNAtNZLdTdIZyWCUmJulfr4GskKOI+ZvgiNfU/T5MqrxTwC32awFASyiZKRHnSvHhfOVxCx7oQ71xIynQ==} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@keyv/bigmap@1.3.0': + resolution: {integrity: sha512-KT01GjzV6AQD5+IYrcpoYLkCu1Jod3nau1Z7EsEuViO3TZGRacSbO9MfHmbJ1WaOXFtWLxPVj169cn2WNKPkIg==} + engines: {node: '>= 18'} + peerDependencies: + keyv: ^5.5.4 + + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + + '@logicflow/core@2.1.9': + resolution: {integrity: sha512-MeI5xBdO7rU25AhisVixsMOVc38T9W7/XkFUdoHWyPYwFWF4eySGphbk1yM+Qz95HfAXZI96C+YX7pVyR1Rnjw==} + + '@logicflow/extension@2.1.11': + resolution: {integrity: sha512-oWioTf3MQCxQqiIF7z6VRhUwpCdmjfE9R9+4O59PurEHjlhum7sb2LHDOMool9DHB0qufpLcsgS+vk35Va+EPw==} + peerDependencies: + '@logicflow/core': 2.1.9 + '@logicflow/vue-node-registry': 1.1.10 + + '@logicflow/vue-node-registry@1.1.10': + resolution: {integrity: sha512-ykKWb0DZ6RbQRGBHqkTkxsJxzyPSZgD5U2gQ4at8RISHHAOrkjAqCCEbm7vfIGbAPqC5GGvrnSTrJwmTT0pJxg==} + peerDependencies: + '@logicflow/core': 2.1.9 + '@vue/composition-api': ^1.0.0-rc.10 + vue: ^2.0.0 || >=3.0.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + + '@oxc-parser/binding-android-arm-eabi@0.131.0': + resolution: {integrity: sha512-t2xicr9pfzkSRYx5aPqZqlLaayIwJTqgQ81Jor31Xep2nGyL2Aq3d0K5wOfeR7VevaSdxaS9dzSQP9xDwn8fDg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.131.0': + resolution: {integrity: sha512-nlGIod6gw75x1aEDgLS+srj+JRGY0HHm9MI9YgzE/B64l6d6+H3MSP9NOgp0+HTg8tp4vV9rVfgQGgd+TfVZcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.131.0': + resolution: {integrity: sha512-jukuV6xe5RbQKFo7QD34NDCLDZp4PSOm8rmckhNdH/60ymG5zXbDzGBEyc+nTkuLQNama2aSGCt+CPfpjNTqyw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.131.0': + resolution: {integrity: sha512-g3JOo4khe9rslHm5WYaVDWb0HS/M1MLR3I9S8560MkKIcC96VQY00QjOlsuRyfSj/JDXj8i9T7ryPO2RidiXVg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.131.0': + resolution: {integrity: sha512-1hziITDTxjMePnX+dR9ocVT+EuZkQ8wm4FPAbmbEiKG+Phbo73J1ZnPAA6Y/aGsWF3McOFnQuZIktAFwalkfJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.131.0': + resolution: {integrity: sha512-9uRxfXwyKG9+MwmGQBo2ncPNwZH5HTmCETFM2WiuDBNDCW4NC5ttSQkwCAMrTAWgwMzVBH1CP8pM0v7nebCWXQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.131.0': + resolution: {integrity: sha512-mgbLvzRShXOLBdWGInf08Af4q+pfj1xD8hSgLClDZ9of/BXkB6+LIhTH7fihiDUipqB3yoSkKBWaZ3Ejlf5Yag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.131.0': + resolution: {integrity: sha512-OPT8++4aN6j2GJ8+3IZHS/byXoZP4aSBn+FoG6rgBJ2fKwPKXWF3MqrFMNW7NKHM28FLY579xYLxJSfgobEqPA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.131.0': + resolution: {integrity: sha512-vtPiwmfVTAXzaxDKsOXG+LwgRAA7WEnaeHzhS5z0GE89gAK18KSXnly7Z6saXXq6L3dVMyK44uoTI03zKxrpmw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.131.0': + resolution: {integrity: sha512-8AW8L7w5cGHSdZPcyZX2yR0+GUODsT15rbRjfdD54rv6DMbtuEB19ysLOpKJlRGfH6UNYNpCHaU1uJWgTWf1/w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.131.0': + resolution: {integrity: sha512-vvpjkjEOUsPcsYf8evE4MO3aGx9+3wodXEBOicGNnOwTuAik8eBONNkgSdhkGsAblQmfVHJyanRnpxglddTXIA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.131.0': + resolution: {integrity: sha512-AqmcNC3fClXX+fxQ6VGEN1667xVFiRBkY0CZmDMSiaeFUsv1+UkBPYYi48IUKcA9/ivvoKNRzQl2I4//kT9F/w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.131.0': + resolution: {integrity: sha512-7d3jOMKy7RSQCcDLIci+ySll2FgsOMl/GiRux4q2JNv0zg4EdhFISa9idvrdN/HEUIQQJNg6dmveUeJl2YErGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.131.0': + resolution: {integrity: sha512-JHK/h95qVqVQ+ITER837kcTdwBDFpFaNnOTYGCP0zdUSX/mLKC7tXOoyrTb6vG7iRPwGlcgBil3v2IjYw1FqJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.131.0': + resolution: {integrity: sha512-b2BO82O8azXAyf7EUgOPKu145nWypbNyk07HbU09fkzhm9lEA5oPvaN/M8Nlo7tOErVTa2WOgS4QbOnxAPXdDQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.131.0': + resolution: {integrity: sha512-GHO9glZaX7LkX/OGfluEPf1yjg+ehiFbUdowbX6uNWOQhmwKWU4m4+nZ9FJkrHNKuxyI1KKertMdGjVKCApKWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.131.0': + resolution: {integrity: sha512-3SkikPaEFoih1N83qLVEDLRLeY4nYsf6JT9SnWiMCQ5lGQdKup6bEuKCqkRiG9dD1IIaFeYz9RjlciPmYoFIWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.131.0': + resolution: {integrity: sha512-Os5bEhryeA2jkH+ZrnZyAC1EP5gs+X4YB1Fjqml7UPD5kU7ecsK1MPEVMfCrdt/GDNpDbavYXiOXOdyJ5b3OPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.131.0': + resolution: {integrity: sha512-m+jNz9EuF0NXoiptc6B9h5yompZQVW/a5MJeOu5zojfH5yWk82tvF2ccrHkfhgtrS9h9DD5l1Qv8dWlfY7Nz8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.131.0': + resolution: {integrity: sha512-o14Hk8dAyiEUMFEWEgmAwFZvBt1RzAYLM3xeQ+5315JXgVYhoemivgYcbYVRbsFkS71ShMGlAFE0kPnr460rww==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.131.0': + resolution: {integrity: sha512-PgnWDfV0h+b16XNKbXU7Daib/BFSt/J2mEzfYIBu6JB/wNdlU+kVYXCkGA1A9fWkTbOgbjh4e6NhPeQOYvFhEA==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@purge-icons/core@0.10.0': + resolution: {integrity: sha512-AtJbZv5Yy+vWX5v32DPTr+CW7AkSK8HJx52orDbrYt/9s4lGM2t4KKAmwaTQEH2HYr2HVh1mlqs54/S1s3WT1g==} + + '@purge-icons/generated@0.10.0': + resolution: {integrity: sha512-I+1yN7/yDy/eZzfhAZqKF8Z6FM8D/O1vempbPrHJ0m9HlZwvf8sWXOArPJ2qRQGB6mJUVSpaXkoGBuoz1GQX5A==} + + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@rollup/plugin-babel@5.3.1': + resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} + engines: {node: '>= 10.0.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@types/babel__core': ^7.1.9 + rollup: ^1.20.0||^2.0.0 + peerDependenciesMeta: + '@types/babel__core': + optional: true + + '@rollup/plugin-node-resolve@15.3.1': + resolution: {integrity: sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-replace@2.4.2': + resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==} + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + + '@rollup/plugin-terser@0.4.4': + resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@3.1.0': + resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} + engines: {node: '>= 8.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 + + '@rollup/pluginutils@4.2.1': + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.52.5': + resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.52.5': + resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.52.5': + resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.52.5': + resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.52.5': + resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.52.5': + resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.52.5': + resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.52.5': + resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.52.5': + resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.52.5': + resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-gnu@4.52.5': + resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.52.5': + resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.52.5': + resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.52.5': + resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.52.5': + resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.52.5': + resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openharmony-arm64@4.52.5': + resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.52.5': + resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.52.5': + resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.52.5': + resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.52.5': + resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} + cpu: [x64] + os: [win32] + + '@scarf/scarf@1.4.0': + resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} + + '@simonwep/pickr@1.8.2': + resolution: {integrity: sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==} + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@sphinxxxx/color-conversion@2.2.2': + resolution: {integrity: sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw==} + + '@surma/rollup-plugin-off-main-thread@2.2.3': + resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} + + '@tinymce/tinymce-vue@4.0.7': + resolution: {integrity: sha512-1esB8wGWrjPCY+rK8vy3QB1cxwXo7HLJWuNrcyPl6LOVR+QJjub0OiV/C+TUEsLN6OpCtRv+QnIqMC5vXz783Q==} + peerDependencies: + vue: ^3.0.0 + + '@traptitech/markdown-it-katex@3.6.0': + resolution: {integrity: sha512-CnJzTWxsgLGXFdSrWRaGz7GZ1kUUi8g3E9HzJmeveX1YwVJavrKYqysktfHZQsujdnRqV5O7g8FPKEA/aeTkOQ==} + + '@trysound/sax@0.2.0': + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + + '@tsconfig/node10@1.0.12': + resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/codemirror@5.60.17': + resolution: {integrity: sha512-AZq2FIsUHVMlp7VSe2hTfl5w4pcUkoFkM3zVsRKsn1ca8CXRDYvnin04+HP2REkwsxemuHqvDofdlhUWNpbwfw==} + + '@types/conventional-commits-parser@5.0.2': + resolution: {integrity: sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g==} + + '@types/crypto-js@4.2.2': + resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==} + + '@types/estree@0.0.39': + resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/fs-extra@11.0.4': + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/inquirer@9.0.9': + resolution: {integrity: sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw==} + + '@types/intro.js@5.1.5': + resolution: {integrity: sha512-TT1d8ayz07svlBcoqh26sNpQaU6bBpdFcCC+IMZHp46NNX2mYAHAVefM3wCmQSd4UWhhObeMjFByw2IaPKOXlw==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/jsonfile@6.1.4': + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.23': + resolution: {integrity: sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==} + + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + + '@types/mockjs@1.0.10': + resolution: {integrity: sha512-SXgrhajHG7boLv6oU93CcmdDm0HYRiceuz6b+7z+/2lCJPTWDv0V5YiwFHT2ejE4bQqgSXQiVPQYPWv7LGsK1g==} + + '@types/node@20.19.30': + resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@types/nprogress@0.2.3': + resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==} + + '@types/pinyin@2.10.2': + resolution: {integrity: sha512-jLzlRkaLRLg+lgYPjOuP3HX2cozUkhXls5GTXopsKuKJ9lDGlIAb88OoIztH6TbNUsoJnl/7e/kjaumA5IKKJg==} + + '@types/qrcode@1.5.6': + resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + + '@types/showdown@2.0.6': + resolution: {integrity: sha512-pTvD/0CIeqe4x23+YJWlX2gArHa8G0J0Oh6GKaVXV7TAeickpkkZiNOgFcFcmLQ5lB/K0qBJL1FtRYltBfbGCQ==} + + '@types/sortablejs@1.15.9': + resolution: {integrity: sha512-7HP+rZGE2p886PKV9c9OJzLBI6BBJu1O7lJGYnPyG3fS4/duUCcngkNCjsLwIMV+WMqANe3tt4irrXHSIe68OQ==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/svgo@2.6.4': + resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==} + + '@types/tern@0.23.9': + resolution: {integrity: sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==} + + '@types/through@0.0.33': + resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/web-bluetooth@0.0.20': + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + + '@typescript-eslint/eslint-plugin@6.21.0': + resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@6.21.0': + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@5.62.0': + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/type-utils@6.21.0': + resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@5.62.0': + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/typescript-estree@5.62.0': + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@5.62.0': + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@typescript-eslint/utils@6.21.0': + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@5.62.0': + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@unocss/cli@66.7.0': + resolution: {integrity: sha512-Pkd/R+WDs8gc1i9fI5XDW8N5r7aoCWjNzt8wGGj5FWTbmsEzewueaL5LQTH4ymv/BhSHFcYpL7geQ5vYont+BA==} + hasBin: true + + '@unocss/config@66.7.0': + resolution: {integrity: sha512-C6waL+xzqAPzz5n47qnrs4GbyAtlusNYYmcV6DKYh1MgUP4EFhMnEMyuR2mw3M3tsBpyGuehLdRK5+ECF5yLkA==} + + '@unocss/core@66.7.0': + resolution: {integrity: sha512-j6MFMx5C3iIwW4T4hVbh+30fKWgSGkmS3bCcdjlfqM88lRT+dHFBN9nkfNOBJT6e6IHN9415nexuzcQvTjJXxw==} + + '@unocss/extractor-arbitrary-variants@66.7.0': + resolution: {integrity: sha512-iHMlXmb8aGtYgPQ8o3D4rMz9DKOqSp8g/LWvEoiM7arSjJF9jMpcpYlqZXzkg1mSQX23TRW76Qxj1gyI4lRd9w==} + + '@unocss/inspector@66.7.0': + resolution: {integrity: sha512-W1MOO2d/1ZHwFR8+mrUUW6KW3MgJ6FdrSWfWeQ1+fgb0TVj8CweNzeSh46sTEXEcpvlui5mnGYFxanpLMoyOtA==} + + '@unocss/preset-attributify@66.7.0': + resolution: {integrity: sha512-n8ikthHHkAOeHWUwqRNIMGijV6LuIhiZb3D6kreV3oK98wJYupGNGYMByx7R9gQD9uBNLGs0f1jsaAScV3S2Sw==} + + '@unocss/preset-icons@66.7.0': + resolution: {integrity: sha512-y6I2qZ2cwNAS2XRBig1lHzdFG9qTnZM/mx3fL3XnURnEYZird4uidLyWOyUGcdy1kMot1QcYVb0D/s9NgrEOgQ==} + + '@unocss/preset-mini@66.7.0': + resolution: {integrity: sha512-+YtRlr1Fjd24GYWhPB93r6fjefTBCnFUsZCASncSEU29u2Mi8MYINjefLfKlSJFMKg6AKzWbelKMCQfoKlUkbg==} + + '@unocss/preset-tagify@66.7.0': + resolution: {integrity: sha512-MaM07ChHsX8XZM3tlMPuRsZxbNvRVTbmIncOj9cCCrFpeOu8hy2ggRAgGOalWIwnIHc+5KQIkqucbgmXN7KBdA==} + + '@unocss/preset-typography@66.7.0': + resolution: {integrity: sha512-Ekdz7jw/TYTiH+QFqMWcWNMnvBnUZ/XPUF938C8DfqD79hZyLdRPo0kQaZh62cit13xNRAb49QP3HIUJIUxoGA==} + + '@unocss/preset-uno@66.7.0': + resolution: {integrity: sha512-fcLTj5Wax3QydCSdJq9yQHhqKph6Mx9exwNnkaBCXtmBvrSBd2O6O9ZAylcISnACeXJkjSverU8qVu1X2tITdA==} + + '@unocss/preset-web-fonts@66.7.0': + resolution: {integrity: sha512-jpNLjo/2X/J2J+G/M9W+y6VuiIhjZ9AyOnyNJHsdhmSJYCsnMuaYtP9eFn4d9+oe3Sz2lVNPV0A9RiqQ+xVAyQ==} + + '@unocss/preset-wind3@66.7.0': + resolution: {integrity: sha512-1xxHBV4TtUHXPpYnWH8J2UHhxaMF84fTvadVzRRaXkyVrXD86Hveh/lfbXHXnCyf0JxwqsleOq8CnCTT8AgoAg==} + + '@unocss/preset-wind4@66.7.0': + resolution: {integrity: sha512-5o3y2BcImLbkxa0fNtwaH8iB47FrOKzT7Xogni5PIDOX/DsoHfDLNiNBRrHHHHBeZ3jqvjv2T9T5kEkD5/WtYQ==} + + '@unocss/preset-wind@66.7.0': + resolution: {integrity: sha512-K2cCgQawl4GzFyI9almN4jZ33OJNI6U7WxZ9s7D5bivZYfHPaStLTPffNxWlmawwUZF0qpH0S+S7H745ZzgajQ==} + + '@unocss/rule-utils@66.7.0': + resolution: {integrity: sha512-DMJIiey/m+xr0hpSbxFhSbKlC3e7QsuwdAEdgMmIM7pEcu/AEMl7oH198QYX9880P2X0hy5iFE6UCWx9CwGAXQ==} + + '@unocss/transformer-attributify-jsx@66.7.0': + resolution: {integrity: sha512-mmsAUluRprSyejbzeQnC3ST056EUj2IxD2hMpPJPtnA4QkkyIgBNCmi2OSeVcEWMat1s3r4LcYSTqN88EcX5Kg==} + + '@unocss/transformer-compile-class@66.7.0': + resolution: {integrity: sha512-Z3K/s3TmUqUBGgB2SThWbUAP5E9VIFacbs2+bNJNn+53y1kqdLA1MIje3xlU5hig3OzGk8newwiMflh3s3Jzhg==} + + '@unocss/transformer-directives@66.7.0': + resolution: {integrity: sha512-6T9JxrPfLQlJFNLv7paG4yGiIgGrJY30268HWLkBFwgsldNPtQ+GeQJOzfemCH19UXXqqXMP0WnvbsFucdclmg==} + + '@unocss/transformer-variant-group@66.7.0': + resolution: {integrity: sha512-2TXYLowPMXszGNYRXAWzQ1G9nGP6EYv9XeJvwNvnf0w9J6qOQVVtW/ZViIz61Kk083c06cZ6nai8pCBnc8aPAw==} + + '@unocss/vite@66.7.0': + resolution: {integrity: sha512-7J8Mk9M1j55S3bXL87/yHOspnuAaftQWbc3HjCH0/sXNLZJZnlpmUue3EtT5Ez+PE01T3DK1D9Xxn/MvtdvXtA==} + peerDependencies: + vite: ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0 + + '@vant/area-data@1.5.2': + resolution: {integrity: sha512-Gtxgt6Rjgopt6234ANpO0bBsSwtjZ23lBlVDHIy8Mi2NJqyoj1vgVWY0dri8/2LCZAWzQ6EnwRrUVViUZ0cvMA==} + + '@vitejs/plugin-vue-jsx@5.1.5': + resolution: {integrity: sha512-jIAsvHOEtWpslLOI2MeElGFxH7M8pM83BU/Tor4RLyiwH0FM4nUW3xdvbw20EeU9wc5IspQwMq225K3CMnJEpA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: ^3.0.0 + + '@vitejs/plugin-vue@6.0.7': + resolution: {integrity: sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: ^3.2.25 + + '@volar/language-core@1.11.1': + resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==} + + '@volar/source-map@1.11.1': + resolution: {integrity: sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==} + + '@volar/typescript@1.11.1': + resolution: {integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==} + + '@vue/babel-helper-vue-transform-on@2.0.1': + resolution: {integrity: sha512-uZ66EaFbnnZSYqYEyplWvn46GhZ1KuYSThdT68p+am7MgBNbQ3hphTL9L+xSIsWkdktwhPYLwPgVWqo96jDdRA==} + + '@vue/babel-plugin-jsx@2.0.1': + resolution: {integrity: sha512-a8CaLQjD/s4PVdhrLD/zT574ZNPnZBOY+IhdtKWRB4HRZ0I2tXBi5ne7d9eCfaYwp5gU5+4KIyFTV1W1YL9xZA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + + '@vue/babel-plugin-resolve-type@2.0.1': + resolution: {integrity: sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@vue/compiler-core@3.5.27': + resolution: {integrity: sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==} + + '@vue/compiler-dom@3.5.27': + resolution: {integrity: sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==} + + '@vue/compiler-sfc@3.5.27': + resolution: {integrity: sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==} + + '@vue/compiler-ssr@3.5.27': + resolution: {integrity: sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/language-core@1.8.27': + resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.27': + resolution: {integrity: sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==} + + '@vue/runtime-core@3.5.27': + resolution: {integrity: sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==} + + '@vue/runtime-dom@3.5.27': + resolution: {integrity: sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==} + + '@vue/server-renderer@3.5.27': + resolution: {integrity: sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==} + peerDependencies: + vue: 3.5.27 + + '@vue/shared@3.5.27': + resolution: {integrity: sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==} + + '@vue/test-utils@2.4.6': + resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} + + '@vueuse/core@10.11.1': + resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==} + + '@vueuse/metadata@10.11.1': + resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==} + + '@vueuse/shared@10.11.1': + resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==} + + '@vxe-ui/core@4.2.21': + resolution: {integrity: sha512-WcwKvNwovuAy+t0t40PwkDvMlCCOWCGSl19qmTq+NYn4GuMfItUsHwfdszO6UD90ozOMyPJzdxLwcfVijYpLng==} + peerDependencies: + vue: ^3.2.0 + + '@zxcvbn-ts/core@3.0.4': + resolution: {integrity: sha512-aQeiT0F09FuJaAqNrxynlAwZ2mW/1MdXakKWNmGM1Qp/VaY6CnB/GfnMS2T8gB2231Esp1/maCWd8vTG4OuShw==} + + JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true + + abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + add-stream@1.0.0: + resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-escapes@7.2.0: + resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} + engines: {node: '>=18'} + + ansi-regex@2.1.1: + resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} + engines: {node: '>=0.10.0'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@2.2.1: + resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} + engines: {node: '>=0.10.0'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + ant-design-vue@4.2.6: + resolution: {integrity: sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==} + engines: {node: '>=12.22.0'} + peerDependencies: + vue: '>=3.2.0' + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + arr-diff@4.0.0: + resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==} + engines: {node: '>=0.10.0'} + + arr-flatten@1.1.0: + resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==} + engines: {node: '>=0.10.0'} + + arr-union@3.1.0: + resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} + engines: {node: '>=0.10.0'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + + array-tree-filter@2.1.0: + resolution: {integrity: sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array-unique@0.3.2: + resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==} + engines: {node: '>=0.10.0'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + + assign-symbols@1.0.0: + resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} + engines: {node: '>=0.10.0'} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + atob@2.1.2: + resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} + engines: {node: '>= 4.5.0'} + hasBin: true + + autoprefixer@10.4.23: + resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axios@0.26.1: + resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} + + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-plugin-polyfill-corejs2@0.4.14: + resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.13.0: + resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.5: + resolution: {integrity: sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-transform-runtime@6.23.0: + resolution: {integrity: sha512-cpGMVC1vt/772y3jx1gwSaTitQVZuFDlllgreMsZ+rTYC6jlYXRyf5FQOgSnckOiA5QmzbXTyBY2A5AmZXF1fA==} + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-runtime@6.26.0: + resolution: {integrity: sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@2.0.0: + resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + base@0.11.2: + resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} + engines: {node: '>=0.10.0'} + + baseline-browser-mapping@2.9.15: + resolution: {integrity: sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==} + hasBin: true + + basic-auth@2.0.1: + resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} + engines: {node: '>= 0.8'} + + batch-processor@1.0.0: + resolution: {integrity: sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==} + + big.js@5.2.2: + resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + + big.js@6.2.2: + resolution: {integrity: sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@2.3.2: + resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} + engines: {node: '>=0.10.0'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + + cache-base@1.0.1: + resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} + engines: {node: '>=0.10.0'} + + cacheable@2.3.2: + resolution: {integrity: sha512-w+ZuRNmex9c1TR9RcsxbfTKCjSL0rh1WA5SABbrWprIHeNBdmyQLSYonlDy9gpD+63XT8DgZ/wNh1Smvc9WnJA==} + + cachedir@2.3.0: + resolution: {integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==} + engines: {node: '>=6'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camel-case@4.1.2: + resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} + + camelcase-keys@6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001765: + resolution: {integrity: sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==} + + chalk@1.1.3: + resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} + engines: {node: '>=0.10.0'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + + charenc@0.0.2: + resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + + cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + + cheerio@1.1.2: + resolution: {integrity: sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==} + engines: {node: '>=20.18.1'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + class-utils@0.3.6: + resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==} + engines: {node: '>=0.10.0'} + + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + clean-css@5.3.3: + resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} + engines: {node: '>= 10.0'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + + cli-width@3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + clipboard@2.0.11: + resolution: {integrity: sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==} + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + codemirror@5.65.20: + resolution: {integrity: sha512-i5dLDDxwkFCbhjvL2pNjShsojoL3XHyDwsGv1jqETUoW+lzpBKKqNTUWgQwVAOa0tUm4BwekT455ujafi8payA==} + + collect-v8-coverage@1.0.3: + resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} + + collection-visit@1.0.0: + resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==} + engines: {node: '>=0.10.0'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + commander@14.0.2: + resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} + engines: {node: '>=20'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + + commitizen@4.3.1: + resolution: {integrity: sha512-gwAPAVTy/j5YcOOebcCRIijn+mSjWJC+IYKivTu6aG8Ei/scoXgfsMRnuAk6b0GRste2J4NGxVdMN3ZpfNaVaw==} + engines: {node: '>= 12'} + hasBin: true + + common-tags@1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} + engines: {node: '>=4.0.0'} + + compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + + compute-scroll-into-view@1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + + computeds@0.0.1: + resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + + connect-history-api-fallback@1.6.0: + resolution: {integrity: sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==} + engines: {node: '>=0.8'} + + connect@3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + engines: {node: '>= 0.10.0'} + + consola@2.15.3: + resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + conventional-changelog-angular@7.0.0: + resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} + engines: {node: '>=16'} + + conventional-changelog-atom@4.0.0: + resolution: {integrity: sha512-q2YtiN7rnT1TGwPTwjjBSIPIzDJCRE+XAUahWxnh+buKK99Kks4WLMHoexw38GXx9OUxAsrp44f9qXe5VEMYhw==} + engines: {node: '>=16'} + + conventional-changelog-cli@4.1.0: + resolution: {integrity: sha512-MscvILWZ6nWOoC+p/3Nn3D2cVLkjeQjyZPUr0bQ+vUORE/SPrkClJh8BOoMNpS4yk+zFJ5LlgXACxH6XGQoRXA==} + engines: {node: '>=16'} + hasBin: true + + conventional-changelog-codemirror@4.0.0: + resolution: {integrity: sha512-hQSojc/5imn1GJK3A75m9hEZZhc3urojA5gMpnar4JHmgLnuM3CUIARPpEk86glEKr3c54Po3WV/vCaO/U8g3Q==} + engines: {node: '>=16'} + + conventional-changelog-conventionalcommits@7.0.2: + resolution: {integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==} + engines: {node: '>=16'} + + conventional-changelog-core@7.0.0: + resolution: {integrity: sha512-UYgaB1F/COt7VFjlYKVE/9tTzfU3VUq47r6iWf6lM5T7TlOxr0thI63ojQueRLIpVbrtHK4Ffw+yQGduw2Bhdg==} + engines: {node: '>=16'} + + conventional-changelog-ember@4.0.0: + resolution: {integrity: sha512-D0IMhwcJUg1Y8FSry6XAplEJcljkHVlvAZddhhsdbL1rbsqRsMfGx/PIkPYq0ru5aDgn+OxhQ5N5yR7P9mfsvA==} + engines: {node: '>=16'} + + conventional-changelog-eslint@5.0.0: + resolution: {integrity: sha512-6JtLWqAQIeJLn/OzUlYmzd9fKeNSWmQVim9kql+v4GrZwLx807kAJl3IJVc3jTYfVKWLxhC3BGUxYiuVEcVjgA==} + engines: {node: '>=16'} + + conventional-changelog-express@4.0.0: + resolution: {integrity: sha512-yWyy5c7raP9v7aTvPAWzqrztACNO9+FEI1FSYh7UP7YT1AkWgv5UspUeB5v3Ibv4/o60zj2o9GF2tqKQ99lIsw==} + engines: {node: '>=16'} + + conventional-changelog-jquery@5.0.0: + resolution: {integrity: sha512-slLjlXLRNa/icMI3+uGLQbtrgEny3RgITeCxevJB+p05ExiTgHACP5p3XiMKzjBn80n+Rzr83XMYfRInEtCPPw==} + engines: {node: '>=16'} + + conventional-changelog-jshint@4.0.0: + resolution: {integrity: sha512-LyXq1bbl0yG0Ai1SbLxIk8ZxUOe3AjnlwE6sVRQmMgetBk+4gY9EO3d00zlEt8Y8gwsITytDnPORl8al7InTjg==} + engines: {node: '>=16'} + + conventional-changelog-preset-loader@4.1.0: + resolution: {integrity: sha512-HozQjJicZTuRhCRTq4rZbefaiCzRM2pr6u2NL3XhrmQm4RMnDXfESU6JKu/pnKwx5xtdkYfNCsbhN5exhiKGJA==} + engines: {node: '>=16'} + + conventional-changelog-writer@7.0.1: + resolution: {integrity: sha512-Uo+R9neH3r/foIvQ0MKcsXkX642hdm9odUp7TqgFS7BsalTcjzRlIfWZrZR1gbxOozKucaKt5KAbjW8J8xRSmA==} + engines: {node: '>=16'} + hasBin: true + + conventional-changelog@5.1.0: + resolution: {integrity: sha512-aWyE/P39wGYRPllcCEZDxTVEmhyLzTc9XA6z6rVfkuCD2UBnhV/sgSOKbQrEG5z9mEZJjnopjgQooTKxEg8mAg==} + engines: {node: '>=16'} + + conventional-commit-types@3.0.0: + resolution: {integrity: sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg==} + + conventional-commits-filter@4.0.0: + resolution: {integrity: sha512-rnpnibcSOdFcdclpFwWa+pPlZJhXE7l+XK04zxhbWrhgpR96h33QLz8hITTXbcYICxVr3HZFtbtUAQ+4LdBo9A==} + engines: {node: '>=16'} + + conventional-commits-parser@5.0.0: + resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==} + engines: {node: '>=16'} + hasBin: true + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + copy-anything@2.0.6: + resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} + + copy-descriptor@0.1.1: + resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} + engines: {node: '>=0.10.0'} + + core-js-compat@3.47.0: + resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==} + + core-js@2.6.12: + resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==} + deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js. + + core-js@3.47.0: + resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + + corser@2.0.1: + resolution: {integrity: sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==} + engines: {node: '>= 0.4.0'} + + cosmiconfig-typescript-loader@5.1.0: + resolution: {integrity: sha512-7PtBB+6FdsOvZyJtlF3hEPpACq7RQX6BVGsgC7/lfVXnKMvNCu/XY3ykreqG5w/rBNdu2z8LCIKoF3kpHHdHlA==} + engines: {node: '>=v16'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=8.2' + typescript: '>=4' + + cosmiconfig-typescript-loader@6.2.0: + resolution: {integrity: sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==} + engines: {node: '>=v18'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=9' + typescript: '>=5' + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cron-parser@4.9.0: + resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} + engines: {node: '>=12.0.0'} + + cropperjs@1.6.2: + resolution: {integrity: sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==} + + cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + + cross-fetch@3.2.0: + resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} + + cross-spawn@6.0.6: + resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} + engines: {node: '>=4.8'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crypt@0.0.2: + resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} + + crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + + crypto-random-string@2.0.0: + resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} + engines: {node: '>=8'} + + css-functions-list@3.2.3: + resolution: {integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==} + engines: {node: '>=12 || >=16'} + + css-select@4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + cssfilter@0.0.10: + resolution: {integrity: sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==} + + csso@4.2.0: + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} + engines: {node: '>=8.0.0'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + cz-conventional-changelog@3.3.0: + resolution: {integrity: sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==} + engines: {node: '>= 10'} + + cz-git@1.12.0: + resolution: {integrity: sha512-LaZ+8whPPUOo6Y0Zy4nIbf6JOleV3ejp41sT6N4RPKiKKA+ICWf4ueeIlxIO8b6JtdlDxRzHH/EcRji07nDxcg==} + engines: {node: '>=v12.20.0'} + + czg@1.12.0: + resolution: {integrity: sha512-LGltcoZ5m7vhe3WNw16QXqd5WurnHegx9V15MwZJtFAU2AVCYLCqDbwxPUgZOnAcdzzooq33ONcU148HOQsjdA==} + engines: {node: '>=v12.20.0'} + hasBin: true + + dargs@7.0.0: + resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} + engines: {node: '>=8'} + + dargs@8.1.0: + resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} + engines: {node: '>=12'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + + dedent@0.7.0: + resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + + dedent@1.7.1: + resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + define-property@0.2.5: + resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==} + engines: {node: '>=0.10.0'} + + define-property@1.0.0: + resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==} + engines: {node: '>=0.10.0'} + + define-property@2.0.2: + resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==} + engines: {node: '>=0.10.0'} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + delegate@3.2.0: + resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-file@1.0.0: + resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} + engines: {node: '>=0.10.0'} + + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + diff-match-patch@1.0.5: + resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + + dingtalk-jsapi@3.2.5: + resolution: {integrity: sha512-GHtDTmilJQhr07GNarjlzhvgUkPWc0+52zbN2ToW+JzkydaOwmhiJCTO42+BI+onAlhdfLUbtUnGsjQNDTrM1w==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-align@1.12.4: + resolution: {integrity: sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==} + + dom-scroll-into-view@2.0.1: + resolution: {integrity: sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==} + + dom-serializer@0.2.2: + resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + dom-zindex@1.0.6: + resolution: {integrity: sha512-FKWIhiU96bi3xpP9ewRMgANsoVmMUBnMnmpCT6dPMZOunVYJQmJhSRruoI0XSPoHeIif3kyEuiHbFrOJwEJaEA==} + + domelementtype@1.3.1: + resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@2.4.2: + resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@1.7.0: + resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + + dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + + dotenv-expand@8.0.3: + resolution: {integrity: sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==} + engines: {node: '>=12'} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + echarts@5.6.0: + resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==} + + editorconfig@1.0.4: + resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + engines: {node: '>=14'} + hasBin: true + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + + element-resize-detector@1.2.4: + resolution: {integrity: sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-mart-vue-fast@15.0.5: + resolution: {integrity: sha512-wnxLor8ggpqshoOPwIc33MdOC3A1XFeDLgUwYLPtNPL8VeAtXJAVrnFq1CN5PeCYAFoLo4IufHQZ9CfHD4IZiw==} + peerDependencies: + vue: '>2.0.0' + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + emojis-list@3.0.0: + resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} + engines: {node: '>= 4'} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encoding-sniffer@0.2.1: + resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==} + + enquire.js@2.1.6: + resolution: {integrity: sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw==} + + entities@1.1.2: + resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + entities@7.0.0: + resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==} + engines: {node: '>=0.12'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + errno@0.1.8: + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} + hasBin: true + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-abstract@1.24.1: + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + esbuild-android-64@0.14.54: + resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + esbuild-android-arm64@0.14.54: + resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + esbuild-darwin-64@0.14.54: + resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + esbuild-darwin-arm64@0.14.54: + resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + esbuild-freebsd-64@0.14.54: + resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + esbuild-freebsd-arm64@0.14.54: + resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + esbuild-linux-32@0.14.54: + resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + esbuild-linux-64@0.14.54: + resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + esbuild-linux-arm64@0.14.54: + resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + esbuild-linux-arm@0.14.54: + resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + esbuild-linux-mips64le@0.14.54: + resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + esbuild-linux-ppc64le@0.14.54: + resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + esbuild-linux-riscv64@0.14.54: + resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + esbuild-linux-s390x@0.14.54: + resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + esbuild-netbsd-64@0.14.54: + resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + esbuild-openbsd-64@0.14.54: + resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + esbuild-sunos-64@0.14.54: + resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + esbuild-windows-32@0.14.54: + resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + esbuild-windows-64@0.14.54: + resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + esbuild-windows-arm64@0.14.54: + resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + esbuild@0.14.54: + resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@9.1.2: + resolution: {integrity: sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-define-config@2.1.0: + resolution: {integrity: sha512-QUp6pM9pjKEVannNAbSJNeRuYwW3LshejfyBBpjeMGaJjaDUpVps4C6KVR8R7dWZnD3i0synmrE36znjTkJvdQ==} + engines: {node: '>=18.0.0', npm: '>=9.0.0', pnpm: '>=8.6.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + eslint-plugin-jest@27.9.0: + resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^5.0.0 || ^6.0.0 || ^7.0.0 + eslint: ^7.0.0 || ^8.0.0 + jest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true + + eslint-plugin-prettier@5.5.5: + resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-vue@9.33.0: + resolution: {integrity: sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + esno@4.8.0: + resolution: {integrity: sha512-acMtooReAQGzLU0zcuEDHa8S62meh5aIyi8jboYxyvAePdmuWx2Mpwmt0xjwO0bs9/SXf+dvXJ0QJoDWw814Iw==} + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@1.0.1: + resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + event-source-polyfill@1.0.31: + resolution: {integrity: sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expand-brackets@2.1.4: + resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==} + engines: {node: '>=0.10.0'} + + expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + + extend-shallow@3.0.2: + resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==} + engines: {node: '>=0.10.0'} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + extglob@2.0.4: + resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + + file-entry-cache@11.1.2: + resolution: {integrity: sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log==} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + filelist@1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + + fill-range@4.0.0: + resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==} + engines: {node: '>=0.10.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + engines: {node: '>= 0.8'} + + find-node-modules@2.1.3: + resolution: {integrity: sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg==} + + find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + find-up@6.3.0: + resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + findup-sync@4.0.0: + resolution: {integrity: sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==} + engines: {node: '>= 8'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flat-cache@6.1.20: + resolution: {integrity: sha512-AhHYqwvN62NVLp4lObVXGVluiABTHapoB57EyegZVmazN+hhGhLTn3uZbOofoTw4DSDvVCadzzyChXhOAvy8uQ==} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + for-in@1.0.2: + resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} + engines: {node: '>=0.10.0'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fragment-cache@0.2.1: + resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==} + engines: {node: '>=0.10.0'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@11.3.3: + resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} + engines: {node: '>=14.14'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-own-enumerable-property-symbols@3.0.2: + resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + get-value@2.0.6: + resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} + engines: {node: '>=0.10.0'} + + git-raw-commits@2.0.11: + resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} + engines: {node: '>=10'} + hasBin: true + + git-raw-commits@4.0.0: + resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} + engines: {node: '>=16'} + hasBin: true + + git-semver-tags@7.0.1: + resolution: {integrity: sha512-NY0ZHjJzyyNXHTDZmj+GG7PyuAKtMsyWSwh07CR2hOZFa+/yoTsXci/nF2obzL8UDhakFNkD9gNdt/Ed+cxh2Q==} + engines: {node: '>=16'} + hasBin: true + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + hasBin: true + + glob@11.1.0: + resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} + engines: {node: 20 || >=22} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + global-directory@4.0.1: + resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} + engines: {node: '>=18'} + + global-dirs@0.1.1: + resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} + engines: {node: '>=4'} + + global-modules@1.0.0: + resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} + engines: {node: '>=0.10.0'} + + global-modules@2.0.0: + resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} + engines: {node: '>=6'} + + global-prefix@1.0.2: + resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} + engines: {node: '>=0.10.0'} + + global-prefix@3.0.0: + resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} + engines: {node: '>=6'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + globjoin@0.1.4: + resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} + + good-listener@1.2.2: + resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + gzip-size@6.0.0: + resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} + engines: {node: '>=10'} + + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + + has-ansi@2.0.0: + resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} + engines: {node: '>=0.10.0'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@1.0.0: + resolution: {integrity: sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==} + engines: {node: '>=0.10.0'} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has-value@0.3.1: + resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==} + engines: {node: '>=0.10.0'} + + has-value@1.0.0: + resolution: {integrity: sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==} + engines: {node: '>=0.10.0'} + + has-values@0.1.4: + resolution: {integrity: sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==} + engines: {node: '>=0.10.0'} + + has-values@1.0.0: + resolution: {integrity: sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==} + engines: {node: '>=0.10.0'} + + hashery@1.4.0: + resolution: {integrity: sha512-Wn2i1In6XFxl8Az55kkgnFRiAlIAushzh26PTjL2AKtQcEfXrcLa7Hn5QOWGZEf3LU057P9TwwZjFyxfS1VuvQ==} + engines: {node: '>=20'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} + + hoist-non-react-statics@2.5.5: + resolution: {integrity: sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==} + + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + + hookified@1.15.0: + resolution: {integrity: sha512-51w+ZZGt7Zw5q7rM3nC4t3aLn/xvKDETsXqMczndvwyVQhAHfUmUuFBRFcos8Iyebtk7OAE9dL26wFNzZVVOkw==} + + hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + + hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + + html-encoding-sniffer@3.0.0: + resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} + engines: {node: '>=12'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + html-minifier-terser@6.1.0: + resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} + engines: {node: '>=12'} + hasBin: true + + html-tags@3.3.1: + resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} + engines: {node: '>=8'} + + htmlparser2@10.0.0: + resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} + + htmlparser2@3.10.1: + resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} + + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + + http-server@14.1.1: + resolution: {integrity: sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==} + engines: {node: '>=12'} + hasBin: true + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + husky@8.0.3: + resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} + engines: {node: '>=14'} + hasBin: true + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + idb@7.1.1: + resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + image-size@0.5.5: + resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} + engines: {node: '>=0.10.0'} + hasBin: true + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ini@4.1.1: + resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + inquirer@8.2.5: + resolution: {integrity: sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==} + engines: {node: '>=12.0.0'} + + inquirer@9.3.8: + resolution: {integrity: sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w==} + engines: {node: '>=18'} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + intro.js@7.2.0: + resolution: {integrity: sha512-qbMfaB70rOXVBceIWNYnYTpVTiZsvQh/MIkfdQbpA9di9VBfj1GigUPfcCv3aOfsbrtPcri8vTLTA4FcEDcHSQ==} + + is-accessor-descriptor@1.0.1: + resolution: {integrity: sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==} + engines: {node: '>= 0.10'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-ci@3.0.1: + resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} + hasBin: true + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-descriptor@1.0.1: + resolution: {integrity: sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-descriptor@0.1.7: + resolution: {integrity: sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==} + engines: {node: '>= 0.4'} + + is-descriptor@1.0.3: + resolution: {integrity: sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==} + engines: {node: '>= 0.4'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + + is-extendable@1.0.1: + resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} + engines: {node: '>=0.10.0'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@3.0.0: + resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-obj@1.0.1: + resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} + engines: {node: '>=0.10.0'} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + + is-plain-object@3.0.1: + resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==} + engines: {node: '>=0.10.0'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-regexp@1.0.0: + resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} + engines: {node: '>=0.10.0'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-text-path@2.0.0: + resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} + engines: {node: '>=8'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-utf8@0.2.1: + resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + is-what@3.14.1: + resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isobject@2.1.0: + resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} + engines: {node: '>=0.10.0'} + + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} + engines: {node: 20 || >=22} + + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} + engines: {node: '>=10'} + hasBin: true + + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-base64@2.6.4: + resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==} + + js-beautify@1.15.4: + resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} + engines: {node: '>=14'} + hasBin: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-parse-even-better-errors@3.0.2: + resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + + jsonpointer@5.0.1: + resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} + engines: {node: '>=0.10.0'} + + katex@0.16.27: + resolution: {integrity: sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + keyv@5.5.5: + resolution: {integrity: sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==} + + kind-of@3.2.2: + resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} + engines: {node: '>=0.10.0'} + + kind-of@4.0.0: + resolution: {integrity: sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==} + engines: {node: '>=0.10.0'} + + kind-of@5.1.0: + resolution: {integrity: sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==} + engines: {node: '>=0.10.0'} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + less@4.5.1: + resolution: {integrity: sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==} + engines: {node: '>=14'} + hasBin: true + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.0.0: + resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lines-and-columns@2.0.4: + resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + lint-staged@15.2.2: + resolution: {integrity: sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==} + engines: {node: '>=18.12.0'} + hasBin: true + + listr2@8.0.1: + resolution: {integrity: sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==} + engines: {node: '>=18.0.0'} + + load-json-file@4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + + loader-utils@1.4.2: + resolution: {integrity: sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==} + engines: {node: '>=4.0.0'} + + local-pkg@0.4.3: + resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} + engines: {node: '>=14'} + + local-pkg@0.5.1: + resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} + engines: {node: '>=14'} + + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lodash-es@4.17.22: + resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.get@4.4.2: + resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. + + lodash.isfunction@3.0.9: + resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.kebabcase@4.1.1: + resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} + + lodash.map@4.6.0: + resolution: {integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.mergewith@4.6.2: + resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + + lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + + lodash.truncate@4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + lodash.upperfirst@4.3.1: + resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + longest@2.0.1: + resolution: {integrity: sha512-Ajzxb8CM6WAnFjgiloPsI3bF+WCxcvhdIG3KNA2KN962+tdBsHcuQ4k4qX/EcS/2CRkcc0iAkR956Nib6aXU/Q==} + engines: {node: '>=0.10.0'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.2.4: + resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + lunar-javascript@1.7.7: + resolution: {integrity: sha512-u/KYiwPIBo/0bT+WWfU7qO1d+aqeB90Tuy4ErXenr2Gam0QcWeezUvtiOIyXR7HbVnW2I1DKfU0NBvzMZhbVQw==} + + luxon@3.7.2: + resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} + engines: {node: '>=12'} + + magic-regexp@0.10.0: + resolution: {integrity: sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg==} + + magic-string@0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + make-dir@2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} + engines: {node: '>=6'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + map-cache@0.2.2: + resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} + engines: {node: '>=0.10.0'} + + map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + + map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + + map-visit@1.0.0: + resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} + engines: {node: '>=0.10.0'} + + markdown-it-link-attributes@4.0.1: + resolution: {integrity: sha512-pg5OK0jPLg62H4k7M9mRJLT61gUp9nvG0XveKYHMOOluASo9OEF13WlXrpAp2aj35LbedAy3QOCgQCw0tkLKAQ==} + + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + + material-colors@1.2.6: + resolution: {integrity: sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mathml-tag-names@2.1.3: + resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} + + md5@2.3.0: + resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} + + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + + medium-editor@5.23.3: + resolution: {integrity: sha512-he9/TdjX8f8MGdXGfCs8AllrYnqXJJvjNkDKmPg3aPW/uoIrlRqtkFthrwvmd+u4QyzEiadhCCM0EwTiRdUCJw==} + + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + + meow@12.1.1: + resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} + engines: {node: '>=16.10'} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + + meow@8.1.2: + resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} + engines: {node: '>=10'} + + merge-options@1.0.1: + resolution: {integrity: sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==} + engines: {node: '>=4'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + merge@2.1.1: + resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==} + + micromatch@3.1.0: + resolution: {integrity: sha512-3StSelAE+hnRvMs8IdVW7Uhk8CVed5tp+kLLGlBP6WiRAXS21GPGu/Nat4WNPXj2Eoc24B02SaeoyozPMfj0/g==} + engines: {node: '>=0.10.0'} + + micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + engines: {node: 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@7.4.9: + resolution: {integrity: sha512-Brg/fp/iAVDOQoHxkuN5bEYhyQlZhxddI78yWsCbeEwTHXQjlNLtiJDUsp1GIptVqMI7/gkJMz4vVAc01mpoBw==} + engines: {node: '>=10'} + + minimatch@9.0.1: + resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + + minimist@1.2.7: + resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mixin-deep@1.3.2: + resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} + engines: {node: '>=0.10.0'} + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + mobx-preact@3.0.0: + resolution: {integrity: sha512-ijan/cBs3WmRye87E5+3JmoFBB00KDAwNA3pm7bMwYLPHBAXlN86aC3gdrXw8aKzM5RI8V3a993PphzPv6P4FA==} + peerDependencies: + mobx: 5.x + preact: '>=8' + + mobx-utils@5.6.2: + resolution: {integrity: sha512-a/WlXyGkp6F12b01sTarENpxbmlRgPHFyR1Xv2bsSjQBm5dcOtd16ONb40/vOqck8L99NHpI+C9MXQ+SZ8f+yw==} + peerDependencies: + mobx: ^4.13.1 || ^5.13.1 + + mobx@5.15.7: + resolution: {integrity: sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==} + + mockjs@1.1.0: + resolution: {integrity: sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==} + hasBin: true + + mousetrap@1.6.5: + resolution: {integrity: sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.3.1: + resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==} + + mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + + mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanomatch@1.2.13: + resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} + engines: {node: '>=0.10.0'} + + nanopop@2.4.2: + resolution: {integrity: sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + needle@3.3.1: + resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==} + engines: {node: '>= 4.4.x'} + hasBin: true + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + nice-try@1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-html-parser@5.4.2: + resolution: {integrity: sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==} + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + + normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + + normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + + normalize-package-data@6.0.2: + resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} + engines: {node: ^16.14.0 || >=18.0.0} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-all@4.1.5: + resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==} + engines: {node: '>= 4'} + hasBin: true + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + nprogress@0.2.0: + resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-copy@0.1.0: + resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object-visit@1.0.1: + resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==} + engines: {node: '>=0.10.0'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.pick@1.3.0: + resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} + engines: {node: '>=0.10.0'} + + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + opener@1.5.2: + resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} + hasBin: true + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + oxc-parser@0.131.0: + resolution: {integrity: sha512-SJ3/7ZPbgie8dr5Z9BI/M51zZbpXba+hRSG0MDzVwMW5CRQg2fjYE0jHGlLX4eeiibGgC/mzoDFKSDHwVZEHRQ==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-walker@0.7.0: + resolution: {integrity: sha512-54B4KUhrzbzc4sKvKwVYm7E2PgeROpGba0/2nlNZMqfDyca+yOor5IMb4WLGBatGDT0nkzYdYuzylg7n3YfB7A==} + peerDependencies: + oxc-parser: '>=0.98.0' + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + package-manager-detector@0.2.11: + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + + param-case@3.0.4: + resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-json@7.1.1: + resolution: {integrity: sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==} + engines: {node: '>=16'} + + parse-node-version@1.0.1: + resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} + engines: {node: '>= 0.10'} + + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + + parse5-htmlparser2-tree-adapter@7.1.0: + resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} + + parse5-parser-stream@7.1.2: + resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + + pascalcase@0.1.1: + resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} + engines: {node: '>=0.10.0'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} + engines: {node: 20 || >=22} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + path-type@3.0.0: + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} + engines: {node: '>=4'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@0.2.0: + resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + + perfect-scrollbar@1.5.6: + resolution: {integrity: sha512-rixgxw3SxyJbCaSpo1n35A/fwI1r2rdwMKOTCg/AcG+xOEyZcE8UHVjpZMFCVImzsFoCZeJTT+M/rdEIQYO2nw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pidtree@0.3.1: + resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==} + engines: {node: '>=0.10'} + hasBin: true + + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + pinia@2.1.7: + resolution: {integrity: sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==} + peerDependencies: + '@vue/composition-api': ^1.4.0 + typescript: '>=4.4.4' + vue: ^2.6.14 || ^3.3.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + typescript: + optional: true + + pinyin-pro@3.28.0: + resolution: {integrity: sha512-mMRty6RisoyYNphJrTo3pnvp3w8OMZBrXm9YSWkxhAfxKj1KZk2y8T2PDIZlDDRsvZ0No+Hz6FI4sZpA6Ey25g==} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + + portfinder@1.0.38: + resolution: {integrity: sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==} + engines: {node: '>= 10.12'} + + posix-character-classes@0.1.1: + resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} + engines: {node: '>=0.10.0'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-html@1.8.1: + resolution: {integrity: sha512-OLF6P7qctfAWayOhLpcVnTGqVeJzu2W3WpIYelfz2+JV5oGxfkcEvweN9U4XpeqE0P98dcD9ssusGwlF0TK0uQ==} + engines: {node: ^12 || >=14} + + postcss-less@6.0.0: + resolution: {integrity: sha512-FPX16mQLyEjLzEuuJtxA8X3ejDLNGGEG503d2YGZR5Ask1SpDN8KmZUMpzCvyalWRywAn1n1VOA5dcqfCLo5rg==} + engines: {node: '>=12'} + peerDependencies: + postcss: ^8.3.5 + + postcss-prefix-selector@1.16.1: + resolution: {integrity: sha512-Umxu+FvKMwlY6TyDzGFoSUnzW+NOfMBLyC1tAkIjgX+Z/qGspJeRjVC903D7mx7TuBpJlwti2ibXtWuA7fKMeQ==} + peerDependencies: + postcss: '>4 <9' + + postcss-resolve-nested-selector@0.1.6: + resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==} + + postcss-safe-parser@6.0.0: + resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.3.3 + + postcss-safe-parser@7.0.1: + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.4.31 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} + + postcss-sorting@8.0.2: + resolution: {integrity: sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==} + peerDependencies: + postcss: ^8.4.20 + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@5.2.18: + resolution: {integrity: sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==} + engines: {node: '>=0.12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + posthtml-parser@0.2.1: + resolution: {integrity: sha512-nPC53YMqJnc/+1x4fRYFfm81KV2V+G9NZY+hTohpYg64Ay7NemWWcV4UWuy/SgMupqQ3kJ88M/iRfZmSnxT+pw==} + + posthtml-rename-id@1.0.12: + resolution: {integrity: sha512-UKXf9OF/no8WZo9edRzvuMenb6AD5hDLzIepJW+a4oJT+T/Lx7vfMYWT4aWlGNQh0WMhnUx1ipN9OkZ9q+ddEw==} + + posthtml-render@1.4.0: + resolution: {integrity: sha512-W1779iVHGfq0Fvh2PROhCe2QhB8mEErgqzo1wpIt36tCgChafP+hbXIhLDOM8ePJrZcFs0vkNEtdibEWVqChqw==} + engines: {node: '>=10'} + + posthtml-svg-mode@1.0.3: + resolution: {integrity: sha512-hEqw9NHZ9YgJ2/0G7CECOeuLQKZi8HjWLkBaSVtOWjygQ9ZD8P7tqeowYs7WrFdKsWEKG7o+IlsPY8jrr0CJpQ==} + + posthtml@0.9.2: + resolution: {integrity: sha512-spBB5sgC4cv2YcW03f/IAUN1pgDJWNWD8FzkyY4mArLUMJW+KlQhlmUdKAHQuPfb00Jl5xIfImeOsf6YL8QK7Q==} + engines: {node: '>=0.10.0'} + + preact@10.28.2: + resolution: {integrity: sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.1: + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} + engines: {node: '>=6.0.0'} + + prettier@3.8.0: + resolution: {integrity: sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==} + engines: {node: '>=14'} + hasBin: true + + pretty-bytes@5.6.0: + resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} + engines: {node: '>=6'} + + pretty-bytes@6.1.1: + resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} + engines: {node: ^14.13.1 || >=16.0.0} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + pretty-quick@4.2.2: + resolution: {integrity: sha512-uAh96tBW1SsD34VhhDmWuEmqbpfYc/B3j++5MC/6b3Cb8Ow7NJsvKFhg0eoGu2xXX+o9RkahkTK6sUdd8E7g5w==} + engines: {node: '>=14'} + hasBin: true + peerDependencies: + prettier: ^3.0.0 + + print-js@1.6.0: + resolution: {integrity: sha512-BfnOIzSKbqGRtO4o0rnj/K3681BSd2QUrsIZy/+WdCIugjIswjmx3lDEZpXB2ruGf9d4b3YNINri81+J0FsBWg==} + + promise-polyfill@7.1.2: + resolution: {integrity: sha512-FuEc12/eKqqoRYIGBrUptCBRhobL19PS2U31vMNTfyck1FxPyMfgsXyW4Mav85y/ZN1hop3hOwRlUDok23oYfQ==} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + prr@1.0.1: + resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + qified@0.6.0: + resolution: {integrity: sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA==} + engines: {node: '>=20'} + + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} + hasBin: true + + qs@6.14.1: + resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} + engines: {node: '>=0.6'} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + + query-string@4.3.4: + resolution: {integrity: sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==} + engines: {node: '>=0.10.0'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quick-lru@4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + rangy@1.3.2: + resolution: {integrity: sha512-fS1C4MOyk8T+ZJZdLcgrukPWxkyDXa+Hd2Kj+Zg4wIK71yrWgmjzHubzPMY1G+WD9EgGxMp3fIL0zQ1ickmSWA==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + read-pkg-up@10.1.0: + resolution: {integrity: sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==} + engines: {node: '>=16'} + + read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + + read-pkg@3.0.0: + resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} + engines: {node: '>=4'} + + read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + + read-pkg@8.1.0: + resolution: {integrity: sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==} + engines: {node: '>=16'} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regenerate-unicode-properties@10.2.2: + resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regenerator-runtime@0.11.1: + resolution: {integrity: sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==} + + regex-not@1.0.2: + resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} + engines: {node: '>=0.10.0'} + + regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + regexpu-core@6.4.0: + resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.13.0: + resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} + hasBin: true + + relateurl@0.2.7: + resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} + engines: {node: '>= 0.10'} + + repeat-element@1.1.4: + resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==} + engines: {node: '>=0.10.0'} + + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-dir@1.0.1: + resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-global@1.0.0: + resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve-url@0.2.1: + resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} + deprecated: https://github.com/lydell/resolve-url#deprecated + + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + ret@0.1.15: + resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} + engines: {node: '>=0.12'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + + rollup-plugin-purge-icons@0.10.0: + resolution: {integrity: sha512-GD2ftg4L9G/sagIhtCmBn5vdyzePOisniythubpbywP0Q3ix9rZuDeFvgXTPemOsc22pvH7t22ryYQIl0rwGog==} + engines: {node: '>= 12'} + + rollup-plugin-visualizer@5.14.0: + resolution: {integrity: sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + rolldown: 1.x + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rolldown: + optional: true + rollup: + optional: true + + rollup@2.79.2: + resolution: {integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==} + engines: {node: '>=10.0.0'} + hasBin: true + + rollup@4.52.5: + resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + + run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safe-regex@1.1.0: + resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sax@1.4.4: + resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} + engines: {node: '>=11.0.0'} + + scroll-into-view-if-needed@2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + + secure-compare@3.0.1: + resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==} + + select@1.1.2: + resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} + engines: {node: '>=10'} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + set-value@2.0.1: + resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} + engines: {node: '>=0.10.0'} + + shallow-equal@1.2.1: + resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==} + + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + showdown@2.1.0: + resolution: {integrity: sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==} + hasBin: true + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + smob@1.5.0: + resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + + snapdragon-node@2.1.1: + resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} + engines: {node: '>=0.10.0'} + + snapdragon-util@3.0.1: + resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==} + engines: {node: '>=0.10.0'} + + snapdragon@0.8.2: + resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} + engines: {node: '>=0.10.0'} + + sortablejs@1.14.0: + resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==} + + sortablejs@1.15.6: + resolution: {integrity: sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-resolve@0.5.3: + resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} + deprecated: See https://github.com/lydell/source-map-resolve#deprecated + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map-url@0.4.1: + resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} + deprecated: See https://github.com/lydell/source-map-url#deprecated + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions + + sourcemap-codec@1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.22: + resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} + + split-string@3.1.0: + resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} + engines: {node: '>=0.10.0'} + + split2@3.2.2: + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stable@0.1.8: + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + static-extend@0.1.2: + resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} + engines: {node: '>=0.10.0'} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + strict-uri-encode@1.1.0: + resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==} + engines: {node: '>=0.10.0'} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.padend@3.1.6: + resolution: {integrity: sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==} + engines: {node: '>= 0.4'} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + stringify-object@3.3.0: + resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} + engines: {node: '>=4'} + + strip-ansi@3.0.1: + resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} + engines: {node: '>=0.10.0'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-comments@2.0.1: + resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==} + engines: {node: '>=10'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + stylelint-config-html@1.1.0: + resolution: {integrity: sha512-IZv4IVESjKLumUGi+HWeb7skgO6/g4VMuAYrJdlqQFndgbj6WJAXPhaysvBiXefX79upBdQVumgYcdd17gCpjQ==} + engines: {node: ^12 || >=14} + peerDependencies: + postcss-html: ^1.0.0 + stylelint: '>=14.0.0' + + stylelint-config-prettier@9.0.5: + resolution: {integrity: sha512-U44lELgLZhbAD/xy/vncZ2Pq8sh2TnpiPvo38Ifg9+zeioR+LAkHu0i6YORIOxFafZoVg0xqQwex6e6F25S5XA==} + engines: {node: '>= 12'} + hasBin: true + peerDependencies: + stylelint: '>= 11.x < 15' + + stylelint-config-recommended-vue@1.6.1: + resolution: {integrity: sha512-lLW7hTIMBiTfjenGuDq2kyHA6fBWd/+Df7MO4/AWOxiFeXP9clbpKgg27kHfwA3H7UNMGC7aeP3mNlZB5LMmEQ==} + engines: {node: ^12 || >=14} + peerDependencies: + postcss-html: ^1.0.0 + stylelint: '>=14.0.0' + + stylelint-config-recommended@14.0.1: + resolution: {integrity: sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.1.0 + + stylelint-config-standard@36.0.1: + resolution: {integrity: sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.1.0 + + stylelint-order@6.0.4: + resolution: {integrity: sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA==} + peerDependencies: + stylelint: ^14.0.0 || ^15.0.0 || ^16.0.1 + + stylelint@16.26.1: + resolution: {integrity: sha512-v20V59/crfc8sVTAtge0mdafI3AdnzQ2KsWe6v523L4OA1bJO02S7MO2oyXDCS6iWb9ckIPnqAFVItqSBQr7jw==} + engines: {node: '>=18.12.0'} + hasBin: true + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + + supports-color@2.0.0: + resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} + engines: {node: '>=0.8.0'} + + supports-color@3.2.3: + resolution: {integrity: sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==} + engines: {node: '>=0.8.0'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-hyperlinks@3.2.0: + resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==} + engines: {node: '>=14.18'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + svg-baker@1.7.0: + resolution: {integrity: sha512-nibslMbkXOIkqKVrfcncwha45f97fGuAOn1G99YwnwTj8kF9YiM6XexPcUso97NxOm6GsP0SIvYVIosBis1xLg==} + + svg-tags@1.0.0: + resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} + + svgo@2.8.0: + resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} + engines: {node: '>=10.13.0'} + hasBin: true + + swagger-ui-dist@5.31.0: + resolution: {integrity: sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==} + + synckit@0.11.12: + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} + engines: {node: ^14.18.0 || >=16.0.0} + + table@6.9.0: + resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} + engines: {node: '>=10.0.0'} + + temp-dir@2.0.0: + resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} + engines: {node: '>=8'} + + temp-dir@3.0.0: + resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} + engines: {node: '>=14.16'} + + tempfile@5.0.0: + resolution: {integrity: sha512-bX655WZI/F7EoTDw9JvQURqAXiPHi8o8+yFxPF2lWYyz1aHnmMRuXWqL6YB6GmeO0o4DIYWHLgGNi/X64T+X4Q==} + engines: {node: '>=14.18'} + + tempy@0.6.0: + resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==} + engines: {node: '>=10'} + + terser@5.46.0: + resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==} + engines: {node: '>=10'} + hasBin: true + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + text-extensions@2.4.0: + resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} + engines: {node: '>=8'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + throttle-debounce@5.0.2: + resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} + engines: {node: '>=12.22'} + + through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tiny-emitter@2.1.0: + resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} + + tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinymce@5.10.9: + resolution: {integrity: sha512-5bkrors87X9LhYX2xq8GgPHrIgJYHl87YNs+kBcjQ5I3CiUgzo/vFcGvT3MZQ9QHsEeYMhYO6a5CLGGffR8hMg==} + + tinymce@6.6.2: + resolution: {integrity: sha512-ShoaznNP3qI8dPtEnYt3ByhAJfMhzIY1K04CoFu1IPDeAxmAZCUJLgfiplo8etP4wN8zrBIxHEqpwYYb2IllOQ==} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-object-path@0.3.0: + resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} + engines: {node: '>=0.10.0'} + + to-regex-range@2.1.1: + resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==} + engines: {node: '>=0.10.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + to-regex@3.0.2: + resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==} + engines: {node: '>=0.10.0'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + + traverse@0.6.11: + resolution: {integrity: sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==} + engines: {node: '>= 0.4'} + + trim-newlines@3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-jest@29.4.6: + resolution: {integrity: sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 || ^30.0.0 + '@jest/types': ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + esbuild: '*' + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsutils@3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.16.0: + resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} + engines: {node: '>=10'} + + type-fest@0.18.1: + resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} + engines: {node: '>=10'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + + type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + + type-fest@3.13.1: + resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} + engines: {node: '>=14.16'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + type-level-regexp@0.1.17: + resolution: {integrity: sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typedarray.prototype.slice@1.0.5: + resolution: {integrity: sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==} + engines: {node: '>= 0.4'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + + unconfig@7.5.0: + resolution: {integrity: sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + undici@7.18.2: + resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==} + engines: {node: '>=20.18.1'} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.1: + resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.2.0: + resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} + engines: {node: '>=4'} + + union-value@1.0.1: + resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} + engines: {node: '>=0.10.0'} + + union@0.5.0: + resolution: {integrity: sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==} + engines: {node: '>= 0.8.0'} + + unique-string@2.0.0: + resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} + engines: {node: '>=8'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unocss@66.7.0: + resolution: {integrity: sha512-dVfkL7SQv3fOiZdqeRX1PdpQqXlB+wHcEQjVR0D/2nXsuqmUqTVnX3EUUWFjqVR83Z51zQ0EyVgym8ooDfcVvw==} + peerDependencies: + '@unocss/astro': 66.7.0 + '@unocss/postcss': 66.7.0 + '@unocss/webpack': 66.7.0 + peerDependenciesMeta: + '@unocss/astro': + optional: true + '@unocss/postcss': + optional: true + '@unocss/webpack': + optional: true + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + unplugin-icons@0.22.0: + resolution: {integrity: sha512-CP+iZq5U7doOifer5bcM0jQ9t3Is7EGybIYt3myVxceI8Zuk8EZEpe1NPtJvh7iqMs1VdbK0L41t9+um9VuuLw==} + peerDependencies: + '@svgr/core': '>=7.0.0' + '@svgx/core': ^1.0.1 + '@vue/compiler-sfc': ^3.0.2 || ^2.7.0 + svelte: ^3.0.0 || ^4.0.0 || ^5.0.0 + vue-template-compiler: ^2.6.12 + vue-template-es2015-compiler: ^1.9.0 + peerDependenciesMeta: + '@svgr/core': + optional: true + '@svgx/core': + optional: true + '@vue/compiler-sfc': + optional: true + svelte: + optional: true + vue-template-compiler: + optional: true + vue-template-es2015-compiler: + optional: true + + unplugin-utils@0.3.1: + resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} + engines: {node: '>=20.19.0'} + + unplugin-vue-components@0.24.1: + resolution: {integrity: sha512-T3A8HkZoIE1Cja95xNqolwza0yD5IVlgZZ1PVAGvVCx8xthmjsv38xWRCtHtwl+rvZyL9uif42SRkDGw9aCfMA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/parser': ^7.15.8 + '@nuxt/kit': ^3.2.2 + vue: 2 || 3 + peerDependenciesMeta: + '@babel/parser': + optional: true + '@nuxt/kit': + optional: true + + unplugin@1.16.1: + resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} + engines: {node: '>=14.0.0'} + + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + + unset-value@1.0.0: + resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==} + engines: {node: '>=0.10.0'} + + upath@1.2.0: + resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} + engines: {node: '>=4'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + urix@0.1.0: + resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} + deprecated: Please see https://github.com/lydell/urix#deprecated + + url-join@4.0.1: + resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} + + use@3.1.1: + resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} + engines: {node: '>=0.10.0'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + vanilla-picker@2.12.3: + resolution: {integrity: sha512-qVkT1E7yMbUsB2mmJNFmaXMWE2hF8ffqzMMwe9zdAikd8u2VfnsVY2HQcOUi2F38bgbxzlJBEdS1UUhOXdF9GQ==} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vditor@3.11.2: + resolution: {integrity: sha512-8QguQQUPWbBFocnfQmWjz4jiykQnvsmCuhOomGIVVK7vc+dQq2h8w9qQQuEjUTZpnZT5fEdYbj4aLr1NGdAZaA==} + + vite-plugin-compression@0.5.1: + resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==} + peerDependencies: + vite: '>=2.0.0' + + vite-plugin-html@3.2.2: + resolution: {integrity: sha512-vb9C9kcdzcIo/Oc3CLZVS03dL5pDlOFuhGlZYDCJ840BhWl/0nGeZWf3Qy7NlOayscY4Cm/QRgULCQkEZige5Q==} + peerDependencies: + vite: '>=2.0.0' + + vite-plugin-mkcert@1.17.9: + resolution: {integrity: sha512-SwI7yqp2Cq4r2XItarnHRCj2uzHPqevbxFNMLpyN+LDXd5w1vmZeM4l5X/wCZoP4mjPQYN+9+4kmE6e3nPO5fg==} + engines: {node: '>=v16.7.0'} + peerDependencies: + vite: '>=3' + + vite-plugin-mock@2.9.8: + resolution: {integrity: sha512-YTQM5Sn7t+/DNOwTkr+W26QGTCk1PrDkhGHslTJ90lIPJhJtDTwuSkEYMAuLP9TcVQ/qExTFx/x/GE3kxJ05sw==} + engines: {node: '>=12.0.0'} + peerDependencies: + mockjs: '>=1.1.0' + vite: '>=2.0.0' + + vite-plugin-optimize-persist@0.1.2: + resolution: {integrity: sha512-H/Ebn2kZO8PvwUF08SsT5K5xMJNCWKoGX71+e9/ER3yNj7GHiFjNQlvGg5ih/zEx09MZ9m7WCxOwmEKbeIVzww==} + peerDependencies: + vite: ^2.0.0 + vite-plugin-package-config: ^0.1.0 + + vite-plugin-package-config@0.1.1: + resolution: {integrity: sha512-w9B3I8ZnqoyhlbzimXjXNk85imrMZgvI9m8f6j3zonK5IVA5KXzpT+PZOHlDz8lqh1vqvoEI1uhy+ZDoLAiA/w==} + peerDependencies: + vite: ^2.0.0 + + vite-plugin-purge-icons@0.10.0: + resolution: {integrity: sha512-4fMJKQuBu9lAPJWjqGEytRaxty1pP9bWgQLA68dwbbaCXu6NBrOUb/3kMaUc7TP09kerEk+qTriCk05OZXpjwA==} + engines: {node: '>= 12'} + peerDependencies: + vite: '>=2' + + vite-plugin-pwa@1.2.0: + resolution: {integrity: sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@vite-pwa/assets-generator': ^1.0.0 + vite: ^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + workbox-build: ^7.4.0 + workbox-window: ^7.4.0 + peerDependenciesMeta: + '@vite-pwa/assets-generator': + optional: true + + vite-plugin-qiankun@1.0.15: + resolution: {integrity: sha512-0QB0Wr8Eu/LGcuJAfuNXDb7BAFDszo3GCxq4bzgXdSFAlK425u1/UGMxaDEBVA1uPFrLsZPzig83Ufdfl6J45A==} + peerDependencies: + typescript: '>=4' + vite: '>=2' + + vite-plugin-svg-icons@2.0.1: + resolution: {integrity: sha512-6ktD+DhV6Rz3VtedYvBKKVA2eXF+sAQVaKkKLDSqGUfnhqXl3bj5PPkVTl3VexfTuZy66PmINi8Q6eFnVfRUmA==} + peerDependencies: + vite: '>=2.0.0' + + vite-plugin-vue-setup-extend-plus@0.1.0: + resolution: {integrity: sha512-pa27KIsHIBvBMv4xz9uB3UCfAuP2tr7PLlFhCS9vw+aXd326LEHsvhqd3hCQDOR5MjlQVyQH6vwuGr3u+KRiiw==} + + vite@7.3.5: + resolution: {integrity: sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vue-color@3.3.3: + resolution: {integrity: sha512-FW/9JXFbCHfFEGv6SFUuK/SE+K61pHoL7lxJMQXcU+Zt7s36Ui1/tMDc3vzGXPrxxRBB+NSzFt+71nEaUJ29dQ==} + peerDependencies: + vue: '>=2.7.0 <4.0.0' + + vue-component-type-helpers@2.2.12: + resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==} + + vue-cropper@0.6.5: + resolution: {integrity: sha512-lSvY6IpeA/Tv/iPZ/FOkMHVRBPSlm7t57nuHEZFBMRNOH8ElvfqVlnHGDOAMlvPhh9gHiddiQoASS+fY0MFX0g==} + + vue-cropperjs@5.0.0: + resolution: {integrity: sha512-RhnC8O33uRZNkn74aiHZwNHnBJOXWlS4P6gsRI0lw4cZlWjKSCywZI9oSI9POlIPI6OYv30jvnHMXGch85tw7w==} + peerDependencies: + vue: '>=3.0.0' + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-eslint-parser@9.4.3: + resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + vue-grid-layout-v3@3.1.2: + resolution: {integrity: sha512-4VB6pel2OklFNnFSUVg4wXwx/fKuKKvnhleLyscwk5ay7aEFk+HghcVKbComg+EgMvjuPzZ7hgIES3FlxuVK6w==} + + vue-i18n@9.14.5: + resolution: {integrity: sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==} + engines: {node: '>= 16'} + deprecated: v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html + peerDependencies: + vue: ^3.0.0 + + vue-infinite-scroll@2.0.2: + resolution: {integrity: sha512-n+YghR059YmciANGJh9SsNWRi1YZEBVlODtmnb/12zI+4R72QZSWd+EuZ5mW6auEo/yaJXgxzwsuhvALVnm73A==} + + vue-print-nb-jeecg@1.0.13: + resolution: {integrity: sha512-8Yv8OCJQyFEI6hUfAKYWHa6LvAQi+xYnwFvxd/WXsI6exHhBfde26iyHyI42QCZpH6zURUq0oA0SAk8EpYiRng==} + + vue-router@4.6.4: + resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==} + peerDependencies: + vue: ^3.5.0 + + vue-template-compiler@2.7.16: + resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==} + + vue-tsc@1.8.27: + resolution: {integrity: sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==} + hasBin: true + peerDependencies: + typescript: '*' + + vue-types@3.0.2: + resolution: {integrity: sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==} + engines: {node: '>=10.15.0'} + peerDependencies: + vue: ^3.0.0 + + vue-types@5.1.3: + resolution: {integrity: sha512-3Wy6QcZl0VusCCHX3vYrWSILFlrOB2EQDoySnuYmASM5cUp1FivJGfkS5lp1CutDgyRb41g32r/1QCmiBj5i1Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + vue: ^2.0.0 || ^3.0.0 + peerDependenciesMeta: + vue: + optional: true + + vue@3.5.27: + resolution: {integrity: sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + vuedraggable@4.1.0: + resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==} + peerDependencies: + vue: ^3.0.1 + + vxe-pc-ui@4.6.12: + resolution: {integrity: sha512-57sRB/ksP8ip4l0hPcph5qXt/qGlrCjO2/Y6ZL4sHkGdb+CBWgbzvUPcq3GYgSSPdZg+Ae++UcGqgRGMZss+RQ==} + + vxe-table-plugin-antd@4.0.8: + resolution: {integrity: sha512-/ZGw8Iz0R6YfDnf7FL3A0VZpQnxEjRnfE0DW4dQTuLnFCP6UmQqQuKVWU9Vj7vPGM3x3p8rwAVHtU8YtMCXZqQ==} + peerDependencies: + vxe-table: ^4.5.0 + + vxe-table@4.13.31: + resolution: {integrity: sha512-ibSM7jXYwJyY+eqXoRy/yXEVLENGFzL96cOEwtnFjBYbbaZV6/ptlM3tsyewGFBCUo5AtIyM+98hswxfjyXxMA==} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + warning@4.0.3: + resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + whatwg-encoding@2.0.0: + resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + workbox-background-sync@7.4.0: + resolution: {integrity: sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w==} + + workbox-broadcast-update@7.4.0: + resolution: {integrity: sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA==} + + workbox-build@7.4.0: + resolution: {integrity: sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA==} + engines: {node: '>=20.0.0'} + + workbox-cacheable-response@7.4.0: + resolution: {integrity: sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ==} + + workbox-core@7.4.0: + resolution: {integrity: sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ==} + + workbox-expiration@7.4.0: + resolution: {integrity: sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw==} + + workbox-google-analytics@7.4.0: + resolution: {integrity: sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ==} + + workbox-navigation-preload@7.4.0: + resolution: {integrity: sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w==} + + workbox-precaching@7.4.0: + resolution: {integrity: sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg==} + + workbox-range-requests@7.4.0: + resolution: {integrity: sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw==} + + workbox-recipes@7.4.0: + resolution: {integrity: sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ==} + + workbox-routing@7.4.0: + resolution: {integrity: sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ==} + + workbox-strategies@7.4.0: + resolution: {integrity: sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg==} + + workbox-streams@7.4.0: + resolution: {integrity: sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg==} + + workbox-sw@7.4.0: + resolution: {integrity: sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw==} + + workbox-window@7.4.0: + resolution: {integrity: sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + xe-utils@3.5.26: + resolution: {integrity: sha512-u9R7RqWDumamToEelrCv2nVA2PBJSPPUubvmiMcuHeFxwbYeBsouoi/opejmr7AdPlSj92FifF7IKFzFrczU7w==} + + xe-utils@3.8.4: + resolution: {integrity: sha512-1X5k3nUcMatNo+99fZsHFrZ/WW4k4E6tT1ODO6wRncXOdq10QfVaqc70mFwOrHE/P1W8hDhbrnWXxSHwWY3rFQ==} + + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + + xss@1.0.15: + resolution: {integrity: sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==} + engines: {node: '>= 0.10.0'} + hasBin: true + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yaml@2.3.4: + resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} + engines: {node: '>= 14'} + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + + zrender@5.6.1: + resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==} + +snapshots: + + '@ant-design/colors@6.0.0': + dependencies: + '@ctrl/tinycolor': 3.6.1 + + '@ant-design/colors@7.2.1': + dependencies: + '@ant-design/fast-color': 2.0.6 + + '@ant-design/fast-color@2.0.6': + dependencies: + '@babel/runtime': 7.28.6 + + '@ant-design/icons-svg@4.4.2': {} + + '@ant-design/icons-vue@7.0.1(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@ant-design/colors': 6.0.0 + '@ant-design/icons-svg': 4.4.2 + vue: 3.5.27(typescript@5.9.3) + + '@antfu/install-pkg@0.5.0': + dependencies: + package-manager-detector: 0.2.11 + tinyexec: 0.3.2 + + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.6.0 + tinyexec: 1.0.2 + + '@antfu/utils@0.7.10': {} + + '@antfu/utils@8.1.1': {} + + '@antv/hierarchy@0.6.14': {} + + '@apideck/better-ajv-errors@0.3.6(ajv@8.17.1)': + dependencies: + ajv: 8.17.1 + json-schema: 0.4.0 + jsonpointer: 5.0.1 + leven: 3.1.0 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.7 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + regexpu-core: 6.4.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + debug: 4.4.3 + lodash.debounce: 4.0.8 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-member-expression-to-functions@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-wrap-function': 7.28.6 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helper-wrap-function@7.28.6': + dependencies: + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.28.6': + dependencies: + '@babel/types': 7.28.6 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-async-generator-functions@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.7) + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/template': 7.29.7 + + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.28.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-regenerator@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/preset-env@7.28.6(@babel/core@7.29.7)': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.7) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-async-generator-functions': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-systemjs': 7.28.5(@babel/core@7.29.7) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-regenerator': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.7) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.7) + babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.29.7) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.7) + babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.29.7) + core-js-compat: 3.47.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/types': 7.29.7 + esutils: 2.0.3 + + '@babel/runtime@7.28.6': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.6': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@0.2.3': {} + + '@cacheable/memory@2.0.7': + dependencies: + '@cacheable/utils': 2.3.3 + '@keyv/bigmap': 1.3.0(keyv@5.5.5) + hookified: 1.15.0 + keyv: 5.5.5 + + '@cacheable/utils@2.3.3': + dependencies: + hashery: 1.4.0 + keyv: 5.5.5 + + '@commitlint/cli@18.6.1(@types/node@20.19.30)(typescript@5.9.3)': + dependencies: + '@commitlint/format': 18.6.1 + '@commitlint/lint': 18.6.1 + '@commitlint/load': 18.6.1(@types/node@20.19.30)(typescript@5.9.3) + '@commitlint/read': 18.6.1 + '@commitlint/types': 18.6.1 + execa: 5.1.1 + lodash.isfunction: 3.0.9 + resolve-from: 5.0.0 + resolve-global: 1.0.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/config-conventional@18.6.3': + dependencies: + '@commitlint/types': 18.6.1 + conventional-changelog-conventionalcommits: 7.0.2 + + '@commitlint/config-validator@18.6.1': + dependencies: + '@commitlint/types': 18.6.1 + ajv: 8.17.1 + + '@commitlint/config-validator@20.3.1': + dependencies: + '@commitlint/types': 20.3.1 + ajv: 8.17.1 + optional: true + + '@commitlint/ensure@18.6.1': + dependencies: + '@commitlint/types': 18.6.1 + lodash.camelcase: 4.3.0 + lodash.kebabcase: 4.1.1 + lodash.snakecase: 4.1.1 + lodash.startcase: 4.4.0 + lodash.upperfirst: 4.3.1 + + '@commitlint/execute-rule@18.6.1': {} + + '@commitlint/execute-rule@20.0.0': + optional: true + + '@commitlint/format@18.6.1': + dependencies: + '@commitlint/types': 18.6.1 + chalk: 4.1.2 + + '@commitlint/is-ignored@18.6.1': + dependencies: + '@commitlint/types': 18.6.1 + semver: 7.6.0 + + '@commitlint/lint@18.6.1': + dependencies: + '@commitlint/is-ignored': 18.6.1 + '@commitlint/parse': 18.6.1 + '@commitlint/rules': 18.6.1 + '@commitlint/types': 18.6.1 + + '@commitlint/load@18.6.1(@types/node@20.19.30)(typescript@5.9.3)': + dependencies: + '@commitlint/config-validator': 18.6.1 + '@commitlint/execute-rule': 18.6.1 + '@commitlint/resolve-extends': 18.6.1 + '@commitlint/types': 18.6.1 + chalk: 4.1.2 + cosmiconfig: 8.3.6(typescript@5.9.3) + cosmiconfig-typescript-loader: 5.1.0(@types/node@20.19.30)(cosmiconfig@8.3.6(typescript@5.9.3))(typescript@5.9.3) + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + lodash.uniq: 4.5.0 + resolve-from: 5.0.0 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/load@20.3.1(@types/node@20.19.30)(typescript@5.9.3)': + dependencies: + '@commitlint/config-validator': 20.3.1 + '@commitlint/execute-rule': 20.0.0 + '@commitlint/resolve-extends': 20.3.1 + '@commitlint/types': 20.3.1 + chalk: 5.6.2 + cosmiconfig: 9.0.0(typescript@5.9.3) + cosmiconfig-typescript-loader: 6.2.0(@types/node@20.19.30)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + lodash.uniq: 4.5.0 + transitivePeerDependencies: + - '@types/node' + - typescript + optional: true + + '@commitlint/message@18.6.1': {} + + '@commitlint/parse@18.6.1': + dependencies: + '@commitlint/types': 18.6.1 + conventional-changelog-angular: 7.0.0 + conventional-commits-parser: 5.0.0 + + '@commitlint/read@18.6.1': + dependencies: + '@commitlint/top-level': 18.6.1 + '@commitlint/types': 18.6.1 + git-raw-commits: 2.0.11 + minimist: 1.2.8 + + '@commitlint/resolve-extends@18.6.1': + dependencies: + '@commitlint/config-validator': 18.6.1 + '@commitlint/types': 18.6.1 + import-fresh: 3.3.1 + lodash.mergewith: 4.6.2 + resolve-from: 5.0.0 + resolve-global: 1.0.0 + + '@commitlint/resolve-extends@20.3.1': + dependencies: + '@commitlint/config-validator': 20.3.1 + '@commitlint/types': 20.3.1 + global-directory: 4.0.1 + import-meta-resolve: 4.2.0 + lodash.mergewith: 4.6.2 + resolve-from: 5.0.0 + optional: true + + '@commitlint/rules@18.6.1': + dependencies: + '@commitlint/ensure': 18.6.1 + '@commitlint/message': 18.6.1 + '@commitlint/to-lines': 18.6.1 + '@commitlint/types': 18.6.1 + execa: 5.1.1 + + '@commitlint/to-lines@18.6.1': {} + + '@commitlint/top-level@18.6.1': + dependencies: + find-up: 5.0.0 + + '@commitlint/types@18.6.1': + dependencies: + chalk: 4.1.2 + + '@commitlint/types@20.3.1': + dependencies: + '@types/conventional-commits-parser': 5.0.2 + chalk: 5.6.2 + optional: true + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.25': {} + + '@csstools/css-tokenizer@3.0.4': {} + + '@csstools/media-query-list-parser@4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.1)': + dependencies: + postcss-selector-parser: 7.1.1 + + '@ctrl/tinycolor@3.6.1': {} + + '@dual-bundle/import-meta-resolve@4.2.1': {} + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emotion/hash@0.9.2': {} + + '@emotion/unitless@0.8.1': {} + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.14.54': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@hutson/parse-repository-url@5.0.0': {} + + '@iconify/iconify@2.1.2': + dependencies: + cross-fetch: 3.2.0 + transitivePeerDependencies: + - encoding + + '@iconify/iconify@3.1.1': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/json@2.2.430': + dependencies: + '@iconify/types': 2.0.0 + pathe: 2.0.3 + + '@iconify/types@2.0.0': {} + + '@iconify/utils@2.3.0': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@antfu/utils': 8.1.1 + '@iconify/types': 2.0.0 + debug: 4.4.3 + globals: 15.15.0 + kolorist: 1.8.0 + local-pkg: 1.1.2 + mlly: 1.8.0 + transitivePeerDependencies: + - supports-color + + '@iconify/utils@3.1.3': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + import-meta-resolve: 4.2.0 + + '@inquirer/external-editor@1.0.3(@types/node@20.19.30)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 20.19.30 + + '@inquirer/figures@1.0.15': {} + + '@interactjs/actions@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/core': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + + '@interactjs/auto-scroll@1.10.27(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + + '@interactjs/auto-start@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/core': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + + '@interactjs/core@1.10.27(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/utils': 1.10.27 + + '@interactjs/dev-tools@1.10.27(@interactjs/modifiers@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)(typescript@5.9.3)': + dependencies: + '@interactjs/modifiers': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + vue: 3.5.27(typescript@5.9.3) + transitivePeerDependencies: + - typescript + + '@interactjs/interact@1.10.27': + dependencies: + '@interactjs/core': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + + '@interactjs/modifiers@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/core': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/snappers': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + + '@interactjs/snappers@1.10.27(@interactjs/utils@1.10.27)': + dependencies: + '@interactjs/utils': 1.10.27 + optionalDependencies: + '@interactjs/interact': 1.10.27 + + '@interactjs/utils@1.10.27': {} + + '@intlify/core-base@9.14.5': + dependencies: + '@intlify/message-compiler': 9.14.5 + '@intlify/shared': 9.14.5 + + '@intlify/message-compiler@9.14.5': + dependencies: + '@intlify/shared': 9.14.5 + source-map-js: 1.2.1 + + '@intlify/shared@9.14.5': {} + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.2 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jeecg/aiflow@3.9.2-beta': {} + + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.19.30 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.30 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.30 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.19.30 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/globals@29.7.0': + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@29.7.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.31 + '@types/node': 20.19.30 + chalk: 4.1.2 + collect-v8-coverage: 1.0.3 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.2.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.3 + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.29.7 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.31 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.19.30 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@keyv/bigmap@1.3.0(keyv@5.5.5)': + dependencies: + hashery: 1.4.0 + hookified: 1.15.0 + keyv: 5.5.5 + + '@keyv/serialize@1.1.1': {} + + '@logicflow/core@2.1.9': + dependencies: + classnames: 2.5.1 + lodash-es: 4.17.22 + mobx: 5.15.7 + mobx-preact: 3.0.0(mobx@5.15.7)(preact@10.28.2) + mobx-utils: 5.6.2(mobx@5.15.7) + mousetrap: 1.6.5 + preact: 10.28.2 + uuid: 9.0.1 + + '@logicflow/extension@2.1.11(@logicflow/core@2.1.9)(@logicflow/vue-node-registry@1.1.10(@logicflow/core@2.1.9)(vue@3.5.27(typescript@5.9.3)))': + dependencies: + '@antv/hierarchy': 0.6.14 + '@logicflow/core': 2.1.9 + '@logicflow/vue-node-registry': 1.1.10(@logicflow/core@2.1.9)(vue@3.5.27(typescript@5.9.3)) + classnames: 2.5.1 + lodash-es: 4.17.22 + medium-editor: 5.23.3 + mobx: 5.15.7 + preact: 10.28.2 + rangy: 1.3.2 + vanilla-picker: 2.12.3 + + '@logicflow/vue-node-registry@1.1.10(@logicflow/core@2.1.9)(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@logicflow/core': 2.1.9 + lodash-es: 4.17.22 + vue: 3.5.27(typescript@5.9.3) + vue-demi: 0.14.10(vue@3.5.27(typescript@5.9.3)) + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@one-ini/wasm@0.1.1': {} + + '@oxc-parser/binding-android-arm-eabi@0.131.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.131.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.131.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.131.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.131.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.131.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.131.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.131.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.131.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.131.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.131.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.131.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.131.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.131.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.131.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.131.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.131.0': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.131.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.131.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.131.0': + optional: true + + '@oxc-project/types@0.131.0': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.2.9': {} + + '@polka/url@1.0.0-next.29': {} + + '@purge-icons/core@0.10.0': + dependencies: + '@iconify/iconify': 2.1.2 + axios: 0.26.1(debug@4.4.3) + debug: 4.4.3 + fast-glob: 3.3.3 + fs-extra: 10.1.0 + transitivePeerDependencies: + - encoding + - supports-color + + '@purge-icons/generated@0.10.0': + dependencies: + '@iconify/iconify': 3.1.1 + + '@quansync/fs@1.0.0': + dependencies: + quansync: 1.0.0 + + '@rolldown/pluginutils@1.0.1': {} + + '@rollup/plugin-babel@5.3.1(@babel/core@7.29.7)(@types/babel__core@7.20.5)(rollup@2.79.2)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@rollup/pluginutils': 3.1.0(rollup@2.79.2) + rollup: 2.79.2 + optionalDependencies: + '@types/babel__core': 7.20.5 + transitivePeerDependencies: + - supports-color + + '@rollup/plugin-node-resolve@15.3.1(rollup@2.79.2)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@2.79.2) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.11 + optionalDependencies: + rollup: 2.79.2 + + '@rollup/plugin-replace@2.4.2(rollup@2.79.2)': + dependencies: + '@rollup/pluginutils': 3.1.0(rollup@2.79.2) + magic-string: 0.25.9 + rollup: 2.79.2 + + '@rollup/plugin-terser@0.4.4(rollup@2.79.2)': + dependencies: + serialize-javascript: 6.0.2 + smob: 1.5.0 + terser: 5.46.0 + optionalDependencies: + rollup: 2.79.2 + + '@rollup/pluginutils@3.1.0(rollup@2.79.2)': + dependencies: + '@types/estree': 0.0.39 + estree-walker: 1.0.1 + picomatch: 2.3.1 + rollup: 2.79.2 + + '@rollup/pluginutils@4.2.1': + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.1 + + '@rollup/pluginutils@5.3.0(rollup@2.79.2)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 2.79.2 + + '@rollup/pluginutils@5.3.0(rollup@4.52.5)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.52.5 + + '@rollup/rollup-android-arm-eabi@4.52.5': + optional: true + + '@rollup/rollup-android-arm64@4.52.5': + optional: true + + '@rollup/rollup-darwin-arm64@4.52.5': + optional: true + + '@rollup/rollup-darwin-x64@4.52.5': + optional: true + + '@rollup/rollup-freebsd-arm64@4.52.5': + optional: true + + '@rollup/rollup-freebsd-x64@4.52.5': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.52.5': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.52.5': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.52.5': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-x64-musl@4.52.5': + optional: true + + '@rollup/rollup-openharmony-arm64@4.52.5': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.52.5': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.52.5': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.52.5': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.52.5': + optional: true + + '@scarf/scarf@1.4.0': {} + + '@simonwep/pickr@1.8.2': + dependencies: + core-js: 3.47.0 + nanopop: 2.4.2 + + '@sinclair/typebox@0.27.8': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@sphinxxxx/color-conversion@2.2.2': {} + + '@surma/rollup-plugin-off-main-thread@2.2.3': + dependencies: + ejs: 3.1.10 + json5: 2.2.3 + magic-string: 0.25.9 + string.prototype.matchall: 4.0.12 + + '@tinymce/tinymce-vue@4.0.7(vue@3.5.27(typescript@5.9.3))': + dependencies: + tinymce: 5.10.9 + vue: 3.5.27(typescript@5.9.3) + + '@traptitech/markdown-it-katex@3.6.0': + dependencies: + katex: 0.16.27 + + '@trysound/sax@0.2.0': {} + + '@tsconfig/node10@1.0.12': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/codemirror@5.60.17': + dependencies: + '@types/tern': 0.23.9 + + '@types/conventional-commits-parser@5.0.2': + dependencies: + '@types/node': 20.19.30 + optional: true + + '@types/crypto-js@4.2.2': {} + + '@types/estree@0.0.39': {} + + '@types/estree@1.0.8': {} + + '@types/fs-extra@11.0.4': + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 20.19.30 + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 20.19.30 + + '@types/inquirer@9.0.9': + dependencies: + '@types/through': 0.0.33 + rxjs: 7.8.2 + + '@types/intro.js@5.1.5': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/json-schema@7.0.15': {} + + '@types/jsonfile@6.1.4': + dependencies: + '@types/node': 20.19.30 + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.23 + + '@types/lodash@4.17.23': {} + + '@types/minimist@1.2.5': {} + + '@types/mockjs@1.0.10': {} + + '@types/node@20.19.30': + dependencies: + undici-types: 6.21.0 + + '@types/normalize-package-data@2.4.4': {} + + '@types/nprogress@0.2.3': {} + + '@types/pinyin@2.10.2': {} + + '@types/qrcode@1.5.6': + dependencies: + '@types/node': 20.19.30 + + '@types/qs@6.14.0': {} + + '@types/resolve@1.20.2': {} + + '@types/semver@7.7.1': {} + + '@types/showdown@2.0.6': {} + + '@types/sortablejs@1.15.9': {} + + '@types/stack-utils@2.0.3': {} + + '@types/svgo@2.6.4': + dependencies: + '@types/node': 20.19.30 + + '@types/tern@0.23.9': + dependencies: + '@types/estree': 1.0.8 + + '@types/through@0.0.33': + dependencies: + '@types/node': 20.19.30 + + '@types/trusted-types@2.0.7': {} + + '@types/web-bluetooth@0.0.20': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + semver: 7.7.3 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + + '@typescript-eslint/scope-manager@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + + '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 8.57.1 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@5.62.0': {} + + '@typescript-eslint/types@6.21.0': {} + + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.4.3 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.7.3 + tsutils: 3.21.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.7.3 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.7.1 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.3) + eslint: 8.57.1 + eslint-scope: 5.1.1 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.7.1 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + eslint: 8.57.1 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + eslint-visitor-keys: 3.4.3 + + '@typescript-eslint/visitor-keys@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.3.0': {} + + '@unocss/cli@66.7.0': + dependencies: + '@jridgewell/remapping': 2.3.5 + '@unocss/config': 66.7.0 + '@unocss/core': 66.7.0 + '@unocss/preset-wind3': 66.7.0 + '@unocss/preset-wind4': 66.7.0 + '@unocss/transformer-directives': 66.7.0 + cac: 7.0.0 + chokidar: 5.0.0 + colorette: 2.0.20 + consola: 3.4.2 + magic-string: 0.30.21 + pathe: 2.0.3 + perfect-debounce: 2.1.0 + tinyglobby: 0.2.17 + unplugin-utils: 0.3.1 + + '@unocss/config@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + colorette: 2.0.20 + consola: 3.4.2 + unconfig: 7.5.0 + + '@unocss/core@66.7.0': {} + + '@unocss/extractor-arbitrary-variants@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + + '@unocss/inspector@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + '@unocss/rule-utils': 66.7.0 + colorette: 2.0.20 + gzip-size: 6.0.0 + sirv: 3.0.2 + + '@unocss/preset-attributify@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + + '@unocss/preset-icons@66.7.0': + dependencies: + '@iconify/utils': 3.1.3 + '@unocss/core': 66.7.0 + ofetch: 1.5.1 + + '@unocss/preset-mini@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + '@unocss/extractor-arbitrary-variants': 66.7.0 + '@unocss/rule-utils': 66.7.0 + + '@unocss/preset-tagify@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + + '@unocss/preset-typography@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + '@unocss/rule-utils': 66.7.0 + + '@unocss/preset-uno@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + '@unocss/preset-wind3': 66.7.0 + + '@unocss/preset-web-fonts@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + ofetch: 1.5.1 + + '@unocss/preset-wind3@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + '@unocss/preset-mini': 66.7.0 + '@unocss/rule-utils': 66.7.0 + + '@unocss/preset-wind4@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + '@unocss/extractor-arbitrary-variants': 66.7.0 + '@unocss/rule-utils': 66.7.0 + + '@unocss/preset-wind@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + '@unocss/preset-wind3': 66.7.0 + + '@unocss/rule-utils@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + magic-string: 0.30.21 + + '@unocss/transformer-attributify-jsx@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + oxc-parser: 0.131.0 + oxc-walker: 0.7.0(oxc-parser@0.131.0) + + '@unocss/transformer-compile-class@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + + '@unocss/transformer-directives@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + '@unocss/rule-utils': 66.7.0 + css-tree: 3.2.1 + + '@unocss/transformer-variant-group@66.7.0': + dependencies: + '@unocss/core': 66.7.0 + + '@unocss/vite@66.7.0(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))': + dependencies: + '@jridgewell/remapping': 2.3.5 + '@unocss/config': 66.7.0 + '@unocss/core': 66.7.0 + '@unocss/inspector': 66.7.0 + chokidar: 5.0.0 + magic-string: 0.30.21 + pathe: 2.0.3 + tinyglobby: 0.2.17 + unplugin-utils: 0.3.1 + vite: 7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0) + + '@vant/area-data@1.5.2': {} + + '@vitejs/plugin-vue-jsx@5.1.5(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + '@rolldown/pluginutils': 1.0.1 + '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.7) + vite: 7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0) + vue: 3.5.27(typescript@5.9.3) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-vue@6.0.7(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0) + vue: 3.5.27(typescript@5.9.3) + + '@volar/language-core@1.11.1': + dependencies: + '@volar/source-map': 1.11.1 + + '@volar/source-map@1.11.1': + dependencies: + muggle-string: 0.3.1 + + '@volar/typescript@1.11.1': + dependencies: + '@volar/language-core': 1.11.1 + path-browserify: 1.0.1 + + '@vue/babel-helper-vue-transform-on@2.0.1': {} + + '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.29.7)': + dependencies: + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@vue/babel-helper-vue-transform-on': 2.0.1 + '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.29.7) + '@vue/shared': 3.5.27 + optionalDependencies: + '@babel/core': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.29.7)': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/parser': 7.29.7 + '@vue/compiler-sfc': 3.5.27 + transitivePeerDependencies: + - supports-color + + '@vue/compiler-core@3.5.27': + dependencies: + '@babel/parser': 7.28.6 + '@vue/shared': 3.5.27 + entities: 7.0.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.27': + dependencies: + '@vue/compiler-core': 3.5.27 + '@vue/shared': 3.5.27 + + '@vue/compiler-sfc@3.5.27': + dependencies: + '@babel/parser': 7.28.6 + '@vue/compiler-core': 3.5.27 + '@vue/compiler-dom': 3.5.27 + '@vue/compiler-ssr': 3.5.27 + '@vue/shared': 3.5.27 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.27': + dependencies: + '@vue/compiler-dom': 3.5.27 + '@vue/shared': 3.5.27 + + '@vue/devtools-api@6.6.4': {} + + '@vue/language-core@1.8.27(typescript@5.9.3)': + dependencies: + '@volar/language-core': 1.11.1 + '@volar/source-map': 1.11.1 + '@vue/compiler-dom': 3.5.27 + '@vue/shared': 3.5.27 + computeds: 0.0.1 + minimatch: 9.0.5 + muggle-string: 0.3.1 + path-browserify: 1.0.1 + vue-template-compiler: 2.7.16 + optionalDependencies: + typescript: 5.9.3 + + '@vue/reactivity@3.5.27': + dependencies: + '@vue/shared': 3.5.27 + + '@vue/runtime-core@3.5.27': + dependencies: + '@vue/reactivity': 3.5.27 + '@vue/shared': 3.5.27 + + '@vue/runtime-dom@3.5.27': + dependencies: + '@vue/reactivity': 3.5.27 + '@vue/runtime-core': 3.5.27 + '@vue/shared': 3.5.27 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.27(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@vue/compiler-ssr': 3.5.27 + '@vue/shared': 3.5.27 + vue: 3.5.27(typescript@5.9.3) + + '@vue/shared@3.5.27': {} + + '@vue/test-utils@2.4.6': + dependencies: + js-beautify: 1.15.4 + vue-component-type-helpers: 2.2.12 + + '@vueuse/core@10.11.1(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 10.11.1 + '@vueuse/shared': 10.11.1(vue@3.5.27(typescript@5.9.3)) + vue-demi: 0.14.10(vue@3.5.27(typescript@5.9.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/metadata@10.11.1': {} + + '@vueuse/shared@10.11.1(vue@3.5.27(typescript@5.9.3))': + dependencies: + vue-demi: 0.14.10(vue@3.5.27(typescript@5.9.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vxe-ui/core@4.2.21(vue@3.5.27(typescript@5.9.3))': + dependencies: + dom-zindex: 1.0.6 + vue: 3.5.27(typescript@5.9.3) + xe-utils: 3.8.4 + + '@zxcvbn-ts/core@3.0.4': + dependencies: + fastest-levenshtein: 1.0.16 + + JSONStream@1.3.5: + dependencies: + jsonparse: 1.3.1 + through: 2.3.8 + + abbrev@2.0.0: {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + add-stream@1.0.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-escapes@7.2.0: + dependencies: + environment: 1.1.0 + + ansi-regex@2.1.1: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@2.2.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + ant-design-vue@4.2.6(vue@3.5.27(typescript@5.9.3)): + dependencies: + '@ant-design/colors': 6.0.0 + '@ant-design/icons-vue': 7.0.1(vue@3.5.27(typescript@5.9.3)) + '@babel/runtime': 7.28.6 + '@ctrl/tinycolor': 3.6.1 + '@emotion/hash': 0.9.2 + '@emotion/unitless': 0.8.1 + '@simonwep/pickr': 1.8.2 + array-tree-filter: 2.1.0 + async-validator: 4.2.5 + csstype: 3.2.3 + dayjs: 1.11.19 + dom-align: 1.12.4 + dom-scroll-into-view: 2.0.1 + lodash: 4.17.21 + lodash-es: 4.17.22 + resize-observer-polyfill: 1.5.1 + scroll-into-view-if-needed: 2.2.31 + shallow-equal: 1.2.1 + stylis: 4.3.6 + throttle-debounce: 5.0.2 + vue: 3.5.27(typescript@5.9.3) + vue-types: 3.0.2(vue@3.5.27(typescript@5.9.3)) + warning: 4.0.3 + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@4.1.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + arr-diff@4.0.0: {} + + arr-flatten@1.1.0: {} + + arr-union@3.1.0: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-ify@1.0.0: {} + + array-tree-filter@2.1.0: {} + + array-union@2.1.0: {} + + array-unique@0.3.2: {} + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + arrify@1.0.1: {} + + assign-symbols@1.0.0: {} + + astral-regex@2.0.0: {} + + async-function@1.0.0: {} + + async-validator@4.2.5: {} + + async@3.2.6: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + atob@2.1.2: {} + + autoprefixer@10.4.23(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001765 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axios@0.26.1(debug@4.4.3): + dependencies: + follow-redirects: 1.15.11(debug@4.4.3) + transitivePeerDependencies: + - debug + + axios@1.13.2(debug@4.4.3): + dependencies: + follow-redirects: 1.15.11(debug@4.4.3) + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + babel-jest@29.7.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.29.7) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.29.7 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + + babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.29.7): + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.29.7) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.29.7) + core-js-compat: 3.47.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + babel-plugin-transform-runtime@6.23.0: + dependencies: + babel-runtime: 6.26.0 + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7) + + babel-preset-jest@29.6.3(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) + + babel-runtime@6.26.0: + dependencies: + core-js: 2.6.12 + regenerator-runtime: 0.11.1 + + balanced-match@1.0.2: {} + + balanced-match@2.0.0: {} + + base64-js@1.5.1: {} + + base@0.11.2: + dependencies: + cache-base: 1.0.1 + class-utils: 0.3.6 + component-emitter: 1.3.1 + define-property: 1.0.0 + isobject: 3.0.1 + mixin-deep: 1.3.2 + pascalcase: 0.1.1 + + baseline-browser-mapping@2.9.15: {} + + basic-auth@2.0.1: + dependencies: + safe-buffer: 5.1.2 + + batch-processor@1.0.0: {} + + big.js@5.2.2: {} + + big.js@6.2.2: {} + + binary-extensions@2.3.0: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + bluebird@3.7.2: {} + + boolbase@1.0.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@2.3.2: + dependencies: + arr-flatten: 1.1.0 + array-unique: 0.3.2 + extend-shallow: 2.0.1 + fill-range: 4.0.0 + isobject: 3.0.1 + repeat-element: 1.1.4 + snapdragon: 0.8.2 + snapdragon-node: 2.1.1 + split-string: 3.1.0 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.15 + caniuse-lite: 1.0.30001765 + electron-to-chromium: 1.5.267 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + cac@7.0.0: {} + + cache-base@1.0.1: + dependencies: + collection-visit: 1.0.0 + component-emitter: 1.3.1 + get-value: 2.0.6 + has-value: 1.0.0 + isobject: 3.0.1 + set-value: 2.0.1 + to-object-path: 0.3.0 + union-value: 1.0.1 + unset-value: 1.0.0 + + cacheable@2.3.2: + dependencies: + '@cacheable/memory': 2.0.7 + '@cacheable/utils': 2.3.3 + hookified: 1.15.0 + keyv: 5.5.5 + qified: 0.6.0 + + cachedir@2.3.0: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camel-case@4.1.2: + dependencies: + pascal-case: 3.1.2 + tslib: 2.8.1 + + camelcase-keys@6.2.2: + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001765: {} + + chalk@1.1.3: + dependencies: + ansi-styles: 2.2.1 + escape-string-regexp: 1.0.5 + has-ansi: 2.0.0 + strip-ansi: 3.0.1 + supports-color: 2.0.0 + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.3.0: {} + + chalk@5.6.2: + optional: true + + char-regex@1.0.2: {} + + chardet@0.7.0: {} + + chardet@2.1.1: {} + + charenc@0.0.2: {} + + cheerio-select@2.1.0: + dependencies: + boolbase: 1.0.0 + css-select: 5.2.2 + css-what: 6.2.2 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + + cheerio@1.1.2: + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.2.2 + encoding-sniffer: 0.2.1 + htmlparser2: 10.0.0 + parse5: 7.3.0 + parse5-htmlparser2-tree-adapter: 7.1.0 + parse5-parser-stream: 7.1.2 + undici: 7.18.2 + whatwg-mimetype: 4.0.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + ci-info@3.9.0: {} + + cjs-module-lexer@1.4.3: {} + + class-utils@0.3.6: + dependencies: + arr-union: 3.1.0 + define-property: 0.2.5 + isobject: 3.0.1 + static-extend: 0.1.2 + + classnames@2.5.1: {} + + clean-css@5.3.3: + dependencies: + source-map: 0.6.1 + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@2.9.2: {} + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + + cli-width@3.0.0: {} + + cli-width@4.1.0: {} + + clipboard@2.0.11: + dependencies: + good-listener: 1.2.2 + select: 1.1.2 + tiny-emitter: 2.1.0 + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@1.0.4: {} + + clone@2.1.2: {} + + co@4.6.0: {} + + codemirror@5.65.20: {} + + collect-v8-coverage@1.0.3: {} + + collection-visit@1.0.0: + dependencies: + map-visit: 1.0.0 + object-visit: 1.0.1 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + colord@2.9.3: {} + + colorette@2.0.20: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@10.0.1: {} + + commander@11.1.0: {} + + commander@14.0.2: {} + + commander@2.20.3: {} + + commander@7.2.0: {} + + commander@8.3.0: {} + + commander@9.5.0: {} + + commitizen@4.3.1(@types/node@20.19.30)(typescript@5.9.3): + dependencies: + cachedir: 2.3.0 + cz-conventional-changelog: 3.3.0(@types/node@20.19.30)(typescript@5.9.3) + dedent: 0.7.0 + detect-indent: 6.1.0 + find-node-modules: 2.1.3 + find-root: 1.1.0 + fs-extra: 9.1.0 + glob: 7.2.3 + inquirer: 8.2.5 + is-utf8: 0.2.1 + lodash: 4.17.21 + minimist: 1.2.7 + strip-bom: 4.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - '@types/node' + - typescript + + common-tags@1.8.2: {} + + compare-func@2.0.0: + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + + component-emitter@1.3.1: {} + + compute-scroll-into-view@1.0.20: {} + + computeds@0.0.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + + connect-history-api-fallback@1.6.0: {} + + connect@3.7.0: + dependencies: + debug: 2.6.9 + finalhandler: 1.1.2 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + + consola@2.15.3: {} + + consola@3.4.2: {} + + conventional-changelog-angular@7.0.0: + dependencies: + compare-func: 2.0.0 + + conventional-changelog-atom@4.0.0: {} + + conventional-changelog-cli@4.1.0: + dependencies: + add-stream: 1.0.0 + conventional-changelog: 5.1.0 + meow: 12.1.1 + tempfile: 5.0.0 + + conventional-changelog-codemirror@4.0.0: {} + + conventional-changelog-conventionalcommits@7.0.2: + dependencies: + compare-func: 2.0.0 + + conventional-changelog-core@7.0.0: + dependencies: + '@hutson/parse-repository-url': 5.0.0 + add-stream: 1.0.0 + conventional-changelog-writer: 7.0.1 + conventional-commits-parser: 5.0.0 + git-raw-commits: 4.0.0 + git-semver-tags: 7.0.1 + hosted-git-info: 7.0.2 + normalize-package-data: 6.0.2 + read-pkg: 8.1.0 + read-pkg-up: 10.1.0 + + conventional-changelog-ember@4.0.0: {} + + conventional-changelog-eslint@5.0.0: {} + + conventional-changelog-express@4.0.0: {} + + conventional-changelog-jquery@5.0.0: {} + + conventional-changelog-jshint@4.0.0: + dependencies: + compare-func: 2.0.0 + + conventional-changelog-preset-loader@4.1.0: {} + + conventional-changelog-writer@7.0.1: + dependencies: + conventional-commits-filter: 4.0.0 + handlebars: 4.7.8 + json-stringify-safe: 5.0.1 + meow: 12.1.1 + semver: 7.7.3 + split2: 4.2.0 + + conventional-changelog@5.1.0: + dependencies: + conventional-changelog-angular: 7.0.0 + conventional-changelog-atom: 4.0.0 + conventional-changelog-codemirror: 4.0.0 + conventional-changelog-conventionalcommits: 7.0.2 + conventional-changelog-core: 7.0.0 + conventional-changelog-ember: 4.0.0 + conventional-changelog-eslint: 5.0.0 + conventional-changelog-express: 4.0.0 + conventional-changelog-jquery: 5.0.0 + conventional-changelog-jshint: 4.0.0 + conventional-changelog-preset-loader: 4.1.0 + + conventional-commit-types@3.0.0: {} + + conventional-commits-filter@4.0.0: {} + + conventional-commits-parser@5.0.0: + dependencies: + JSONStream: 1.3.5 + is-text-path: 2.0.0 + meow: 12.1.1 + split2: 4.2.0 + + convert-source-map@2.0.0: {} + + copy-anything@2.0.6: + dependencies: + is-what: 3.14.1 + + copy-descriptor@0.1.1: {} + + core-js-compat@3.47.0: + dependencies: + browserslist: 4.28.1 + + core-js@2.6.12: {} + + core-js@3.47.0: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + corser@2.0.1: {} + + cosmiconfig-typescript-loader@5.1.0(@types/node@20.19.30)(cosmiconfig@8.3.6(typescript@5.9.3))(typescript@5.9.3): + dependencies: + '@types/node': 20.19.30 + cosmiconfig: 8.3.6(typescript@5.9.3) + jiti: 1.21.7 + typescript: 5.9.3 + + cosmiconfig-typescript-loader@6.2.0(@types/node@20.19.30)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): + dependencies: + '@types/node': 20.19.30 + cosmiconfig: 9.0.0(typescript@5.9.3) + jiti: 2.6.1 + typescript: 5.9.3 + optional: true + + cosmiconfig@8.3.6(typescript@5.9.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.9.3 + + cosmiconfig@9.0.0(typescript@5.9.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.9.3 + + create-jest@29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + create-require@1.1.1: {} + + cron-parser@4.9.0: + dependencies: + luxon: 3.7.2 + + cropperjs@1.6.2: {} + + cross-env@7.0.3: + dependencies: + cross-spawn: 7.0.6 + + cross-fetch@3.2.0: + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + cross-spawn@6.0.6: + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.2 + shebang-command: 1.2.0 + which: 1.3.1 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypt@0.0.2: {} + + crypto-js@4.2.0: {} + + crypto-random-string@2.0.0: {} + + css-functions-list@3.2.3: {} + + css-select@4.3.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + + cssesc@3.0.0: {} + + cssfilter@0.0.10: {} + + csso@4.2.0: + dependencies: + css-tree: 1.1.3 + + csstype@3.2.3: {} + + cz-conventional-changelog@3.3.0(@types/node@20.19.30)(typescript@5.9.3): + dependencies: + chalk: 2.4.2 + commitizen: 4.3.1(@types/node@20.19.30)(typescript@5.9.3) + conventional-commit-types: 3.0.0 + lodash.map: 4.6.0 + longest: 2.0.1 + word-wrap: 1.2.5 + optionalDependencies: + '@commitlint/load': 20.3.1(@types/node@20.19.30)(typescript@5.9.3) + transitivePeerDependencies: + - '@types/node' + - typescript + + cz-git@1.12.0: {} + + czg@1.12.0: {} + + dargs@7.0.0: {} + + dargs@8.1.0: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + dayjs@1.11.19: {} + + de-indent@1.0.2: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.3.4: + dependencies: + ms: 2.1.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decamelize-keys@1.1.1: + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + + decamelize@1.2.0: {} + + decode-uri-component@0.2.2: {} + + dedent@0.7.0: {} + + dedent@1.7.1: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-lazy-prop@2.0.0: {} + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + define-property@0.2.5: + dependencies: + is-descriptor: 0.1.7 + + define-property@1.0.0: + dependencies: + is-descriptor: 1.0.3 + + define-property@2.0.2: + dependencies: + is-descriptor: 1.0.3 + isobject: 3.0.1 + + defu@6.1.4: {} + + delayed-stream@1.0.0: {} + + delegate@3.2.0: {} + + destr@2.0.5: {} + + detect-file@1.0.0: {} + + detect-indent@6.1.0: {} + + detect-newline@3.1.0: {} + + diff-match-patch@1.0.5: {} + + diff-sequences@29.6.3: {} + + diff@4.0.2: {} + + dijkstrajs@1.0.3: {} + + dingtalk-jsapi@3.2.5: + dependencies: + promise-polyfill: 7.1.2 + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-align@1.12.4: {} + + dom-scroll-into-view@2.0.1: {} + + dom-serializer@0.2.2: + dependencies: + domelementtype: 2.3.0 + entities: 2.2.0 + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + dom-zindex@1.0.6: {} + + domelementtype@1.3.1: {} + + domelementtype@2.3.0: {} + + domhandler@2.4.2: + dependencies: + domelementtype: 1.3.1 + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@1.7.0: + dependencies: + dom-serializer: 0.2.2 + domelementtype: 1.3.1 + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + + dot-prop@5.3.0: + dependencies: + is-obj: 2.0.0 + + dotenv-expand@8.0.3: {} + + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + duplexer@0.1.2: {} + + eastasianwidth@0.2.0: {} + + echarts@5.6.0: + dependencies: + tslib: 2.3.0 + zrender: 5.6.1 + + editorconfig@1.0.4: + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.1 + semver: 7.7.3 + + ee-first@1.1.1: {} + + ejs@3.1.10: + dependencies: + jake: 10.9.4 + + electron-to-chromium@1.5.267: {} + + element-resize-detector@1.2.4: + dependencies: + batch-processor: 1.0.0 + + emittery@0.13.1: {} + + emoji-mart-vue-fast@15.0.5(vue@3.5.27(typescript@5.9.3)): + dependencies: + '@babel/runtime': 7.28.6 + core-js: 3.47.0 + vue: 3.5.27(typescript@5.9.3) + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + emojis-list@3.0.0: {} + + encodeurl@1.0.2: {} + + encoding-sniffer@0.2.1: + dependencies: + iconv-lite: 0.6.3 + whatwg-encoding: 3.1.1 + + enquire.js@2.1.6: {} + + entities@1.1.2: {} + + entities@2.2.0: {} + + entities@4.5.0: {} + + entities@6.0.1: {} + + entities@7.0.0: {} + + env-paths@2.2.1: {} + + environment@1.1.0: {} + + errno@0.1.8: + dependencies: + prr: 1.0.1 + optional: true + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.24.1: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.20 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + esbuild-android-64@0.14.54: + optional: true + + esbuild-android-arm64@0.14.54: + optional: true + + esbuild-darwin-64@0.14.54: + optional: true + + esbuild-darwin-arm64@0.14.54: + optional: true + + esbuild-freebsd-64@0.14.54: + optional: true + + esbuild-freebsd-arm64@0.14.54: + optional: true + + esbuild-linux-32@0.14.54: + optional: true + + esbuild-linux-64@0.14.54: + optional: true + + esbuild-linux-arm64@0.14.54: + optional: true + + esbuild-linux-arm@0.14.54: + optional: true + + esbuild-linux-mips64le@0.14.54: + optional: true + + esbuild-linux-ppc64le@0.14.54: + optional: true + + esbuild-linux-riscv64@0.14.54: + optional: true + + esbuild-linux-s390x@0.14.54: + optional: true + + esbuild-netbsd-64@0.14.54: + optional: true + + esbuild-openbsd-64@0.14.54: + optional: true + + esbuild-sunos-64@0.14.54: + optional: true + + esbuild-windows-32@0.14.54: + optional: true + + esbuild-windows-64@0.14.54: + optional: true + + esbuild-windows-arm64@0.14.54: + optional: true + + esbuild@0.14.54: + optionalDependencies: + '@esbuild/linux-loong64': 0.14.54 + esbuild-android-64: 0.14.54 + esbuild-android-arm64: 0.14.54 + esbuild-darwin-64: 0.14.54 + esbuild-darwin-arm64: 0.14.54 + esbuild-freebsd-64: 0.14.54 + esbuild-freebsd-arm64: 0.14.54 + esbuild-linux-32: 0.14.54 + esbuild-linux-64: 0.14.54 + esbuild-linux-arm: 0.14.54 + esbuild-linux-arm64: 0.14.54 + esbuild-linux-mips64le: 0.14.54 + esbuild-linux-ppc64le: 0.14.54 + esbuild-linux-riscv64: 0.14.54 + esbuild-linux-s390x: 0.14.54 + esbuild-netbsd-64: 0.14.54 + esbuild-openbsd-64: 0.14.54 + esbuild-sunos-64: 0.14.54 + esbuild-windows-32: 0.14.54 + esbuild-windows-64: 0.14.54 + esbuild-windows-arm64: 0.14.54 + + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@9.1.2(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-define-config@2.1.0: {} + + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(jest@29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)))(typescript@5.9.3): + dependencies: + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3) + eslint: 8.57.1 + optionalDependencies: + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + jest: 29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)) + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-prettier@5.5.5(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.8.0): + dependencies: + eslint: 8.57.1 + prettier: 3.8.0 + prettier-linter-helpers: 1.0.1 + synckit: 0.11.12 + optionalDependencies: + eslint-config-prettier: 9.1.2(eslint@8.57.1) + + eslint-plugin-vue@9.33.0(eslint@8.57.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + eslint: 8.57.1 + globals: 13.24.0 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.7.3 + vue-eslint-parser: 9.4.3(eslint@8.57.1) + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - supports-color + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.1 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + esno@4.8.0: + dependencies: + tsx: 4.21.0 + + espree@9.6.1: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 3.4.3 + + esprima@4.0.1: {} + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + estree-walker@1.0.1: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + etag@1.8.1: {} + + event-source-polyfill@1.0.31: {} + + eventemitter3@4.0.7: {} + + eventemitter3@5.0.4: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + exit@0.1.2: {} + + expand-brackets@2.1.4: + dependencies: + debug: 2.6.9 + define-property: 0.2.5 + extend-shallow: 2.0.1 + posix-character-classes: 0.1.1 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + expand-tilde@2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + exsolve@1.0.8: {} + + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + + extend-shallow@3.0.2: + dependencies: + assign-symbols: 1.0.0 + is-extendable: 1.0.1 + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + extglob@2.0.4: + dependencies: + array-unique: 0.3.2 + define-property: 1.0.0 + expand-brackets: 2.1.4 + extend-shallow: 2.0.1 + fragment-cache: 0.2.1 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-uri@3.1.0: {} + + fastest-levenshtein@1.0.16: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + + file-entry-cache@11.1.2: + dependencies: + flat-cache: 6.1.20 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + filelist@1.0.4: + dependencies: + minimatch: 5.1.6 + + fill-range@4.0.0: + dependencies: + extend-shallow: 2.0.1 + is-number: 3.0.0 + repeat-string: 1.6.1 + to-regex-range: 2.1.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.1.2: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.5.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-node-modules@2.1.3: + dependencies: + findup-sync: 4.0.0 + merge: 2.1.1 + + find-root@1.1.0: {} + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + find-up@6.3.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + + findup-sync@4.0.0: + dependencies: + detect-file: 1.0.0 + is-glob: 4.0.3 + micromatch: 4.0.8 + resolve-dir: 1.0.1 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + rimraf: 3.0.2 + + flat-cache@6.1.20: + dependencies: + cacheable: 2.3.2 + flatted: 3.3.3 + hookified: 1.15.0 + + flatted@3.3.3: {} + + follow-redirects@1.15.11(debug@4.4.3): + optionalDependencies: + debug: 4.4.3 + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + for-in@1.0.2: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fraction.js@5.3.4: {} + + fragment-cache@0.2.1: + dependencies: + map-cache: 0.2.2 + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@11.3.3: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.4.0: {} + + get-intrinsic@1.3.0: + 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 + + get-own-enumerable-property-symbols@3.0.2: {} + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@6.0.1: {} + + get-stream@8.0.1: {} + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + get-value@2.0.6: {} + + git-raw-commits@2.0.11: + dependencies: + dargs: 7.0.0 + lodash: 4.17.21 + meow: 8.1.2 + split2: 3.2.2 + through2: 4.0.2 + + git-raw-commits@4.0.0: + dependencies: + dargs: 8.1.0 + meow: 12.1.1 + split2: 4.2.0 + + git-semver-tags@7.0.1: + dependencies: + meow: 12.1.1 + semver: 7.7.3 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@11.1.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.1.1 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + global-directory@4.0.1: + dependencies: + ini: 4.1.1 + optional: true + + global-dirs@0.1.1: + dependencies: + ini: 1.3.8 + + global-modules@1.0.0: + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + + global-modules@2.0.0: + dependencies: + global-prefix: 3.0.0 + + global-prefix@1.0.2: + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 + + global-prefix@3.0.0: + dependencies: + ini: 1.3.8 + kind-of: 6.0.3 + which: 1.3.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globals@15.15.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + globjoin@0.1.4: {} + + good-listener@1.2.2: + dependencies: + delegate: 3.2.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + gzip-size@6.0.0: + dependencies: + duplexer: 0.1.2 + + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + hard-rejection@2.1.0: {} + + has-ansi@2.0.0: + dependencies: + ansi-regex: 2.1.1 + + has-bigints@1.1.0: {} + + has-flag@1.0.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + has-value@0.3.1: + dependencies: + get-value: 2.0.6 + has-values: 0.1.4 + isobject: 2.1.0 + + has-value@1.0.0: + dependencies: + get-value: 2.0.6 + has-values: 1.0.0 + isobject: 3.0.1 + + has-values@0.1.4: {} + + has-values@1.0.0: + dependencies: + is-number: 3.0.0 + kind-of: 4.0.0 + + hashery@1.4.0: + dependencies: + hookified: 1.15.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + highlight.js@11.11.1: {} + + hoist-non-react-statics@2.5.5: {} + + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + + hookified@1.15.0: {} + + hosted-git-info@2.8.9: {} + + hosted-git-info@4.1.0: + dependencies: + lru-cache: 6.0.0 + + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + + html-encoding-sniffer@3.0.0: + dependencies: + whatwg-encoding: 2.0.0 + + html-escaper@2.0.2: {} + + html-minifier-terser@6.1.0: + dependencies: + camel-case: 4.1.2 + clean-css: 5.3.3 + commander: 8.3.0 + he: 1.2.0 + param-case: 3.0.4 + relateurl: 0.2.7 + terser: 5.46.0 + + html-tags@3.3.1: {} + + htmlparser2@10.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 6.0.1 + + htmlparser2@3.10.1: + dependencies: + domelementtype: 1.3.1 + domhandler: 2.4.2 + domutils: 1.7.0 + entities: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + + http-proxy@1.18.1: + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.11(debug@4.4.3) + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + + http-server@14.1.1: + dependencies: + basic-auth: 2.0.1 + chalk: 4.1.2 + corser: 2.0.1 + he: 1.2.0 + html-encoding-sniffer: 3.0.0 + http-proxy: 1.18.1 + mime: 1.6.0 + minimist: 1.2.8 + opener: 1.5.2 + portfinder: 1.0.38 + secure-compare: 3.0.1 + union: 0.5.0 + url-join: 4.0.1 + transitivePeerDependencies: + - debug + - supports-color + + human-signals@2.1.0: {} + + human-signals@5.0.0: {} + + husky@8.0.3: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + idb@7.1.1: {} + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + image-size@0.5.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + import-meta-resolve@4.2.0: {} + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@1.3.8: {} + + ini@4.1.1: + optional: true + + inquirer@8.2.5: + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + ora: 5.4.1 + run-async: 2.4.1 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + wrap-ansi: 7.0.0 + + inquirer@9.3.8(@types/node@20.19.30): + dependencies: + '@inquirer/external-editor': 1.0.3(@types/node@20.19.30) + '@inquirer/figures': 1.0.15 + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + transitivePeerDependencies: + - '@types/node' + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + intro.js@7.2.0: {} + + is-accessor-descriptor@1.0.1: + dependencies: + hasown: 2.0.2 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-buffer@1.1.6: {} + + is-callable@1.2.7: {} + + is-ci@3.0.1: + dependencies: + ci-info: 3.9.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-descriptor@1.0.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-descriptor@0.1.7: + dependencies: + is-accessor-descriptor: 1.0.1 + is-data-descriptor: 1.0.1 + + is-descriptor@1.0.3: + dependencies: + is-accessor-descriptor: 1.0.1 + is-data-descriptor: 1.0.1 + + is-docker@2.2.1: {} + + is-extendable@0.1.1: {} + + is-extendable@1.0.1: + dependencies: + is-plain-object: 2.0.4 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.4.0 + + is-generator-fn@2.1.0: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-interactive@1.0.0: {} + + is-map@2.0.3: {} + + is-module@1.0.0: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@3.0.0: + dependencies: + kind-of: 3.2.2 + + is-number@7.0.0: {} + + is-obj@1.0.1: {} + + is-obj@2.0.0: {} + + is-path-inside@3.0.3: {} + + is-plain-obj@1.1.0: {} + + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + + is-plain-object@3.0.1: {} + + is-plain-object@5.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-regexp@1.0.0: {} + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-stream@3.0.0: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-text-path@2.0.0: + dependencies: + text-extensions: 2.4.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-unicode-supported@0.1.0: {} + + is-utf8@0.2.1: {} + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-what@3.14.1: {} + + is-windows@1.0.2: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isobject@2.1.0: + dependencies: + isarray: 1.0.0 + + isobject@3.0.1: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + + jake@10.9.4: + dependencies: + async: 3.2.6 + filelist: 1.0.4 + picocolors: 1.1.1 + + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + jest-circus@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.30 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.7.1 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-config@29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)): + dependencies: + '@babel/core': 7.29.7 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.7) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.19.30 + ts-node: 10.9.2(@types/node@20.19.30)(typescript@5.9.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.30 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 20.19.30 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.29.7 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.19.30 + jest-util: 29.7.0 + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.11 + resolve.exports: 2.0.3 + slash: 3.0.0 + + jest-runner@29.7.0: + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.30 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.30 + chalk: 4.1.2 + cjs-module-lexer: 1.4.3 + collect-v8-coverage: 1.0.3 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/types': 7.29.7 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.19.30 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.30 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + + jest-worker@29.7.0: + dependencies: + '@types/node': 20.19.30 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jiti@1.21.7: {} + + jiti@2.6.1: {} + + js-base64@2.6.4: {} + + js-beautify@1.15.4: + dependencies: + config-chain: 1.1.13 + editorconfig: 1.0.4 + glob: 10.5.0 + js-cookie: 3.0.5 + nopt: 7.2.1 + + js-cookie@3.0.5: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-better-errors@1.0.2: {} + + json-parse-even-better-errors@2.3.1: {} + + json-parse-even-better-errors@3.0.2: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-schema@0.4.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json-stringify-safe@5.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonparse@1.3.1: {} + + jsonpointer@5.0.1: {} + + katex@0.16.27: + dependencies: + commander: 8.3.0 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + keyv@5.5.5: + dependencies: + '@keyv/serialize': 1.1.1 + + kind-of@3.2.2: + dependencies: + is-buffer: 1.1.6 + + kind-of@4.0.0: + dependencies: + is-buffer: 1.1.6 + + kind-of@5.1.0: {} + + kind-of@6.0.3: {} + + kleur@3.0.3: {} + + known-css-properties@0.37.0: {} + + kolorist@1.8.0: {} + + less@4.5.1: + dependencies: + copy-anything: 2.0.6 + parse-node-version: 1.0.1 + tslib: 2.8.1 + optionalDependencies: + errno: 0.1.8 + graceful-fs: 4.2.11 + image-size: 0.5.5 + make-dir: 2.1.0 + mime: 1.6.0 + needle: 3.3.1 + source-map: 0.6.1 + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.0.0: {} + + lines-and-columns@1.2.4: {} + + lines-and-columns@2.0.4: {} + + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + lint-staged@15.2.2: + dependencies: + chalk: 5.3.0 + commander: 11.1.0 + debug: 4.3.4 + execa: 8.0.1 + lilconfig: 3.0.0 + listr2: 8.0.1 + micromatch: 4.0.5 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.3.4 + transitivePeerDependencies: + - supports-color + + listr2@8.0.1: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.4 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + + load-json-file@4.0.0: + dependencies: + graceful-fs: 4.2.11 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + + loader-utils@1.4.2: + dependencies: + big.js: 5.2.2 + emojis-list: 3.0.0 + json5: 1.0.2 + + local-pkg@0.4.3: {} + + local-pkg@0.5.1: + dependencies: + mlly: 1.8.0 + pkg-types: 1.3.1 + + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 + + lodash-es@4.17.22: {} + + lodash.camelcase@4.3.0: {} + + lodash.debounce@4.0.8: {} + + lodash.get@4.4.2: {} + + lodash.isfunction@3.0.9: {} + + lodash.isplainobject@4.0.6: {} + + lodash.kebabcase@4.1.1: {} + + lodash.map@4.6.0: {} + + lodash.memoize@4.1.2: {} + + lodash.merge@4.6.2: {} + + lodash.mergewith@4.6.2: {} + + lodash.snakecase@4.1.1: {} + + lodash.sortby@4.7.0: {} + + lodash.startcase@4.4.0: {} + + lodash.truncate@4.4.2: {} + + lodash.uniq@4.5.0: {} + + lodash.upperfirst@4.3.1: {} + + lodash@4.17.21: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.2.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.1.2 + wrap-ansi: 9.0.2 + + longest@2.0.1: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + + lru-cache@10.4.3: {} + + lru-cache@11.2.4: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lunar-javascript@1.7.7: {} + + luxon@3.7.2: {} + + magic-regexp@0.10.0: + dependencies: + estree-walker: 3.0.3 + magic-string: 0.30.21 + mlly: 1.8.0 + regexp-tree: 0.1.27 + type-level-regexp: 0.1.17 + ufo: 1.6.3 + unplugin: 2.3.11 + + magic-string@0.25.9: + dependencies: + sourcemap-codec: 1.4.8 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-dir@2.1.0: + dependencies: + pify: 4.0.1 + semver: 5.7.2 + optional: true + + make-dir@4.0.0: + dependencies: + semver: 7.7.3 + + make-error@1.3.6: {} + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + map-cache@0.2.2: {} + + map-obj@1.0.1: {} + + map-obj@4.3.0: {} + + map-visit@1.0.0: + dependencies: + object-visit: 1.0.1 + + markdown-it-link-attributes@4.0.1: {} + + markdown-it@14.1.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + + material-colors@1.2.6: {} + + math-intrinsics@1.1.0: {} + + mathml-tag-names@2.1.3: {} + + md5@2.3.0: + dependencies: + charenc: 0.0.2 + crypt: 0.0.2 + is-buffer: 1.1.6 + + mdn-data@2.0.14: {} + + mdn-data@2.12.2: {} + + mdn-data@2.27.1: {} + + mdurl@2.0.0: {} + + medium-editor@5.23.3: {} + + memorystream@0.3.1: {} + + meow@12.1.1: {} + + meow@13.2.0: {} + + meow@8.1.2: + dependencies: + '@types/minimist': 1.2.5 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.18.1 + yargs-parser: 20.2.9 + + merge-options@1.0.1: + dependencies: + is-plain-obj: 1.1.0 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + merge@2.1.1: {} + + micromatch@3.1.0: + dependencies: + arr-diff: 4.0.0 + array-unique: 0.3.2 + braces: 2.3.2 + define-property: 1.0.0 + extend-shallow: 2.0.1 + extglob: 2.0.4 + fragment-cache: 0.2.1 + kind-of: 5.1.0 + nanomatch: 1.2.13 + object.pick: 1.3.0 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.5: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mimic-fn@2.1.0: {} + + mimic-fn@4.0.0: {} + + mimic-function@5.0.1: {} + + min-indent@1.0.1: {} + + minimatch@10.1.1: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + minimatch@7.4.9: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.1: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.3: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist-options@4.1.0: + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + + minimist@1.2.7: {} + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + mitt@3.0.1: {} + + mixin-deep@1.3.2: + dependencies: + for-in: 1.0.2 + is-extendable: 1.0.1 + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + mobx-preact@3.0.0(mobx@5.15.7)(preact@10.28.2): + dependencies: + hoist-non-react-statics: 2.5.5 + mobx: 5.15.7 + preact: 10.28.2 + + mobx-utils@5.6.2(mobx@5.15.7): + dependencies: + mobx: 5.15.7 + + mobx@5.15.7: {} + + mockjs@1.1.0: + dependencies: + commander: 14.0.2 + + mousetrap@1.6.5: {} + + mri@1.2.0: {} + + mrmime@2.0.1: {} + + ms@2.0.0: {} + + ms@2.1.2: {} + + ms@2.1.3: {} + + muggle-string@0.3.1: {} + + mute-stream@0.0.8: {} + + mute-stream@1.0.0: {} + + nanoid@3.3.11: {} + + nanomatch@1.2.13: + dependencies: + arr-diff: 4.0.0 + array-unique: 0.3.2 + define-property: 2.0.2 + extend-shallow: 3.0.2 + fragment-cache: 0.2.1 + is-windows: 1.0.2 + kind-of: 6.0.3 + object.pick: 1.3.0 + regex-not: 1.0.2 + snapdragon: 0.8.2 + to-regex: 3.0.2 + transitivePeerDependencies: + - supports-color + + nanopop@2.4.2: {} + + natural-compare@1.4.0: {} + + needle@3.3.1: + dependencies: + iconv-lite: 0.6.3 + sax: 1.4.4 + optional: true + + neo-async@2.6.2: {} + + nice-try@1.0.5: {} + + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + + node-fetch-native@1.6.7: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-html-parser@5.4.2: + dependencies: + css-select: 4.3.0 + he: 1.2.0 + + node-int64@0.4.0: {} + + node-releases@2.0.27: {} + + nopt@7.2.1: + dependencies: + abbrev: 2.0.0 + + normalize-package-data@2.5.0: + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.11 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + + normalize-package-data@3.0.3: + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.16.1 + semver: 7.7.3 + validate-npm-package-license: 3.0.4 + + normalize-package-data@6.0.2: + dependencies: + hosted-git-info: 7.0.2 + semver: 7.7.3 + validate-npm-package-license: 3.0.4 + + normalize-path@3.0.0: {} + + npm-run-all@4.1.5: + dependencies: + ansi-styles: 3.2.1 + chalk: 2.4.2 + cross-spawn: 6.0.6 + memorystream: 0.3.1 + minimatch: 3.1.2 + pidtree: 0.3.1 + read-pkg: 3.0.0 + shell-quote: 1.8.3 + string.prototype.padend: 3.1.6 + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + nprogress@0.2.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + object-assign@4.1.1: {} + + object-copy@0.1.0: + dependencies: + copy-descriptor: 0.1.1 + define-property: 0.2.5 + kind-of: 3.2.2 + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object-visit@1.0.1: + dependencies: + isobject: 3.0.1 + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.pick@1.3.0: + dependencies: + isobject: 3.0.1 + + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.3 + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + opener@1.5.2: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + os-tmpdir@1.0.2: {} + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + oxc-parser@0.131.0: + dependencies: + '@oxc-project/types': 0.131.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.131.0 + '@oxc-parser/binding-android-arm64': 0.131.0 + '@oxc-parser/binding-darwin-arm64': 0.131.0 + '@oxc-parser/binding-darwin-x64': 0.131.0 + '@oxc-parser/binding-freebsd-x64': 0.131.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.131.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.131.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.131.0 + '@oxc-parser/binding-linux-arm64-musl': 0.131.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.131.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.131.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.131.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.131.0 + '@oxc-parser/binding-linux-x64-gnu': 0.131.0 + '@oxc-parser/binding-linux-x64-musl': 0.131.0 + '@oxc-parser/binding-openharmony-arm64': 0.131.0 + '@oxc-parser/binding-wasm32-wasi': 0.131.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.131.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.131.0 + '@oxc-parser/binding-win32-x64-msvc': 0.131.0 + + oxc-walker@0.7.0(oxc-parser@0.131.0): + dependencies: + magic-regexp: 0.10.0 + oxc-parser: 0.131.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-limit@4.0.0: + dependencies: + yocto-queue: 1.2.2 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + package-manager-detector@0.2.11: + dependencies: + quansync: 0.2.11 + + package-manager-detector@1.6.0: {} + + param-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@4.0.0: + dependencies: + error-ex: 1.3.4 + json-parse-better-errors: 1.0.2 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-json@7.1.1: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 3.0.2 + lines-and-columns: 2.0.4 + type-fest: 3.13.1 + + parse-node-version@1.0.1: {} + + parse-passwd@1.0.0: {} + + parse5-htmlparser2-tree-adapter@7.1.0: + dependencies: + domhandler: 5.0.3 + parse5: 7.3.0 + + parse5-parser-stream@7.1.2: + dependencies: + parse5: 7.3.0 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + parseurl@1.3.3: {} + + pascal-case@3.1.2: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + + pascalcase@0.1.1: {} + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-exists@5.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@2.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-scurry@2.0.1: + dependencies: + lru-cache: 11.2.4 + minipass: 7.1.2 + + path-to-regexp@6.3.0: {} + + path-type@3.0.0: + dependencies: + pify: 3.0.0 + + path-type@4.0.0: {} + + pathe@0.2.0: {} + + pathe@2.0.3: {} + + perfect-debounce@2.1.0: {} + + perfect-scrollbar@1.5.6: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + picomatch@4.0.4: {} + + pidtree@0.3.1: {} + + pidtree@0.6.0: {} + + pify@3.0.0: {} + + pify@4.0.1: + optional: true + + pinia@2.1.7(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.27(typescript@5.9.3) + vue-demi: 0.14.10(vue@3.5.27(typescript@5.9.3)) + optionalDependencies: + typescript: 5.9.3 + + pinyin-pro@3.28.0: {} + + pirates@4.0.7: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.8 + pathe: 2.0.3 + + pngjs@5.0.0: {} + + portfinder@1.0.38: + dependencies: + async: 3.2.6 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + posix-character-classes@0.1.1: {} + + possible-typed-array-names@1.1.0: {} + + postcss-html@1.8.1: + dependencies: + htmlparser2: 8.0.2 + js-tokens: 9.0.1 + postcss: 8.5.6 + postcss-safe-parser: 6.0.0(postcss@8.5.6) + + postcss-less@6.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-prefix-selector@1.16.1(postcss@5.2.18): + dependencies: + postcss: 5.2.18 + + postcss-resolve-nested-selector@0.1.6: {} + + postcss-safe-parser@6.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-safe-parser@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-sorting@8.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-value-parser@4.2.0: {} + + postcss@5.2.18: + dependencies: + chalk: 1.1.3 + js-base64: 2.6.4 + source-map: 0.5.7 + supports-color: 3.2.3 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + posthtml-parser@0.2.1: + dependencies: + htmlparser2: 3.10.1 + isobject: 2.1.0 + + posthtml-rename-id@1.0.12: + dependencies: + escape-string-regexp: 1.0.5 + + posthtml-render@1.4.0: {} + + posthtml-svg-mode@1.0.3: + dependencies: + merge-options: 1.0.1 + posthtml: 0.9.2 + posthtml-parser: 0.2.1 + posthtml-render: 1.4.0 + + posthtml@0.9.2: + dependencies: + posthtml-parser: 0.2.1 + posthtml-render: 1.4.0 + + preact@10.28.2: {} + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.1: + dependencies: + fast-diff: 1.3.0 + + prettier@3.8.0: {} + + pretty-bytes@5.6.0: {} + + pretty-bytes@6.1.1: {} + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + pretty-quick@4.2.2(prettier@3.8.0): + dependencies: + '@pkgr/core': 0.2.9 + ignore: 7.0.5 + mri: 1.2.0 + picocolors: 1.1.1 + picomatch: 4.0.3 + prettier: 3.8.0 + tinyexec: 0.3.2 + tslib: 2.8.1 + + print-js@1.6.0: {} + + promise-polyfill@7.1.2: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + proto-list@1.2.4: {} + + proxy-from-env@1.1.0: {} + + prr@1.0.1: + optional: true + + punycode.js@2.3.1: {} + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + qified@0.6.0: + dependencies: + hookified: 1.15.0 + + qrcode@1.5.4: + dependencies: + dijkstrajs: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + + qs@6.14.1: + dependencies: + side-channel: 1.1.0 + + quansync@0.2.11: {} + + quansync@1.0.0: {} + + query-string@4.3.4: + dependencies: + object-assign: 4.1.1 + strict-uri-encode: 1.1.0 + + queue-microtask@1.2.3: {} + + quick-lru@4.0.1: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + rangy@1.3.2: {} + + react-is@18.3.1: {} + + read-pkg-up@10.1.0: + dependencies: + find-up: 6.3.0 + read-pkg: 8.1.0 + type-fest: 4.41.0 + + read-pkg-up@7.0.1: + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + + read-pkg@3.0.0: + dependencies: + load-json-file: 4.0.0 + normalize-package-data: 2.5.0 + path-type: 3.0.0 + + read-pkg@5.2.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + + read-pkg@8.1.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 6.0.2 + parse-json: 7.1.1 + type-fest: 4.41.0 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@5.0.0: {} + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regenerate-unicode-properties@10.2.2: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.11.1: {} + + regex-not@1.0.2: + dependencies: + extend-shallow: 3.0.2 + safe-regex: 1.1.0 + + regexp-tree@0.1.27: {} + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + regexpu-core@6.4.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.2 + regjsgen: 0.8.0 + regjsparser: 0.13.0 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.1 + + regjsgen@0.8.0: {} + + regjsparser@0.13.0: + dependencies: + jsesc: 3.1.0 + + relateurl@0.2.7: {} + + repeat-element@1.1.4: {} + + repeat-string@1.6.1: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + require-main-filename@2.0.0: {} + + requires-port@1.0.0: {} + + resize-observer-polyfill@1.5.1: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-dir@1.0.1: + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-global@1.0.0: + dependencies: + global-dirs: 0.1.1 + + resolve-pkg-maps@1.0.0: {} + + resolve-url@0.2.1: {} + + resolve.exports@2.0.3: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + ret@0.1.15: {} + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rimraf@5.0.10: + dependencies: + glob: 10.5.0 + + rollup-plugin-purge-icons@0.10.0: + dependencies: + '@purge-icons/core': 0.10.0 + '@purge-icons/generated': 0.10.0 + transitivePeerDependencies: + - encoding + - supports-color + + rollup-plugin-visualizer@5.14.0(rollup@4.52.5): + dependencies: + open: 8.4.2 + picomatch: 4.0.3 + source-map: 0.7.6 + yargs: 17.7.2 + optionalDependencies: + rollup: 4.52.5 + + rollup@2.79.2: + optionalDependencies: + fsevents: 2.3.3 + + rollup@4.52.5: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.52.5 + '@rollup/rollup-android-arm64': 4.52.5 + '@rollup/rollup-darwin-arm64': 4.52.5 + '@rollup/rollup-darwin-x64': 4.52.5 + '@rollup/rollup-freebsd-arm64': 4.52.5 + '@rollup/rollup-freebsd-x64': 4.52.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.5 + '@rollup/rollup-linux-arm-musleabihf': 4.52.5 + '@rollup/rollup-linux-arm64-gnu': 4.52.5 + '@rollup/rollup-linux-arm64-musl': 4.52.5 + '@rollup/rollup-linux-loong64-gnu': 4.52.5 + '@rollup/rollup-linux-ppc64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-musl': 4.52.5 + '@rollup/rollup-linux-s390x-gnu': 4.52.5 + '@rollup/rollup-linux-x64-gnu': 4.52.5 + '@rollup/rollup-linux-x64-musl': 4.52.5 + '@rollup/rollup-openharmony-arm64': 4.52.5 + '@rollup/rollup-win32-arm64-msvc': 4.52.5 + '@rollup/rollup-win32-ia32-msvc': 4.52.5 + '@rollup/rollup-win32-x64-gnu': 4.52.5 + '@rollup/rollup-win32-x64-msvc': 4.52.5 + fsevents: 2.3.3 + + run-async@2.4.1: {} + + run-async@3.0.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safe-regex@1.1.0: + dependencies: + ret: 0.1.15 + + safer-buffer@2.1.2: {} + + sax@1.4.4: + optional: true + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + secure-compare@3.0.1: {} + + select@1.1.2: {} + + semver@5.7.2: {} + + semver@6.3.1: {} + + semver@7.6.0: + dependencies: + lru-cache: 6.0.0 + + semver@7.7.3: {} + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + set-blocking@2.0.0: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + set-value@2.0.1: + dependencies: + extend-shallow: 2.0.1 + is-extendable: 0.1.1 + is-plain-object: 2.0.4 + split-string: 3.1.0 + + shallow-equal@1.2.1: {} + + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@1.0.0: {} + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + showdown@2.1.0: + dependencies: + commander: 9.5.0 + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + smob@1.5.0: {} + + snapdragon-node@2.1.1: + dependencies: + define-property: 1.0.0 + isobject: 3.0.1 + snapdragon-util: 3.0.1 + + snapdragon-util@3.0.1: + dependencies: + kind-of: 3.2.2 + + snapdragon@0.8.2: + dependencies: + base: 0.11.2 + debug: 2.6.9 + define-property: 0.2.5 + extend-shallow: 2.0.1 + map-cache: 0.2.2 + source-map: 0.5.7 + source-map-resolve: 0.5.3 + use: 3.1.1 + transitivePeerDependencies: + - supports-color + + sortablejs@1.14.0: {} + + sortablejs@1.15.6: {} + + source-map-js@1.2.1: {} + + source-map-resolve@0.5.3: + dependencies: + atob: 2.1.2 + decode-uri-component: 0.2.2 + resolve-url: 0.2.1 + source-map-url: 0.4.1 + urix: 0.1.0 + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-url@0.4.1: {} + + source-map@0.5.7: {} + + source-map@0.6.1: {} + + source-map@0.7.6: {} + + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + + sourcemap-codec@1.4.8: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.22 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.22 + + spdx-license-ids@3.0.22: {} + + split-string@3.1.0: + dependencies: + extend-shallow: 3.0.2 + + split2@3.2.2: + dependencies: + readable-stream: 3.6.2 + + split2@4.2.0: {} + + sprintf-js@1.0.3: {} + + stable@0.1.8: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + static-extend@0.1.2: + dependencies: + define-property: 0.2.5 + object-copy: 0.1.0 + + statuses@1.5.0: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + strict-uri-encode@1.1.0: {} + + string-argv@0.3.2: {} + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.padend@3.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + stringify-object@3.3.0: + dependencies: + get-own-enumerable-property-symbols: 3.0.2 + is-obj: 1.0.1 + is-regexp: 1.0.0 + + strip-ansi@3.0.1: + dependencies: + ansi-regex: 2.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@3.0.0: {} + + strip-bom@4.0.0: {} + + strip-comments@2.0.1: {} + + strip-final-newline@2.0.0: {} + + strip-final-newline@3.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + stylelint-config-html@1.1.0(postcss-html@1.8.1)(stylelint@16.26.1(typescript@5.9.3)): + dependencies: + postcss-html: 1.8.1 + stylelint: 16.26.1(typescript@5.9.3) + + stylelint-config-prettier@9.0.5(stylelint@16.26.1(typescript@5.9.3)): + dependencies: + stylelint: 16.26.1(typescript@5.9.3) + + stylelint-config-recommended-vue@1.6.1(postcss-html@1.8.1)(stylelint@16.26.1(typescript@5.9.3)): + dependencies: + postcss-html: 1.8.1 + semver: 7.7.3 + stylelint: 16.26.1(typescript@5.9.3) + stylelint-config-html: 1.1.0(postcss-html@1.8.1)(stylelint@16.26.1(typescript@5.9.3)) + stylelint-config-recommended: 14.0.1(stylelint@16.26.1(typescript@5.9.3)) + + stylelint-config-recommended@14.0.1(stylelint@16.26.1(typescript@5.9.3)): + dependencies: + stylelint: 16.26.1(typescript@5.9.3) + + stylelint-config-standard@36.0.1(stylelint@16.26.1(typescript@5.9.3)): + dependencies: + stylelint: 16.26.1(typescript@5.9.3) + stylelint-config-recommended: 14.0.1(stylelint@16.26.1(typescript@5.9.3)) + + stylelint-order@6.0.4(stylelint@16.26.1(typescript@5.9.3)): + dependencies: + postcss: 8.5.6 + postcss-sorting: 8.0.2(postcss@8.5.6) + stylelint: 16.26.1(typescript@5.9.3) + + stylelint@16.26.1(typescript@5.9.3): + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-syntax-patches-for-csstree': 1.0.25 + '@csstools/css-tokenizer': 3.0.4 + '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) + '@dual-bundle/import-meta-resolve': 4.2.1 + balanced-match: 2.0.0 + colord: 2.9.3 + cosmiconfig: 9.0.0(typescript@5.9.3) + css-functions-list: 3.2.3 + css-tree: 3.1.0 + debug: 4.4.3 + fast-glob: 3.3.3 + fastest-levenshtein: 1.0.16 + file-entry-cache: 11.1.2 + global-modules: 2.0.0 + globby: 11.1.0 + globjoin: 0.1.4 + html-tags: 3.3.1 + ignore: 7.0.5 + imurmurhash: 0.1.4 + is-plain-object: 5.0.0 + known-css-properties: 0.37.0 + mathml-tag-names: 2.1.3 + meow: 13.2.0 + micromatch: 4.0.8 + normalize-path: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-resolve-nested-selector: 0.1.6 + postcss-safe-parser: 7.0.1(postcss@8.5.6) + postcss-selector-parser: 7.1.1 + postcss-value-parser: 4.2.0 + resolve-from: 5.0.0 + string-width: 4.2.3 + supports-hyperlinks: 3.2.0 + svg-tags: 1.0.0 + table: 6.9.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + - typescript + + stylis@4.3.6: {} + + supports-color@2.0.0: {} + + supports-color@3.2.3: + dependencies: + has-flag: 1.0.0 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-hyperlinks@3.2.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + svg-baker@1.7.0: + dependencies: + bluebird: 3.7.2 + clone: 2.1.2 + he: 1.2.0 + image-size: 0.5.5 + loader-utils: 1.4.2 + merge-options: 1.0.1 + micromatch: 3.1.0 + postcss: 5.2.18 + postcss-prefix-selector: 1.16.1(postcss@5.2.18) + posthtml-rename-id: 1.0.12 + posthtml-svg-mode: 1.0.3 + query-string: 4.3.4 + traverse: 0.6.11 + transitivePeerDependencies: + - supports-color + + svg-tags@1.0.0: {} + + svgo@2.8.0: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.1.1 + stable: 0.1.8 + + swagger-ui-dist@5.31.0: + dependencies: + '@scarf/scarf': 1.4.0 + + synckit@0.11.12: + dependencies: + '@pkgr/core': 0.2.9 + + table@6.9.0: + dependencies: + ajv: 8.17.1 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + temp-dir@2.0.0: {} + + temp-dir@3.0.0: {} + + tempfile@5.0.0: + dependencies: + temp-dir: 3.0.0 + + tempy@0.6.0: + dependencies: + is-stream: 2.0.1 + temp-dir: 2.0.0 + type-fest: 0.16.0 + unique-string: 2.0.0 + + terser@5.46.0: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + text-extensions@2.4.0: {} + + text-table@0.2.0: {} + + throttle-debounce@5.0.2: {} + + through2@4.0.2: + dependencies: + readable-stream: 3.6.2 + + through@2.3.8: {} + + tiny-emitter@2.1.0: {} + + tinycolor2@1.6.0: {} + + tinyexec@0.3.2: {} + + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinymce@5.10.9: {} + + tinymce@6.6.2: {} + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + tmpl@1.0.5: {} + + to-object-path@0.3.0: + dependencies: + kind-of: 3.2.2 + + to-regex-range@2.1.1: + dependencies: + is-number: 3.0.0 + repeat-string: 1.6.1 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + to-regex@3.0.2: + dependencies: + define-property: 2.0.2 + extend-shallow: 3.0.2 + regex-not: 1.0.2 + safe-regex: 1.1.0 + + totalist@3.0.1: {} + + tr46@0.0.3: {} + + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + + traverse@0.6.11: + dependencies: + gopd: 1.2.0 + typedarray.prototype.slice: 1.0.5 + which-typed-array: 1.1.20 + + trim-newlines@3.0.1: {} + + ts-api-utils@1.4.3(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-jest@29.4.6(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)))(typescript@5.9.3): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.8 + jest: 29.7.0(@types/node@20.19.30)(ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3)) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.3 + type-fest: 4.41.0 + typescript: 5.9.3 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.29.7 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.7) + jest-util: 29.7.0 + + ts-node@10.9.2(@types/node@20.19.30)(typescript@5.9.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.19.30 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tslib@1.14.1: {} + + tslib@2.3.0: {} + + tslib@2.8.1: {} + + tsutils@3.21.0(typescript@5.9.3): + dependencies: + tslib: 1.14.1 + typescript: 5.9.3 + + tsx@4.21.0: + dependencies: + esbuild: 0.27.2 + get-tsconfig: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.16.0: {} + + type-fest@0.18.1: {} + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-fest@0.6.0: {} + + type-fest@0.8.1: {} + + type-fest@3.13.1: {} + + type-fest@4.41.0: {} + + type-level-regexp@0.1.17: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typedarray.prototype.slice@1.0.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + math-intrinsics: 1.1.0 + typed-array-buffer: 1.0.3 + typed-array-byte-offset: 1.0.4 + + typescript@5.9.3: {} + + uc.micro@2.1.0: {} + + ufo@1.6.3: {} + + uglify-js@3.19.3: + optional: true + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + quansync: 1.0.0 + + unconfig@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + defu: 6.1.4 + jiti: 2.6.1 + quansync: 1.0.0 + unconfig-core: 7.5.0 + + undici-types@6.21.0: {} + + undici@7.18.2: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.2.0 + + unicode-match-property-value-ecmascript@2.2.1: {} + + unicode-property-aliases-ecmascript@2.2.0: {} + + union-value@1.0.1: + dependencies: + arr-union: 3.1.0 + get-value: 2.0.6 + is-extendable: 0.1.1 + set-value: 2.0.1 + + union@0.5.0: + dependencies: + qs: 6.14.1 + + unique-string@2.0.0: + dependencies: + crypto-random-string: 2.0.0 + + universalify@2.0.1: {} + + unocss@66.7.0(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)): + dependencies: + '@unocss/cli': 66.7.0 + '@unocss/core': 66.7.0 + '@unocss/preset-attributify': 66.7.0 + '@unocss/preset-icons': 66.7.0 + '@unocss/preset-mini': 66.7.0 + '@unocss/preset-tagify': 66.7.0 + '@unocss/preset-typography': 66.7.0 + '@unocss/preset-uno': 66.7.0 + '@unocss/preset-web-fonts': 66.7.0 + '@unocss/preset-wind': 66.7.0 + '@unocss/preset-wind3': 66.7.0 + '@unocss/preset-wind4': 66.7.0 + '@unocss/transformer-attributify-jsx': 66.7.0 + '@unocss/transformer-compile-class': 66.7.0 + '@unocss/transformer-directives': 66.7.0 + '@unocss/transformer-variant-group': 66.7.0 + '@unocss/vite': 66.7.0(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)) + transitivePeerDependencies: + - vite + + unpipe@1.0.0: {} + + unplugin-icons@0.22.0(@vue/compiler-sfc@3.5.27)(vue-template-compiler@2.7.16): + dependencies: + '@antfu/install-pkg': 0.5.0 + '@antfu/utils': 0.7.10 + '@iconify/utils': 2.3.0 + debug: 4.4.3 + kolorist: 1.8.0 + local-pkg: 0.5.1 + unplugin: 2.3.11 + optionalDependencies: + '@vue/compiler-sfc': 3.5.27 + vue-template-compiler: 2.7.16 + transitivePeerDependencies: + - supports-color + + unplugin-utils@0.3.1: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.3 + + unplugin-vue-components@0.24.1(@babel/parser@7.29.7)(rollup@4.52.5)(vue@3.5.27(typescript@5.9.3)): + dependencies: + '@antfu/utils': 0.7.10 + '@rollup/pluginutils': 5.3.0(rollup@4.52.5) + chokidar: 3.6.0 + debug: 4.4.3 + fast-glob: 3.3.3 + local-pkg: 0.4.3 + magic-string: 0.30.21 + minimatch: 7.4.9 + resolve: 1.22.11 + unplugin: 1.16.1 + vue: 3.5.27(typescript@5.9.3) + optionalDependencies: + '@babel/parser': 7.29.7 + transitivePeerDependencies: + - rollup + - supports-color + + unplugin@1.16.1: + dependencies: + acorn: 8.15.0 + webpack-virtual-modules: 0.6.2 + + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.15.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + + unset-value@1.0.0: + dependencies: + has-value: 0.3.1 + isobject: 3.0.1 + + upath@1.2.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + urix@0.1.0: {} + + url-join@4.0.1: {} + + use@3.1.1: {} + + util-deprecate@1.0.2: {} + + utils-merge@1.0.1: {} + + uuid@9.0.1: {} + + v8-compile-cache-lib@3.0.1: {} + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + vanilla-picker@2.12.3: + dependencies: + '@sphinxxxx/color-conversion': 2.2.2 + + vary@1.1.2: {} + + vditor@3.11.2: + dependencies: + diff-match-patch: 1.0.5 + + vite-plugin-compression@0.5.1(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)): + dependencies: + chalk: 4.1.2 + debug: 4.4.3 + fs-extra: 10.1.0 + vite: 7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0) + transitivePeerDependencies: + - supports-color + + vite-plugin-html@3.2.2(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)): + dependencies: + '@rollup/pluginutils': 4.2.1 + colorette: 2.0.20 + connect-history-api-fallback: 1.6.0 + consola: 2.15.3 + dotenv: 16.6.1 + dotenv-expand: 8.0.3 + ejs: 3.1.10 + fast-glob: 3.3.3 + fs-extra: 10.1.0 + html-minifier-terser: 6.1.0 + node-html-parser: 5.4.2 + pathe: 0.2.0 + vite: 7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0) + + vite-plugin-mkcert@1.17.9(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)): + dependencies: + axios: 1.13.2(debug@4.4.3) + debug: 4.4.3 + picocolors: 1.1.1 + vite: 7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0) + transitivePeerDependencies: + - supports-color + + vite-plugin-mock@2.9.8(mockjs@1.1.0)(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)): + dependencies: + '@types/mockjs': 1.0.10 + chalk: 4.1.2 + chokidar: 3.6.0 + connect: 3.7.0 + debug: 4.4.3 + esbuild: 0.14.54 + fast-glob: 3.3.3 + mockjs: 1.1.0 + path-to-regexp: 6.3.0 + vite: 7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0) + transitivePeerDependencies: + - supports-color + + vite-plugin-optimize-persist@0.1.2(vite-plugin-package-config@0.1.1(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)))(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)): + dependencies: + debug: 4.4.3 + fs-extra: 10.1.0 + vite: 7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0) + vite-plugin-package-config: 0.1.1(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)) + transitivePeerDependencies: + - supports-color + + vite-plugin-package-config@0.1.1(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)): + dependencies: + debug: 4.4.3 + vite: 7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0) + transitivePeerDependencies: + - supports-color + + vite-plugin-purge-icons@0.10.0(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)): + dependencies: + '@purge-icons/core': 0.10.0 + '@purge-icons/generated': 0.10.0 + rollup-plugin-purge-icons: 0.10.0 + vite: 7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0) + transitivePeerDependencies: + - encoding + - supports-color + + vite-plugin-pwa@1.2.0(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0): + dependencies: + debug: 4.4.3 + pretty-bytes: 6.1.1 + tinyglobby: 0.2.15 + vite: 7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0) + workbox-build: 7.4.0(@types/babel__core@7.20.5) + workbox-window: 7.4.0 + transitivePeerDependencies: + - supports-color + + vite-plugin-qiankun@1.0.15(typescript@5.9.3)(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)): + dependencies: + cheerio: 1.1.2 + typescript: 5.9.3 + vite: 7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0) + + vite-plugin-svg-icons@2.0.1(vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)): + dependencies: + '@types/svgo': 2.6.4 + cors: 2.8.5 + debug: 4.4.3 + etag: 1.8.1 + fs-extra: 10.1.0 + pathe: 0.2.0 + svg-baker: 1.7.0 + svgo: 2.8.0 + vite: 7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0) + transitivePeerDependencies: + - supports-color + + vite-plugin-vue-setup-extend-plus@0.1.0: {} + + vite@7.3.5(@types/node@20.19.30)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0): + dependencies: + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.5 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 20.19.30 + fsevents: 2.3.3 + jiti: 2.6.1 + less: 4.5.1 + terser: 5.46.0 + tsx: 4.21.0 + + vue-color@3.3.3(vue@3.5.27(typescript@5.9.3)): + dependencies: + material-colors: 1.2.6 + tinycolor2: 1.6.0 + vue: 3.5.27(typescript@5.9.3) + + vue-component-type-helpers@2.2.12: {} + + vue-cropper@0.6.5: {} + + vue-cropperjs@5.0.0(vue@3.5.27(typescript@5.9.3)): + dependencies: + cropperjs: 1.6.2 + vue: 3.5.27(typescript@5.9.3) + + vue-demi@0.14.10(vue@3.5.27(typescript@5.9.3)): + dependencies: + vue: 3.5.27(typescript@5.9.3) + + vue-eslint-parser@9.4.3(eslint@8.57.1): + dependencies: + debug: 4.4.3 + eslint: 8.57.1 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.7.0 + lodash: 4.17.21 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + + vue-grid-layout-v3@3.1.2(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)(typescript@5.9.3): + dependencies: + '@interactjs/actions': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/auto-scroll': 1.10.27(@interactjs/utils@1.10.27) + '@interactjs/auto-start': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + '@interactjs/dev-tools': 1.10.27(@interactjs/modifiers@1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)(typescript@5.9.3) + '@interactjs/interact': 1.10.27 + '@interactjs/modifiers': 1.10.27(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27) + element-resize-detector: 1.2.4 + mitt: 3.0.1 + transitivePeerDependencies: + - '@interactjs/core' + - '@interactjs/utils' + - typescript + + vue-i18n@9.14.5(vue@3.5.27(typescript@5.9.3)): + dependencies: + '@intlify/core-base': 9.14.5 + '@intlify/shared': 9.14.5 + '@vue/devtools-api': 6.6.4 + vue: 3.5.27(typescript@5.9.3) + + vue-infinite-scroll@2.0.2: {} + + vue-print-nb-jeecg@1.0.13: + dependencies: + babel-plugin-transform-runtime: 6.23.0 + + vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.27(typescript@5.9.3) + + vue-template-compiler@2.7.16: + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + vue-tsc@1.8.27(typescript@5.9.3): + dependencies: + '@volar/typescript': 1.11.1 + '@vue/language-core': 1.8.27(typescript@5.9.3) + semver: 7.7.3 + typescript: 5.9.3 + + vue-types@3.0.2(vue@3.5.27(typescript@5.9.3)): + dependencies: + is-plain-object: 3.0.1 + vue: 3.5.27(typescript@5.9.3) + + vue-types@5.1.3(vue@3.5.27(typescript@5.9.3)): + dependencies: + is-plain-object: 5.0.0 + optionalDependencies: + vue: 3.5.27(typescript@5.9.3) + + vue@3.5.27(typescript@5.9.3): + dependencies: + '@vue/compiler-dom': 3.5.27 + '@vue/compiler-sfc': 3.5.27 + '@vue/runtime-dom': 3.5.27 + '@vue/server-renderer': 3.5.27(vue@3.5.27(typescript@5.9.3)) + '@vue/shared': 3.5.27 + optionalDependencies: + typescript: 5.9.3 + + vuedraggable@4.1.0(vue@3.5.27(typescript@5.9.3)): + dependencies: + sortablejs: 1.14.0 + vue: 3.5.27(typescript@5.9.3) + + vxe-pc-ui@4.6.12(vue@3.5.27(typescript@5.9.3)): + dependencies: + '@vxe-ui/core': 4.2.21(vue@3.5.27(typescript@5.9.3)) + transitivePeerDependencies: + - vue + + vxe-table-plugin-antd@4.0.8(vxe-table@4.13.31(vue@3.5.27(typescript@5.9.3))): + dependencies: + vxe-table: 4.13.31(vue@3.5.27(typescript@5.9.3)) + + vxe-table@4.13.31(vue@3.5.27(typescript@5.9.3)): + dependencies: + vxe-pc-ui: 4.6.12(vue@3.5.27(typescript@5.9.3)) + transitivePeerDependencies: + - vue + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + warning@4.0.3: + dependencies: + loose-envify: 1.4.0 + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + webidl-conversions@3.0.1: {} + + webidl-conversions@4.0.2: {} + + webpack-virtual-modules@0.6.2: {} + + whatwg-encoding@2.0.0: + dependencies: + iconv-lite: 0.6.3 + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.20 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-module@2.0.1: {} + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wordwrap@1.0.0: {} + + workbox-background-sync@7.4.0: + dependencies: + idb: 7.1.1 + workbox-core: 7.4.0 + + workbox-broadcast-update@7.4.0: + dependencies: + workbox-core: 7.4.0 + + workbox-build@7.4.0(@types/babel__core@7.20.5): + dependencies: + '@apideck/better-ajv-errors': 0.3.6(ajv@8.17.1) + '@babel/core': 7.29.7 + '@babel/preset-env': 7.28.6(@babel/core@7.29.7) + '@babel/runtime': 7.28.6 + '@rollup/plugin-babel': 5.3.1(@babel/core@7.29.7)(@types/babel__core@7.20.5)(rollup@2.79.2) + '@rollup/plugin-node-resolve': 15.3.1(rollup@2.79.2) + '@rollup/plugin-replace': 2.4.2(rollup@2.79.2) + '@rollup/plugin-terser': 0.4.4(rollup@2.79.2) + '@surma/rollup-plugin-off-main-thread': 2.2.3 + ajv: 8.17.1 + common-tags: 1.8.2 + fast-json-stable-stringify: 2.1.0 + fs-extra: 9.1.0 + glob: 11.1.0 + lodash: 4.17.21 + pretty-bytes: 5.6.0 + rollup: 2.79.2 + source-map: 0.8.0-beta.0 + stringify-object: 3.3.0 + strip-comments: 2.0.1 + tempy: 0.6.0 + upath: 1.2.0 + workbox-background-sync: 7.4.0 + workbox-broadcast-update: 7.4.0 + workbox-cacheable-response: 7.4.0 + workbox-core: 7.4.0 + workbox-expiration: 7.4.0 + workbox-google-analytics: 7.4.0 + workbox-navigation-preload: 7.4.0 + workbox-precaching: 7.4.0 + workbox-range-requests: 7.4.0 + workbox-recipes: 7.4.0 + workbox-routing: 7.4.0 + workbox-strategies: 7.4.0 + workbox-streams: 7.4.0 + workbox-sw: 7.4.0 + workbox-window: 7.4.0 + transitivePeerDependencies: + - '@types/babel__core' + - supports-color + + workbox-cacheable-response@7.4.0: + dependencies: + workbox-core: 7.4.0 + + workbox-core@7.4.0: {} + + workbox-expiration@7.4.0: + dependencies: + idb: 7.1.1 + workbox-core: 7.4.0 + + workbox-google-analytics@7.4.0: + dependencies: + workbox-background-sync: 7.4.0 + workbox-core: 7.4.0 + workbox-routing: 7.4.0 + workbox-strategies: 7.4.0 + + workbox-navigation-preload@7.4.0: + dependencies: + workbox-core: 7.4.0 + + workbox-precaching@7.4.0: + dependencies: + workbox-core: 7.4.0 + workbox-routing: 7.4.0 + workbox-strategies: 7.4.0 + + workbox-range-requests@7.4.0: + dependencies: + workbox-core: 7.4.0 + + workbox-recipes@7.4.0: + dependencies: + workbox-cacheable-response: 7.4.0 + workbox-core: 7.4.0 + workbox-expiration: 7.4.0 + workbox-precaching: 7.4.0 + workbox-routing: 7.4.0 + workbox-strategies: 7.4.0 + + workbox-routing@7.4.0: + dependencies: + workbox-core: 7.4.0 + + workbox-strategies@7.4.0: + dependencies: + workbox-core: 7.4.0 + + workbox-streams@7.4.0: + dependencies: + workbox-core: 7.4.0 + workbox-routing: 7.4.0 + + workbox-sw@7.4.0: {} + + workbox-window@7.4.0: + dependencies: + '@types/trusted-types': 2.0.7 + workbox-core: 7.4.0 + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + + xe-utils@3.5.26: {} + + xe-utils@3.8.4: {} + + xml-name-validator@4.0.0: {} + + xss@1.0.15: + dependencies: + commander: 2.20.3 + cssfilter: 0.0.10 + + y18n@4.0.3: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yaml@2.3.4: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs-parser@20.2.9: {} + + yargs-parser@21.1.1: {} + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} + + yocto-queue@1.2.2: {} + + yoctocolors-cjs@2.1.3: {} + + zrender@5.6.1: + dependencies: + tslib: 2.3.0 diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..a47ef4f --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + autoprefixer: {}, + }, +}; diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..4b07f91 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,20 @@ +module.exports = { + printWidth: 150, + tabWidth: 2, + useTabs: false, + semi: true, //语句末尾使用分号 + vueIndentScriptAndStyle: true, + singleQuote: true, // 使用单引号 + quoteProps: 'as-needed', + bracketSpacing: true, + trailingComma: 'es5', + jsxBracketSameLine: false, + jsxSingleQuote: false, + arrowParens: 'always', + insertPragma: false, + requirePragma: false, + proseWrap: 'never', + htmlWhitespaceSensitivity: 'strict', + endOfLine: 'auto', // 自动处理换行符(LF/CRLF) + rangeStart: 0, +}; diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..ddd5fb3 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..8072ced Binary files /dev/null and b/public/logo.png differ diff --git a/public/resource/img/logo.png b/public/resource/img/logo.png new file mode 100644 index 0000000..8072ced Binary files /dev/null and b/public/resource/img/logo.png differ diff --git a/public/resource/js/iconfont.js b/public/resource/js/iconfont.js new file mode 100644 index 0000000..8bb5237 --- /dev/null +++ b/public/resource/js/iconfont.js @@ -0,0 +1 @@ +window._iconfont_svg_string_3814468='',function(l){var c=(c=document.getElementsByTagName("script"))[c.length-1],h=c.getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var a,o,t,z,i,v=function(c,h){h.parentNode.insertBefore(c,h)};if(h&&!l.__iconfont__svg__cssinject__){l.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}a=function(){var c,h=document.createElement("div");h.innerHTML=l._iconfont_svg_string_3814468,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(c=document.body).firstChild?v(h,c.firstChild):c.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(a,0):(o=function(){document.removeEventListener("DOMContentLoaded",o,!1),a()},document.addEventListener("DOMContentLoaded",o,!1)):document.attachEvent&&(t=a,z=l.document,i=!1,m(),z.onreadystatechange=function(){"complete"==z.readyState&&(z.onreadystatechange=null,s())})}function s(){i||(i=!0,t())}function m(){try{z.documentElement.doScroll("left")}catch(c){return void setTimeout(m,50)}s()}}(window); \ No newline at end of file diff --git a/public/resource/tinymce/langs/en.js b/public/resource/tinymce/langs/en.js new file mode 100644 index 0000000..27337c3 --- /dev/null +++ b/public/resource/tinymce/langs/en.js @@ -0,0 +1,419 @@ +tinymce.addI18n('es', { + Redo: 'Rehacer', + Undo: 'Deshacer', + Cut: 'Cortar', + Copy: 'Copiar', + Paste: 'Pegar', + 'Select all': 'Seleccionar todo', + 'New document': 'Nuevo documento', + Ok: 'Ok', + Cancel: 'Cancelar', + 'Visual aids': 'Ayudas visuales', + Bold: 'Negrita', + Italic: 'Cursiva', + Underline: 'Subrayado', + Strikethrough: 'Tachado', + Superscript: 'Super\u00edndice', + Subscript: 'Sub\u00edndice', + 'Clear formatting': 'Limpiar formato', + 'Align left': 'Alinear a la izquierda', + 'Align center': 'Alinear al centro', + 'Align right': 'Alinear a la derecha', + Justify: 'Justificar', + 'Bullet list': 'Lista de vi\u00f1etas', + 'Numbered list': 'Lista numerada', + 'Decrease indent': 'Disminuir sangr\u00eda', + 'Increase indent': 'Incrementar sangr\u00eda', + Close: 'Cerrar', + Formats: 'Formatos', + "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": 'Su navegador no es compatible con el acceso directo al portapapeles. Use las teclas Crtl+X\/C\/V de su teclado.', + Headers: 'Encabezados', + 'Header 1': 'Encabezado 1', + 'Header 2': 'Encabezado 2', + 'Header 3': 'Encabezado 3', + 'Header 4': 'Encabezado 4', + 'Header 5': 'Encabezado 5', + 'Header 6': 'Encabezado 6', + Headings: 'Encabezados', + 'Heading 1': 'Encabezado 1', + 'Heading 2': 'Encabezado 2', + 'Heading 3': 'Encabezado 3', + 'Heading 4': 'Encabezado 4', + 'Heading 5': 'Encabezado 5', + 'Heading 6': 'Encabezado 6', + Preformatted: 'Con formato previo', + Div: 'Div', + Pre: 'Pre', + Code: 'C\u00f3digo', + Paragraph: 'P\u00e1rrafo', + Blockquote: 'Blockquote', + Inline: 'Alineado', + Blocks: 'Bloques', + 'Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.': 'Pegar est\u00e1 ahora en modo de texto plano. El contenido se pegar\u00e1 como texto plano hasta que desactive esta opci\u00f3n.', + Fonts: 'Fuentes', + 'Font Sizes': 'Tama\u00f1os de fuente', + Class: 'Clase', + 'Browse for an image': 'Buscar una imagen', + OR: 'OR', + 'Drop an image here': 'Arrastre una imagen aqu\u00ed', + Upload: 'Cargar', + Block: 'Bloque', + Align: 'Alinear', + Default: 'Por defecto', + Circle: 'C\u00edrculo', + Disc: 'Disco', + Square: 'Cuadrado', + 'Lower Alpha': 'Inferior Alfa', + 'Lower Greek': 'Inferior Griega', + 'Lower Roman': 'Inferior Romana', + 'Upper Alpha': 'Superior Alfa', + 'Upper Roman': 'Superior Romana', + 'Anchor...': 'Anclaje...', + Name: 'Nombre', + Id: 'Id', + 'Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.': 'Deber\u00eda comenzar por una letra, seguida solo de letras, n\u00fameros, guiones, puntos, dos puntos o guiones bajos.', + 'You have unsaved changes are you sure you want to navigate away?': 'Tiene cambios sin guardar. \u00bfEst\u00e1 seguro de que quiere salir?', + 'Restore last draft': 'Restaurar el \u00faltimo borrador', + 'Special character...': 'Car\u00e1cter especial...', + 'Source code': 'C\u00f3digo fuente', + 'Insert\/Edit code sample': 'Insertar\/editar c\u00f3digo de prueba', + Language: 'Idioma', + 'Code sample...': 'Ejemplo de c\u00f3digo...', + 'Color Picker': 'Selector de colores', + R: 'R', + G: 'V', + B: 'A', + 'Left to right': 'De izquierda a derecha', + 'Right to left': 'De derecha a izquierda', + 'Emoticons...': 'Emoticones...', + 'Metadata and Document Properties': 'Metadatos y propiedades del documento', + Title: 'T\u00edtulo', + Keywords: 'Palabras clave', + Description: 'Descripci\u00f3n', + Robots: 'Robots', + Author: 'Autor', + Encoding: 'Codificaci\u00f3n', + Fullscreen: 'Pantalla completa', + Action: 'Acci\u00f3n', + Shortcut: 'Atajo', + Help: 'Ayuda', + Address: 'Direcci\u00f3n', + 'Focus to menubar': 'Enfocar la barra del men\u00fa', + 'Focus to toolbar': 'Enfocar la barra de herramientas', + 'Focus to element path': 'Enfocar la ruta del elemento', + 'Focus to contextual toolbar': 'Enfocar la barra de herramientas contextual', + 'Insert link (if link plugin activated)': 'Insertar enlace (si el complemento de enlace est\u00e1 activado)', + 'Save (if save plugin activated)': 'Guardar (si el componente de salvar est\u00e1 activado)', + 'Find (if searchreplace plugin activated)': 'Buscar (si el complemento buscar-remplazar est\u00e1 activado)', + 'Plugins installed ({0}):': 'Plugins instalados ({0}):', + 'Premium plugins:': 'Complementos premium:', + 'Learn more...': 'Aprende m\u00e1s...', + 'You are using {0}': 'Estas usando {0}', + Plugins: 'Complementos', + 'Handy Shortcuts': 'Accesos directos', + 'Horizontal line': 'L\u00ednea horizontal', + 'Insert\/edit image': 'Insertar\/editar imagen', + 'Image description': 'Descripci\u00f3n de la imagen', + Source: 'Enlace', + Dimensions: 'Dimensiones', + 'Constrain proportions': 'Restringir proporciones', + General: 'General', + Advanced: 'Avanzado', + Style: 'Estilo', + 'Vertical space': 'Espacio vertical', + 'Horizontal space': 'Espacio horizontal', + Border: 'Borde', + 'Insert image': 'Insertar imagen', + 'Image...': 'Imagen...', + 'Image list': 'Lista de im\u00e1genes', + 'Rotate counterclockwise': 'Girar a la izquierda', + 'Rotate clockwise': 'Girar a la derecha', + 'Flip vertically': 'Invertir verticalmente', + 'Flip horizontally': 'Invertir horizontalmente', + 'Edit image': 'Editar imagen', + 'Image options': 'Opciones de imagen', + 'Zoom in': 'Acercar', + 'Zoom out': 'Alejar', + Crop: 'Recortar', + Resize: 'Redimensionar', + Orientation: 'Orientaci\u00f3n', + Brightness: 'Brillo', + Sharpen: 'Forma', + Contrast: 'Contraste', + 'Color levels': 'Niveles de color', + Gamma: 'Gamma', + Invert: 'Invertir', + Apply: 'Aplicar', + Back: 'Atr\u00e1s', + 'Insert date\/time': 'Insertar fecha\/hora', + 'Date\/time': 'Fecha\/hora', + 'Insert\/Edit Link': 'Insertar\/editar enlace', + 'Insert\/edit link': 'Insertar\/editar enlace', + 'Text to display': 'Texto para mostrar', + Url: 'URL', + 'Open link in...': 'Abrir enlace en...', + 'Current window': 'Ventana actual', + None: 'Ninguno', + 'New window': 'Nueva ventana', + 'Remove link': 'Quitar enlace', + Anchors: 'Anclas', + 'Link...': 'Enlace...', + 'Paste or type a link': 'Pega o introduce un enlace', + 'The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?': 'El enlace que has introducido no parece ser una direcci\u00f3n de correo electr\u00f3nico. Quieres a\u00f1adir el prefijo necesario mailto: ?', + 'The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?': 'El enlace que has introducido no parece ser una enlace externo. Quieres a\u00f1adir el prefijo necesario http:\/\/ ?', + 'Link list': 'Lista de enlaces', + 'Insert video': 'Insertar video', + 'Insert\/edit video': 'Insertar\/editar video', + 'Insert\/edit media': 'Insertar\/editar medio', + 'Alternative source': 'Enlace alternativo', + 'Alternative source URL': 'Origen de URL alternativo', + 'Media poster (Image URL)': 'P\u00f3ster de medio (URL de imagen)', + 'Paste your embed code below:': 'Pega tu c\u00f3digo embebido debajo', + Embed: 'Incrustado', + 'Media...': 'Medios...', + 'Nonbreaking space': 'Espacio fijo', + 'Page break': 'Salto de p\u00e1gina', + 'Paste as text': 'Pegar como texto', + Preview: 'Previsualizar', + 'Print...': 'Imprimir...', + Save: 'Guardar', + Find: 'Buscar', + 'Replace with': 'Reemplazar con', + Replace: 'Reemplazar', + 'Replace all': 'Reemplazar todo', + Previous: 'Anterior', + Next: 'Siguiente', + 'Find and replace...': 'Buscar y reemplazar...', + 'Could not find the specified string.': 'No se encuentra la cadena de texto especificada', + 'Match case': 'Coincidencia exacta', + 'Find whole words only': 'Solo palabras completas', + 'Spell check': 'Revisar ortograf\u00eda', + Ignore: 'Ignorar', + 'Ignore all': 'Ignorar todos', + Finish: 'Finalizar', + 'Add to Dictionary': 'A\u00f1adir al Diccionario', + 'Insert table': 'Insertar tabla', + 'Table properties': 'Propiedades de la tabla', + 'Delete table': 'Eliminar tabla', + Cell: 'Celda', + Row: 'Fila', + Column: 'Columna', + 'Cell properties': 'Propiedades de la celda', + 'Merge cells': 'Combinar celdas', + 'Split cell': 'Dividir celdas', + 'Insert row before': 'Insertar fila antes', + 'Insert row after': 'Insertar fila despu\u00e9s ', + 'Delete row': 'Eliminar fila', + 'Row properties': 'Propiedades de la fila', + 'Cut row': 'Cortar fila', + 'Copy row': 'Copiar fila', + 'Paste row before': 'Pegar la fila antes', + 'Paste row after': 'Pegar la fila despu\u00e9s', + 'Insert column before': 'Insertar columna antes', + 'Insert column after': 'Insertar columna despu\u00e9s', + 'Delete column': 'Eliminar columna', + Cols: 'Columnas', + Rows: 'Filas', + Width: 'Ancho', + Height: 'Alto', + 'Cell spacing': 'Espacio entre celdas', + 'Cell padding': 'Relleno de celda', + 'Show caption': 'Mostrar t\u00edtulo', + Left: 'Izquierda', + Center: 'Centrado', + Right: 'Derecha', + 'Cell type': 'Tipo de celda', + Scope: '\u00c1mbito', + Alignment: 'Alineaci\u00f3n', + 'H Align': 'Alineamiento Horizontal', + 'V Align': 'Alineamiento Vertical', + Top: 'Arriba', + Middle: 'Centro', + Bottom: 'Abajo', + 'Header cell': 'Celda de la cebecera', + 'Row group': 'Grupo de filas', + 'Column group': 'Grupo de columnas', + 'Row type': 'Tipo de fila', + Header: 'Cabecera', + Body: 'Cuerpo', + Footer: 'Pie de p\u00e1gina', + 'Border color': 'Color del borde', + 'Insert template...': 'Insertar plantilla...', + Templates: 'Plantillas', + Template: 'Plantilla', + 'Text color': 'Color del texto', + 'Background color': 'Color de fondo', + 'Custom...': 'Personalizar...', + 'Custom color': 'Color personalizado', + 'No color': 'Sin color', + 'Remove color': 'Quitar color', + 'Table of Contents': 'Tabla de contenidos', + 'Show blocks': 'Mostrar bloques', + 'Show invisible characters': 'Mostrar caracteres invisibles', + 'Word count': 'Contar palabras', + Count: 'Recuento', + Document: 'Documento', + Selection: 'Selecci\u00f3n', + Words: 'Palabras', + 'Words: {0}': 'Palabras: {0}', + '{0} words': '{0} palabras', + File: 'Archivo', + Edit: 'Editar', + Insert: 'Insertar', + View: 'Ver', + Format: 'Formato', + Table: 'Tabla', + Tools: 'Herramientas', + 'Powered by {0}': 'Desarrollado por {0}', + 'Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help': '\u00c1rea de texto enriquecido. Pulse ALT-F9 para el menu. Pulse ALT-F10 para la barra de herramientas. Pulse ALT-0 para ayuda', + 'Image title': 'Titulo de imagen', + 'Border width': 'Ancho de borde', + 'Border style': 'Estilo de borde', + Error: 'Error', + Warn: 'Advertencia', + Valid: 'V\u00e1lido', + 'To open the popup, press Shift+Enter': 'Para abrir el elemento emergente, pulse May\u00fas+Intro', + 'Rich Text Area. Press ALT-0 for help.': '\u00c1rea de texto enriquecido. Pulse ALT-0 para abrir la ayuda.', + 'System Font': 'Fuente de sistema', + 'Failed to upload image: {0}': 'Fallo al cargar imagen: {0}', + 'Failed to load plugin: {0} from url {1}': 'Fallo al cargar complemento: {0} desde URL {1}', + 'Failed to load plugin url: {0}': 'Fallo al cargar URL del complemento: {0}', + 'Failed to initialize plugin: {0}': 'Fallo al iniciar el complemento: {0}', + example: 'ejemplo', + Search: 'Buscar', + All: 'Todo', + Currency: 'Divisa', + Text: 'Texto', + Quotations: 'Comillas', + Mathematical: 'S\u00edmbolo matem\u00e1tico', + 'Extended Latin': 'Latino extendido A', + Symbols: 'S\u00edmbolos', + Arrows: 'Flechas', + 'User Defined': 'Definido por el usuario', + 'dollar sign': 'signo de d\u00f3lar', + 'currency sign': 'signo de divisa', + 'euro-currency sign': 'signo de euro', + 'colon sign': 'signo de dos puntos', + 'cruzeiro sign': 'signo de cruceiro', + 'french franc sign': 'signo de franco franc\u00e9s', + 'lira sign': 'signo de lira', + 'mill sign': 'signo de mill', + 'naira sign': 'signo de naira', + 'peseta sign': 'signo de peseta', + 'rupee sign': 'signo de rupia', + 'won sign': 'signo de won', + 'new sheqel sign': 'signo de nuevo s\u00e9quel', + 'dong sign': 'signo de dong', + 'kip sign': 'signo de kip', + 'tugrik sign': 'signo de tugrik', + 'drachma sign': 'signo de dracma', + 'german penny symbol': 'signo de penique alem\u00e1n', + 'peso sign': 'signo de peso', + 'guarani sign': 'signo de guaran\u00ed', + 'austral sign': 'signo de austral', + 'hryvnia sign': 'signo de grivna', + 'cedi sign': 'signo de cedi', + 'livre tournois sign': 'signo de libra tornesa', + 'spesmilo sign': 'signo de spesmilo', + 'tenge sign': 'signo de tenge', + 'indian rupee sign': 'signo de rupia india', + 'turkish lira sign': 'signo de lira turca', + 'nordic mark sign': 'signo de marco n\u00f3rdico', + 'manat sign': 'signo de manat', + 'ruble sign': 'signo de rublo', + 'yen character': 'car\u00e1cter de yen', + 'yuan character': 'car\u00e1cter de yuan', + 'yuan character, in hong kong and taiwan': 'car\u00e1cter de yuan en Hong Kong y Taiw\u00e1n', + 'yen\/yuan character variant one': 'Variante uno de car\u00e1cter de yen\/yuan', + 'Loading emoticons...': 'Cargando emoticonos...', + 'Could not load emoticons': 'No se han podido cargar los emoticonos', + People: 'Personas', + 'Animals and Nature': 'Animales y naturaleza', + 'Food and Drink': 'Comida y bebida', + Activity: 'Actividad', + 'Travel and Places': 'Viajes y lugares', + Objects: 'Objetos', + Flags: 'Banderas', + Characters: 'Caracteres', + 'Characters (no spaces)': 'Caracteres (sin espacios)', + '{0} characters': '{0} caracteres', + 'Error: Form submit field collision.': 'Error: Colisi\u00f3n de campo al enviar formulario.', + 'Error: No form element found.': 'Error: No se encuentra ning\u00fan elemento de formulario.', + Update: 'Actualizar', + 'Color swatch': 'Muestrario de colores', + Turquoise: 'Turquesa', + Green: 'Verde', + Blue: 'Azul', + Purple: 'P\u00farpura', + 'Navy Blue': 'Azul marino', + 'Dark Turquoise': 'Turquesa oscuro', + 'Dark Green': 'Verde oscuro', + 'Medium Blue': 'Azul medio', + 'Medium Purple': 'P\u00farpura medio', + 'Midnight Blue': 'Azul medio', + Yellow: 'Amarillo', + Orange: 'Naranja', + Red: 'Rojo', + 'Light Gray': 'Gris claro', + Gray: 'Gris', + 'Dark Yellow': 'Amarillo oscuro', + 'Dark Orange': 'Naranja oscuro', + 'Dark Red': 'Rojo oscuro', + 'Medium Gray': 'Gris medio', + 'Dark Gray': 'Gris oscuro', + 'Light Green': 'Verde claro', + 'Light Yellow': 'Amarillo claro', + 'Light Red': 'Rojo claro', + 'Light Purple': 'Morado claro', + 'Light Blue': 'Azul claro', + 'Dark Purple': 'Morado oscuro', + 'Dark Blue': 'Azul oscuro', + Black: 'Negro', + White: 'Blanco', + 'Switch to or from fullscreen mode': 'Activar o desactivar modo pantalla completa', + 'Open help dialog': 'Abrir di\u00e1logo de ayuda', + history: 'historial', + styles: 'estilos', + formatting: 'formato', + alignment: 'alineaci\u00f3n', + indentation: 'sangr\u00eda', + 'permanent pen': 'bol\u00edgrafo permanente', + comments: 'comentarios', + 'Format Painter': 'Copiar formato', + 'Insert\/edit iframe': 'Insertar\/editar iframe', + Capitalization: 'Uso de may\u00fasculas', + lowercase: 'min\u00fasculas', + UPPERCASE: 'MAY\u00daSCULAS', + 'Title Case': 'Tipo T\u00edtulo', + 'Permanent Pen Properties': 'Propiedades del bol\u00edgrafo permanente', + 'Permanent pen properties...': 'Propiedades del bol\u00edgrafo permanente...', + Font: 'Fuente', + Size: 'Tama\u00f1o', + 'More...': 'M\u00e1s...', + 'Spellcheck Language': 'Corrector', + 'Select...': 'Seleccionar...', + Preferences: 'Preferencias', + Yes: 'S\u00ed', + No: 'No', + 'Keyboard Navigation': 'Navegaci\u00f3n con el teclado', + Version: 'Versi\u00f3n', + Anchor: 'Ancla', + 'Special character': 'Car\u00e1cter especial', + 'Code sample': 'Ejemplo de c\u00f3digo', + Color: 'Color', + Emoticons: 'Emoticonos', + 'Document properties': 'Propiedades del documento', + Image: 'Imagen', + 'Insert link': 'Insertar enlace', + Target: 'Destino', + Link: 'Enlace', + Poster: 'Miniatura', + Media: 'Media', + Print: 'Imprimir', + Prev: 'Anterior', + 'Find and replace': 'Buscar y reemplazar', + 'Whole words': 'Palabras completas', + Spellcheck: 'Corrector ortogr\u00e1fico', + Caption: 'Subt\u00edtulo', + 'Insert template': 'Insertar plantilla' +}) diff --git a/public/resource/tinymce/langs/zh_CN.js b/public/resource/tinymce/langs/zh_CN.js new file mode 100644 index 0000000..f9d8b5c --- /dev/null +++ b/public/resource/tinymce/langs/zh_CN.js @@ -0,0 +1,389 @@ +tinymce.addI18n('zh_CN',{ +"Redo": "\u91cd\u505a", +"Undo": "\u64a4\u9500", +"Cut": "\u526a\u5207", +"Copy": "\u590d\u5236", +"Paste": "\u7c98\u8d34", +"Select all": "\u5168\u9009", +"New document": "\u65b0\u6587\u4ef6", +"Ok": "\u786e\u5b9a", +"Cancel": "\u53d6\u6d88", +"Visual aids": "\u7f51\u683c\u7ebf", +"Bold": "\u7c97\u4f53", +"Italic": "\u659c\u4f53", +"Underline": "\u4e0b\u5212\u7ebf", +"Strikethrough": "\u5220\u9664\u7ebf", +"Superscript": "\u4e0a\u6807", +"Subscript": "\u4e0b\u6807", +"Clear formatting": "\u6e05\u9664\u683c\u5f0f", +"Align left": "\u5de6\u8fb9\u5bf9\u9f50", +"Align center": "\u4e2d\u95f4\u5bf9\u9f50", +"Align right": "\u53f3\u8fb9\u5bf9\u9f50", +"Justify": "\u4e24\u7aef\u5bf9\u9f50", +"Bullet list": "\u9879\u76ee\u7b26\u53f7", +"Numbered list": "\u7f16\u53f7\u5217\u8868", +"Decrease indent": "\u51cf\u5c11\u7f29\u8fdb", +"Increase indent": "\u589e\u52a0\u7f29\u8fdb", +"Close": "\u5173\u95ed", +"Formats": "\u683c\u5f0f", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u6253\u5f00\u526a\u8d34\u677f\uff0c\u8bf7\u4f7f\u7528Ctrl+X\/C\/V\u7b49\u5feb\u6377\u952e\u3002", +"Headers": "\u6807\u9898", +"Header 1": "\u6807\u98981", +"Header 2": "\u6807\u98982", +"Header 3": "\u6807\u98983", +"Header 4": "\u6807\u98984", +"Header 5": "\u6807\u98985", +"Header 6": "\u6807\u98986", +"Headings": "\u6807\u9898", +"Heading 1": "\u6807\u98981", +"Heading 2": "\u6807\u98982", +"Heading 3": "\u6807\u98983", +"Heading 4": "\u6807\u98984", +"Heading 5": "\u6807\u98985", +"Heading 6": "\u6807\u98986", +"Preformatted": "\u9884\u5148\u683c\u5f0f\u5316\u7684", +"Div": "Div", +"Pre": "Pre", +"Code": "\u4ee3\u7801", +"Paragraph": "\u6bb5\u843d", +"Blockquote": "\u5f15\u6587\u533a\u5757", +"Inline": "\u6587\u672c", +"Blocks": "\u57fa\u5757", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002", +"Fonts": "\u5b57\u4f53", +"Font Sizes": "\u5b57\u53f7", +"Class": "\u7c7b\u578b", +"Browse for an image": "\u6d4f\u89c8\u56fe\u50cf", +"OR": "\u6216", +"Drop an image here": "\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64", +"Upload": "\u4e0a\u4f20", +"Block": "\u5757", +"Align": "\u5bf9\u9f50", +"Default": "\u9ed8\u8ba4", +"Circle": "\u7a7a\u5fc3\u5706", +"Disc": "\u5b9e\u5fc3\u5706", +"Square": "\u65b9\u5757", +"Lower Alpha": "\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd", +"Lower Greek": "\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd", +"Lower Roman": "\u5c0f\u5199\u7f57\u9a6c\u5b57\u6bcd", +"Upper Alpha": "\u5927\u5199\u82f1\u6587\u5b57\u6bcd", +"Upper Roman": "\u5927\u5199\u7f57\u9a6c\u5b57\u6bcd", +"Anchor...": "\u951a\u70b9...", +"Name": "\u540d\u79f0", +"Id": "\u6807\u8bc6\u7b26", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u6807\u8bc6\u7b26\u5e94\u8be5\u4ee5\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u8ddf\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002", +"You have unsaved changes are you sure you want to navigate away?": "\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f", +"Restore last draft": "\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f", +"Special characters...": "\u7279\u6b8a\u5b57\u7b26...", +"Source code": "\u6e90\u4ee3\u7801", +"Insert\/Edit code sample": "\u63d2\u5165\/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b", +"Language": "\u8bed\u8a00", +"Code sample...": "\u793a\u4f8b\u4ee3\u7801...", +"Color Picker": "\u9009\u8272\u5668", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "\u4ece\u5de6\u5230\u53f3", +"Right to left": "\u4ece\u53f3\u5230\u5de6", +"Emoticons...": "\u8868\u60c5\u7b26\u53f7...", +"Metadata and Document Properties": "\u5143\u6570\u636e\u548c\u6587\u6863\u5c5e\u6027", +"Title": "\u6807\u9898", +"Keywords": "\u5173\u952e\u8bcd", +"Description": "\u63cf\u8ff0", +"Robots": "\u673a\u5668\u4eba", +"Author": "\u4f5c\u8005", +"Encoding": "\u7f16\u7801", +"Fullscreen": "\u5168\u5c4f", +"Action": "\u64cd\u4f5c", +"Shortcut": "\u5feb\u6377\u952e", +"Help": "\u5e2e\u52a9", +"Address": "\u5730\u5740", +"Focus to menubar": "\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f", +"Focus to toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f", +"Focus to element path": "\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84", +"Focus to contextual toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355", +"Insert link (if link plugin activated)": "\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)", +"Save (if save plugin activated)": "\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)", +"Find (if searchreplace plugin activated)": "\u67e5\u627e(\u5982\u679c\u67e5\u627e\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)", +"Plugins installed ({0}):": "\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):", +"Premium plugins:": "\u4f18\u79c0\u63d2\u4ef6\uff1a", +"Learn more...": "\u4e86\u89e3\u66f4\u591a...", +"You are using {0}": "\u4f60\u6b63\u5728\u4f7f\u7528 {0}", +"Plugins": "\u63d2\u4ef6", +"Handy Shortcuts": "\u5feb\u6377\u952e", +"Horizontal line": "\u6c34\u5e73\u5206\u5272\u7ebf", +"Insert\/edit image": "\u63d2\u5165\/\u7f16\u8f91\u56fe\u7247", +"Image description": "\u56fe\u7247\u63cf\u8ff0", +"Source": "\u5730\u5740", +"Dimensions": "\u5927\u5c0f", +"Constrain proportions": "\u4fdd\u6301\u7eb5\u6a2a\u6bd4", +"General": "\u666e\u901a", +"Advanced": "\u9ad8\u7ea7", +"Style": "\u6837\u5f0f", +"Vertical space": "\u5782\u76f4\u8fb9\u8ddd", +"Horizontal space": "\u6c34\u5e73\u8fb9\u8ddd", +"Border": "\u8fb9\u6846", +"Insert image": "\u63d2\u5165\u56fe\u7247", +"Image...": "\u56fe\u7247...", +"Image list": "\u56fe\u7247\u5217\u8868", +"Rotate counterclockwise": "\u9006\u65f6\u9488\u65cb\u8f6c", +"Rotate clockwise": "\u987a\u65f6\u9488\u65cb\u8f6c", +"Flip vertically": "\u5782\u76f4\u7ffb\u8f6c", +"Flip horizontally": "\u6c34\u5e73\u7ffb\u8f6c", +"Edit image": "\u7f16\u8f91\u56fe\u7247", +"Image options": "\u56fe\u7247\u9009\u9879", +"Zoom in": "\u653e\u5927", +"Zoom out": "\u7f29\u5c0f", +"Crop": "\u88c1\u526a", +"Resize": "\u8c03\u6574\u5927\u5c0f", +"Orientation": "\u65b9\u5411", +"Brightness": "\u4eae\u5ea6", +"Sharpen": "\u9510\u5316", +"Contrast": "\u5bf9\u6bd4\u5ea6", +"Color levels": "\u989c\u8272\u5c42\u6b21", +"Gamma": "\u4f3d\u9a6c\u503c", +"Invert": "\u53cd\u8f6c", +"Apply": "\u5e94\u7528", +"Back": "\u540e\u9000", +"Insert date\/time": "\u63d2\u5165\u65e5\u671f\/\u65f6\u95f4", +"Date\/time": "\u65e5\u671f\/\u65f6\u95f4", +"Insert\/Edit Link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5", +"Insert\/edit link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5", +"Text to display": "\u663e\u793a\u6587\u5b57", +"Url": "\u5730\u5740", +"Open link in...": "\u94fe\u63a5\u6253\u5f00\u4f4d\u7f6e...", +"Current window": "\u5f53\u524d\u7a97\u53e3", +"None": "\u65e0", +"New window": "\u5728\u65b0\u7a97\u53e3\u6253\u5f00", +"Remove link": "\u5220\u9664\u94fe\u63a5", +"Anchors": "\u951a\u70b9", +"Link...": "\u94fe\u63a5...", +"Paste or type a link": "\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7f00\u5417\uff1f", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7f00\u5417\uff1f", +"Link list": "\u94fe\u63a5\u5217\u8868", +"Insert video": "\u63d2\u5165\u89c6\u9891", +"Insert\/edit video": "\u63d2\u5165\/\u7f16\u8f91\u89c6\u9891", +"Insert\/edit media": "\u63d2\u5165\/\u7f16\u8f91\u5a92\u4f53", +"Alternative source": "\u955c\u50cf", +"Alternative source URL": "\u66ff\u4ee3\u6765\u6e90\u7f51\u5740", +"Media poster (Image URL)": "\u5c01\u9762(\u56fe\u7247\u5730\u5740)", +"Paste your embed code below:": "\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:", +"Embed": "\u5185\u5d4c", +"Media...": "\u591a\u5a92\u4f53...", +"Nonbreaking space": "\u4e0d\u95f4\u65ad\u7a7a\u683c", +"Page break": "\u5206\u9875\u7b26", +"Paste as text": "\u7c98\u8d34\u4e3a\u6587\u672c", +"Preview": "\u9884\u89c8", +"Print...": "\u6253\u5370...", +"Save": "\u4fdd\u5b58", +"Find": "\u67e5\u627e", +"Replace with": "\u66ff\u6362\u4e3a", +"Replace": "\u66ff\u6362", +"Replace all": "\u5168\u90e8\u66ff\u6362", +"Previous": "\u4e0a\u4e00\u4e2a", +"Next": "\u4e0b\u4e00\u4e2a", +"Find and replace...": "\u67e5\u627e\u5e76\u66ff\u6362...", +"Could not find the specified string.": "\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9.", +"Match case": "\u533a\u5206\u5927\u5c0f\u5199", +"Find whole words only": "\u5168\u5b57\u5339\u914d", +"Spell check": "\u62fc\u5199\u68c0\u67e5", +"Ignore": "\u5ffd\u7565", +"Ignore all": "\u5168\u90e8\u5ffd\u7565", +"Finish": "\u5b8c\u6210", +"Add to Dictionary": "\u6dfb\u52a0\u5230\u5b57\u5178", +"Insert table": "\u63d2\u5165\u8868\u683c", +"Table properties": "\u8868\u683c\u5c5e\u6027", +"Delete table": "\u5220\u9664\u8868\u683c", +"Cell": "\u5355\u5143\u683c", +"Row": "\u884c", +"Column": "\u5217", +"Cell properties": "\u5355\u5143\u683c\u5c5e\u6027", +"Merge cells": "\u5408\u5e76\u5355\u5143\u683c", +"Split cell": "\u62c6\u5206\u5355\u5143\u683c", +"Insert row before": "\u5728\u4e0a\u65b9\u63d2\u5165", +"Insert row after": "\u5728\u4e0b\u65b9\u63d2\u5165", +"Delete row": "\u5220\u9664\u884c", +"Row properties": "\u884c\u5c5e\u6027", +"Cut row": "\u526a\u5207\u884c", +"Copy row": "\u590d\u5236\u884c", +"Paste row before": "\u7c98\u8d34\u5230\u4e0a\u65b9", +"Paste row after": "\u7c98\u8d34\u5230\u4e0b\u65b9", +"Insert column before": "\u5728\u5de6\u4fa7\u63d2\u5165", +"Insert column after": "\u5728\u53f3\u4fa7\u63d2\u5165", +"Delete column": "\u5220\u9664\u5217", +"Cols": "\u5217", +"Rows": "\u884c", +"Width": "\u5bbd", +"Height": "\u9ad8", +"Cell spacing": "\u5355\u5143\u683c\u5916\u95f4\u8ddd", +"Cell padding": "\u5355\u5143\u683c\u5185\u8fb9\u8ddd", +"Show caption": "\u663e\u793a\u6807\u9898", +"Left": "\u5de6\u5bf9\u9f50", +"Center": "\u5c45\u4e2d", +"Right": "\u53f3\u5bf9\u9f50", +"Cell type": "\u5355\u5143\u683c\u7c7b\u578b", +"Scope": "\u8303\u56f4", +"Alignment": "\u5bf9\u9f50\u65b9\u5f0f", +"H Align": "\u6c34\u5e73\u5bf9\u9f50", +"V Align": "\u5782\u76f4\u5bf9\u9f50", +"Top": "\u9876\u90e8\u5bf9\u9f50", +"Middle": "\u5782\u76f4\u5c45\u4e2d", +"Bottom": "\u5e95\u90e8\u5bf9\u9f50", +"Header cell": "\u8868\u5934\u5355\u5143\u683c", +"Row group": "\u884c\u7ec4", +"Column group": "\u5217\u7ec4", +"Row type": "\u884c\u7c7b\u578b", +"Header": "\u8868\u5934", +"Body": "\u8868\u4f53", +"Footer": "\u8868\u5c3e", +"Border color": "\u8fb9\u6846\u989c\u8272", +"Insert template...": "\u63d2\u5165\u6a21\u677f...", +"Templates": "\u6a21\u677f", +"Template": "\u6a21\u677f", +"Text color": "\u6587\u5b57\u989c\u8272", +"Background color": "\u80cc\u666f\u8272", +"Custom...": "\u81ea\u5b9a\u4e49...", +"Custom color": "\u81ea\u5b9a\u4e49\u989c\u8272", +"No color": "\u65e0", +"Remove color": "\u79fb\u9664\u989c\u8272", +"Table of Contents": "\u5185\u5bb9\u5217\u8868", +"Show blocks": "\u663e\u793a\u533a\u5757\u8fb9\u6846", +"Show invisible characters": "\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26", +"Word count": "\u5b57\u6570", +"Words: {0}": "\u5b57\u6570\uff1a{0}", +"{0} words": "{0} \u5b57", +"File": "\u6587\u4ef6", +"Edit": "\u7f16\u8f91", +"Insert": "\u63d2\u5165", +"View": "\u89c6\u56fe", +"Format": "\u683c\u5f0f", +"Table": "\u8868\u683c", +"Tools": "\u5de5\u5177", +"Powered by {0}": "\u7531{0}\u9a71\u52a8", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u5728\u7f16\u8f91\u533a\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9", +"Image title": "\u56fe\u7247\u6807\u9898", +"Border width": "\u8fb9\u6846\u5bbd\u5ea6", +"Border style": "\u8fb9\u6846\u6837\u5f0f", +"Error": "\u9519\u8bef", +"Warn": "\u8b66\u544a", +"Valid": "\u6709\u6548", +"To open the popup, press Shift+Enter": "\u6309Shitf+Enter\u952e\u6253\u5f00\u5bf9\u8bdd\u6846", +"Rich Text Area. Press ALT-0 for help.": "\u7f16\u8f91\u533a\u3002\u6309Alt+0\u952e\u6253\u5f00\u5e2e\u52a9\u3002", +"System Font": "\u7cfb\u7edf\u5b57\u4f53", +"Failed to upload image: {0}": "\u56fe\u7247\u4e0a\u4f20\u5931\u8d25: {0}", +"Failed to load plugin: {0} from url {1}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25: {0} \u6765\u81ea\u94fe\u63a5 {1}", +"Failed to load plugin url: {0}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25 \u94fe\u63a5: {0}", +"Failed to initialize plugin: {0}": "\u63d2\u4ef6\u521d\u59cb\u5316\u5931\u8d25: {0}", +"example": "\u793a\u4f8b", +"Search": "\u641c\u7d22", +"All": "\u5168\u90e8", +"Currency": "\u8d27\u5e01", +"Text": "\u6587\u5b57", +"Quotations": "\u5f15\u7528", +"Mathematical": "\u6570\u5b66", +"Extended Latin": "\u62c9\u4e01\u8bed\u6269\u5145", +"Symbols": "\u7b26\u53f7", +"Arrows": "\u7bad\u5934", +"User Defined": "\u81ea\u5b9a\u4e49", +"dollar sign": "\u7f8e\u5143\u7b26\u53f7", +"currency sign": "\u8d27\u5e01\u7b26\u53f7", +"euro-currency sign": "\u6b27\u5143\u7b26\u53f7", +"colon sign": "\u5192\u53f7", +"cruzeiro sign": "\u514b\u9c81\u8d5b\u7f57\u5e01\u7b26\u53f7", +"french franc sign": "\u6cd5\u90ce\u7b26\u53f7", +"lira sign": "\u91cc\u62c9\u7b26\u53f7", +"mill sign": "\u5bc6\u5c14\u7b26\u53f7", +"naira sign": "\u5948\u62c9\u7b26\u53f7", +"peseta sign": "\u6bd4\u585e\u5854\u7b26\u53f7", +"rupee sign": "\u5362\u6bd4\u7b26\u53f7", +"won sign": "\u97e9\u5143\u7b26\u53f7", +"new sheqel sign": "\u65b0\u8c22\u514b\u5c14\u7b26\u53f7", +"dong sign": "\u8d8a\u5357\u76fe\u7b26\u53f7", +"kip sign": "\u8001\u631d\u57fa\u666e\u7b26\u53f7", +"tugrik sign": "\u56fe\u683c\u91cc\u514b\u7b26\u53f7", +"drachma sign": "\u5fb7\u62c9\u514b\u9a6c\u7b26\u53f7", +"german penny symbol": "\u5fb7\u56fd\u4fbf\u58eb\u7b26\u53f7", +"peso sign": "\u6bd4\u7d22\u7b26\u53f7", +"guarani sign": "\u74dc\u62c9\u5c3c\u7b26\u53f7", +"austral sign": "\u6fb3\u5143\u7b26\u53f7", +"hryvnia sign": "\u683c\u91cc\u592b\u5c3c\u4e9a\u7b26\u53f7", +"cedi sign": "\u585e\u5730\u7b26\u53f7", +"livre tournois sign": "\u91cc\u5f17\u5f17\u5c14\u7b26\u53f7", +"spesmilo sign": "spesmilo\u7b26\u53f7", +"tenge sign": "\u575a\u6208\u7b26\u53f7", +"indian rupee sign": "\u5370\u5ea6\u5362\u6bd4", +"turkish lira sign": "\u571f\u8033\u5176\u91cc\u62c9", +"nordic mark sign": "\u5317\u6b27\u9a6c\u514b", +"manat sign": "\u9a6c\u7eb3\u7279\u7b26\u53f7", +"ruble sign": "\u5362\u5e03\u7b26\u53f7", +"yen character": "\u65e5\u5143\u5b57\u6837", +"yuan character": "\u4eba\u6c11\u5e01\u5143\u5b57\u6837", +"yuan character, in hong kong and taiwan": "\u5143\u5b57\u6837\uff08\u6e2f\u53f0\u5730\u533a\uff09", +"yen\/yuan character variant one": "\u5143\u5b57\u6837\uff08\u5927\u5199\uff09", +"Loading emoticons...": "\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7...", +"Could not load emoticons": "\u4e0d\u80fd\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7", +"People": "\u4eba\u7c7b", +"Animals and Nature": "\u52a8\u7269\u548c\u81ea\u7136", +"Food and Drink": "\u98df\u7269\u548c\u996e\u54c1", +"Activity": "\u6d3b\u52a8", +"Travel and Places": "\u65c5\u6e38\u548c\u5730\u70b9", +"Objects": "\u7269\u4ef6", +"Flags": "\u65d7\u5e1c", +"Characters": "\u5b57\u7b26", +"Characters (no spaces)": "\u5b57\u7b26(\u65e0\u7a7a\u683c)", +"Error: Form submit field collision.": "\u9519\u8bef: \u8868\u5355\u63d0\u4ea4\u5b57\u6bb5\u51b2\u7a81\u3002", +"Error: No form element found.": "\u9519\u8bef: \u6ca1\u6709\u8868\u5355\u63a7\u4ef6\u3002", +"Update": "\u66f4\u65b0", +"Color swatch": "\u989c\u8272\u6837\u672c", +"Turquoise": "\u9752\u7eff\u8272", +"Green": "\u7eff\u8272", +"Blue": "\u84dd\u8272", +"Purple": "\u7d2b\u8272", +"Navy Blue": "\u6d77\u519b\u84dd", +"Dark Turquoise": "\u6df1\u84dd\u7eff\u8272", +"Dark Green": "\u6df1\u7eff\u8272", +"Medium Blue": "\u4e2d\u84dd\u8272", +"Medium Purple": "\u4e2d\u7d2b\u8272", +"Midnight Blue": "\u6df1\u84dd\u8272", +"Yellow": "\u9ec4\u8272", +"Orange": "\u6a59\u8272", +"Red": "\u7ea2\u8272", +"Light Gray": "\u6d45\u7070\u8272", +"Gray": "\u7070\u8272", +"Dark Yellow": "\u6697\u9ec4\u8272", +"Dark Orange": "\u6df1\u6a59\u8272", +"Dark Red": "\u6df1\u7ea2\u8272", +"Medium Gray": "\u4e2d\u7070\u8272", +"Dark Gray": "\u6df1\u7070\u8272", +"Black": "\u9ed1\u8272", +"White": "\u767d\u8272", +"Switch to or from fullscreen mode": "\u5207\u6362\u5168\u5c4f\u6a21\u5f0f", +"Open help dialog": "\u6253\u5f00\u5e2e\u52a9\u5bf9\u8bdd\u6846", +"history": "\u5386\u53f2", +"styles": "\u6837\u5f0f", +"formatting": "\u683c\u5f0f\u5316", +"alignment": "\u5bf9\u9f50", +"indentation": "\u7f29\u8fdb", +"permanent pen": "\u8bb0\u53f7\u7b14", +"comments": "\u5907\u6ce8", +"Anchor": "\u951a\u70b9", +"Special character": "\u7279\u6b8a\u7b26\u53f7", +"Code sample": "\u4ee3\u7801\u793a\u4f8b", +"Color": "\u989c\u8272", +"Emoticons": "\u8868\u60c5", +"Document properties": "\u6587\u6863\u5c5e\u6027", +"Image": "\u56fe\u7247", +"Insert link": "\u63d2\u5165\u94fe\u63a5", +"Target": "\u6253\u5f00\u65b9\u5f0f", +"Link": "\u94fe\u63a5", +"Poster": "\u5c01\u9762", +"Media": "\u5a92\u4f53", +"Print": "\u6253\u5370", +"Prev": "\u4e0a\u4e00\u4e2a", +"Find and replace": "\u67e5\u627e\u548c\u66ff\u6362", +"Whole words": "\u5168\u5b57\u5339\u914d", +"Spellcheck": "\u62fc\u5199\u68c0\u67e5", +"Caption": "\u6807\u9898", +"Insert template": "\u63d2\u5165\u6a21\u677f" +}); \ No newline at end of file diff --git a/public/resource/tinymce/skins/ui/jeecg/content.css b/public/resource/tinymce/skins/ui/jeecg/content.css new file mode 100644 index 0000000..c9dc16d --- /dev/null +++ b/public/resource/tinymce/skins/ui/jeecg/content.css @@ -0,0 +1,711 @@ +/** +* Copyright (c) Tiny Technologies, Inc. All rights reserved. +* Licensed under the LGPL or a commercial license. +* For LGPL see License.txt in the project root for license information. +* For commercial licenses see https://www.tiny.cloud/ +*/ +.mce-content-body .mce-item-anchor { + background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A") no-repeat center; + cursor: default; + display: inline-block; + height: 12px !important; + padding: 0 2px; + -webkit-user-modify: read-only; + -moz-user-modify: read-only; + -webkit-user-select: all; + -ms-user-select: all; + user-select: all; + width: 8px !important; +} +.mce-content-body .mce-item-anchor[data-mce-selected] { + outline-offset: 1px; +} +.tox-comments-visible .tox-comment { + background-color: #fff0b7; +} +.tox-comments-visible .tox-comment--active { + background-color: #ffe168; +} +.tox-checklist > li:not(.tox-checklist--hidden) { + list-style: none; + margin: 0.25em 0; +} +.tox-checklist > li:not(.tox-checklist--hidden)::before { + content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A"); + cursor: pointer; + height: 1em; + margin-left: -1.5em; + margin-top: 0.125em; + position: absolute; + width: 1em; +} +.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before { + content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A"); +} +[dir=rtl] .tox-checklist > li:not(.tox-checklist--hidden)::before { + margin-left: 0; + margin-right: -1.5em; +} +/* stylelint-disable */ +/* http://prismjs.com/ */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + -moz-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} +pre[class*="language-"]::selection, +pre[class*="language-"] ::selection, +code[class*="language-"]::selection, +code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; +} +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} +.token.punctuation { + color: #999; +} +.namespace { + opacity: 0.7; +} +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + background: hsla(0, 0%, 100%, 0.5); +} +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} +.token.function, +.token.class-name { + color: #DD4A68; +} +.token.regex, +.token.important, +.token.variable { + color: #e90; +} +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} +.token.entity { + cursor: help; +} +/* stylelint-enable */ +.mce-content-body { + overflow-wrap: break-word; + word-wrap: break-word; +} +.mce-content-body .mce-visual-caret { + background-color: black; + background-color: currentColor; + position: absolute; +} +.mce-content-body .mce-visual-caret-hidden { + display: none; +} +.mce-content-body *[data-mce-caret] { + left: -1000px; + margin: 0; + padding: 0; + position: absolute; + right: auto; + top: 0; +} +.mce-content-body .mce-offscreen-selection { + left: -2000000px; + max-width: 1000000px; + position: absolute; +} +.mce-content-body *[contentEditable=false] { + cursor: default; +} +.mce-content-body *[contentEditable=true] { + cursor: text; +} +.tox-cursor-format-painter { + cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"), default; +} +.mce-content-body figure.align-left { + float: left; +} +.mce-content-body figure.align-right { + float: right; +} +.mce-content-body figure.image.align-center { + display: table; + margin-left: auto; + margin-right: auto; +} +.mce-preview-object { + border: 1px solid gray; + display: inline-block; + line-height: 0; + margin: 0 2px 0 2px; + position: relative; +} +.mce-preview-object .mce-shim { + background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.mce-preview-object[data-mce-selected="2"] .mce-shim { + display: none; +} +.mce-object { + background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center; + border: 1px dashed #aaa; +} +.mce-pagebreak { + border: 1px dashed #aaa; + cursor: default; + display: block; + height: 5px; + margin-top: 15px; + page-break-before: always; + width: 100%; +} +@media print { + .mce-pagebreak { + border: 0; + } +} +.tiny-pageembed .mce-shim { + background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.tiny-pageembed[data-mce-selected="2"] .mce-shim { + display: none; +} +.tiny-pageembed { + display: inline-block; + position: relative; +} +.tiny-pageembed--21by9, +.tiny-pageembed--16by9, +.tiny-pageembed--4by3, +.tiny-pageembed--1by1 { + display: block; + overflow: hidden; + padding: 0; + position: relative; + width: 100%; +} +.tiny-pageembed--21by9 { + padding-top: 42.857143%; +} +.tiny-pageembed--16by9 { + padding-top: 56.25%; +} +.tiny-pageembed--4by3 { + padding-top: 75%; +} +.tiny-pageembed--1by1 { + padding-top: 100%; +} +.tiny-pageembed--21by9 iframe, +.tiny-pageembed--16by9 iframe, +.tiny-pageembed--4by3 iframe, +.tiny-pageembed--1by1 iframe { + border: 0; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.mce-content-body[data-mce-placeholder] { + position: relative; +} +.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before { + color: rgba(84, 111, 94, 0.7); + content: attr(data-mce-placeholder); + position: absolute; +} +.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before { + left: 1px; +} +.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before { + right: 1px; +} +.mce-content-body div.mce-resizehandle { + background-color: #4099ff; + border-color: #4099ff; + border-style: solid; + border-width: 1px; + box-sizing: border-box; + height: 10px; + position: absolute; + width: 10px; + z-index: 10000; +} +.mce-content-body div.mce-resizehandle:hover { + background-color: #4099ff; +} +.mce-content-body div.mce-resizehandle:nth-of-type(1) { + cursor: nwse-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(2) { + cursor: nesw-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(3) { + cursor: nwse-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(4) { + cursor: nesw-resize; +} +.mce-content-body .mce-resize-backdrop { + z-index: 10000; +} +.mce-content-body .mce-clonedresizable { + cursor: default; + opacity: 0.5; + outline: 1px dashed black; + position: absolute; + z-index: 10001; +} +.mce-content-body .mce-clonedresizable.mce-resizetable-columns th, +.mce-content-body .mce-clonedresizable.mce-resizetable-columns td { + border: 0; +} +.mce-content-body .mce-resize-helper { + background: #555; + background: rgba(0, 0, 0, 0.75); + border: 1px; + border-radius: 3px; + color: white; + display: none; + font-family: sans-serif; + font-size: 12px; + line-height: 14px; + margin: 5px 10px; + padding: 5px; + position: absolute; + white-space: nowrap; + z-index: 10002; +} +.tox-rtc-user-selection { + position: relative; +} +.tox-rtc-user-cursor { + bottom: 0; + cursor: default; + position: absolute; + top: 0; + width: 2px; +} +.tox-rtc-user-cursor::before { + background-color: inherit; + border-radius: 50%; + content: ''; + display: block; + height: 8px; + position: absolute; + right: -3px; + top: -3px; + width: 8px; +} +.tox-rtc-user-cursor:hover::after { + background-color: inherit; + border-radius: 100px; + box-sizing: border-box; + color: #fff; + content: attr(data-user); + display: block; + font-size: 12px; + font-weight: normal; + left: -5px; + min-height: 8px; + min-width: 8px; + padding: 0 12px; + position: absolute; + top: -11px; + white-space: nowrap; + z-index: 1000; +} +.tox-rtc-user-selection--1 .tox-rtc-user-cursor { + background-color: #2dc26b; +} +.tox-rtc-user-selection--2 .tox-rtc-user-cursor { + background-color: #e03e2d; +} +.tox-rtc-user-selection--3 .tox-rtc-user-cursor { + background-color: #f1c40f; +} +.tox-rtc-user-selection--4 .tox-rtc-user-cursor { + background-color: #3598db; +} +.tox-rtc-user-selection--5 .tox-rtc-user-cursor { + background-color: #b96ad9; +} +.tox-rtc-user-selection--6 .tox-rtc-user-cursor { + background-color: #e67e23; +} +.tox-rtc-user-selection--7 .tox-rtc-user-cursor { + background-color: #aaa69d; +} +.tox-rtc-user-selection--8 .tox-rtc-user-cursor { + background-color: #f368e0; +} +.tox-rtc-remote-image { + background: #eaeaea url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A") no-repeat center center; + border: 1px solid #ccc; + min-height: 240px; + min-width: 320px; +} +.mce-match-marker { + background: #aaa; + color: #fff; +} +.mce-match-marker-selected { + background: #39f; + color: #fff; +} +.mce-match-marker-selected::selection { + background: #39f; + color: #fff; +} +.mce-content-body img[data-mce-selected], +.mce-content-body video[data-mce-selected], +.mce-content-body audio[data-mce-selected], +.mce-content-body object[data-mce-selected], +.mce-content-body embed[data-mce-selected], +.mce-content-body table[data-mce-selected] { + outline: 3px solid #b4d7ff; +} +.mce-content-body hr[data-mce-selected] { + outline: 3px solid #b4d7ff; + outline-offset: 1px; +} +.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus { + outline: 3px solid #b4d7ff; +} +.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover { + outline: 3px solid #b4d7ff; +} +.mce-content-body *[contentEditable=false][data-mce-selected] { + cursor: not-allowed; + outline: 3px solid #b4d7ff; +} +.mce-content-body.mce-content-readonly *[contentEditable=true]:focus, +.mce-content-body.mce-content-readonly *[contentEditable=true]:hover { + outline: none; +} +.mce-content-body *[data-mce-selected="inline-boundary"] { + background-color: #b4d7ff; +} +.mce-content-body .mce-edit-focus { + outline: 3px solid #b4d7ff; +} +.mce-content-body td[data-mce-selected], +.mce-content-body th[data-mce-selected] { + position: relative; +} +.mce-content-body td[data-mce-selected]::selection, +.mce-content-body th[data-mce-selected]::selection { + background: none; +} +.mce-content-body td[data-mce-selected] *, +.mce-content-body th[data-mce-selected] * { + outline: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mce-content-body td[data-mce-selected]::after, +.mce-content-body th[data-mce-selected]::after { + background-color: rgba(180, 215, 255, 0.7); + border: 1px solid rgba(180, 215, 255, 0.7); + bottom: -1px; + content: ''; + left: -1px; + mix-blend-mode: multiply; + position: absolute; + right: -1px; + top: -1px; +} +@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { + .mce-content-body td[data-mce-selected]::after, + .mce-content-body th[data-mce-selected]::after { + border-color: rgba(0, 84, 180, 0.7); + } +} +.mce-content-body img::selection { + background: none; +} +.ephox-snooker-resizer-bar { + background-color: #b4d7ff; + opacity: 0; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +.ephox-snooker-resizer-cols { + cursor: col-resize; +} +.ephox-snooker-resizer-rows { + cursor: row-resize; +} +.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging { + opacity: 1; +} +.mce-spellchecker-word { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A"); + background-position: 0 calc(100% + 1px); + background-repeat: repeat-x; + background-size: auto 6px; + cursor: default; + height: 2rem; +} +.mce-spellchecker-grammar { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A"); + background-position: 0 calc(100% + 1px); + background-repeat: repeat-x; + background-size: auto 6px; + cursor: default; +} +.mce-toc { + border: 1px solid gray; +} +.mce-toc h2 { + margin: 4px; +} +.mce-toc li { + list-style-type: none; +} +table[style*="border-width: 0px"], +.mce-item-table:not([border]), +.mce-item-table[border="0"], +table[style*="border-width: 0px"] td, +.mce-item-table:not([border]) td, +.mce-item-table[border="0"] td, +table[style*="border-width: 0px"] th, +.mce-item-table:not([border]) th, +.mce-item-table[border="0"] th, +table[style*="border-width: 0px"] caption, +.mce-item-table:not([border]) caption, +.mce-item-table[border="0"] caption { + border: 1px dashed #bbb; +} +.mce-visualblocks p, +.mce-visualblocks h1, +.mce-visualblocks h2, +.mce-visualblocks h3, +.mce-visualblocks h4, +.mce-visualblocks h5, +.mce-visualblocks h6, +.mce-visualblocks div:not([data-mce-bogus]), +.mce-visualblocks section, +.mce-visualblocks article, +.mce-visualblocks blockquote, +.mce-visualblocks address, +.mce-visualblocks pre, +.mce-visualblocks figure, +.mce-visualblocks figcaption, +.mce-visualblocks hgroup, +.mce-visualblocks aside, +.mce-visualblocks ul, +.mce-visualblocks ol, +.mce-visualblocks dl { + background-repeat: no-repeat; + border: 1px dashed #bbb; + margin-left: 3px; + padding-top: 10px; +} +.mce-visualblocks p { + background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7); +} +.mce-visualblocks h1 { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==); +} +.mce-visualblocks h2 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==); +} +.mce-visualblocks h3 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7); +} +.mce-visualblocks h4 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==); +} +.mce-visualblocks h5 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==); +} +.mce-visualblocks h6 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==); +} +.mce-visualblocks div:not([data-mce-bogus]) { + background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7); +} +.mce-visualblocks section { + background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=); +} +.mce-visualblocks article { + background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7); +} +.mce-visualblocks blockquote { + background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7); +} +.mce-visualblocks address { + background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=); +} +.mce-visualblocks pre { + background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==); +} +.mce-visualblocks figure { + background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7); +} +.mce-visualblocks figcaption { + border: 1px dashed #bbb; +} +.mce-visualblocks hgroup { + background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7); +} +.mce-visualblocks aside { + background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=); +} +.mce-visualblocks ul { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==); +} +.mce-visualblocks ol { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==); +} +.mce-visualblocks dl { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==); +} +.mce-visualblocks:not([dir=rtl]) p, +.mce-visualblocks:not([dir=rtl]) h1, +.mce-visualblocks:not([dir=rtl]) h2, +.mce-visualblocks:not([dir=rtl]) h3, +.mce-visualblocks:not([dir=rtl]) h4, +.mce-visualblocks:not([dir=rtl]) h5, +.mce-visualblocks:not([dir=rtl]) h6, +.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]), +.mce-visualblocks:not([dir=rtl]) section, +.mce-visualblocks:not([dir=rtl]) article, +.mce-visualblocks:not([dir=rtl]) blockquote, +.mce-visualblocks:not([dir=rtl]) address, +.mce-visualblocks:not([dir=rtl]) pre, +.mce-visualblocks:not([dir=rtl]) figure, +.mce-visualblocks:not([dir=rtl]) figcaption, +.mce-visualblocks:not([dir=rtl]) hgroup, +.mce-visualblocks:not([dir=rtl]) aside, +.mce-visualblocks:not([dir=rtl]) ul, +.mce-visualblocks:not([dir=rtl]) ol, +.mce-visualblocks:not([dir=rtl]) dl { + margin-left: 3px; +} +.mce-visualblocks[dir=rtl] p, +.mce-visualblocks[dir=rtl] h1, +.mce-visualblocks[dir=rtl] h2, +.mce-visualblocks[dir=rtl] h3, +.mce-visualblocks[dir=rtl] h4, +.mce-visualblocks[dir=rtl] h5, +.mce-visualblocks[dir=rtl] h6, +.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]), +.mce-visualblocks[dir=rtl] section, +.mce-visualblocks[dir=rtl] article, +.mce-visualblocks[dir=rtl] blockquote, +.mce-visualblocks[dir=rtl] address, +.mce-visualblocks[dir=rtl] pre, +.mce-visualblocks[dir=rtl] figure, +.mce-visualblocks[dir=rtl] figcaption, +.mce-visualblocks[dir=rtl] hgroup, +.mce-visualblocks[dir=rtl] aside, +.mce-visualblocks[dir=rtl] ul, +.mce-visualblocks[dir=rtl] ol, +.mce-visualblocks[dir=rtl] dl { + background-position-x: right; + margin-right: 3px; +} +.mce-nbsp, +.mce-shy { + background: #aaa; +} +.mce-shy::after { + content: '-'; +} +body { + font-family: sans-serif; +} +table { + border-collapse: collapse; +} diff --git a/public/resource/tinymce/skins/ui/jeecg/content.inline.css b/public/resource/tinymce/skins/ui/jeecg/content.inline.css new file mode 100644 index 0000000..9eebd5b --- /dev/null +++ b/public/resource/tinymce/skins/ui/jeecg/content.inline.css @@ -0,0 +1,705 @@ +/** +* Copyright (c) Tiny Technologies, Inc. All rights reserved. +* Licensed under the LGPL or a commercial license. +* For LGPL see License.txt in the project root for license information. +* For commercial licenses see https://www.tiny.cloud/ +*/ +.mce-content-body .mce-item-anchor { + background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A") no-repeat center; + cursor: default; + display: inline-block; + height: 12px !important; + padding: 0 2px; + -webkit-user-modify: read-only; + -moz-user-modify: read-only; + -webkit-user-select: all; + -ms-user-select: all; + user-select: all; + width: 8px !important; +} +.mce-content-body .mce-item-anchor[data-mce-selected] { + outline-offset: 1px; +} +.tox-comments-visible .tox-comment { + background-color: #fff0b7; +} +.tox-comments-visible .tox-comment--active { + background-color: #ffe168; +} +.tox-checklist > li:not(.tox-checklist--hidden) { + list-style: none; + margin: 0.25em 0; +} +.tox-checklist > li:not(.tox-checklist--hidden)::before { + content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A"); + cursor: pointer; + height: 1em; + margin-left: -1.5em; + margin-top: 0.125em; + position: absolute; + width: 1em; +} +.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before { + content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A"); +} +[dir=rtl] .tox-checklist > li:not(.tox-checklist--hidden)::before { + margin-left: 0; + margin-right: -1.5em; +} +/* stylelint-disable */ +/* http://prismjs.com/ */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + -moz-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} +pre[class*="language-"]::selection, +pre[class*="language-"] ::selection, +code[class*="language-"]::selection, +code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; +} +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} +.token.punctuation { + color: #999; +} +.namespace { + opacity: 0.7; +} +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + background: hsla(0, 0%, 100%, 0.5); +} +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} +.token.function, +.token.class-name { + color: #DD4A68; +} +.token.regex, +.token.important, +.token.variable { + color: #e90; +} +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} +.token.entity { + cursor: help; +} +/* stylelint-enable */ +.mce-content-body { + overflow-wrap: break-word; + word-wrap: break-word; +} +.mce-content-body .mce-visual-caret { + background-color: black; + background-color: currentColor; + position: absolute; +} +.mce-content-body .mce-visual-caret-hidden { + display: none; +} +.mce-content-body *[data-mce-caret] { + left: -1000px; + margin: 0; + padding: 0; + position: absolute; + right: auto; + top: 0; +} +.mce-content-body .mce-offscreen-selection { + left: -2000000px; + max-width: 1000000px; + position: absolute; +} +.mce-content-body *[contentEditable=false] { + cursor: default; +} +.mce-content-body *[contentEditable=true] { + cursor: text; +} +.tox-cursor-format-painter { + cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"), default; +} +.mce-content-body figure.align-left { + float: left; +} +.mce-content-body figure.align-right { + float: right; +} +.mce-content-body figure.image.align-center { + display: table; + margin-left: auto; + margin-right: auto; +} +.mce-preview-object { + border: 1px solid gray; + display: inline-block; + line-height: 0; + margin: 0 2px 0 2px; + position: relative; +} +.mce-preview-object .mce-shim { + background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.mce-preview-object[data-mce-selected="2"] .mce-shim { + display: none; +} +.mce-object { + background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center; + border: 1px dashed #aaa; +} +.mce-pagebreak { + border: 1px dashed #aaa; + cursor: default; + display: block; + height: 5px; + margin-top: 15px; + page-break-before: always; + width: 100%; +} +@media print { + .mce-pagebreak { + border: 0; + } +} +.tiny-pageembed .mce-shim { + background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.tiny-pageembed[data-mce-selected="2"] .mce-shim { + display: none; +} +.tiny-pageembed { + display: inline-block; + position: relative; +} +.tiny-pageembed--21by9, +.tiny-pageembed--16by9, +.tiny-pageembed--4by3, +.tiny-pageembed--1by1 { + display: block; + overflow: hidden; + padding: 0; + position: relative; + width: 100%; +} +.tiny-pageembed--21by9 { + padding-top: 42.857143%; +} +.tiny-pageembed--16by9 { + padding-top: 56.25%; +} +.tiny-pageembed--4by3 { + padding-top: 75%; +} +.tiny-pageembed--1by1 { + padding-top: 100%; +} +.tiny-pageembed--21by9 iframe, +.tiny-pageembed--16by9 iframe, +.tiny-pageembed--4by3 iframe, +.tiny-pageembed--1by1 iframe { + border: 0; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.mce-content-body[data-mce-placeholder] { + position: relative; +} +.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before { + color: rgba(84, 111, 94, 0.7); + content: attr(data-mce-placeholder); + position: absolute; +} +.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before { + left: 1px; +} +.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before { + right: 1px; +} +.mce-content-body div.mce-resizehandle { + background-color: #4099ff; + border-color: #4099ff; + border-style: solid; + border-width: 1px; + box-sizing: border-box; + height: 10px; + position: absolute; + width: 10px; + z-index: 10000; +} +.mce-content-body div.mce-resizehandle:hover { + background-color: #4099ff; +} +.mce-content-body div.mce-resizehandle:nth-of-type(1) { + cursor: nwse-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(2) { + cursor: nesw-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(3) { + cursor: nwse-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(4) { + cursor: nesw-resize; +} +.mce-content-body .mce-resize-backdrop { + z-index: 10000; +} +.mce-content-body .mce-clonedresizable { + cursor: default; + opacity: 0.5; + outline: 1px dashed black; + position: absolute; + z-index: 10001; +} +.mce-content-body .mce-clonedresizable.mce-resizetable-columns th, +.mce-content-body .mce-clonedresizable.mce-resizetable-columns td { + border: 0; +} +.mce-content-body .mce-resize-helper { + background: #555; + background: rgba(0, 0, 0, 0.75); + border: 1px; + border-radius: 3px; + color: white; + display: none; + font-family: sans-serif; + font-size: 12px; + line-height: 14px; + margin: 5px 10px; + padding: 5px; + position: absolute; + white-space: nowrap; + z-index: 10002; +} +.tox-rtc-user-selection { + position: relative; +} +.tox-rtc-user-cursor { + bottom: 0; + cursor: default; + position: absolute; + top: 0; + width: 2px; +} +.tox-rtc-user-cursor::before { + background-color: inherit; + border-radius: 50%; + content: ''; + display: block; + height: 8px; + position: absolute; + right: -3px; + top: -3px; + width: 8px; +} +.tox-rtc-user-cursor:hover::after { + background-color: inherit; + border-radius: 100px; + box-sizing: border-box; + color: #fff; + content: attr(data-user); + display: block; + font-size: 12px; + font-weight: normal; + left: -5px; + min-height: 8px; + min-width: 8px; + padding: 0 12px; + position: absolute; + top: -11px; + white-space: nowrap; + z-index: 1000; +} +.tox-rtc-user-selection--1 .tox-rtc-user-cursor { + background-color: #2dc26b; +} +.tox-rtc-user-selection--2 .tox-rtc-user-cursor { + background-color: #e03e2d; +} +.tox-rtc-user-selection--3 .tox-rtc-user-cursor { + background-color: #f1c40f; +} +.tox-rtc-user-selection--4 .tox-rtc-user-cursor { + background-color: #3598db; +} +.tox-rtc-user-selection--5 .tox-rtc-user-cursor { + background-color: #b96ad9; +} +.tox-rtc-user-selection--6 .tox-rtc-user-cursor { + background-color: #e67e23; +} +.tox-rtc-user-selection--7 .tox-rtc-user-cursor { + background-color: #aaa69d; +} +.tox-rtc-user-selection--8 .tox-rtc-user-cursor { + background-color: #f368e0; +} +.tox-rtc-remote-image { + background: #eaeaea url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A") no-repeat center center; + border: 1px solid #ccc; + min-height: 240px; + min-width: 320px; +} +.mce-match-marker { + background: #aaa; + color: #fff; +} +.mce-match-marker-selected { + background: #39f; + color: #fff; +} +.mce-match-marker-selected::selection { + background: #39f; + color: #fff; +} +.mce-content-body img[data-mce-selected], +.mce-content-body video[data-mce-selected], +.mce-content-body audio[data-mce-selected], +.mce-content-body object[data-mce-selected], +.mce-content-body embed[data-mce-selected], +.mce-content-body table[data-mce-selected] { + outline: 3px solid #b4d7ff; +} +.mce-content-body hr[data-mce-selected] { + outline: 3px solid #b4d7ff; + outline-offset: 1px; +} +.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus { + outline: 3px solid #b4d7ff; +} +.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover { + outline: 3px solid #b4d7ff; +} +.mce-content-body *[contentEditable=false][data-mce-selected] { + cursor: not-allowed; + outline: 3px solid #b4d7ff; +} +.mce-content-body.mce-content-readonly *[contentEditable=true]:focus, +.mce-content-body.mce-content-readonly *[contentEditable=true]:hover { + outline: none; +} +.mce-content-body *[data-mce-selected="inline-boundary"] { + background-color: #b4d7ff; +} +.mce-content-body .mce-edit-focus { + outline: 3px solid #b4d7ff; +} +.mce-content-body td[data-mce-selected], +.mce-content-body th[data-mce-selected] { + position: relative; +} +.mce-content-body td[data-mce-selected]::selection, +.mce-content-body th[data-mce-selected]::selection { + background: none; +} +.mce-content-body td[data-mce-selected] *, +.mce-content-body th[data-mce-selected] * { + outline: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mce-content-body td[data-mce-selected]::after, +.mce-content-body th[data-mce-selected]::after { + background-color: rgba(180, 215, 255, 0.7); + border: 1px solid rgba(180, 215, 255, 0.7); + bottom: -1px; + content: ''; + left: -1px; + mix-blend-mode: multiply; + position: absolute; + right: -1px; + top: -1px; +} +@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { + .mce-content-body td[data-mce-selected]::after, + .mce-content-body th[data-mce-selected]::after { + border-color: rgba(0, 84, 180, 0.7); + } +} +.mce-content-body img::selection { + background: none; +} +.ephox-snooker-resizer-bar { + background-color: #b4d7ff; + opacity: 0; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +.ephox-snooker-resizer-cols { + cursor: col-resize; +} +.ephox-snooker-resizer-rows { + cursor: row-resize; +} +.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging { + opacity: 1; +} +.mce-spellchecker-word { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A"); + background-position: 0 calc(100% + 1px); + background-repeat: repeat-x; + background-size: auto 6px; + cursor: default; + height: 2rem; +} +.mce-spellchecker-grammar { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A"); + background-position: 0 calc(100% + 1px); + background-repeat: repeat-x; + background-size: auto 6px; + cursor: default; +} +.mce-toc { + border: 1px solid gray; +} +.mce-toc h2 { + margin: 4px; +} +.mce-toc li { + list-style-type: none; +} +table[style*="border-width: 0px"], +.mce-item-table:not([border]), +.mce-item-table[border="0"], +table[style*="border-width: 0px"] td, +.mce-item-table:not([border]) td, +.mce-item-table[border="0"] td, +table[style*="border-width: 0px"] th, +.mce-item-table:not([border]) th, +.mce-item-table[border="0"] th, +table[style*="border-width: 0px"] caption, +.mce-item-table:not([border]) caption, +.mce-item-table[border="0"] caption { + border: 1px dashed #bbb; +} +.mce-visualblocks p, +.mce-visualblocks h1, +.mce-visualblocks h2, +.mce-visualblocks h3, +.mce-visualblocks h4, +.mce-visualblocks h5, +.mce-visualblocks h6, +.mce-visualblocks div:not([data-mce-bogus]), +.mce-visualblocks section, +.mce-visualblocks article, +.mce-visualblocks blockquote, +.mce-visualblocks address, +.mce-visualblocks pre, +.mce-visualblocks figure, +.mce-visualblocks figcaption, +.mce-visualblocks hgroup, +.mce-visualblocks aside, +.mce-visualblocks ul, +.mce-visualblocks ol, +.mce-visualblocks dl { + background-repeat: no-repeat; + border: 1px dashed #bbb; + margin-left: 3px; + padding-top: 10px; +} +.mce-visualblocks p { + background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7); +} +.mce-visualblocks h1 { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==); +} +.mce-visualblocks h2 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==); +} +.mce-visualblocks h3 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7); +} +.mce-visualblocks h4 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==); +} +.mce-visualblocks h5 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==); +} +.mce-visualblocks h6 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==); +} +.mce-visualblocks div:not([data-mce-bogus]) { + background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7); +} +.mce-visualblocks section { + background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=); +} +.mce-visualblocks article { + background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7); +} +.mce-visualblocks blockquote { + background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7); +} +.mce-visualblocks address { + background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=); +} +.mce-visualblocks pre { + background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==); +} +.mce-visualblocks figure { + background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7); +} +.mce-visualblocks figcaption { + border: 1px dashed #bbb; +} +.mce-visualblocks hgroup { + background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7); +} +.mce-visualblocks aside { + background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=); +} +.mce-visualblocks ul { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==); +} +.mce-visualblocks ol { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==); +} +.mce-visualblocks dl { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==); +} +.mce-visualblocks:not([dir=rtl]) p, +.mce-visualblocks:not([dir=rtl]) h1, +.mce-visualblocks:not([dir=rtl]) h2, +.mce-visualblocks:not([dir=rtl]) h3, +.mce-visualblocks:not([dir=rtl]) h4, +.mce-visualblocks:not([dir=rtl]) h5, +.mce-visualblocks:not([dir=rtl]) h6, +.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]), +.mce-visualblocks:not([dir=rtl]) section, +.mce-visualblocks:not([dir=rtl]) article, +.mce-visualblocks:not([dir=rtl]) blockquote, +.mce-visualblocks:not([dir=rtl]) address, +.mce-visualblocks:not([dir=rtl]) pre, +.mce-visualblocks:not([dir=rtl]) figure, +.mce-visualblocks:not([dir=rtl]) figcaption, +.mce-visualblocks:not([dir=rtl]) hgroup, +.mce-visualblocks:not([dir=rtl]) aside, +.mce-visualblocks:not([dir=rtl]) ul, +.mce-visualblocks:not([dir=rtl]) ol, +.mce-visualblocks:not([dir=rtl]) dl { + margin-left: 3px; +} +.mce-visualblocks[dir=rtl] p, +.mce-visualblocks[dir=rtl] h1, +.mce-visualblocks[dir=rtl] h2, +.mce-visualblocks[dir=rtl] h3, +.mce-visualblocks[dir=rtl] h4, +.mce-visualblocks[dir=rtl] h5, +.mce-visualblocks[dir=rtl] h6, +.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]), +.mce-visualblocks[dir=rtl] section, +.mce-visualblocks[dir=rtl] article, +.mce-visualblocks[dir=rtl] blockquote, +.mce-visualblocks[dir=rtl] address, +.mce-visualblocks[dir=rtl] pre, +.mce-visualblocks[dir=rtl] figure, +.mce-visualblocks[dir=rtl] figcaption, +.mce-visualblocks[dir=rtl] hgroup, +.mce-visualblocks[dir=rtl] aside, +.mce-visualblocks[dir=rtl] ul, +.mce-visualblocks[dir=rtl] ol, +.mce-visualblocks[dir=rtl] dl { + background-position-x: right; + margin-right: 3px; +} +.mce-nbsp, +.mce-shy { + background: #aaa; +} +.mce-shy::after { + content: '-'; +} diff --git a/public/resource/tinymce/skins/ui/jeecg/content.inline.min.css b/public/resource/tinymce/skins/ui/jeecg/content.inline.min.css new file mode 100644 index 0000000..9acf095 --- /dev/null +++ b/public/resource/tinymce/skins/ui/jeecg/content.inline.min.css @@ -0,0 +1,7 @@ +/** +* Copyright (c) Tiny Technologies, Inc. All rights reserved. +* Licensed under the LGPL or a commercial license. +* For LGPL see License.txt in the project root for license information. +* For commercial licenses see https://www.tiny.cloud/ +*/ +.mce-content-body .mce-item-anchor{background:transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;cursor:default;display:inline-block;height:12px!important;padding:0 2px;-webkit-user-modify:read-only;-moz-user-modify:read-only;-webkit-user-select:all;-ms-user-select:all;user-select:all;width:8px!important}.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset:1px}.tox-comments-visible .tox-comment{background-color:#fff0b7}.tox-comments-visible .tox-comment--active{background-color:#ffe168}.tox-checklist>li:not(.tox-checklist--hidden){list-style:none;margin:.25em 0}.tox-checklist>li:not(.tox-checklist--hidden)::before{content:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A");cursor:pointer;height:1em;margin-left:-1.5em;margin-top:.125em;position:absolute;width:1em}.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{content:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A")}[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-left:0;margin-right:-1.5em}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.mce-content-body{overflow-wrap:break-word;word-wrap:break-word}.mce-content-body .mce-visual-caret{background-color:#000;background-color:currentColor;position:absolute}.mce-content-body .mce-visual-caret-hidden{display:none}.mce-content-body [data-mce-caret]{left:-1000px;margin:0;padding:0;position:absolute;right:auto;top:0}.mce-content-body .mce-offscreen-selection{left:-2000000px;max-width:1000000px;position:absolute}.mce-content-body [contentEditable=false]{cursor:default}.mce-content-body [contentEditable=true]{cursor:text}.tox-cursor-format-painter{cursor:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"),default}.mce-content-body figure.align-left{float:left}.mce-content-body figure.align-right{float:right}.mce-content-body figure.image.align-center{display:table;margin-left:auto;margin-right:auto}.mce-preview-object{border:1px solid gray;display:inline-block;line-height:0;margin:0 2px 0 2px;position:relative}.mce-preview-object .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.mce-preview-object[data-mce-selected="2"] .mce-shim{display:none}.mce-object{background:transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;border:1px dashed #aaa}.mce-pagebreak{border:1px dashed #aaa;cursor:default;display:block;height:5px;margin-top:15px;page-break-before:always;width:100%}@media print{.mce-pagebreak{border:0}}.tiny-pageembed .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.tiny-pageembed[data-mce-selected="2"] .mce-shim{display:none}.tiny-pageembed{display:inline-block;position:relative}.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{display:block;overflow:hidden;padding:0;position:relative;width:100%}.tiny-pageembed--21by9{padding-top:42.857143%}.tiny-pageembed--16by9{padding-top:56.25%}.tiny-pageembed--4by3{padding-top:75%}.tiny-pageembed--1by1{padding-top:100%}.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.mce-content-body[data-mce-placeholder]{position:relative}.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:rgba(84,111,94,.7);content:attr(data-mce-placeholder);position:absolute}.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before{left:1px}.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before{right:1px}.mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:10000}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}.tox-rtc-user-selection{position:relative}.tox-rtc-user-cursor{bottom:0;cursor:default;position:absolute;top:0;width:2px}.tox-rtc-user-cursor::before{background-color:inherit;border-radius:50%;content:'';display:block;height:8px;position:absolute;right:-3px;top:-3px;width:8px}.tox-rtc-user-cursor:hover::after{background-color:inherit;border-radius:100px;box-sizing:border-box;color:#fff;content:attr(data-user);display:block;font-size:12px;font-weight:400;left:-5px;min-height:8px;min-width:8px;padding:0 12px;position:absolute;top:-11px;white-space:nowrap;z-index:1000}.tox-rtc-user-selection--1 .tox-rtc-user-cursor{background-color:#2dc26b}.tox-rtc-user-selection--2 .tox-rtc-user-cursor{background-color:#e03e2d}.tox-rtc-user-selection--3 .tox-rtc-user-cursor{background-color:#f1c40f}.tox-rtc-user-selection--4 .tox-rtc-user-cursor{background-color:#3598db}.tox-rtc-user-selection--5 .tox-rtc-user-cursor{background-color:#b96ad9}.tox-rtc-user-selection--6 .tox-rtc-user-cursor{background-color:#e67e23}.tox-rtc-user-selection--7 .tox-rtc-user-cursor{background-color:#aaa69d}.tox-rtc-user-selection--8 .tox-rtc-user-cursor{background-color:#f368e0}.tox-rtc-remote-image{background:#eaeaea url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A") no-repeat center center;border:1px solid #ccc;min-height:240px;min-width:320px}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-match-marker-selected::selection{background:#39f;color:#fff}.mce-content-body audio[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body table[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #b4d7ff}.mce-content-body hr[data-mce-selected]{outline:3px solid #b4d7ff;outline-offset:1px}.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false][data-mce-selected]{cursor:not-allowed;outline:3px solid #b4d7ff}.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline:0}.mce-content-body [data-mce-selected=inline-boundary]{background-color:#b4d7ff}.mce-content-body .mce-edit-focus{outline:3px solid #b4d7ff}.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{position:relative}.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background:0 0}.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{background-color:rgba(180,215,255,.7);border:1px solid rgba(180,215,255,.7);bottom:-1px;content:'';left:-1px;mix-blend-mode:multiply;position:absolute;right:-1px;top:-1px}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{border-color:rgba(0,84,180,.7)}}.mce-content-body img::selection{background:0 0}.ephox-snooker-resizer-bar{background-color:#b4d7ff;opacity:0;-webkit-user-select:none;-ms-user-select:none;user-select:none}.ephox-snooker-resizer-cols{cursor:col-resize}.ephox-snooker-resizer-rows{cursor:row-resize}.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity:1}.mce-spellchecker-word{background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default;height:2rem}.mce-spellchecker-grammar{background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc li{list-style-type:none}.mce-item-table:not([border]),.mce-item-table:not([border]) caption,.mce-item-table:not([border]) td,.mce-item-table:not([border]) th,.mce-item-table[border="0"],.mce-item-table[border="0"] caption,.mce-item-table[border="0"] td,.mce-item-table[border="0"] th,table[style*="border-width: 0px"],table[style*="border-width: 0px"] caption,table[style*="border-width: 0px"] td,table[style*="border-width: 0px"] th{border:1px dashed #bbb}.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{background-repeat:no-repeat;border:1px dashed #bbb;margin-left:3px;padding-top:10px}.mce-visualblocks p{background-image:url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7)}.mce-visualblocks h1{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==)}.mce-visualblocks h2{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==)}.mce-visualblocks h3{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7)}.mce-visualblocks h4{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==)}.mce-visualblocks h5{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==)}.mce-visualblocks h6{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==)}.mce-visualblocks div:not([data-mce-bogus]){background-image:url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7)}.mce-visualblocks section{background-image:url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=)}.mce-visualblocks article{background-image:url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7)}.mce-visualblocks blockquote{background-image:url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7)}.mce-visualblocks address{background-image:url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=)}.mce-visualblocks pre{background-image:url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==)}.mce-visualblocks figure{background-image:url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7)}.mce-visualblocks figcaption{border:1px dashed #bbb}.mce-visualblocks hgroup{background-image:url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7)}.mce-visualblocks aside{background-image:url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=)}.mce-visualblocks ul{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==)}.mce-visualblocks ol{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==)}.mce-visualblocks dl{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==)}.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left:3px}.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x:right;margin-right:3px}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'} \ No newline at end of file diff --git a/public/resource/tinymce/skins/ui/jeecg/content.min.css b/public/resource/tinymce/skins/ui/jeecg/content.min.css new file mode 100644 index 0000000..e9a1d89 --- /dev/null +++ b/public/resource/tinymce/skins/ui/jeecg/content.min.css @@ -0,0 +1,7 @@ +/** +* Copyright (c) Tiny Technologies, Inc. All rights reserved. +* Licensed under the LGPL or a commercial license. +* For LGPL see License.txt in the project root for license information. +* For commercial licenses see https://www.tiny.cloud/ +*/ +.mce-content-body .mce-item-anchor{background:transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;cursor:default;display:inline-block;height:12px!important;padding:0 2px;-webkit-user-modify:read-only;-moz-user-modify:read-only;-webkit-user-select:all;-ms-user-select:all;user-select:all;width:8px!important}.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset:1px}.tox-comments-visible .tox-comment{background-color:#fff0b7}.tox-comments-visible .tox-comment--active{background-color:#ffe168}.tox-checklist>li:not(.tox-checklist--hidden){list-style:none;margin:.25em 0}.tox-checklist>li:not(.tox-checklist--hidden)::before{content:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A");cursor:pointer;height:1em;margin-left:-1.5em;margin-top:.125em;position:absolute;width:1em}.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{content:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A")}[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-left:0;margin-right:-1.5em}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.mce-content-body{overflow-wrap:break-word;word-wrap:break-word}.mce-content-body .mce-visual-caret{background-color:#000;background-color:currentColor;position:absolute}.mce-content-body .mce-visual-caret-hidden{display:none}.mce-content-body [data-mce-caret]{left:-1000px;margin:0;padding:0;position:absolute;right:auto;top:0}.mce-content-body .mce-offscreen-selection{left:-2000000px;max-width:1000000px;position:absolute}.mce-content-body [contentEditable=false]{cursor:default}.mce-content-body [contentEditable=true]{cursor:text}.tox-cursor-format-painter{cursor:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"),default}.mce-content-body figure.align-left{float:left}.mce-content-body figure.align-right{float:right}.mce-content-body figure.image.align-center{display:table;margin-left:auto;margin-right:auto}.mce-preview-object{border:1px solid gray;display:inline-block;line-height:0;margin:0 2px 0 2px;position:relative}.mce-preview-object .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.mce-preview-object[data-mce-selected="2"] .mce-shim{display:none}.mce-object{background:transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;border:1px dashed #aaa}.mce-pagebreak{border:1px dashed #aaa;cursor:default;display:block;height:5px;margin-top:15px;page-break-before:always;width:100%}@media print{.mce-pagebreak{border:0}}.tiny-pageembed .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.tiny-pageembed[data-mce-selected="2"] .mce-shim{display:none}.tiny-pageembed{display:inline-block;position:relative}.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{display:block;overflow:hidden;padding:0;position:relative;width:100%}.tiny-pageembed--21by9{padding-top:42.857143%}.tiny-pageembed--16by9{padding-top:56.25%}.tiny-pageembed--4by3{padding-top:75%}.tiny-pageembed--1by1{padding-top:100%}.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.mce-content-body[data-mce-placeholder]{position:relative}.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:rgba(84,111,94,.7);content:attr(data-mce-placeholder);position:absolute}.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before{left:1px}.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before{right:1px}.mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:10000}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}.tox-rtc-user-selection{position:relative}.tox-rtc-user-cursor{bottom:0;cursor:default;position:absolute;top:0;width:2px}.tox-rtc-user-cursor::before{background-color:inherit;border-radius:50%;content:'';display:block;height:8px;position:absolute;right:-3px;top:-3px;width:8px}.tox-rtc-user-cursor:hover::after{background-color:inherit;border-radius:100px;box-sizing:border-box;color:#fff;content:attr(data-user);display:block;font-size:12px;font-weight:400;left:-5px;min-height:8px;min-width:8px;padding:0 12px;position:absolute;top:-11px;white-space:nowrap;z-index:1000}.tox-rtc-user-selection--1 .tox-rtc-user-cursor{background-color:#2dc26b}.tox-rtc-user-selection--2 .tox-rtc-user-cursor{background-color:#e03e2d}.tox-rtc-user-selection--3 .tox-rtc-user-cursor{background-color:#f1c40f}.tox-rtc-user-selection--4 .tox-rtc-user-cursor{background-color:#3598db}.tox-rtc-user-selection--5 .tox-rtc-user-cursor{background-color:#b96ad9}.tox-rtc-user-selection--6 .tox-rtc-user-cursor{background-color:#e67e23}.tox-rtc-user-selection--7 .tox-rtc-user-cursor{background-color:#aaa69d}.tox-rtc-user-selection--8 .tox-rtc-user-cursor{background-color:#f368e0}.tox-rtc-remote-image{background:#eaeaea url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A") no-repeat center center;border:1px solid #ccc;min-height:240px;min-width:320px}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-match-marker-selected::selection{background:#39f;color:#fff}.mce-content-body audio[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body table[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #b4d7ff}.mce-content-body hr[data-mce-selected]{outline:3px solid #b4d7ff;outline-offset:1px}.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false][data-mce-selected]{cursor:not-allowed;outline:3px solid #b4d7ff}.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline:0}.mce-content-body [data-mce-selected=inline-boundary]{background-color:#b4d7ff}.mce-content-body .mce-edit-focus{outline:3px solid #b4d7ff}.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{position:relative}.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background:0 0}.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{background-color:rgba(180,215,255,.7);border:1px solid rgba(180,215,255,.7);bottom:-1px;content:'';left:-1px;mix-blend-mode:multiply;position:absolute;right:-1px;top:-1px}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{border-color:rgba(0,84,180,.7)}}.mce-content-body img::selection{background:0 0}.ephox-snooker-resizer-bar{background-color:#b4d7ff;opacity:0;-webkit-user-select:none;-ms-user-select:none;user-select:none}.ephox-snooker-resizer-cols{cursor:col-resize}.ephox-snooker-resizer-rows{cursor:row-resize}.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity:1}.mce-spellchecker-word{background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default;height:2rem}.mce-spellchecker-grammar{background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc li{list-style-type:none}.mce-item-table:not([border]),.mce-item-table:not([border]) caption,.mce-item-table:not([border]) td,.mce-item-table:not([border]) th,.mce-item-table[border="0"],.mce-item-table[border="0"] caption,.mce-item-table[border="0"] td,.mce-item-table[border="0"] th,table[style*="border-width: 0px"],table[style*="border-width: 0px"] caption,table[style*="border-width: 0px"] td,table[style*="border-width: 0px"] th{border:1px dashed #bbb}.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{background-repeat:no-repeat;border:1px dashed #bbb;margin-left:3px;padding-top:10px}.mce-visualblocks p{background-image:url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7)}.mce-visualblocks h1{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==)}.mce-visualblocks h2{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==)}.mce-visualblocks h3{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7)}.mce-visualblocks h4{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==)}.mce-visualblocks h5{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==)}.mce-visualblocks h6{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==)}.mce-visualblocks div:not([data-mce-bogus]){background-image:url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7)}.mce-visualblocks section{background-image:url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=)}.mce-visualblocks article{background-image:url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7)}.mce-visualblocks blockquote{background-image:url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7)}.mce-visualblocks address{background-image:url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=)}.mce-visualblocks pre{background-image:url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==)}.mce-visualblocks figure{background-image:url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7)}.mce-visualblocks figcaption{border:1px dashed #bbb}.mce-visualblocks hgroup{background-image:url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7)}.mce-visualblocks aside{background-image:url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=)}.mce-visualblocks ul{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==)}.mce-visualblocks ol{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==)}.mce-visualblocks dl{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==)}.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left:3px}.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x:right;margin-right:3px}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'}body{font-family:sans-serif}table{border-collapse:collapse} \ No newline at end of file diff --git a/public/resource/tinymce/skins/ui/jeecg/content.mobile.css b/public/resource/tinymce/skins/ui/jeecg/content.mobile.css new file mode 100644 index 0000000..64783f0 --- /dev/null +++ b/public/resource/tinymce/skins/ui/jeecg/content.mobile.css @@ -0,0 +1,29 @@ +/** +* Copyright (c) Tiny Technologies, Inc. All rights reserved. +* Licensed under the LGPL or a commercial license. +* For LGPL see License.txt in the project root for license information. +* For commercial licenses see https://www.tiny.cloud/ +*/ +.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection { + /* Note: this file is used inside the content, so isn't part of theming */ + background-color: green; + display: inline-block; + opacity: 0.5; + position: absolute; +} +body { + -webkit-text-size-adjust: none; +} +body img { + /* this is related to the content margin */ + max-width: 96vw; +} +body table img { + max-width: 95%; +} +body { + font-family: sans-serif; +} +table { + border-collapse: collapse; +} diff --git a/public/resource/tinymce/skins/ui/jeecg/content.mobile.min.css b/public/resource/tinymce/skins/ui/jeecg/content.mobile.min.css new file mode 100644 index 0000000..1b87246 --- /dev/null +++ b/public/resource/tinymce/skins/ui/jeecg/content.mobile.min.css @@ -0,0 +1,7 @@ +/** +* Copyright (c) Tiny Technologies, Inc. All rights reserved. +* Licensed under the LGPL or a commercial license. +* For LGPL see License.txt in the project root for license information. +* For commercial licenses see https://www.tiny.cloud/ +*/ +.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse} \ No newline at end of file diff --git a/public/resource/tinymce/skins/ui/jeecg/fonts/tinymce-mobile.woff b/public/resource/tinymce/skins/ui/jeecg/fonts/tinymce-mobile.woff new file mode 100644 index 0000000..1e3be03 Binary files /dev/null and b/public/resource/tinymce/skins/ui/jeecg/fonts/tinymce-mobile.woff differ diff --git a/public/resource/tinymce/skins/ui/jeecg/skin.css b/public/resource/tinymce/skins/ui/jeecg/skin.css new file mode 100644 index 0000000..7d54f3c --- /dev/null +++ b/public/resource/tinymce/skins/ui/jeecg/skin.css @@ -0,0 +1,3045 @@ +/** +* Copyright (c) Tiny Technologies, Inc. All rights reserved. +* Licensed under the LGPL or a commercial license. +* For LGPL see License.txt in the project root for license information. +* For commercial licenses see https://www.tiny.cloud/ +*/ +.tox { + box-shadow: none; + box-sizing: content-box; + color: rgba(84, 111, 94, 0.85); + cursor: auto; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 10px; + font-style: normal; + font-weight: normal; + line-height: normal; + -webkit-tap-highlight-color: transparent; + text-decoration: none; + text-shadow: none; + text-transform: none; + vertical-align: initial; + white-space: normal; +} +.tox *:not(svg):not(rect) { + box-sizing: inherit; + color: inherit; + cursor: inherit; + direction: inherit; + font-family: inherit; + font-size: inherit; + font-style: inherit; + font-weight: inherit; + line-height: inherit; + -webkit-tap-highlight-color: inherit; + text-align: inherit; + text-decoration: inherit; + text-shadow: inherit; + text-transform: inherit; + vertical-align: inherit; + white-space: inherit; +} +.tox *:not(svg):not(rect) { + /* stylelint-disable-line no-duplicate-selectors */ + background: transparent; + border: 0; + box-shadow: none; + float: none; + height: auto; + margin: 0; + max-width: none; + outline: 0; + padding: 0; + position: static; + width: auto; +} +.tox:not([dir=rtl]) { + direction: ltr; + text-align: left; +} +.tox[dir=rtl] { + direction: rtl; + text-align: right; +} +.tox-tinymce { + border: 1px solid #d9d9d9; + border-radius: 0px; + box-shadow: none; + box-sizing: border-box; + display: flex; + flex-direction: column; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + overflow: hidden; + position: relative; + visibility: inherit !important; +} +.tox-tinymce-inline { + border: none; + box-shadow: none; +} +.tox-tinymce-inline .tox-editor-header { + background-color: transparent; + border: 1px solid #d9d9d9; + border-radius: 0px; + box-shadow: none; +} +.tox-tinymce-aux { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + z-index: 1300; +} +.tox-tinymce *:focus, +.tox-tinymce-aux *:focus { + outline: none; +} +button::-moz-focus-inner { + border: 0; +} +.tox[dir=rtl] .tox-icon--flip svg { + transform: rotateY(180deg); +} +.tox .accessibility-issue__header { + align-items: center; + display: flex; + margin-bottom: 2.5px; +} +.tox .accessibility-issue__description { + align-items: stretch; + border: 1px solid #d9d9d9; + border-radius: 3px; + display: flex; + justify-content: space-between; +} +.tox .accessibility-issue__description > div { + padding-bottom: 2.5px; +} +.tox .accessibility-issue__description > div > div { + align-items: center; + display: flex; + margin-bottom: 2.5px; +} +.tox .accessibility-issue__description > *:last-child:not(:only-child) { + border-color: #d9d9d9; + border-style: solid; +} +.tox .accessibility-issue__repair { + margin-top: 16px; +} +.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description { + background-color: rgba(10, 143, 233, 0.1); + border-color: rgba(10, 143, 233, 0.4); + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description > *:last-child { + border-color: rgba(10, 143, 233, 0.4); +} +.tox .tox-dialog__body-content .accessibility-issue--info .tox-form__group h2 { + color: #0a8fe9; +} +.tox .tox-dialog__body-content .accessibility-issue--info .tox-icon svg { + fill: #0a8fe9; +} +.tox .tox-dialog__body-content .accessibility-issue--info a .tox-icon { + color: #0a8fe9; +} +.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description { + background-color: rgba(255, 165, 0, 0.1); + border-color: rgba(255, 165, 0, 0.5); + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description > *:last-child { + border-color: rgba(255, 165, 0, 0.5); +} +.tox .tox-dialog__body-content .accessibility-issue--warn .tox-form__group h2 { + color: #cc8500; +} +.tox .tox-dialog__body-content .accessibility-issue--warn .tox-icon svg { + fill: #cc8500; +} +.tox .tox-dialog__body-content .accessibility-issue--warn a .tox-icon { + color: #cc8500; +} +.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description { + background-color: rgba(204, 0, 0, 0.1); + border-color: rgba(204, 0, 0, 0.4); + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description > *:last-child { + border-color: rgba(204, 0, 0, 0.4); +} +.tox .tox-dialog__body-content .accessibility-issue--error .tox-form__group h2 { + color: #c00; +} +.tox .tox-dialog__body-content .accessibility-issue--error .tox-icon svg { + fill: #c00; +} +.tox .tox-dialog__body-content .accessibility-issue--error a .tox-icon { + color: #c00; +} +.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description { + background-color: rgba(120, 171, 70, 0.1); + border-color: rgba(120, 171, 70, 0.4); + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description > *:last-child { + border-color: rgba(120, 171, 70, 0.4); +} +.tox .tox-dialog__body-content .accessibility-issue--success .tox-form__group h2 { + color: #78AB46; +} +.tox .tox-dialog__body-content .accessibility-issue--success .tox-icon svg { + fill: #78AB46; +} +.tox .tox-dialog__body-content .accessibility-issue--success a .tox-icon { + color: #78AB46; +} +.tox .tox-dialog__body-content .accessibility-issue__header h1, +.tox .tox-dialog__body-content .tox-form__group .accessibility-issue__description h2 { + margin-top: 0; +} +.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header .tox-button { + margin-left: 2.5px; +} +.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header > *:nth-last-child(2) { + margin-left: auto; +} +.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description { + padding: 2.5px 2.5px 2.5px 5px; +} +.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description > *:last-child { + border-left-width: 1px; + padding-left: 2.5px; +} +.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header .tox-button { + margin-right: 2.5px; +} +.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header > *:nth-last-child(2) { + margin-right: auto; +} +.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description { + padding: 2.5px 5px 2.5px 2.5px; +} +.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description > *:last-child { + border-right-width: 1px; + padding-right: 2.5px; +} +.tox .tox-anchorbar { + display: flex; + flex: 0 0 auto; +} +.tox .tox-bar { + display: flex; + flex: 0 0 auto; +} +.tox .tox-button { + background-color: #0a8fe9; + background-image: none; + background-position: 0 0; + background-repeat: repeat; + border-color: #0a8fe9; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + color: #fff; + cursor: pointer; + display: inline-block; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 8.75px; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-height: 24px; + margin: 0; + outline: none; + padding: 2.5px 10px; + text-align: center; + text-decoration: none; + text-transform: none; + white-space: nowrap; +} +.tox .tox-button[disabled] { + background-color: #0a8fe9; + background-image: none; + border-color: #0a8fe9; + box-shadow: none; + color: rgba(255, 255, 255, 0.5); + cursor: not-allowed; +} +.tox .tox-button:focus:not(:disabled) { + background-color: #0980d1; + background-image: none; + border-color: #0980d1; + box-shadow: none; + color: #fff; +} +.tox .tox-button:hover:not(:disabled) { + background-color: #0980d1; + background-image: none; + border-color: #0980d1; + box-shadow: none; + color: #fff; +} +.tox .tox-button:active:not(:disabled) { + background-color: #0871b8; + background-image: none; + border-color: #0871b8; + box-shadow: none; + color: #fff; +} +.tox .tox-button--secondary { + background-color: #f0f0f0; + background-image: none; + background-position: 0 0; + background-repeat: repeat; + border-color: #f0f0f0; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + color: rgba(84, 111, 94, 0.85); + font-size: 8.75px; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + outline: none; + padding: 2.5px 10px; + text-decoration: none; + text-transform: none; +} +.tox .tox-button--secondary[disabled] { + background-color: #f0f0f0; + background-image: none; + border-color: #f0f0f0; + box-shadow: none; + color: rgba(84, 111, 94, 0.5); +} +.tox .tox-button--secondary:focus:not(:disabled) { + background-color: #e3e3e3; + background-image: none; + border-color: #e3e3e3; + box-shadow: none; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-button--secondary:hover:not(:disabled) { + background-color: #e3e3e3; + background-image: none; + border-color: #e3e3e3; + box-shadow: none; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-button--secondary:active:not(:disabled) { + background-color: #d6d6d6; + background-image: none; + border-color: #d6d6d6; + box-shadow: none; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-button--icon, +.tox .tox-button.tox-button--icon, +.tox .tox-button.tox-button--secondary.tox-button--icon { + padding: 2.5px; +} +.tox .tox-button--icon .tox-icon svg, +.tox .tox-button.tox-button--icon .tox-icon svg, +.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg { + display: block; + fill: currentColor; +} +.tox .tox-button-link { + background: 0; + border: none; + box-sizing: border-box; + cursor: pointer; + display: inline-block; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 10px; + font-weight: normal; + line-height: 1.3; + margin: 0; + padding: 0; + white-space: nowrap; +} +.tox .tox-button-link--sm { + font-size: 8.75px; +} +.tox .tox-button--naked { + background-color: transparent; + border-color: transparent; + box-shadow: unset; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-button--naked[disabled] { + background-color: #f0f0f0; + border-color: #f0f0f0; + box-shadow: none; + color: rgba(84, 111, 94, 0.5); +} +.tox .tox-button--naked:hover:not(:disabled) { + background-color: #e3e3e3; + border-color: #e3e3e3; + box-shadow: none; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-button--naked:focus:not(:disabled) { + background-color: #e3e3e3; + border-color: #e3e3e3; + box-shadow: none; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-button--naked:active:not(:disabled) { + background-color: #d6d6d6; + border-color: #d6d6d6; + box-shadow: none; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-button--naked .tox-icon svg { + fill: currentColor; +} +.tox .tox-button--naked.tox-button--icon:hover:not(:disabled) { + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-checkbox { + align-items: center; + border-radius: 3px; + cursor: pointer; + display: flex; + height: 36px; + min-width: 36px; +} +.tox .tox-checkbox__input { + /* Hide from view but visible to screen readers */ + height: 1px; + overflow: hidden; + position: absolute; + top: auto; + width: 1px; +} +.tox .tox-checkbox__icons { + align-items: center; + border-radius: 3px; + box-shadow: 0 0 0 2px transparent; + box-sizing: content-box; + display: flex; + height: 24px; + justify-content: center; + padding: calc(2.5px - 1px); + width: 24px; +} +.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg { + display: block; + fill: rgba(84, 111, 94, 0.3); +} +.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg { + display: none; + fill: #0a8fe9; +} +.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg { + display: none; + fill: #0a8fe9; +} +.tox .tox-checkbox--disabled { + color: rgba(84, 111, 94, 0.5); + cursor: not-allowed; +} +.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__checked svg { + fill: rgba(84, 111, 94, 0.5); +} +.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__unchecked svg { + fill: rgba(84, 111, 94, 0.5); +} +.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg { + fill: rgba(84, 111, 94, 0.5); +} +.tox input.tox-checkbox__input:checked + .tox-checkbox__icons .tox-checkbox-icon__unchecked svg { + display: none; +} +.tox input.tox-checkbox__input:checked + .tox-checkbox__icons .tox-checkbox-icon__checked svg { + display: block; +} +.tox input.tox-checkbox__input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__unchecked svg { + display: none; +} +.tox input.tox-checkbox__input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg { + display: block; +} +.tox input.tox-checkbox__input:focus + .tox-checkbox__icons { + border-radius: 3px; + box-shadow: inset 0 0 0 1px #0a8fe9; + padding: calc(2.5px - 1px); +} +.tox:not([dir=rtl]) .tox-checkbox__label { + margin-left: 2.5px; +} +.tox:not([dir=rtl]) .tox-checkbox__input { + left: -10000px; +} +.tox:not([dir=rtl]) .tox-bar .tox-checkbox { + margin-left: 2.5px; +} +.tox[dir=rtl] .tox-checkbox__label { + margin-right: 2.5px; +} +.tox[dir=rtl] .tox-checkbox__input { + right: -10000px; +} +.tox[dir=rtl] .tox-bar .tox-checkbox { + margin-right: 2.5px; +} +.tox { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox .tox-collection--toolbar .tox-collection__group { + display: flex; + padding: 0; +} +.tox .tox-collection--grid .tox-collection__group { + display: flex; + flex-wrap: wrap; + max-height: 208px; + overflow-x: hidden; + overflow-y: auto; + padding: 0; +} +.tox .tox-collection--list .tox-collection__group { + border-bottom-width: 0; + border-color: #d9d9d9; + border-left-width: 0; + border-right-width: 0; + border-style: solid; + border-top-width: 1px; + padding: 2.5px 0; +} +.tox .tox-collection--list .tox-collection__group:first-child { + border-top-width: 0; +} +.tox .tox-collection__group-heading { + background-color: #f3f3f3; + color: rgba(84, 111, 94, 0.7); + cursor: default; + font-size: 12px; + font-style: normal; + font-weight: normal; + margin-bottom: 2.5px; + margin-top: -2.5px; + padding: 2.5px 5px; + text-transform: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +.tox .tox-collection__item { + align-items: center; + color: rgba(84, 111, 94, 0.85); + cursor: pointer; + display: flex; + -webkit-touch-callout: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +.tox .tox-collection--list .tox-collection__item { + padding: 2.5px 5px; +} +.tox .tox-collection--toolbar .tox-collection__item { + border-radius: 3px; + padding: 2.5px; +} +.tox .tox-collection--grid .tox-collection__item { + border-radius: 3px; + padding: 2.5px; +} +.tox .tox-collection--list .tox-collection__item--enabled { + background-color: #fff; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-collection--list .tox-collection__item--active { + background-color: #e5e9e7; +} +.tox .tox-collection--toolbar .tox-collection__item--enabled { + background-color: #e5e9e7; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-collection--toolbar .tox-collection__item--active { + background-color: #e5e9e7; +} +.tox .tox-collection--grid .tox-collection__item--enabled { + background-color: #e5e9e7; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled) { + background-color: #e5e9e7; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled) { + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled) { + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-collection__item-icon, +.tox .tox-collection__item-checkmark { + align-items: center; + display: flex; + height: 24px; + justify-content: center; + width: 24px; +} +.tox .tox-collection__item-icon svg, +.tox .tox-collection__item-checkmark svg { + fill: currentColor; +} +.tox .tox-collection--toolbar-lg .tox-collection__item-icon { + height: 48px; + width: 48px; +} +.tox .tox-collection__item-label { + color: currentColor; + display: inline-block; + flex: 1; + -ms-flex-preferred-size: auto; + font-size: 8.75px; + font-style: normal; + font-weight: normal; + line-height: 24px; + text-transform: none; + word-break: break-all; +} +.tox .tox-collection__item-accessory { + color: rgba(84, 111, 94, 0.7); + display: inline-block; + font-size: 8.75px; + height: 24px; + line-height: 24px; + text-transform: none; +} +.tox .tox-collection__item-caret { + align-items: center; + display: flex; + min-height: 24px; +} +.tox .tox-collection__item-caret::after { + content: ''; + font-size: 0; + min-height: inherit; +} +.tox .tox-collection__item-caret svg { + fill: rgba(84, 111, 94, 0.85); +} +.tox .tox-collection__item--state-disabled { + background-color: transparent; + color: rgba(84, 111, 94, 0.5); + cursor: not-allowed; +} +.tox .tox-collection__item--state-disabled .tox-collection__item-caret svg { + fill: rgba(84, 111, 94, 0.5); +} +.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg { + display: none; +} +.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-accessory + .tox-collection__item-checkmark { + display: none; +} +.tox .tox-collection--horizontal { + background-color: #fff; + border: 1px solid #d9d9d9; + border-radius: 3px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); + display: flex; + flex: 0 0 auto; + flex-shrink: 0; + flex-wrap: nowrap; + margin-bottom: 0; + overflow-x: auto; + padding: 0; +} +.tox .tox-collection--horizontal .tox-collection__group { + align-items: center; + display: flex; + flex-wrap: nowrap; + margin: 0; + padding: 0 2.5px; +} +.tox .tox-collection--horizontal .tox-collection__item { + height: 34px; + margin: 2px 0 3px 0; + padding: 0 4px; +} +.tox .tox-collection--horizontal .tox-collection__item-label { + white-space: nowrap; +} +.tox .tox-collection--horizontal .tox-collection__item-caret { + margin-left: 4px; +} +.tox .tox-collection__item-container { + display: flex; +} +.tox .tox-collection__item-container--row { + align-items: center; + flex: 1 1 auto; + flex-direction: row; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--align-left { + margin-right: auto; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--align-right { + justify-content: flex-end; + margin-left: auto; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--valign-top { + align-items: flex-start; + margin-bottom: auto; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--valign-middle { + align-items: center; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--valign-bottom { + align-items: flex-end; + margin-top: auto; +} +.tox .tox-collection__item-container--column { + -ms-grid-row-align: center; + align-self: center; + flex: 1 1 auto; + flex-direction: column; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--align-left { + align-items: flex-start; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--align-right { + align-items: flex-end; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--valign-top { + align-self: flex-start; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--valign-middle { + -ms-grid-row-align: center; + align-self: center; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--valign-bottom { + align-self: flex-end; +} +.tox:not([dir=rtl]) .tox-collection--horizontal .tox-collection__group:not(:last-of-type) { + border-right: 1px solid #d9d9d9; +} +.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item > *:not(:first-child) { + margin-left: 5px; +} +.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item > .tox-collection__item-label:first-child { + margin-left: 2.5px; +} +.tox:not([dir=rtl]) .tox-collection__item-accessory { + margin-left: 10px; + text-align: right; +} +.tox:not([dir=rtl]) .tox-collection .tox-collection__item-caret { + margin-left: 10px; +} +.tox[dir=rtl] .tox-collection--horizontal .tox-collection__group:not(:last-of-type) { + border-left: 1px solid #d9d9d9; +} +.tox[dir=rtl] .tox-collection--list .tox-collection__item > *:not(:first-child) { + margin-right: 5px; +} +.tox[dir=rtl] .tox-collection--list .tox-collection__item > .tox-collection__item-label:first-child { + margin-right: 2.5px; +} +.tox[dir=rtl] .tox-collection__item-accessory { + margin-right: 10px; + text-align: left; +} +.tox[dir=rtl] .tox-collection .tox-collection__item-caret { + margin-right: 10px; + transform: rotateY(180deg); +} +.tox[dir=rtl] .tox-collection--horizontal .tox-collection__item-caret { + margin-right: 4px; +} +.tox .tox-color-picker-container { + display: flex; + flex-direction: row; + height: 225px; + margin: 0; +} +.tox .tox-sv-palette { + box-sizing: border-box; + display: flex; + height: 100%; +} +.tox .tox-sv-palette-spectrum { + height: 100%; +} +.tox .tox-sv-palette, +.tox .tox-sv-palette-spectrum { + width: 225px; +} +.tox .tox-sv-palette-thumb { + background: none; + border: 1px solid black; + border-radius: 50%; + box-sizing: content-box; + height: 12px; + position: absolute; + width: 12px; +} +.tox .tox-sv-palette-inner-thumb { + border: 1px solid white; + border-radius: 50%; + height: 10px; + position: absolute; + width: 10px; +} +.tox .tox-hue-slider { + box-sizing: border-box; + height: 100%; + width: 25px; +} +.tox .tox-hue-slider-spectrum { + background: linear-gradient(to bottom, #f00, #ff0080, #f0f, #8000ff, #00f, #0080ff, #0ff, #00ff80, #0f0, #80ff00, #ff0, #ff8000, #f00); + height: 100%; + width: 100%; +} +.tox .tox-hue-slider, +.tox .tox-hue-slider-spectrum { + width: 20px; +} +.tox .tox-hue-slider-thumb { + background: white; + border: 1px solid black; + box-sizing: content-box; + height: 4px; + width: 100%; +} +.tox .tox-rgb-form { + display: flex; + flex-direction: column; + justify-content: space-between; +} +.tox .tox-rgb-form div { + align-items: center; + display: flex; + justify-content: space-between; + margin-bottom: 5px; + width: inherit; +} +.tox .tox-rgb-form input { + width: 6em; +} +.tox .tox-rgb-form input.tox-invalid { + /* Need !important to override Chrome's focus styling unfortunately */ + border: 1px solid red !important; +} +.tox .tox-rgb-form .tox-rgba-preview { + border: 1px solid black; + flex-grow: 2; + margin-bottom: 0; +} +.tox:not([dir=rtl]) .tox-sv-palette { + margin-right: 15px; +} +.tox:not([dir=rtl]) .tox-hue-slider { + margin-right: 15px; +} +.tox:not([dir=rtl]) .tox-hue-slider-thumb { + margin-left: -1px; +} +.tox:not([dir=rtl]) .tox-rgb-form label { + margin-right: 0.5em; +} +.tox[dir=rtl] .tox-sv-palette { + margin-left: 15px; +} +.tox[dir=rtl] .tox-hue-slider { + margin-left: 15px; +} +.tox[dir=rtl] .tox-hue-slider-thumb { + margin-right: -1px; +} +.tox[dir=rtl] .tox-rgb-form label { + margin-left: 0.5em; +} +.tox .tox-toolbar .tox-swatches, +.tox .tox-toolbar__primary .tox-swatches, +.tox .tox-toolbar__overflow .tox-swatches { + margin: 2px 0 3px 4px; +} +.tox .tox-collection--list .tox-collection__group .tox-swatches-menu { + border: 0; + margin: -2.5px 0; +} +.tox .tox-swatches__row { + display: flex; +} +.tox .tox-swatch { + height: 30px; + transition: transform 0.15s, box-shadow 0.15s; + width: 30px; +} +.tox .tox-swatch:hover, +.tox .tox-swatch:focus { + box-shadow: 0 0 0 1px rgba(127, 127, 127, 0.3) inset; + transform: scale(0.8); +} +.tox .tox-swatch--remove { + align-items: center; + display: flex; + justify-content: center; +} +.tox .tox-swatch--remove svg path { + stroke: #e74c3c; +} +.tox .tox-swatches__picker-btn { + align-items: center; + background-color: transparent; + border: 0; + cursor: pointer; + display: flex; + height: 30px; + justify-content: center; + outline: none; + padding: 0; + width: 30px; +} +.tox .tox-swatches__picker-btn svg { + height: 24px; + width: 24px; +} +.tox .tox-swatches__picker-btn:hover { + background: #e5e9e7; +} +.tox:not([dir=rtl]) .tox-swatches__picker-btn { + margin-left: auto; +} +.tox[dir=rtl] .tox-swatches__picker-btn { + margin-right: auto; +} +.tox .tox-comment-thread { + background: #fff; + position: relative; +} +.tox .tox-comment-thread > *:not(:first-child) { + margin-top: 5px; +} +.tox .tox-comment { + background: #fff; + border: 1px solid #d9d9d9; + border-radius: 3px; + box-shadow: 0 4px 8px 0 rgba(84, 111, 94, 0.1); + padding: 5px 5px 10px 5px; + position: relative; +} +.tox .tox-comment__header { + align-items: center; + color: rgba(84, 111, 94, 0.85); + display: flex; + justify-content: space-between; +} +.tox .tox-comment__date { + color: rgba(84, 111, 94, 0.7); + font-size: 12px; +} +.tox .tox-comment__body { + color: rgba(84, 111, 94, 0.85); + font-size: 8.75px; + font-style: normal; + font-weight: normal; + line-height: 1.3; + margin-top: 5px; + position: relative; + text-transform: initial; +} +.tox .tox-comment__body textarea { + resize: none; + white-space: normal; + width: 100%; +} +.tox .tox-comment__expander { + padding-top: 5px; +} +.tox .tox-comment__expander p { + color: rgba(84, 111, 94, 0.7); + font-size: 8.75px; + font-style: normal; +} +.tox .tox-comment__body p { + margin: 0; +} +.tox .tox-comment__buttonspacing { + padding-top: 10px; + text-align: center; +} +.tox .tox-comment-thread__overlay::after { + background: #fff; + bottom: 0; + content: ""; + display: flex; + left: 0; + opacity: 0.9; + position: absolute; + right: 0; + top: 0; + z-index: 5; +} +.tox .tox-comment__reply { + display: flex; + flex-shrink: 0; + flex-wrap: wrap; + justify-content: flex-end; + margin-top: 5px; +} +.tox .tox-comment__reply > *:first-child { + margin-bottom: 5px; + width: 100%; +} +.tox .tox-comment__edit { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + margin-top: 10px; +} +.tox .tox-comment__gradient::after { + background: linear-gradient(rgba(255, 255, 255, 0), #fff); + bottom: 0; + content: ""; + display: block; + height: 5em; + margin-top: -40px; + position: absolute; + width: 100%; +} +.tox .tox-comment__overlay { + background: #fff; + bottom: 0; + display: flex; + flex-direction: column; + flex-grow: 1; + left: 0; + opacity: 0.9; + position: absolute; + right: 0; + text-align: center; + top: 0; + z-index: 5; +} +.tox .tox-comment__loading-text { + align-items: center; + color: rgba(84, 111, 94, 0.85); + display: flex; + flex-direction: column; + position: relative; +} +.tox .tox-comment__loading-text > div { + padding-bottom: 10px; +} +.tox .tox-comment__overlaytext { + bottom: 0; + flex-direction: column; + font-size: 8.75px; + left: 0; + padding: 1em; + position: absolute; + right: 0; + top: 0; + z-index: 10; +} +.tox .tox-comment__overlaytext p { + background-color: #fff; + box-shadow: 0 0 8px 8px #fff; + color: rgba(84, 111, 94, 0.85); + text-align: center; +} +.tox .tox-comment__overlaytext div:nth-of-type(2) { + font-size: 0.8em; +} +.tox .tox-comment__busy-spinner { + align-items: center; + background-color: #fff; + bottom: 0; + display: flex; + justify-content: center; + left: 0; + position: absolute; + right: 0; + top: 0; + z-index: 20; +} +.tox .tox-comment__scroll { + display: flex; + flex-direction: column; + flex-shrink: 1; + overflow: auto; +} +.tox .tox-conversations { + margin: 5px; +} +.tox:not([dir=rtl]) .tox-comment__edit { + margin-left: 5px; +} +.tox:not([dir=rtl]) .tox-comment__buttonspacing > *:last-child, +.tox:not([dir=rtl]) .tox-comment__edit > *:last-child, +.tox:not([dir=rtl]) .tox-comment__reply > *:last-child { + margin-left: 5px; +} +.tox[dir=rtl] .tox-comment__edit { + margin-right: 5px; +} +.tox[dir=rtl] .tox-comment__buttonspacing > *:last-child, +.tox[dir=rtl] .tox-comment__edit > *:last-child, +.tox[dir=rtl] .tox-comment__reply > *:last-child { + margin-right: 5px; +} +.tox .tox-user { + align-items: center; + display: flex; +} +.tox .tox-user__avatar svg { + fill: rgba(84, 111, 94, 0.7); +} +.tox .tox-user__name { + color: rgba(84, 111, 94, 0.7); + font-size: 12px; + font-style: normal; + font-weight: normal; + text-transform: uppercase; +} +.tox:not([dir=rtl]) .tox-user__avatar svg { + margin-right: 5px; +} +.tox:not([dir=rtl]) .tox-user__avatar + .tox-user__name { + margin-left: 5px; +} +.tox[dir=rtl] .tox-user__avatar svg { + margin-left: 5px; +} +.tox[dir=rtl] .tox-user__avatar + .tox-user__name { + margin-right: 5px; +} +.tox .tox-dialog-wrap { + align-items: center; + bottom: 0; + display: flex; + justify-content: center; + left: 0; + position: fixed; + right: 0; + top: 0; + z-index: 1100; +} +.tox .tox-dialog-wrap__backdrop { + background-color: rgba(255, 255, 255, 0.75); + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + z-index: 1; +} +.tox .tox-dialog-wrap__backdrop--opaque { + background-color: #fff; +} +.tox .tox-dialog { + background-color: #fff; + border-color: #d9d9d9; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: 0 16px 16px -10px rgba(84, 111, 94, 0.15), 0 0 40px 1px rgba(84, 111, 94, 0.15); + display: flex; + flex-direction: column; + max-height: 100%; + max-width: 480px; + overflow: hidden; + position: relative; + width: 95vw; + z-index: 2; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox .tox-dialog { + align-self: flex-start; + margin: 5px auto; + width: calc(100vw - 10px); + } +} +.tox .tox-dialog-inline { + z-index: 1100; +} +.tox .tox-dialog__header { + align-items: center; + background-color: #fff; + border-bottom: none; + color: rgba(84, 111, 94, 0.85); + display: flex; + font-size: 10px; + justify-content: space-between; + padding: 5px 10px 0 10px; + position: relative; +} +.tox .tox-dialog__header .tox-button { + z-index: 1; +} +.tox .tox-dialog__draghandle { + cursor: grab; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.tox .tox-dialog__draghandle:active { + cursor: grabbing; +} +.tox .tox-dialog__dismiss { + margin-left: auto; +} +.tox .tox-dialog__title { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 12.5px; + font-style: normal; + font-weight: normal; + line-height: 1.3; + margin: 0; + text-transform: none; +} +.tox .tox-dialog__body { + color: rgba(84, 111, 94, 0.85); + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; + font-size: 10px; + font-style: normal; + font-weight: normal; + line-height: 1.3; + min-width: 0; + text-align: left; + text-transform: none; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox .tox-dialog__body { + flex-direction: column; + } +} +.tox .tox-dialog__body-nav { + align-items: flex-start; + display: flex; + flex-direction: column; + padding: 10px 10px; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox .tox-dialog__body-nav { + flex-direction: row; + -webkit-overflow-scrolling: touch; + overflow-x: auto; + padding-bottom: 0; + } +} +.tox .tox-dialog__body-nav-item { + border-bottom: 2px solid transparent; + color: rgba(84, 111, 94, 0.7); + display: inline-block; + font-size: 8.75px; + line-height: 1.3; + margin-bottom: 5px; + text-decoration: none; + white-space: nowrap; +} +.tox .tox-dialog__body-nav-item:focus { + background-color: rgba(10, 143, 233, 0.1); +} +.tox .tox-dialog__body-nav-item--active { + border-bottom: 2px solid #0a8fe9; + color: #0a8fe9; +} +.tox .tox-dialog__body-content { + box-sizing: border-box; + display: flex; + flex: 1; + flex-direction: column; + -ms-flex-preferred-size: auto; + max-height: 650px; + overflow: auto; + -webkit-overflow-scrolling: touch; + padding: 10px 10px; +} +.tox .tox-dialog__body-content > * { + margin-bottom: 0; + margin-top: 10px; +} +.tox .tox-dialog__body-content > *:first-child { + margin-top: 0; +} +.tox .tox-dialog__body-content > *:last-child { + margin-bottom: 0; +} +.tox .tox-dialog__body-content > *:only-child { + margin-bottom: 0; + margin-top: 0; +} +.tox .tox-dialog__body-content a { + color: #0a8fe9; + cursor: pointer; + text-decoration: none; +} +.tox .tox-dialog__body-content a:hover, +.tox .tox-dialog__body-content a:focus { + color: #0871b8; + text-decoration: none; +} +.tox .tox-dialog__body-content a:active { + color: #0871b8; + text-decoration: none; +} +.tox .tox-dialog__body-content svg { + fill: rgba(84, 111, 94, 0.85); +} +.tox .tox-dialog__body-content ul { + display: block; + list-style-type: disc; + margin-bottom: 10px; + -webkit-margin-end: 0; + margin-inline-end: 0; + -webkit-margin-start: 0; + margin-inline-start: 0; + -webkit-padding-start: 2.5rem; + padding-inline-start: 2.5rem; +} +.tox .tox-dialog__body-content .tox-form__group h1 { + color: rgba(84, 111, 94, 0.85); + font-size: 12.5px; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + margin-bottom: 10px; + margin-top: 2rem; + text-transform: none; +} +.tox .tox-dialog__body-content .tox-form__group h2 { + color: rgba(84, 111, 94, 0.85); + font-size: 10px; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + margin-bottom: 10px; + margin-top: 2rem; + text-transform: none; +} +.tox .tox-dialog__body-content .tox-form__group p { + margin-bottom: 10px; +} +.tox .tox-dialog__body-content .tox-form__group h1:first-child, +.tox .tox-dialog__body-content .tox-form__group h2:first-child, +.tox .tox-dialog__body-content .tox-form__group p:first-child { + margin-top: 0; +} +.tox .tox-dialog__body-content .tox-form__group h1:last-child, +.tox .tox-dialog__body-content .tox-form__group h2:last-child, +.tox .tox-dialog__body-content .tox-form__group p:last-child { + margin-bottom: 0; +} +.tox .tox-dialog__body-content .tox-form__group h1:only-child, +.tox .tox-dialog__body-content .tox-form__group h2:only-child, +.tox .tox-dialog__body-content .tox-form__group p:only-child { + margin-bottom: 0; + margin-top: 0; +} +.tox .tox-dialog--width-lg { + height: 650px; + max-width: 1200px; +} +.tox .tox-dialog--width-md { + max-width: 800px; +} +.tox .tox-dialog--width-md .tox-dialog__body-content { + overflow: auto; +} +.tox .tox-dialog__body-content--centered { + text-align: center; +} +.tox .tox-dialog__footer { + align-items: center; + background-color: #fff; + border-top: 1px solid #d9d9d9; + display: flex; + justify-content: space-between; + padding: 5px 10px; +} +.tox .tox-dialog__footer-start, +.tox .tox-dialog__footer-end { + display: flex; +} +.tox .tox-dialog__busy-spinner { + align-items: center; + background-color: rgba(255, 255, 255, 0.75); + bottom: 0; + display: flex; + justify-content: center; + left: 0; + position: absolute; + right: 0; + top: 0; + z-index: 3; +} +.tox .tox-dialog__table { + border-collapse: collapse; + width: 100%; +} +.tox .tox-dialog__table thead th { + font-weight: normal; + padding-bottom: 5px; +} +.tox .tox-dialog__table tbody tr { + border-bottom: 1px solid #d9d9d9; +} +.tox .tox-dialog__table tbody tr:last-child { + border-bottom: none; +} +.tox .tox-dialog__table td { + padding-bottom: 5px; + padding-top: 5px; +} +.tox .tox-dialog__popups { + position: absolute; + width: 100%; + z-index: 1100; +} +.tox .tox-dialog__body-iframe { + display: flex; + flex: 1; + flex-direction: column; + -ms-flex-preferred-size: auto; +} +.tox .tox-dialog__body-iframe .tox-navobj { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2) { + flex: 1; + -ms-flex-preferred-size: auto; + height: 100%; +} +.tox .tox-dialog-dock-fadeout { + opacity: 0; + visibility: hidden; +} +.tox .tox-dialog-dock-fadein { + opacity: 1; + visibility: visible; +} +.tox .tox-dialog-dock-transition { + transition: visibility 0s linear 0.3s, opacity 0.3s ease; +} +.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein { + transition-delay: 0s; +} +.tox.tox-platform-ie { + /* IE11 CSS styles go here */ +} +.tox.tox-platform-ie .tox-dialog-wrap { + position: -ms-device-fixed; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav { + margin-right: 0; + } +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child) { + margin-left: 5px; + } +} +.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start > *, +.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end > * { + margin-left: 5px; +} +.tox[dir=rtl] .tox-dialog__body { + text-align: right; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav { + margin-left: 0; + } +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child) { + margin-right: 5px; + } +} +.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start > *, +.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end > * { + margin-right: 5px; +} +body.tox-dialog__disable-scroll { + overflow: hidden; +} +.tox .tox-dropzone-container { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-dropzone { + align-items: center; + background: #fff; + border: 2px dashed #d9d9d9; + box-sizing: border-box; + display: flex; + flex-direction: column; + flex-grow: 1; + justify-content: center; + min-height: 100px; + padding: 10px; +} +.tox .tox-dropzone p { + color: rgba(84, 111, 94, 0.7); + margin: 0 0 10px 0; +} +.tox .tox-edit-area { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; + overflow: hidden; + position: relative; +} +.tox .tox-edit-area__iframe { + background-color: #fff; + border: 0; + box-sizing: border-box; + flex: 1; + -ms-flex-preferred-size: auto; + height: 100%; + position: absolute; + width: 100%; +} +.tox.tox-inline-edit-area { + border: 1px dotted #d9d9d9; +} +.tox .tox-editor-container { + display: flex; + flex: 1 1 auto; + flex-direction: column; + overflow: hidden; +} +.tox .tox-editor-header { + z-index: 1; +} +.tox:not(.tox-tinymce-inline) .tox-editor-header { + box-shadow: none; + transition: box-shadow 0.5s; +} +.tox.tox-tinymce--toolbar-bottom .tox-editor-header, +.tox.tox-tinymce-inline .tox-editor-header { + margin-bottom: -1px; +} +.tox.tox-tinymce--toolbar-sticky-on .tox-editor-header { + background-color: transparent; + box-shadow: 0 4px 4px -3px rgba(0, 0, 0, 0.25); +} +.tox-editor-dock-fadeout { + opacity: 0; + visibility: hidden; +} +.tox-editor-dock-fadein { + opacity: 1; + visibility: visible; +} +.tox-editor-dock-transition { + transition: visibility 0s linear 0.25s, opacity 0.25s ease; +} +.tox-editor-dock-transition.tox-editor-dock-fadein { + transition-delay: 0s; +} +.tox .tox-control-wrap { + flex: 1; + position: relative; +} +.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid, +.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown, +.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid { + display: none; +} +.tox .tox-control-wrap svg { + display: block; +} +.tox .tox-control-wrap__status-icon-wrap { + position: absolute; + top: 50%; + transform: translateY(-50%); +} +.tox .tox-control-wrap__status-icon-invalid svg { + fill: #c00; +} +.tox .tox-control-wrap__status-icon-unknown svg { + fill: orange; +} +.tox .tox-control-wrap__status-icon-valid svg { + fill: green; +} +.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield, +.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield, +.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield { + padding-right: 20px; +} +.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap { + right: 2.5px; +} +.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield, +.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield, +.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield { + padding-left: 20px; +} +.tox[dir=rtl] .tox-control-wrap__status-icon-wrap { + left: 2.5px; +} +.tox .tox-autocompleter { + max-width: 25em; +} +.tox .tox-autocompleter .tox-menu { + max-width: 25em; +} +.tox .tox-autocompleter .tox-autocompleter-highlight { + font-weight: normal; +} +.tox .tox-color-input { + display: flex; + position: relative; + z-index: 1; +} +.tox .tox-color-input .tox-textfield { + z-index: -1; +} +.tox .tox-color-input span { + border-color: rgba(84, 111, 94, 0.2); + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + height: 24px; + position: absolute; + top: 6px; + width: 24px; +} +.tox .tox-color-input span:hover:not([aria-disabled=true]), +.tox .tox-color-input span:focus:not([aria-disabled=true]) { + border-color: #0a8fe9; + cursor: pointer; +} +.tox .tox-color-input span::before { + background-image: linear-gradient(45deg, rgba(0, 0, 0, 0.25) 25%, transparent 25%), linear-gradient(-45deg, rgba(0, 0, 0, 0.25) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgba(0, 0, 0, 0.25) 75%), linear-gradient(-45deg, transparent 75%, rgba(0, 0, 0, 0.25) 75%); + background-position: 0 0, 0 6px, 6px -6px, -6px 0; + background-size: 12px 12px; + border: 1px solid #fff; + border-radius: 3px; + box-sizing: border-box; + content: ''; + height: 24px; + left: -1px; + position: absolute; + top: -1px; + width: 24px; + z-index: -1; +} +.tox .tox-color-input span[aria-disabled=true] { + cursor: not-allowed; +} +.tox:not([dir=rtl]) .tox-color-input { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox:not([dir=rtl]) .tox-color-input .tox-textfield { + padding-left: 36px; +} +.tox:not([dir=rtl]) .tox-color-input span { + left: 6px; +} +.tox[dir="rtl"] .tox-color-input { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox[dir="rtl"] .tox-color-input .tox-textfield { + padding-right: 36px; +} +.tox[dir="rtl"] .tox-color-input span { + right: 6px; +} +.tox .tox-label, +.tox .tox-toolbar-label { + color: rgba(84, 111, 94, 0.7); + display: block; + font-size: 8.75px; + font-style: normal; + font-weight: normal; + line-height: 1.3; + padding: 0 5px 0 0; + text-transform: none; + white-space: nowrap; +} +.tox .tox-toolbar-label { + padding: 0 5px; +} +.tox[dir=rtl] .tox-label { + padding: 0 0 0 5px; +} +.tox .tox-form { + display: flex; + flex: 1; + flex-direction: column; + -ms-flex-preferred-size: auto; +} +.tox .tox-form__group { + box-sizing: border-box; + margin-bottom: 2.5px; +} +.tox .tox-form-group--maximize { + flex: 1; +} +.tox .tox-form__group--error { + color: #c00; +} +.tox .tox-form__group--collection { + display: flex; +} +.tox .tox-form__grid { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; +} +.tox .tox-form__grid--2col > .tox-form__group { + width: calc(50% - (5px / 2)); +} +.tox .tox-form__grid--3col > .tox-form__group { + width: calc(100% / 3 - (5px / 2)); +} +.tox .tox-form__grid--4col > .tox-form__group { + width: calc(25% - (5px / 2)); +} +.tox .tox-form__controls-h-stack { + align-items: center; + display: flex; +} +.tox .tox-form__group--inline { + align-items: center; + display: flex; +} +.tox .tox-form__group--stretched { + display: flex; + flex: 1; + flex-direction: column; + -ms-flex-preferred-size: auto; +} +.tox .tox-form__group--stretched .tox-textarea { + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-form__group--stretched .tox-navobj { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-form__group--stretched .tox-navobj :nth-child(2) { + flex: 1; + -ms-flex-preferred-size: auto; + height: 100%; +} +.tox:not([dir=rtl]) .tox-form__controls-h-stack > *:not(:first-child) { + margin-left: 2.5px; +} +.tox[dir=rtl] .tox-form__controls-h-stack > *:not(:first-child) { + margin-right: 2.5px; +} +.tox .tox-lock.tox-locked .tox-lock-icon__unlock, +.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock { + display: none; +} +.tox .tox-textfield, +.tox .tox-toolbar-textfield, +.tox .tox-listboxfield .tox-listbox--select, +.tox .tox-textarea { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: #fff; + border-color: #d9d9d9; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + color: rgba(84, 111, 94, 0.85); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 10px; + line-height: 24px; + margin: 0; + min-height: 34px; + outline: none; + padding: 5px 3.25px; + resize: none; + width: 100%; +} +.tox .tox-textfield[disabled], +.tox .tox-textarea[disabled] { + background-color: #f2f2f2; + color: rgba(84, 111, 94, 0.85); + cursor: not-allowed; +} +.tox .tox-textfield:focus, +.tox .tox-listboxfield .tox-listbox--select:focus, +.tox .tox-textarea:focus { + background-color: #fff; + border-color: #0a8fe9; + box-shadow: none; + outline: none; +} +.tox .tox-toolbar-textfield { + border-width: 0; + margin-bottom: 3px; + margin-top: 2px; + max-width: 250px; +} +.tox .tox-naked-btn { + background-color: transparent; + border: 0; + border-color: transparent; + box-shadow: unset; + color: #0a8fe9; + cursor: pointer; + display: block; + margin: 0; + padding: 0; +} +.tox .tox-naked-btn svg { + display: block; + fill: rgba(84, 111, 94, 0.85); +} +.tox:not([dir=rtl]) .tox-toolbar-textfield + * { + margin-left: 2.5px; +} +.tox[dir=rtl] .tox-toolbar-textfield + * { + margin-right: 2.5px; +} +.tox .tox-listboxfield { + cursor: pointer; + position: relative; +} +.tox .tox-listboxfield .tox-listbox--select[disabled] { + background-color: #f2f2f2; + color: rgba(84, 111, 94, 0.85); + cursor: not-allowed; +} +.tox .tox-listbox__select-label { + cursor: default; + flex: 1; + margin: 0 4px; +} +.tox .tox-listbox__select-chevron { + align-items: center; + display: flex; + justify-content: center; + width: 10px; +} +.tox .tox-listbox__select-chevron svg { + fill: rgba(84, 111, 94, 0.85); +} +.tox .tox-listboxfield .tox-listbox--select { + align-items: center; + display: flex; +} +.tox:not([dir=rtl]) .tox-listboxfield svg { + right: 5px; +} +.tox[dir=rtl] .tox-listboxfield svg { + left: 5px; +} +.tox .tox-selectfield { + cursor: pointer; + position: relative; +} +.tox .tox-selectfield select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: #fff; + border-color: #d9d9d9; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + color: rgba(84, 111, 94, 0.85); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 10px; + line-height: 24px; + margin: 0; + min-height: 34px; + outline: none; + padding: 5px 3.25px; + resize: none; + width: 100%; +} +.tox .tox-selectfield select[disabled] { + background-color: #f2f2f2; + color: rgba(84, 111, 94, 0.85); + cursor: not-allowed; +} +.tox .tox-selectfield select::-ms-expand { + display: none; +} +.tox .tox-selectfield select:focus { + background-color: #fff; + border-color: #0a8fe9; + box-shadow: none; + outline: none; +} +.tox .tox-selectfield svg { + pointer-events: none; + position: absolute; + top: 50%; + transform: translateY(-50%); +} +.tox:not([dir=rtl]) .tox-selectfield select[size="0"], +.tox:not([dir=rtl]) .tox-selectfield select[size="1"] { + padding-right: 15px; +} +.tox:not([dir=rtl]) .tox-selectfield svg { + right: 5px; +} +.tox[dir=rtl] .tox-selectfield select[size="0"], +.tox[dir=rtl] .tox-selectfield select[size="1"] { + padding-left: 15px; +} +.tox[dir=rtl] .tox-selectfield svg { + left: 5px; +} +.tox .tox-textarea { + -webkit-appearance: textarea; + -moz-appearance: textarea; + appearance: textarea; + white-space: pre-wrap; +} +.tox-fullscreen { + border: 0; + height: 100%; + left: 0; + margin: 0; + overflow: hidden; + -ms-scroll-chaining: none; + overscroll-behavior: none; + padding: 0; + position: fixed; + top: 0; + touch-action: pinch-zoom; + width: 100%; +} +.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { + display: none; +} +.tox.tox-tinymce.tox-fullscreen { + background-color: transparent; + z-index: 1200; +} +.tox-shadowhost.tox-fullscreen { + z-index: 1200; +} +.tox-fullscreen .tox.tox-tinymce-aux, +.tox-fullscreen ~ .tox.tox-tinymce-aux { + z-index: 1201; +} +.tox .tox-help__more-link { + list-style: none; + margin-top: 1em; +} +.tox .tox-image-tools { + width: 100%; +} +.tox .tox-image-tools__toolbar { + align-items: center; + display: flex; + justify-content: center; +} +.tox .tox-image-tools__image { + background-color: #666; + height: 380px; + overflow: auto; + position: relative; + width: 100%; +} +.tox .tox-image-tools__image, +.tox .tox-image-tools__image + .tox-image-tools__toolbar { + margin-top: 5px; +} +.tox .tox-image-tools__image-bg { + background: url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==); +} +.tox .tox-image-tools__toolbar > .tox-spacer { + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-croprect-block { + background: black; + filter: alpha(opacity=50); + opacity: 0.5; + position: absolute; + zoom: 1; +} +.tox .tox-croprect-handle { + border: 2px solid white; + height: 20px; + left: 0; + position: absolute; + top: 0; + width: 20px; +} +.tox .tox-croprect-handle-move { + border: 0; + cursor: move; + position: absolute; +} +.tox .tox-croprect-handle-nw { + border-width: 2px 0 0 2px; + cursor: nw-resize; + left: 100px; + margin: -2px 0 0 -2px; + top: 100px; +} +.tox .tox-croprect-handle-ne { + border-width: 2px 2px 0 0; + cursor: ne-resize; + left: 200px; + margin: -2px 0 0 -20px; + top: 100px; +} +.tox .tox-croprect-handle-sw { + border-width: 0 0 2px 2px; + cursor: sw-resize; + left: 100px; + margin: -20px 2px 0 -2px; + top: 200px; +} +.tox .tox-croprect-handle-se { + border-width: 0 2px 2px 0; + cursor: se-resize; + left: 200px; + margin: -20px 0 0 -20px; + top: 200px; +} +.tox:not([dir=rtl]) .tox-image-tools__toolbar > .tox-slider:not(:first-of-type) { + margin-left: 5px; +} +.tox:not([dir=rtl]) .tox-image-tools__toolbar > .tox-button + .tox-slider { + margin-left: 20px; +} +.tox:not([dir=rtl]) .tox-image-tools__toolbar > .tox-slider + .tox-button { + margin-left: 20px; +} +.tox[dir=rtl] .tox-image-tools__toolbar > .tox-slider:not(:first-of-type) { + margin-right: 5px; +} +.tox[dir=rtl] .tox-image-tools__toolbar > .tox-button + .tox-slider { + margin-right: 20px; +} +.tox[dir=rtl] .tox-image-tools__toolbar > .tox-slider + .tox-button { + margin-right: 20px; +} +.tox .tox-insert-table-picker { + display: flex; + flex-wrap: wrap; + width: 110px; +} +.tox .tox-insert-table-picker > div { + border-color: #d9d9d9; + border-style: solid; + border-width: 0 1px 1px 0; + box-sizing: border-box; + height: 11px; + width: 11px; +} +.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker { + margin: -2.5px 0; +} +.tox .tox-insert-table-picker .tox-insert-table-picker__selected { + background-color: rgba(10, 143, 233, 0.5); + border-color: rgba(10, 143, 233, 0.5); +} +.tox .tox-insert-table-picker__label { + color: rgba(84, 111, 94, 0.7); + display: block; + font-size: 8.75px; + padding: 2.5px; + text-align: center; + width: 100%; +} +.tox:not([dir=rtl]) { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox:not([dir=rtl]) .tox-insert-table-picker > div:nth-child(10n) { + border-right: 0; +} +.tox[dir=rtl] { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox[dir=rtl] .tox-insert-table-picker > div:nth-child(10n+1) { + border-right: 0; +} +.tox { + /* stylelint-disable */ + /* stylelint-enable */ +} +.tox .tox-menu { + background-color: #fff; + border: 1px solid #d9d9d9; + border-radius: 3px; + box-shadow: 0 4px 8px 0 rgba(84, 111, 94, 0.1); + display: inline-block; + overflow: hidden; + vertical-align: top; + z-index: 1150; +} +.tox .tox-menu.tox-collection.tox-collection--list { + padding: 0; +} +.tox .tox-menu.tox-collection.tox-collection--toolbar { + padding: 2.5px; +} +.tox .tox-menu.tox-collection.tox-collection--grid { + padding: 2.5px; +} +.tox .tox-menu__label h1, +.tox .tox-menu__label h2, +.tox .tox-menu__label h3, +.tox .tox-menu__label h4, +.tox .tox-menu__label h5, +.tox .tox-menu__label h6, +.tox .tox-menu__label p, +.tox .tox-menu__label blockquote, +.tox .tox-menu__label code { + margin: 0; +} +.tox .tox-menubar { + background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23d9d9d9'/%3E%3C/svg%3E") left 0 top 0 #fff; + background-color: #fff; + display: flex; + flex: 0 0 auto; + flex-shrink: 0; + flex-wrap: wrap; + padding: 0 4px 0 4px; +} +.tox.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar { + border-top: 1px solid #d9d9d9; +} +/* Deprecated. Remove in next major release */ +.tox .tox-mbtn { + align-items: center; + background: transparent; + border: 0; + border-radius: 3px; + box-shadow: none; + color: #817f7c; + display: flex; + flex: 0 0 auto; + font-size: 8.75px; + font-style: normal; + font-weight: normal; + height: 34px; + justify-content: center; + margin: 2px 0 3px 0; + outline: none; + overflow: hidden; + padding: 0 4px; + text-transform: none; + width: auto; +} +.tox .tox-mbtn[disabled] { + background-color: transparent; + border: 0; + box-shadow: none; + color: rgba(129, 127, 124, 0.5); + cursor: not-allowed; +} +.tox .tox-mbtn:focus:not(:disabled) { + background: #e5e9e7; + border: 0; + box-shadow: none; + color: #0a9fe5; +} +.tox .tox-mbtn--active { + background: #e5e9e7; + border: 0; + box-shadow: none; + color: rgba(41, 159, 250, 0.88); +} +.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active) { + background: #e5e9e7; + border: 0; + box-shadow: none; + color: #0a9fe5; +} +.tox .tox-mbtn__select-label { + cursor: default; + font-weight: normal; + margin: 0 4px; +} +.tox .tox-mbtn[disabled] .tox-mbtn__select-label { + cursor: not-allowed; +} +.tox .tox-mbtn__select-chevron { + align-items: center; + display: flex; + justify-content: center; + width: 16px; + display: none; +} +.tox .tox-notification { + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + display: -ms-grid; + display: grid; + font-size: 8.75px; + font-weight: normal; + -ms-grid-columns: minmax(40px, 1fr) auto minmax(40px, 1fr); + grid-template-columns: minmax(40px, 1fr) auto minmax(40px, 1fr); + margin-top: 2.5px; + opacity: 0; + padding: 2.5px; + transition: transform 100ms ease-in, opacity 150ms ease-in; +} +.tox .tox-notification p { + font-size: 8.75px; + font-weight: normal; +} +.tox .tox-notification a { + cursor: pointer; + text-decoration: underline; +} +.tox .tox-notification--in { + opacity: 1; +} +.tox .tox-notification--success { + background-color: #e4eeda; + border-color: #d7e6c8; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-notification--success p { + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-notification--success a { + color: #547831; +} +.tox .tox-notification--success svg { + fill: rgba(84, 111, 94, 0.85); +} +.tox .tox-notification--error { + background-color: #f8dede; + border-color: #f2bfbf; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-notification--error p { + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-notification--error a { + color: #c00; +} +.tox .tox-notification--error svg { + fill: rgba(84, 111, 94, 0.85); +} +.tox .tox-notification--warn, +.tox .tox-notification--warning { + background-color: #fffaea; + border-color: #ffe89d; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-notification--warn p, +.tox .tox-notification--warning p { + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-notification--warn a, +.tox .tox-notification--warning a { + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-notification--warn svg, +.tox .tox-notification--warning svg { + fill: rgba(84, 111, 94, 0.85); +} +.tox .tox-notification--info { + background-color: #d9edf7; + border-color: #779ecb; + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-notification--info p { + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-notification--info a { + color: rgba(84, 111, 94, 0.85); +} +.tox .tox-notification--info svg { + fill: rgba(84, 111, 94, 0.85); +} +.tox .tox-notification__body { + -ms-grid-row-align: center; + align-self: center; + color: rgba(84, 111, 94, 0.85); + font-size: 14px; + -ms-grid-column-span: 1; + grid-column-end: 3; + -ms-grid-column: 2; + grid-column-start: 2; + -ms-grid-row-span: 1; + grid-row-end: 2; + -ms-grid-row: 1; + grid-row-start: 1; + text-align: center; + white-space: normal; + word-break: break-all; + word-break: break-word; +} +.tox .tox-notification__body > * { + margin: 0; +} +.tox .tox-notification__body > * + * { + margin-top: 1rem; +} +.tox .tox-notification__icon { + -ms-grid-row-align: center; + align-self: center; + -ms-grid-column-span: 1; + grid-column-end: 2; + -ms-grid-column: 1; + grid-column-start: 1; + -ms-grid-row-span: 1; + grid-row-end: 2; + -ms-grid-row: 1; + grid-row-start: 1; + -ms-grid-column-align: end; + justify-self: end; +} +.tox .tox-notification__icon svg { + display: block; +} +.tox .tox-notification__dismiss { + -ms-grid-row-align: start; + align-self: start; + -ms-grid-column-span: 1; + grid-column-end: 4; + -ms-grid-column: 3; + grid-column-start: 3; + -ms-grid-row-span: 1; + grid-row-end: 2; + -ms-grid-row: 1; + grid-row-start: 1; + -ms-grid-column-align: end; + justify-self: end; +} +.tox .tox-notification .tox-progress-bar { + -ms-grid-column-span: 3; + grid-column-end: 4; + -ms-grid-column: 1; + grid-column-start: 1; + -ms-grid-row-span: 1; + grid-row-end: 3; + -ms-grid-row: 2; + grid-row-start: 2; + -ms-grid-column-align: center; + justify-self: center; +} +.tox .tox-pop { + display: inline-block; + position: relative; +} +.tox .tox-pop--resizing { + transition: width 0.1s ease; +} +.tox .tox-pop--resizing .tox-toolbar, +.tox .tox-pop--resizing .tox-toolbar__group { + flex-wrap: nowrap; +} +.tox .tox-pop--transition { + transition: 0.15s ease; + transition-property: left, right, top, bottom; +} +.tox .tox-pop--transition::before, +.tox .tox-pop--transition::after { + transition: all 0.15s, visibility 0s, opacity 0.075s ease 0.075s; +} +.tox .tox-pop__dialog { + background-color: #fff; + border: 1px solid #d9d9d9; + border-radius: 3px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); + min-width: 0; + overflow: hidden; +} +.tox .tox-pop__dialog > *:not(.tox-toolbar) { + margin: 2.5px 2.5px 2.5px 5px; +} +.tox .tox-pop__dialog .tox-toolbar { + background-color: transparent; + margin-bottom: -1px; +} +.tox .tox-pop::before, +.tox .tox-pop::after { + border-style: solid; + content: ''; + display: block; + height: 0; + opacity: 1; + position: absolute; + width: 0; +} +.tox .tox-pop.tox-pop--inset::before, +.tox .tox-pop.tox-pop--inset::after { + opacity: 0; + transition: all 0s 0.15s, visibility 0s, opacity 0.075s ease; +} +.tox .tox-pop.tox-pop--bottom::before, +.tox .tox-pop.tox-pop--bottom::after { + left: 50%; + top: 100%; +} +.tox .tox-pop.tox-pop--bottom::after { + border-color: #fff transparent transparent transparent; + border-width: 8px; + margin-left: -8px; + margin-top: -1px; +} +.tox .tox-pop.tox-pop--bottom::before { + border-color: #d9d9d9 transparent transparent transparent; + border-width: 9px; + margin-left: -9px; +} +.tox .tox-pop.tox-pop--top::before, +.tox .tox-pop.tox-pop--top::after { + left: 50%; + top: 0; + transform: translateY(-100%); +} +.tox .tox-pop.tox-pop--top::after { + border-color: transparent transparent #fff transparent; + border-width: 8px; + margin-left: -8px; + margin-top: 1px; +} +.tox .tox-pop.tox-pop--top::before { + border-color: transparent transparent #d9d9d9 transparent; + border-width: 9px; + margin-left: -9px; +} +.tox .tox-pop.tox-pop--left::before, +.tox .tox-pop.tox-pop--left::after { + left: 0; + top: calc(50% - 1px); + transform: translateY(-50%); +} +.tox .tox-pop.tox-pop--left::after { + border-color: transparent #fff transparent transparent; + border-width: 8px; + margin-left: -15px; +} +.tox .tox-pop.tox-pop--left::before { + border-color: transparent #d9d9d9 transparent transparent; + border-width: 10px; + margin-left: -19px; +} +.tox .tox-pop.tox-pop--right::before, +.tox .tox-pop.tox-pop--right::after { + left: 100%; + top: calc(50% + 1px); + transform: translateY(-50%); +} +.tox .tox-pop.tox-pop--right::after { + border-color: transparent transparent transparent #fff; + border-width: 8px; + margin-left: -1px; +} +.tox .tox-pop.tox-pop--right::before { + border-color: transparent transparent transparent #d9d9d9; + border-width: 10px; + margin-left: -1px; +} +.tox .tox-pop.tox-pop--align-left::before, +.tox .tox-pop.tox-pop--align-left::after { + left: 20px; +} +.tox .tox-pop.tox-pop--align-right::before, +.tox .tox-pop.tox-pop--align-right::after { + left: calc(100% - 20px); +} +.tox .tox-sidebar-wrap { + display: flex; + flex-direction: row; + flex-grow: 1; + -ms-flex-preferred-size: 0; + min-height: 0; +} +.tox .tox-sidebar { + background-color: #fff; + display: flex; + flex-direction: row; + justify-content: flex-end; +} +.tox .tox-sidebar__slider { + display: flex; + overflow: hidden; +} +.tox .tox-sidebar__pane-container { + display: flex; +} +.tox .tox-sidebar__pane { + display: flex; +} +.tox .tox-sidebar--sliding-closed { + opacity: 0; +} +.tox .tox-sidebar--sliding-open { + opacity: 1; +} +.tox .tox-sidebar--sliding-growing, +.tox .tox-sidebar--sliding-shrinking { + transition: width 0.5s ease, opacity 0.5s ease; +} +.tox .tox-selector { + background-color: #4099ff; + border-color: #4099ff; + border-style: solid; + border-width: 1px; + box-sizing: border-box; + display: inline-block; + height: 10px; + position: absolute; + width: 10px; +} +.tox.tox-platform-touch .tox-selector { + height: 12px; + width: 12px; +} +.tox .tox-slider { + align-items: center; + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; + height: 24px; + justify-content: center; + position: relative; +} +.tox .tox-slider__rail { + background-color: transparent; + border: 1px solid #d9d9d9; + border-radius: 3px; + height: 10px; + min-width: 120px; + width: 100%; +} +.tox .tox-slider__handle { + background-color: #0a8fe9; + border: 2px solid #0871b8; + border-radius: 3px; + box-shadow: none; + height: 24px; + left: 50%; + position: absolute; + top: 50%; + transform: translateX(-50%) translateY(-50%); + width: 14px; +} +.tox .tox-source-code { + overflow: auto; +} +.tox .tox-spinner { + display: flex; +} +.tox .tox-spinner > div { + animation: tam-bouncing-dots 1.5s ease-in-out 0s infinite both; + background-color: rgba(84, 111, 94, 0.7); + border-radius: 100%; + height: 5px; + width: 5px; +} +.tox .tox-spinner > div:nth-child(1) { + animation-delay: -0.32s; +} +.tox .tox-spinner > div:nth-child(2) { + animation-delay: -0.16s; +} +@keyframes tam-bouncing-dots { + 0%, + 80%, + 100% { + transform: scale(0); + } + 40% { + transform: scale(1); + } +} +.tox:not([dir=rtl]) .tox-spinner > div:not(:first-child) { + margin-left: 2.5px; +} +.tox[dir=rtl] .tox-spinner > div:not(:first-child) { + margin-right: 2.5px; +} +.tox .tox-statusbar { + align-items: center; + background-color: #fff; + border-top: 1px solid #d9d9d9; + color: rgba(84, 111, 94, 0.7); + display: flex; + flex: 0 0 auto; + font-size: 12px; + font-weight: normal; + height: 18px; + overflow: hidden; + padding: 0 5px; + position: relative; + text-transform: uppercase; +} +.tox .tox-statusbar__text-container { + display: flex; + flex: 1 1 auto; + justify-content: flex-end; + overflow: hidden; +} +.tox .tox-statusbar__path { + display: flex; + flex: 1 1 auto; + margin-right: auto; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.tox .tox-statusbar__path > * { + display: inline; + white-space: nowrap; +} +.tox .tox-statusbar__wordcount { + flex: 0 0 auto; + margin-left: 1ch; +} +.tox .tox-statusbar a, +.tox .tox-statusbar__path-item, +.tox .tox-statusbar__wordcount { + color: rgba(84, 111, 94, 0.7); + text-decoration: none; +} +.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]) { + cursor: pointer; + text-decoration: underline; +} +.tox .tox-statusbar__resize-handle { + align-items: flex-end; + align-self: stretch; + cursor: nwse-resize; + display: flex; + flex: 0 0 auto; + justify-content: flex-end; + margin-left: auto; + margin-right: -5px; + padding-left: 1ch; +} +.tox .tox-statusbar__resize-handle svg { + display: block; + fill: rgba(84, 111, 94, 0.7); +} +.tox .tox-statusbar__resize-handle:focus svg { + background-color: #e5e9e7; + border-radius: 1px; + box-shadow: 0 0 0 2px #e5e9e7; +} +.tox:not([dir=rtl]) .tox-statusbar__path > * { + margin-right: 2.5px; +} +.tox:not([dir=rtl]) .tox-statusbar__branding { + margin-left: 1ch; +} +.tox[dir=rtl] .tox-statusbar { + flex-direction: row-reverse; +} +.tox[dir=rtl] .tox-statusbar__path > * { + margin-left: 2.5px; +} +.tox .tox-throbber { + z-index: 1299; +} +.tox .tox-throbber__busy-spinner { + align-items: center; + background-color: rgba(255, 255, 255, 0.6); + bottom: 0; + display: flex; + justify-content: center; + left: 0; + position: absolute; + right: 0; + top: 0; +} +.tox .tox-tbtn { + align-items: center; + background: transparent; + border: 0; + border-radius: 3px; + box-shadow: none; + color: #817f7c; + display: flex; + flex: 0 0 auto; + font-size: 8.75px; + font-style: normal; + font-weight: normal; + height: 34px; + justify-content: center; + margin: 2px 0 3px 0; + outline: none; + overflow: hidden; + padding: 0; + text-transform: none; + width: 34px; +} +.tox .tox-tbtn svg { + display: block; + fill: #817f7c; +} +.tox .tox-tbtn.tox-tbtn-more { + padding-left: 5px; + padding-right: 5px; + width: inherit; +} +.tox .tox-tbtn:focus { + background: #e5e9e7; + border: 0; + box-shadow: none; +} +.tox .tox-tbtn:hover { + background: #e5e9e7; + border: 0; + box-shadow: none; + color: #0a9fe5; +} +.tox .tox-tbtn:hover svg { + fill: #0a9fe5; +} +.tox .tox-tbtn:active { + background: #e5e9e7; + border: 0; + box-shadow: none; + color: rgba(41, 159, 250, 0.88); +} +.tox .tox-tbtn:active svg { + fill: rgba(41, 159, 250, 0.88); +} +.tox .tox-tbtn--disabled, +.tox .tox-tbtn--disabled:hover, +.tox .tox-tbtn:disabled, +.tox .tox-tbtn:disabled:hover { + background: transparent; + border: 0; + box-shadow: none; + color: rgba(129, 127, 124, 0.5); + cursor: not-allowed; +} +.tox .tox-tbtn--disabled svg, +.tox .tox-tbtn--disabled:hover svg, +.tox .tox-tbtn:disabled svg, +.tox .tox-tbtn:disabled:hover svg { + /* stylelint-disable-line no-descending-specificity */ + fill: rgba(129, 127, 124, 0.5); +} +.tox .tox-tbtn--enabled, +.tox .tox-tbtn--enabled:hover { + background: #e5e9e7; + border: 0; + box-shadow: none; + color: rgba(41, 159, 250, 0.88); +} +.tox .tox-tbtn--enabled > *, +.tox .tox-tbtn--enabled:hover > * { + transform: none; +} +.tox .tox-tbtn--enabled svg, +.tox .tox-tbtn--enabled:hover svg { + /* stylelint-disable-line no-descending-specificity */ + fill: rgba(41, 159, 250, 0.88); +} +.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) { + color: #ee930e; +} +.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg { + fill: #ee930e; +} +.tox .tox-tbtn:active > * { + transform: none; +} +.tox .tox-tbtn--md { + height: 51px; + width: 51px; +} +.tox .tox-tbtn--lg { + flex-direction: column; + height: 68px; + width: 68px; +} +.tox .tox-tbtn--return { + -ms-grid-row-align: stretch; + align-self: stretch; + height: unset; + width: 16px; +} +.tox .tox-tbtn--labeled { + padding: 0 4px; + width: unset; +} +.tox .tox-tbtn__vlabel { + display: block; + font-size: 10px; + font-weight: normal; + letter-spacing: -0.025em; + margin-bottom: 2.5px; + white-space: nowrap; +} +.tox .tox-tbtn--select { + margin: 2px 0 3px 0; + padding: 0 4px; + width: auto; +} +.tox .tox-tbtn__select-label { + cursor: default; + font-weight: normal; + margin: 0 4px; +} +.tox .tox-tbtn__select-chevron { + align-items: center; + display: flex; + justify-content: center; + width: 10px; +} +.tox .tox-tbtn__select-chevron svg { + fill: rgba(129, 127, 124, 0.5); +} +.tox .tox-tbtn--bespoke .tox-tbtn__select-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 7em; +} +.tox .tox-split-button { + border: 0; + border-radius: 3px; + box-sizing: border-box; + display: flex; + margin: 2px 0 3px 0; + overflow: hidden; +} +.tox .tox-split-button:hover { + box-shadow: 0 0 0 1px #e5e9e7 inset; +} +.tox .tox-split-button:focus { + background: #e5e9e7; + box-shadow: none; + color: #ee930e; +} +.tox .tox-split-button > * { + border-radius: 0; +} +.tox .tox-split-button__chevron { + width: 10px; +} +.tox .tox-split-button__chevron svg { + fill: rgba(129, 127, 124, 0.5); +} +.tox .tox-split-button .tox-tbtn { + margin: 0; +} +.tox.tox-platform-touch .tox-split-button .tox-tbtn:first-child { + width: 30px; +} +.tox.tox-platform-touch .tox-split-button__chevron { + width: 14px; +} +.tox .tox-split-button.tox-tbtn--disabled:hover, +.tox .tox-split-button.tox-tbtn--disabled:focus, +.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover, +.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus { + background: transparent; + box-shadow: none; + color: rgba(129, 127, 124, 0.5); +} +.tox .tox-toolbar-overlord { + background-color: #fff; +} +.tox .tox-toolbar, +.tox .tox-toolbar__primary, +.tox .tox-toolbar__overflow { + background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23d9d9d9'/%3E%3C/svg%3E") left 0 top 0 #fff; + background-color: #fff; + display: flex; + flex: 0 0 auto; + flex-shrink: 0; + flex-wrap: wrap; + padding: 0 0; +} +.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed { + height: 0; + opacity: 0; + padding-bottom: 0; + padding-top: 0; + visibility: hidden; +} +.tox .tox-toolbar__overflow--growing { + transition: height 0.3s ease, opacity 0.2s linear 0.1s; +} +.tox .tox-toolbar__overflow--shrinking { + transition: opacity 0.3s ease, height 0.2s linear 0.1s, visibility 0s linear 0.3s; +} +.tox .tox-menubar + .tox-toolbar, +.tox .tox-menubar + .tox-toolbar-overlord .tox-toolbar__primary { + border-top: 1px solid #d9d9d9; + margin-top: -1px; +} +.tox .tox-toolbar--scrolling { + flex-wrap: nowrap; + overflow-x: auto; +} +.tox .tox-pop .tox-toolbar { + border-width: 0; +} +.tox .tox-toolbar--no-divider { + background-image: none; +} +.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar:first-child, +.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar-overlord:first-child .tox-toolbar__primary { + border-top: 1px solid #d9d9d9; +} +.tox.tox-tinymce-aux .tox-toolbar__overflow { + background-color: #fff; + border: 1px solid #d9d9d9; + border-radius: 3px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); +} +.tox .tox-toolbar__group { + align-items: center; + display: flex; + flex-wrap: wrap; + margin: 0 0; + padding: 0 4px 0 4px; +} +.tox .tox-toolbar__group--pull-right { + margin-left: auto; +} +.tox .tox-toolbar--scrolling .tox-toolbar__group { + flex-shrink: 0; + flex-wrap: nowrap; +} +.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type) { + border-right: 1px solid #d9d9d9; +} +.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type) { + border-left: 1px solid #d9d9d9; +} +.tox .tox-tooltip { + display: inline-block; + padding: 5px; + position: relative; +} +.tox .tox-tooltip__body { + background-color: rgba(84, 111, 94, 0.85); + border-radius: 3px; + box-shadow: 0 2px 4px rgba(84, 111, 94, 0.3); + color: rgba(255, 255, 255, 0.75); + font-size: 8.75px; + font-style: normal; + font-weight: normal; + padding: 2.5px 5px; + text-transform: none; +} +.tox .tox-tooltip__arrow { + position: absolute; +} +.tox .tox-tooltip--down .tox-tooltip__arrow { + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid rgba(84, 111, 94, 0.85); + bottom: 0; + left: 50%; + position: absolute; + transform: translateX(-50%); +} +.tox .tox-tooltip--up .tox-tooltip__arrow { + border-bottom: 5px solid rgba(84, 111, 94, 0.85); + border-left: 5px solid transparent; + border-right: 5px solid transparent; + left: 50%; + position: absolute; + top: 0; + transform: translateX(-50%); +} +.tox .tox-tooltip--right .tox-tooltip__arrow { + border-bottom: 5px solid transparent; + border-left: 5px solid rgba(84, 111, 94, 0.85); + border-top: 5px solid transparent; + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); +} +.tox .tox-tooltip--left .tox-tooltip__arrow { + border-bottom: 5px solid transparent; + border-right: 5px solid rgba(84, 111, 94, 0.85); + border-top: 5px solid transparent; + left: 0; + position: absolute; + top: 50%; + transform: translateY(-50%); +} +.tox .tox-well { + border: 1px solid #d9d9d9; + border-radius: 3px; + padding: 5px; + width: 100%; +} +.tox .tox-well > *:first-child { + margin-top: 0; +} +.tox .tox-well > *:last-child { + margin-bottom: 0; +} +.tox .tox-well > *:only-child { + margin: 0; +} +.tox .tox-custom-editor { + border: 1px solid #d9d9d9; + border-radius: 3px; + display: flex; + flex: 1; + position: relative; +} +/* stylelint-disable */ +.tox { + /* stylelint-enable */ +} +.tox .tox-dialog-loading::before { + background-color: rgba(0, 0, 0, 0.5); + content: ""; + height: 100%; + position: absolute; + width: 100%; + z-index: 1000; +} +.tox .tox-tab { + cursor: pointer; +} +.tox .tox-dialog__content-js { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-dialog__body-content .tox-collection { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-image-tools-edit-panel { + height: 60px; +} +.tox .tox-image-tools__sidebar { + height: 60px; +} diff --git a/public/resource/tinymce/skins/ui/jeecg/skin.min.css b/public/resource/tinymce/skins/ui/jeecg/skin.min.css new file mode 100644 index 0000000..c86e0c1 --- /dev/null +++ b/public/resource/tinymce/skins/ui/jeecg/skin.min.css @@ -0,0 +1,7 @@ +/** +* Copyright (c) Tiny Technologies, Inc. All rights reserved. +* Licensed under the LGPL or a commercial license. +* For LGPL see License.txt in the project root for license information. +* For commercial licenses see https://www.tiny.cloud/ +*/ +.tox{box-shadow:none;box-sizing:content-box;color:rgba(84,111,94,.85);cursor:auto;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:10px;font-style:normal;font-weight:400;line-height:normal;-webkit-tap-highlight-color:transparent;text-decoration:none;text-shadow:none;text-transform:none;vertical-align:initial;white-space:normal}.tox :not(svg):not(rect){box-sizing:inherit;color:inherit;cursor:inherit;direction:inherit;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;line-height:inherit;-webkit-tap-highlight-color:inherit;text-align:inherit;text-decoration:inherit;text-shadow:inherit;text-transform:inherit;vertical-align:inherit;white-space:inherit}.tox :not(svg):not(rect){background:0 0;border:0;box-shadow:none;float:none;height:auto;margin:0;max-width:none;outline:0;padding:0;position:static;width:auto}.tox:not([dir=rtl]){direction:ltr;text-align:left}.tox[dir=rtl]{direction:rtl;text-align:right}.tox-tinymce{border:1px solid #d9d9d9;border-radius:0;box-shadow:none;box-sizing:border-box;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;overflow:hidden;position:relative;visibility:inherit!important}.tox-tinymce-inline{border:none;box-shadow:none}.tox-tinymce-inline .tox-editor-header{background-color:transparent;border:1px solid #d9d9d9;border-radius:0;box-shadow:none}.tox-tinymce-aux{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;z-index:1300}.tox-tinymce :focus,.tox-tinymce-aux :focus{outline:0}button::-moz-focus-inner{border:0}.tox[dir=rtl] .tox-icon--flip svg{transform:rotateY(180deg)}.tox .accessibility-issue__header{align-items:center;display:flex;margin-bottom:2.5px}.tox .accessibility-issue__description{align-items:stretch;border:1px solid #d9d9d9;border-radius:3px;display:flex;justify-content:space-between}.tox .accessibility-issue__description>div{padding-bottom:2.5px}.tox .accessibility-issue__description>div>div{align-items:center;display:flex;margin-bottom:2.5px}.tox .accessibility-issue__description>:last-child:not(:only-child){border-color:#d9d9d9;border-style:solid}.tox .accessibility-issue__repair{margin-top:16px}.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description{background-color:rgba(10,143,233,.1);border-color:rgba(10,143,233,.4);color:rgba(84,111,94,.85)}.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description>:last-child{border-color:rgba(10,143,233,.4)}.tox .tox-dialog__body-content .accessibility-issue--info .tox-form__group h2{color:#0a8fe9}.tox .tox-dialog__body-content .accessibility-issue--info .tox-icon svg{fill:#0a8fe9}.tox .tox-dialog__body-content .accessibility-issue--info a .tox-icon{color:#0a8fe9}.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description{background-color:rgba(255,165,0,.1);border-color:rgba(255,165,0,.5);color:rgba(84,111,94,.85)}.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description>:last-child{border-color:rgba(255,165,0,.5)}.tox .tox-dialog__body-content .accessibility-issue--warn .tox-form__group h2{color:#cc8500}.tox .tox-dialog__body-content .accessibility-issue--warn .tox-icon svg{fill:#cc8500}.tox .tox-dialog__body-content .accessibility-issue--warn a .tox-icon{color:#cc8500}.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description{background-color:rgba(204,0,0,.1);border-color:rgba(204,0,0,.4);color:rgba(84,111,94,.85)}.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description>:last-child{border-color:rgba(204,0,0,.4)}.tox .tox-dialog__body-content .accessibility-issue--error .tox-form__group h2{color:#c00}.tox .tox-dialog__body-content .accessibility-issue--error .tox-icon svg{fill:#c00}.tox .tox-dialog__body-content .accessibility-issue--error a .tox-icon{color:#c00}.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description{background-color:rgba(120,171,70,.1);border-color:rgba(120,171,70,.4);color:rgba(84,111,94,.85)}.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description>:last-child{border-color:rgba(120,171,70,.4)}.tox .tox-dialog__body-content .accessibility-issue--success .tox-form__group h2{color:#78ab46}.tox .tox-dialog__body-content .accessibility-issue--success .tox-icon svg{fill:#78ab46}.tox .tox-dialog__body-content .accessibility-issue--success a .tox-icon{color:#78ab46}.tox .tox-dialog__body-content .accessibility-issue__header h1,.tox .tox-dialog__body-content .tox-form__group .accessibility-issue__description h2{margin-top:0}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header .tox-button{margin-left:2.5px}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header>:nth-last-child(2){margin-left:auto}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description{padding:2.5px 2.5px 2.5px 5px}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description>:last-child{border-left-width:1px;padding-left:2.5px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header .tox-button{margin-right:2.5px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header>:nth-last-child(2){margin-right:auto}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description{padding:2.5px 5px 2.5px 2.5px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description>:last-child{border-right-width:1px;padding-right:2.5px}.tox .tox-anchorbar{display:flex;flex:0 0 auto}.tox .tox-bar{display:flex;flex:0 0 auto}.tox .tox-button{background-color:#0a8fe9;background-image:none;background-position:0 0;background-repeat:repeat;border-color:#0a8fe9;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#fff;cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:8.75px;font-style:normal;font-weight:400;letter-spacing:normal;line-height:24px;margin:0;outline:0;padding:2.5px 10px;text-align:center;text-decoration:none;text-transform:none;white-space:nowrap}.tox .tox-button[disabled]{background-color:#0a8fe9;background-image:none;border-color:#0a8fe9;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-button:focus:not(:disabled){background-color:#0980d1;background-image:none;border-color:#0980d1;box-shadow:none;color:#fff}.tox .tox-button:hover:not(:disabled){background-color:#0980d1;background-image:none;border-color:#0980d1;box-shadow:none;color:#fff}.tox .tox-button:active:not(:disabled){background-color:#0871b8;background-image:none;border-color:#0871b8;box-shadow:none;color:#fff}.tox .tox-button--secondary{background-color:#f0f0f0;background-image:none;background-position:0 0;background-repeat:repeat;border-color:#f0f0f0;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;color:rgba(84,111,94,.85);font-size:8.75px;font-style:normal;font-weight:400;letter-spacing:normal;outline:0;padding:2.5px 10px;text-decoration:none;text-transform:none}.tox .tox-button--secondary[disabled]{background-color:#f0f0f0;background-image:none;border-color:#f0f0f0;box-shadow:none;color:rgba(84,111,94,.5)}.tox .tox-button--secondary:focus:not(:disabled){background-color:#e3e3e3;background-image:none;border-color:#e3e3e3;box-shadow:none;color:rgba(84,111,94,.85)}.tox .tox-button--secondary:hover:not(:disabled){background-color:#e3e3e3;background-image:none;border-color:#e3e3e3;box-shadow:none;color:rgba(84,111,94,.85)}.tox .tox-button--secondary:active:not(:disabled){background-color:#d6d6d6;background-image:none;border-color:#d6d6d6;box-shadow:none;color:rgba(84,111,94,.85)}.tox .tox-button--icon,.tox .tox-button.tox-button--icon,.tox .tox-button.tox-button--secondary.tox-button--icon{padding:2.5px}.tox .tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg{display:block;fill:currentColor}.tox .tox-button-link{background:0;border:none;box-sizing:border-box;cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:10px;font-weight:400;line-height:1.3;margin:0;padding:0;white-space:nowrap}.tox .tox-button-link--sm{font-size:8.75px}.tox .tox-button--naked{background-color:transparent;border-color:transparent;box-shadow:unset;color:rgba(84,111,94,.85)}.tox .tox-button--naked[disabled]{background-color:#f0f0f0;border-color:#f0f0f0;box-shadow:none;color:rgba(84,111,94,.5)}.tox .tox-button--naked:hover:not(:disabled){background-color:#e3e3e3;border-color:#e3e3e3;box-shadow:none;color:rgba(84,111,94,.85)}.tox .tox-button--naked:focus:not(:disabled){background-color:#e3e3e3;border-color:#e3e3e3;box-shadow:none;color:rgba(84,111,94,.85)}.tox .tox-button--naked:active:not(:disabled){background-color:#d6d6d6;border-color:#d6d6d6;box-shadow:none;color:rgba(84,111,94,.85)}.tox .tox-button--naked .tox-icon svg{fill:currentColor}.tox .tox-button--naked.tox-button--icon:hover:not(:disabled){color:rgba(84,111,94,.85)}.tox .tox-checkbox{align-items:center;border-radius:3px;cursor:pointer;display:flex;height:36px;min-width:36px}.tox .tox-checkbox__input{height:1px;overflow:hidden;position:absolute;top:auto;width:1px}.tox .tox-checkbox__icons{align-items:center;border-radius:3px;box-shadow:0 0 0 2px transparent;box-sizing:content-box;display:flex;height:24px;justify-content:center;padding:calc(2.5px - 1px);width:24px}.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:block;fill:rgba(84,111,94,.3)}.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display:none;fill:#0a8fe9}.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg{display:none;fill:#0a8fe9}.tox .tox-checkbox--disabled{color:rgba(84,111,94,.5);cursor:not-allowed}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__checked svg{fill:rgba(84,111,94,.5)}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{fill:rgba(84,111,94,.5)}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{fill:rgba(84,111,94,.5)}.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:none}.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__checked svg{display:block}.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:none}.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display:block}.tox input.tox-checkbox__input:focus+.tox-checkbox__icons{border-radius:3px;box-shadow:inset 0 0 0 1px #0a8fe9;padding:calc(2.5px - 1px)}.tox:not([dir=rtl]) .tox-checkbox__label{margin-left:2.5px}.tox:not([dir=rtl]) .tox-checkbox__input{left:-10000px}.tox:not([dir=rtl]) .tox-bar .tox-checkbox{margin-left:2.5px}.tox[dir=rtl] .tox-checkbox__label{margin-right:2.5px}.tox[dir=rtl] .tox-checkbox__input{right:-10000px}.tox[dir=rtl] .tox-bar .tox-checkbox{margin-right:2.5px}.tox .tox-collection--toolbar .tox-collection__group{display:flex;padding:0}.tox .tox-collection--grid .tox-collection__group{display:flex;flex-wrap:wrap;max-height:208px;overflow-x:hidden;overflow-y:auto;padding:0}.tox .tox-collection--list .tox-collection__group{border-bottom-width:0;border-color:#d9d9d9;border-left-width:0;border-right-width:0;border-style:solid;border-top-width:1px;padding:2.5px 0}.tox .tox-collection--list .tox-collection__group:first-child{border-top-width:0}.tox .tox-collection__group-heading{background-color:#f3f3f3;color:rgba(84,111,94,.7);cursor:default;font-size:12px;font-style:normal;font-weight:400;margin-bottom:2.5px;margin-top:-2.5px;padding:2.5px 5px;text-transform:none;-webkit-touch-callout:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.tox .tox-collection__item{align-items:center;color:rgba(84,111,94,.85);cursor:pointer;display:flex;-webkit-touch-callout:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.tox .tox-collection--list .tox-collection__item{padding:2.5px 5px}.tox .tox-collection--toolbar .tox-collection__item{border-radius:3px;padding:2.5px}.tox .tox-collection--grid .tox-collection__item{border-radius:3px;padding:2.5px}.tox .tox-collection--list .tox-collection__item--enabled{background-color:#fff;color:rgba(84,111,94,.85)}.tox .tox-collection--list .tox-collection__item--active{background-color:#e5e9e7}.tox .tox-collection--toolbar .tox-collection__item--enabled{background-color:#e5e9e7;color:rgba(84,111,94,.85)}.tox .tox-collection--toolbar .tox-collection__item--active{background-color:#e5e9e7}.tox .tox-collection--grid .tox-collection__item--enabled{background-color:#e5e9e7;color:rgba(84,111,94,.85)}.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled){background-color:#e5e9e7;color:rgba(84,111,94,.85)}.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled){color:rgba(84,111,94,.85)}.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled){color:rgba(84,111,94,.85)}.tox .tox-collection__item-checkmark,.tox .tox-collection__item-icon{align-items:center;display:flex;height:24px;justify-content:center;width:24px}.tox .tox-collection__item-checkmark svg,.tox .tox-collection__item-icon svg{fill:currentColor}.tox .tox-collection--toolbar-lg .tox-collection__item-icon{height:48px;width:48px}.tox .tox-collection__item-label{color:currentColor;display:inline-block;flex:1;-ms-flex-preferred-size:auto;font-size:8.75px;font-style:normal;font-weight:400;line-height:24px;text-transform:none;word-break:break-all}.tox .tox-collection__item-accessory{color:rgba(84,111,94,.7);display:inline-block;font-size:8.75px;height:24px;line-height:24px;text-transform:none}.tox .tox-collection__item-caret{align-items:center;display:flex;min-height:24px}.tox .tox-collection__item-caret::after{content:'';font-size:0;min-height:inherit}.tox .tox-collection__item-caret svg{fill:rgba(84,111,94,.85)}.tox .tox-collection__item--state-disabled{background-color:transparent;color:rgba(84,111,94,.5);cursor:not-allowed}.tox .tox-collection__item--state-disabled .tox-collection__item-caret svg{fill:rgba(84,111,94,.5)}.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg{display:none}.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-accessory+.tox-collection__item-checkmark{display:none}.tox .tox-collection--horizontal{background-color:#fff;border:1px solid #d9d9d9;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.15);display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:nowrap;margin-bottom:0;overflow-x:auto;padding:0}.tox .tox-collection--horizontal .tox-collection__group{align-items:center;display:flex;flex-wrap:nowrap;margin:0;padding:0 2.5px}.tox .tox-collection--horizontal .tox-collection__item{height:34px;margin:2px 0 3px 0;padding:0 4px}.tox .tox-collection--horizontal .tox-collection__item-label{white-space:nowrap}.tox .tox-collection--horizontal .tox-collection__item-caret{margin-left:4px}.tox .tox-collection__item-container{display:flex}.tox .tox-collection__item-container--row{align-items:center;flex:1 1 auto;flex-direction:row}.tox .tox-collection__item-container--row.tox-collection__item-container--align-left{margin-right:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--align-right{justify-content:flex-end;margin-left:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-top{align-items:flex-start;margin-bottom:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-middle{align-items:center}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-bottom{align-items:flex-end;margin-top:auto}.tox .tox-collection__item-container--column{-ms-grid-row-align:center;align-self:center;flex:1 1 auto;flex-direction:column}.tox .tox-collection__item-container--column.tox-collection__item-container--align-left{align-items:flex-start}.tox .tox-collection__item-container--column.tox-collection__item-container--align-right{align-items:flex-end}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-top{align-self:flex-start}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-middle{-ms-grid-row-align:center;align-self:center}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-bottom{align-self:flex-end}.tox:not([dir=rtl]) .tox-collection--horizontal .tox-collection__group:not(:last-of-type){border-right:1px solid #d9d9d9}.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>:not(:first-child){margin-left:5px}.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>.tox-collection__item-label:first-child{margin-left:2.5px}.tox:not([dir=rtl]) .tox-collection__item-accessory{margin-left:10px;text-align:right}.tox:not([dir=rtl]) .tox-collection .tox-collection__item-caret{margin-left:10px}.tox[dir=rtl] .tox-collection--horizontal .tox-collection__group:not(:last-of-type){border-left:1px solid #d9d9d9}.tox[dir=rtl] .tox-collection--list .tox-collection__item>:not(:first-child){margin-right:5px}.tox[dir=rtl] .tox-collection--list .tox-collection__item>.tox-collection__item-label:first-child{margin-right:2.5px}.tox[dir=rtl] .tox-collection__item-accessory{margin-right:10px;text-align:left}.tox[dir=rtl] .tox-collection .tox-collection__item-caret{margin-right:10px;transform:rotateY(180deg)}.tox[dir=rtl] .tox-collection--horizontal .tox-collection__item-caret{margin-right:4px}.tox .tox-color-picker-container{display:flex;flex-direction:row;height:225px;margin:0}.tox .tox-sv-palette{box-sizing:border-box;display:flex;height:100%}.tox .tox-sv-palette-spectrum{height:100%}.tox .tox-sv-palette,.tox .tox-sv-palette-spectrum{width:225px}.tox .tox-sv-palette-thumb{background:0 0;border:1px solid #000;border-radius:50%;box-sizing:content-box;height:12px;position:absolute;width:12px}.tox .tox-sv-palette-inner-thumb{border:1px solid #fff;border-radius:50%;height:10px;position:absolute;width:10px}.tox .tox-hue-slider{box-sizing:border-box;height:100%;width:25px}.tox .tox-hue-slider-spectrum{background:linear-gradient(to bottom,red,#ff0080,#f0f,#8000ff,#00f,#0080ff,#0ff,#00ff80,#0f0,#80ff00,#ff0,#ff8000,red);height:100%;width:100%}.tox .tox-hue-slider,.tox .tox-hue-slider-spectrum{width:20px}.tox .tox-hue-slider-thumb{background:#fff;border:1px solid #000;box-sizing:content-box;height:4px;width:100%}.tox .tox-rgb-form{display:flex;flex-direction:column;justify-content:space-between}.tox .tox-rgb-form div{align-items:center;display:flex;justify-content:space-between;margin-bottom:5px;width:inherit}.tox .tox-rgb-form input{width:6em}.tox .tox-rgb-form input.tox-invalid{border:1px solid red!important}.tox .tox-rgb-form .tox-rgba-preview{border:1px solid #000;flex-grow:2;margin-bottom:0}.tox:not([dir=rtl]) .tox-sv-palette{margin-right:15px}.tox:not([dir=rtl]) .tox-hue-slider{margin-right:15px}.tox:not([dir=rtl]) .tox-hue-slider-thumb{margin-left:-1px}.tox:not([dir=rtl]) .tox-rgb-form label{margin-right:.5em}.tox[dir=rtl] .tox-sv-palette{margin-left:15px}.tox[dir=rtl] .tox-hue-slider{margin-left:15px}.tox[dir=rtl] .tox-hue-slider-thumb{margin-right:-1px}.tox[dir=rtl] .tox-rgb-form label{margin-left:.5em}.tox .tox-toolbar .tox-swatches,.tox .tox-toolbar__overflow .tox-swatches,.tox .tox-toolbar__primary .tox-swatches{margin:2px 0 3px 4px}.tox .tox-collection--list .tox-collection__group .tox-swatches-menu{border:0;margin:-2.5px 0}.tox .tox-swatches__row{display:flex}.tox .tox-swatch{height:30px;transition:transform .15s,box-shadow .15s;width:30px}.tox .tox-swatch:focus,.tox .tox-swatch:hover{box-shadow:0 0 0 1px rgba(127,127,127,.3) inset;transform:scale(.8)}.tox .tox-swatch--remove{align-items:center;display:flex;justify-content:center}.tox .tox-swatch--remove svg path{stroke:#e74c3c}.tox .tox-swatches__picker-btn{align-items:center;background-color:transparent;border:0;cursor:pointer;display:flex;height:30px;justify-content:center;outline:0;padding:0;width:30px}.tox .tox-swatches__picker-btn svg{height:24px;width:24px}.tox .tox-swatches__picker-btn:hover{background:#e5e9e7}.tox:not([dir=rtl]) .tox-swatches__picker-btn{margin-left:auto}.tox[dir=rtl] .tox-swatches__picker-btn{margin-right:auto}.tox .tox-comment-thread{background:#fff;position:relative}.tox .tox-comment-thread>:not(:first-child){margin-top:5px}.tox .tox-comment{background:#fff;border:1px solid #d9d9d9;border-radius:3px;box-shadow:0 4px 8px 0 rgba(84,111,94,.1);padding:5px 5px 10px 5px;position:relative}.tox .tox-comment__header{align-items:center;color:rgba(84,111,94,.85);display:flex;justify-content:space-between}.tox .tox-comment__date{color:rgba(84,111,94,.7);font-size:12px}.tox .tox-comment__body{color:rgba(84,111,94,.85);font-size:8.75px;font-style:normal;font-weight:400;line-height:1.3;margin-top:5px;position:relative;text-transform:initial}.tox .tox-comment__body textarea{resize:none;white-space:normal;width:100%}.tox .tox-comment__expander{padding-top:5px}.tox .tox-comment__expander p{color:rgba(84,111,94,.7);font-size:8.75px;font-style:normal}.tox .tox-comment__body p{margin:0}.tox .tox-comment__buttonspacing{padding-top:10px;text-align:center}.tox .tox-comment-thread__overlay::after{background:#fff;bottom:0;content:"";display:flex;left:0;opacity:.9;position:absolute;right:0;top:0;z-index:5}.tox .tox-comment__reply{display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end;margin-top:5px}.tox .tox-comment__reply>:first-child{margin-bottom:5px;width:100%}.tox .tox-comment__edit{display:flex;flex-wrap:wrap;justify-content:flex-end;margin-top:10px}.tox .tox-comment__gradient::after{background:linear-gradient(rgba(255,255,255,0),#fff);bottom:0;content:"";display:block;height:5em;margin-top:-40px;position:absolute;width:100%}.tox .tox-comment__overlay{background:#fff;bottom:0;display:flex;flex-direction:column;flex-grow:1;left:0;opacity:.9;position:absolute;right:0;text-align:center;top:0;z-index:5}.tox .tox-comment__loading-text{align-items:center;color:rgba(84,111,94,.85);display:flex;flex-direction:column;position:relative}.tox .tox-comment__loading-text>div{padding-bottom:10px}.tox .tox-comment__overlaytext{bottom:0;flex-direction:column;font-size:8.75px;left:0;padding:1em;position:absolute;right:0;top:0;z-index:10}.tox .tox-comment__overlaytext p{background-color:#fff;box-shadow:0 0 8px 8px #fff;color:rgba(84,111,94,.85);text-align:center}.tox .tox-comment__overlaytext div:nth-of-type(2){font-size:.8em}.tox .tox-comment__busy-spinner{align-items:center;background-color:#fff;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:20}.tox .tox-comment__scroll{display:flex;flex-direction:column;flex-shrink:1;overflow:auto}.tox .tox-conversations{margin:5px}.tox:not([dir=rtl]) .tox-comment__edit{margin-left:5px}.tox:not([dir=rtl]) .tox-comment__buttonspacing>:last-child,.tox:not([dir=rtl]) .tox-comment__edit>:last-child,.tox:not([dir=rtl]) .tox-comment__reply>:last-child{margin-left:5px}.tox[dir=rtl] .tox-comment__edit{margin-right:5px}.tox[dir=rtl] .tox-comment__buttonspacing>:last-child,.tox[dir=rtl] .tox-comment__edit>:last-child,.tox[dir=rtl] .tox-comment__reply>:last-child{margin-right:5px}.tox .tox-user{align-items:center;display:flex}.tox .tox-user__avatar svg{fill:rgba(84,111,94,.7)}.tox .tox-user__name{color:rgba(84,111,94,.7);font-size:12px;font-style:normal;font-weight:400;text-transform:uppercase}.tox:not([dir=rtl]) .tox-user__avatar svg{margin-right:5px}.tox:not([dir=rtl]) .tox-user__avatar+.tox-user__name{margin-left:5px}.tox[dir=rtl] .tox-user__avatar svg{margin-left:5px}.tox[dir=rtl] .tox-user__avatar+.tox-user__name{margin-right:5px}.tox .tox-dialog-wrap{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:fixed;right:0;top:0;z-index:1100}.tox .tox-dialog-wrap__backdrop{background-color:rgba(255,255,255,.75);bottom:0;left:0;position:absolute;right:0;top:0;z-index:1}.tox .tox-dialog-wrap__backdrop--opaque{background-color:#fff}.tox .tox-dialog{background-color:#fff;border-color:#d9d9d9;border-radius:3px;border-style:solid;border-width:1px;box-shadow:0 16px 16px -10px rgba(84,111,94,.15),0 0 40px 1px rgba(84,111,94,.15);display:flex;flex-direction:column;max-height:100%;max-width:480px;overflow:hidden;position:relative;width:95vw;z-index:2}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog{align-self:flex-start;margin:5px auto;width:calc(100vw - 10px)}}.tox .tox-dialog-inline{z-index:1100}.tox .tox-dialog__header{align-items:center;background-color:#fff;border-bottom:none;color:rgba(84,111,94,.85);display:flex;font-size:10px;justify-content:space-between;padding:5px 10px 0 10px;position:relative}.tox .tox-dialog__header .tox-button{z-index:1}.tox .tox-dialog__draghandle{cursor:grab;height:100%;left:0;position:absolute;top:0;width:100%}.tox .tox-dialog__draghandle:active{cursor:grabbing}.tox .tox-dialog__dismiss{margin-left:auto}.tox .tox-dialog__title{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:12.5px;font-style:normal;font-weight:400;line-height:1.3;margin:0;text-transform:none}.tox .tox-dialog__body{color:rgba(84,111,94,.85);display:flex;flex:1;-ms-flex-preferred-size:auto;font-size:10px;font-style:normal;font-weight:400;line-height:1.3;min-width:0;text-align:left;text-transform:none}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog__body{flex-direction:column}}.tox .tox-dialog__body-nav{align-items:flex-start;display:flex;flex-direction:column;padding:10px 10px}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog__body-nav{flex-direction:row;-webkit-overflow-scrolling:touch;overflow-x:auto;padding-bottom:0}}.tox .tox-dialog__body-nav-item{border-bottom:2px solid transparent;color:rgba(84,111,94,.7);display:inline-block;font-size:8.75px;line-height:1.3;margin-bottom:5px;text-decoration:none;white-space:nowrap}.tox .tox-dialog__body-nav-item:focus{background-color:rgba(10,143,233,.1)}.tox .tox-dialog__body-nav-item--active{border-bottom:2px solid #0a8fe9;color:#0a8fe9}.tox .tox-dialog__body-content{box-sizing:border-box;display:flex;flex:1;flex-direction:column;-ms-flex-preferred-size:auto;max-height:650px;overflow:auto;-webkit-overflow-scrolling:touch;padding:10px 10px}.tox .tox-dialog__body-content>*{margin-bottom:0;margin-top:10px}.tox .tox-dialog__body-content>:first-child{margin-top:0}.tox .tox-dialog__body-content>:last-child{margin-bottom:0}.tox .tox-dialog__body-content>:only-child{margin-bottom:0;margin-top:0}.tox .tox-dialog__body-content a{color:#0a8fe9;cursor:pointer;text-decoration:none}.tox .tox-dialog__body-content a:focus,.tox .tox-dialog__body-content a:hover{color:#0871b8;text-decoration:none}.tox .tox-dialog__body-content a:active{color:#0871b8;text-decoration:none}.tox .tox-dialog__body-content svg{fill:rgba(84,111,94,.85)}.tox .tox-dialog__body-content ul{display:block;list-style-type:disc;margin-bottom:10px;-webkit-margin-end:0;margin-inline-end:0;-webkit-margin-start:0;margin-inline-start:0;-webkit-padding-start:2.5rem;padding-inline-start:2.5rem}.tox .tox-dialog__body-content .tox-form__group h1{color:rgba(84,111,94,.85);font-size:12.5px;font-style:normal;font-weight:400;letter-spacing:normal;margin-bottom:10px;margin-top:2rem;text-transform:none}.tox .tox-dialog__body-content .tox-form__group h2{color:rgba(84,111,94,.85);font-size:10px;font-style:normal;font-weight:400;letter-spacing:normal;margin-bottom:10px;margin-top:2rem;text-transform:none}.tox .tox-dialog__body-content .tox-form__group p{margin-bottom:10px}.tox .tox-dialog__body-content .tox-form__group h1:first-child,.tox .tox-dialog__body-content .tox-form__group h2:first-child,.tox .tox-dialog__body-content .tox-form__group p:first-child{margin-top:0}.tox .tox-dialog__body-content .tox-form__group h1:last-child,.tox .tox-dialog__body-content .tox-form__group h2:last-child,.tox .tox-dialog__body-content .tox-form__group p:last-child{margin-bottom:0}.tox .tox-dialog__body-content .tox-form__group h1:only-child,.tox .tox-dialog__body-content .tox-form__group h2:only-child,.tox .tox-dialog__body-content .tox-form__group p:only-child{margin-bottom:0;margin-top:0}.tox .tox-dialog--width-lg{height:650px;max-width:1200px}.tox .tox-dialog--width-md{max-width:800px}.tox .tox-dialog--width-md .tox-dialog__body-content{overflow:auto}.tox .tox-dialog__body-content--centered{text-align:center}.tox .tox-dialog__footer{align-items:center;background-color:#fff;border-top:1px solid #d9d9d9;display:flex;justify-content:space-between;padding:5px 10px}.tox .tox-dialog__footer-end,.tox .tox-dialog__footer-start{display:flex}.tox .tox-dialog__busy-spinner{align-items:center;background-color:rgba(255,255,255,.75);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:3}.tox .tox-dialog__table{border-collapse:collapse;width:100%}.tox .tox-dialog__table thead th{font-weight:400;padding-bottom:5px}.tox .tox-dialog__table tbody tr{border-bottom:1px solid #d9d9d9}.tox .tox-dialog__table tbody tr:last-child{border-bottom:none}.tox .tox-dialog__table td{padding-bottom:5px;padding-top:5px}.tox .tox-dialog__popups{position:absolute;width:100%;z-index:1100}.tox .tox-dialog__body-iframe{display:flex;flex:1;flex-direction:column;-ms-flex-preferred-size:auto}.tox .tox-dialog__body-iframe .tox-navobj{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2){flex:1;-ms-flex-preferred-size:auto;height:100%}.tox .tox-dialog-dock-fadeout{opacity:0;visibility:hidden}.tox .tox-dialog-dock-fadein{opacity:1;visibility:visible}.tox .tox-dialog-dock-transition{transition:visibility 0s linear .3s,opacity .3s ease}.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein{transition-delay:0s}.tox.tox-platform-ie .tox-dialog-wrap{position:-ms-device-fixed}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav{margin-right:0}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child){margin-left:5px}}.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end>*,.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start>*{margin-left:5px}.tox[dir=rtl] .tox-dialog__body{text-align:right}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav{margin-left:0}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child){margin-right:5px}}.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end>*,.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start>*{margin-right:5px}body.tox-dialog__disable-scroll{overflow:hidden}.tox .tox-dropzone-container{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-dropzone{align-items:center;background:#fff;border:2px dashed #d9d9d9;box-sizing:border-box;display:flex;flex-direction:column;flex-grow:1;justify-content:center;min-height:100px;padding:10px}.tox .tox-dropzone p{color:rgba(84,111,94,.7);margin:0 0 10px 0}.tox .tox-edit-area{display:flex;flex:1;-ms-flex-preferred-size:auto;overflow:hidden;position:relative}.tox .tox-edit-area__iframe{background-color:#fff;border:0;box-sizing:border-box;flex:1;-ms-flex-preferred-size:auto;height:100%;position:absolute;width:100%}.tox.tox-inline-edit-area{border:1px dotted #d9d9d9}.tox .tox-editor-container{display:flex;flex:1 1 auto;flex-direction:column;overflow:hidden}.tox .tox-editor-header{z-index:1}.tox:not(.tox-tinymce-inline) .tox-editor-header{box-shadow:none;transition:box-shadow .5s}.tox.tox-tinymce--toolbar-bottom .tox-editor-header,.tox.tox-tinymce-inline .tox-editor-header{margin-bottom:-1px}.tox.tox-tinymce--toolbar-sticky-on .tox-editor-header{background-color:transparent;box-shadow:0 4px 4px -3px rgba(0,0,0,.25)}.tox-editor-dock-fadeout{opacity:0;visibility:hidden}.tox-editor-dock-fadein{opacity:1;visibility:visible}.tox-editor-dock-transition{transition:visibility 0s linear .25s,opacity .25s ease}.tox-editor-dock-transition.tox-editor-dock-fadein{transition-delay:0s}.tox .tox-control-wrap{flex:1;position:relative}.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid,.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown,.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid{display:none}.tox .tox-control-wrap svg{display:block}.tox .tox-control-wrap__status-icon-wrap{position:absolute;top:50%;transform:translateY(-50%)}.tox .tox-control-wrap__status-icon-invalid svg{fill:#c00}.tox .tox-control-wrap__status-icon-unknown svg{fill:orange}.tox .tox-control-wrap__status-icon-valid svg{fill:green}.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield{padding-right:20px}.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap{right:2.5px}.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield{padding-left:20px}.tox[dir=rtl] .tox-control-wrap__status-icon-wrap{left:2.5px}.tox .tox-autocompleter{max-width:25em}.tox .tox-autocompleter .tox-menu{max-width:25em}.tox .tox-autocompleter .tox-autocompleter-highlight{font-weight:400}.tox .tox-color-input{display:flex;position:relative;z-index:1}.tox .tox-color-input .tox-textfield{z-index:-1}.tox .tox-color-input span{border-color:rgba(84,111,94,.2);border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;height:24px;position:absolute;top:6px;width:24px}.tox .tox-color-input span:focus:not([aria-disabled=true]),.tox .tox-color-input span:hover:not([aria-disabled=true]){border-color:#0a8fe9;cursor:pointer}.tox .tox-color-input span::before{background-image:linear-gradient(45deg,rgba(0,0,0,.25) 25%,transparent 25%),linear-gradient(-45deg,rgba(0,0,0,.25) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(0,0,0,.25) 75%),linear-gradient(-45deg,transparent 75%,rgba(0,0,0,.25) 75%);background-position:0 0,0 6px,6px -6px,-6px 0;background-size:12px 12px;border:1px solid #fff;border-radius:3px;box-sizing:border-box;content:'';height:24px;left:-1px;position:absolute;top:-1px;width:24px;z-index:-1}.tox .tox-color-input span[aria-disabled=true]{cursor:not-allowed}.tox:not([dir=rtl]) .tox-color-input .tox-textfield{padding-left:36px}.tox:not([dir=rtl]) .tox-color-input span{left:6px}.tox[dir=rtl] .tox-color-input .tox-textfield{padding-right:36px}.tox[dir=rtl] .tox-color-input span{right:6px}.tox .tox-label,.tox .tox-toolbar-label{color:rgba(84,111,94,.7);display:block;font-size:8.75px;font-style:normal;font-weight:400;line-height:1.3;padding:0 5px 0 0;text-transform:none;white-space:nowrap}.tox .tox-toolbar-label{padding:0 5px}.tox[dir=rtl] .tox-label{padding:0 0 0 5px}.tox .tox-form{display:flex;flex:1;flex-direction:column;-ms-flex-preferred-size:auto}.tox .tox-form__group{box-sizing:border-box;margin-bottom:2.5px}.tox .tox-form-group--maximize{flex:1}.tox .tox-form__group--error{color:#c00}.tox .tox-form__group--collection{display:flex}.tox .tox-form__grid{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between}.tox .tox-form__grid--2col>.tox-form__group{width:calc(50% - (5px / 2))}.tox .tox-form__grid--3col>.tox-form__group{width:calc(100% / 3 - (5px / 2))}.tox .tox-form__grid--4col>.tox-form__group{width:calc(25% - (5px / 2))}.tox .tox-form__controls-h-stack{align-items:center;display:flex}.tox .tox-form__group--inline{align-items:center;display:flex}.tox .tox-form__group--stretched{display:flex;flex:1;flex-direction:column;-ms-flex-preferred-size:auto}.tox .tox-form__group--stretched .tox-textarea{flex:1;-ms-flex-preferred-size:auto}.tox .tox-form__group--stretched .tox-navobj{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-form__group--stretched .tox-navobj :nth-child(2){flex:1;-ms-flex-preferred-size:auto;height:100%}.tox:not([dir=rtl]) .tox-form__controls-h-stack>:not(:first-child){margin-left:2.5px}.tox[dir=rtl] .tox-form__controls-h-stack>:not(:first-child){margin-right:2.5px}.tox .tox-lock.tox-locked .tox-lock-icon__unlock,.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock{display:none}.tox .tox-listboxfield .tox-listbox--select,.tox .tox-textarea,.tox .tox-textfield,.tox .tox-toolbar-textfield{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#d9d9d9;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:rgba(84,111,94,.85);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:10px;line-height:24px;margin:0;min-height:34px;outline:0;padding:5px 3.25px;resize:none;width:100%}.tox .tox-textarea[disabled],.tox .tox-textfield[disabled]{background-color:#f2f2f2;color:rgba(84,111,94,.85);cursor:not-allowed}.tox .tox-listboxfield .tox-listbox--select:focus,.tox .tox-textarea:focus,.tox .tox-textfield:focus{background-color:#fff;border-color:#0a8fe9;box-shadow:none;outline:0}.tox .tox-toolbar-textfield{border-width:0;margin-bottom:3px;margin-top:2px;max-width:250px}.tox .tox-naked-btn{background-color:transparent;border:0;border-color:transparent;box-shadow:unset;color:#0a8fe9;cursor:pointer;display:block;margin:0;padding:0}.tox .tox-naked-btn svg{display:block;fill:rgba(84,111,94,.85)}.tox:not([dir=rtl]) .tox-toolbar-textfield+*{margin-left:2.5px}.tox[dir=rtl] .tox-toolbar-textfield+*{margin-right:2.5px}.tox .tox-listboxfield{cursor:pointer;position:relative}.tox .tox-listboxfield .tox-listbox--select[disabled]{background-color:#f2f2f2;color:rgba(84,111,94,.85);cursor:not-allowed}.tox .tox-listbox__select-label{cursor:default;flex:1;margin:0 4px}.tox .tox-listbox__select-chevron{align-items:center;display:flex;justify-content:center;width:10px}.tox .tox-listbox__select-chevron svg{fill:rgba(84,111,94,.85)}.tox .tox-listboxfield .tox-listbox--select{align-items:center;display:flex}.tox:not([dir=rtl]) .tox-listboxfield svg{right:5px}.tox[dir=rtl] .tox-listboxfield svg{left:5px}.tox .tox-selectfield{cursor:pointer;position:relative}.tox .tox-selectfield select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#d9d9d9;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:rgba(84,111,94,.85);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:10px;line-height:24px;margin:0;min-height:34px;outline:0;padding:5px 3.25px;resize:none;width:100%}.tox .tox-selectfield select[disabled]{background-color:#f2f2f2;color:rgba(84,111,94,.85);cursor:not-allowed}.tox .tox-selectfield select::-ms-expand{display:none}.tox .tox-selectfield select:focus{background-color:#fff;border-color:#0a8fe9;box-shadow:none;outline:0}.tox .tox-selectfield svg{pointer-events:none;position:absolute;top:50%;transform:translateY(-50%)}.tox:not([dir=rtl]) .tox-selectfield select[size="0"],.tox:not([dir=rtl]) .tox-selectfield select[size="1"]{padding-right:15px}.tox:not([dir=rtl]) .tox-selectfield svg{right:5px}.tox[dir=rtl] .tox-selectfield select[size="0"],.tox[dir=rtl] .tox-selectfield select[size="1"]{padding-left:15px}.tox[dir=rtl] .tox-selectfield svg{left:5px}.tox .tox-textarea{-webkit-appearance:textarea;-moz-appearance:textarea;appearance:textarea;white-space:pre-wrap}.tox-fullscreen{border:0;height:100%;left:0;margin:0;overflow:hidden;-ms-scroll-chaining:none;overscroll-behavior:none;padding:0;position:fixed;top:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox.tox-tinymce.tox-fullscreen{background-color:transparent;z-index:1200}.tox-shadowhost.tox-fullscreen{z-index:1200}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}.tox .tox-help__more-link{list-style:none;margin-top:1em}.tox .tox-image-tools{width:100%}.tox .tox-image-tools__toolbar{align-items:center;display:flex;justify-content:center}.tox .tox-image-tools__image{background-color:#666;height:380px;overflow:auto;position:relative;width:100%}.tox .tox-image-tools__image,.tox .tox-image-tools__image+.tox-image-tools__toolbar{margin-top:5px}.tox .tox-image-tools__image-bg{background:url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==)}.tox .tox-image-tools__toolbar>.tox-spacer{flex:1;-ms-flex-preferred-size:auto}.tox .tox-croprect-block{background:#000;opacity:.5;position:absolute;zoom:1}.tox .tox-croprect-handle{border:2px solid #fff;height:20px;left:0;position:absolute;top:0;width:20px}.tox .tox-croprect-handle-move{border:0;cursor:move;position:absolute}.tox .tox-croprect-handle-nw{border-width:2px 0 0 2px;cursor:nw-resize;left:100px;margin:-2px 0 0 -2px;top:100px}.tox .tox-croprect-handle-ne{border-width:2px 2px 0 0;cursor:ne-resize;left:200px;margin:-2px 0 0 -20px;top:100px}.tox .tox-croprect-handle-sw{border-width:0 0 2px 2px;cursor:sw-resize;left:100px;margin:-20px 2px 0 -2px;top:200px}.tox .tox-croprect-handle-se{border-width:0 2px 2px 0;cursor:se-resize;left:200px;margin:-20px 0 0 -20px;top:200px}.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-slider:not(:first-of-type){margin-left:5px}.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-button+.tox-slider{margin-left:20px}.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-slider+.tox-button{margin-left:20px}.tox[dir=rtl] .tox-image-tools__toolbar>.tox-slider:not(:first-of-type){margin-right:5px}.tox[dir=rtl] .tox-image-tools__toolbar>.tox-button+.tox-slider{margin-right:20px}.tox[dir=rtl] .tox-image-tools__toolbar>.tox-slider+.tox-button{margin-right:20px}.tox .tox-insert-table-picker{display:flex;flex-wrap:wrap;width:110px}.tox .tox-insert-table-picker>div{border-color:#d9d9d9;border-style:solid;border-width:0 1px 1px 0;box-sizing:border-box;height:11px;width:11px}.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker{margin:-2.5px 0}.tox .tox-insert-table-picker .tox-insert-table-picker__selected{background-color:rgba(10,143,233,.5);border-color:rgba(10,143,233,.5)}.tox .tox-insert-table-picker__label{color:rgba(84,111,94,.7);display:block;font-size:8.75px;padding:2.5px;text-align:center;width:100%}.tox:not([dir=rtl]) .tox-insert-table-picker>div:nth-child(10n){border-right:0}.tox[dir=rtl] .tox-insert-table-picker>div:nth-child(10n+1){border-right:0}.tox .tox-menu{background-color:#fff;border:1px solid #d9d9d9;border-radius:3px;box-shadow:0 4px 8px 0 rgba(84,111,94,.1);display:inline-block;overflow:hidden;vertical-align:top;z-index:1150}.tox .tox-menu.tox-collection.tox-collection--list{padding:0}.tox .tox-menu.tox-collection.tox-collection--toolbar{padding:2.5px}.tox .tox-menu.tox-collection.tox-collection--grid{padding:2.5px}.tox .tox-menu__label blockquote,.tox .tox-menu__label code,.tox .tox-menu__label h1,.tox .tox-menu__label h2,.tox .tox-menu__label h3,.tox .tox-menu__label h4,.tox .tox-menu__label h5,.tox .tox-menu__label h6,.tox .tox-menu__label p{margin:0}.tox .tox-menubar{background:url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23d9d9d9'/%3E%3C/svg%3E") left 0 top 0 #fff;background-color:#fff;display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:wrap;padding:0 4px 0 4px}.tox.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar{border-top:1px solid #d9d9d9}.tox .tox-mbtn{align-items:center;background:0 0;border:0;border-radius:3px;box-shadow:none;color:#817f7c;display:flex;flex:0 0 auto;font-size:8.75px;font-style:normal;font-weight:400;height:34px;justify-content:center;margin:2px 0 3px 0;outline:0;overflow:hidden;padding:0 4px;text-transform:none;width:auto}.tox .tox-mbtn[disabled]{background-color:transparent;border:0;box-shadow:none;color:rgba(129,127,124,.5);cursor:not-allowed}.tox .tox-mbtn:focus:not(:disabled){background:#e5e9e7;border:0;box-shadow:none;color:#0a9fe5}.tox .tox-mbtn--active{background:#e5e9e7;border:0;box-shadow:none;color:rgba(41,159,250,.88)}.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active){background:#e5e9e7;border:0;box-shadow:none;color:#0a9fe5}.tox .tox-mbtn__select-label{cursor:default;font-weight:400;margin:0 4px}.tox .tox-mbtn[disabled] .tox-mbtn__select-label{cursor:not-allowed}.tox .tox-mbtn__select-chevron{align-items:center;display:flex;justify-content:center;width:16px;display:none}.tox .tox-notification{border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;display:-ms-grid;display:grid;font-size:8.75px;font-weight:400;-ms-grid-columns:minmax(40px,1fr) auto minmax(40px,1fr);grid-template-columns:minmax(40px,1fr) auto minmax(40px,1fr);margin-top:2.5px;opacity:0;padding:2.5px;transition:transform .1s ease-in,opacity 150ms ease-in}.tox .tox-notification p{font-size:8.75px;font-weight:400}.tox .tox-notification a{cursor:pointer;text-decoration:underline}.tox .tox-notification--in{opacity:1}.tox .tox-notification--success{background-color:#e4eeda;border-color:#d7e6c8;color:rgba(84,111,94,.85)}.tox .tox-notification--success p{color:rgba(84,111,94,.85)}.tox .tox-notification--success a{color:#547831}.tox .tox-notification--success svg{fill:rgba(84,111,94,.85)}.tox .tox-notification--error{background-color:#f8dede;border-color:#f2bfbf;color:rgba(84,111,94,.85)}.tox .tox-notification--error p{color:rgba(84,111,94,.85)}.tox .tox-notification--error a{color:#c00}.tox .tox-notification--error svg{fill:rgba(84,111,94,.85)}.tox .tox-notification--warn,.tox .tox-notification--warning{background-color:#fffaea;border-color:#ffe89d;color:rgba(84,111,94,.85)}.tox .tox-notification--warn p,.tox .tox-notification--warning p{color:rgba(84,111,94,.85)}.tox .tox-notification--warn a,.tox .tox-notification--warning a{color:rgba(84,111,94,.85)}.tox .tox-notification--warn svg,.tox .tox-notification--warning svg{fill:rgba(84,111,94,.85)}.tox .tox-notification--info{background-color:#d9edf7;border-color:#779ecb;color:rgba(84,111,94,.85)}.tox .tox-notification--info p{color:rgba(84,111,94,.85)}.tox .tox-notification--info a{color:rgba(84,111,94,.85)}.tox .tox-notification--info svg{fill:rgba(84,111,94,.85)}.tox .tox-notification__body{-ms-grid-row-align:center;align-self:center;color:rgba(84,111,94,.85);font-size:14px;-ms-grid-column-span:1;grid-column-end:3;-ms-grid-column:2;grid-column-start:2;-ms-grid-row-span:1;grid-row-end:2;-ms-grid-row:1;grid-row-start:1;text-align:center;white-space:normal;word-break:break-all;word-break:break-word}.tox .tox-notification__body>*{margin:0}.tox .tox-notification__body>*+*{margin-top:1rem}.tox .tox-notification__icon{-ms-grid-row-align:center;align-self:center;-ms-grid-column-span:1;grid-column-end:2;-ms-grid-column:1;grid-column-start:1;-ms-grid-row-span:1;grid-row-end:2;-ms-grid-row:1;grid-row-start:1;-ms-grid-column-align:end;justify-self:end}.tox .tox-notification__icon svg{display:block}.tox .tox-notification__dismiss{-ms-grid-row-align:start;align-self:start;-ms-grid-column-span:1;grid-column-end:4;-ms-grid-column:3;grid-column-start:3;-ms-grid-row-span:1;grid-row-end:2;-ms-grid-row:1;grid-row-start:1;-ms-grid-column-align:end;justify-self:end}.tox .tox-notification .tox-progress-bar{-ms-grid-column-span:3;grid-column-end:4;-ms-grid-column:1;grid-column-start:1;-ms-grid-row-span:1;grid-row-end:3;-ms-grid-row:2;grid-row-start:2;-ms-grid-column-align:center;justify-self:center}.tox .tox-pop{display:inline-block;position:relative}.tox .tox-pop--resizing{transition:width .1s ease}.tox .tox-pop--resizing .tox-toolbar,.tox .tox-pop--resizing .tox-toolbar__group{flex-wrap:nowrap}.tox .tox-pop--transition{transition:.15s ease;transition-property:left,right,top,bottom}.tox .tox-pop--transition::after,.tox .tox-pop--transition::before{transition:all .15s,visibility 0s,opacity 75ms ease 75ms}.tox .tox-pop__dialog{background-color:#fff;border:1px solid #d9d9d9;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.15);min-width:0;overflow:hidden}.tox .tox-pop__dialog>:not(.tox-toolbar){margin:2.5px 2.5px 2.5px 5px}.tox .tox-pop__dialog .tox-toolbar{background-color:transparent;margin-bottom:-1px}.tox .tox-pop::after,.tox .tox-pop::before{border-style:solid;content:'';display:block;height:0;opacity:1;position:absolute;width:0}.tox .tox-pop.tox-pop--inset::after,.tox .tox-pop.tox-pop--inset::before{opacity:0;transition:all 0s .15s,visibility 0s,opacity 75ms ease}.tox .tox-pop.tox-pop--bottom::after,.tox .tox-pop.tox-pop--bottom::before{left:50%;top:100%}.tox .tox-pop.tox-pop--bottom::after{border-color:#fff transparent transparent transparent;border-width:8px;margin-left:-8px;margin-top:-1px}.tox .tox-pop.tox-pop--bottom::before{border-color:#d9d9d9 transparent transparent transparent;border-width:9px;margin-left:-9px}.tox .tox-pop.tox-pop--top::after,.tox .tox-pop.tox-pop--top::before{left:50%;top:0;transform:translateY(-100%)}.tox .tox-pop.tox-pop--top::after{border-color:transparent transparent #fff transparent;border-width:8px;margin-left:-8px;margin-top:1px}.tox .tox-pop.tox-pop--top::before{border-color:transparent transparent #d9d9d9 transparent;border-width:9px;margin-left:-9px}.tox .tox-pop.tox-pop--left::after,.tox .tox-pop.tox-pop--left::before{left:0;top:calc(50% - 1px);transform:translateY(-50%)}.tox .tox-pop.tox-pop--left::after{border-color:transparent #fff transparent transparent;border-width:8px;margin-left:-15px}.tox .tox-pop.tox-pop--left::before{border-color:transparent #d9d9d9 transparent transparent;border-width:10px;margin-left:-19px}.tox .tox-pop.tox-pop--right::after,.tox .tox-pop.tox-pop--right::before{left:100%;top:calc(50% + 1px);transform:translateY(-50%)}.tox .tox-pop.tox-pop--right::after{border-color:transparent transparent transparent #fff;border-width:8px;margin-left:-1px}.tox .tox-pop.tox-pop--right::before{border-color:transparent transparent transparent #d9d9d9;border-width:10px;margin-left:-1px}.tox .tox-pop.tox-pop--align-left::after,.tox .tox-pop.tox-pop--align-left::before{left:20px}.tox .tox-pop.tox-pop--align-right::after,.tox .tox-pop.tox-pop--align-right::before{left:calc(100% - 20px)}.tox .tox-sidebar-wrap{display:flex;flex-direction:row;flex-grow:1;-ms-flex-preferred-size:0;min-height:0}.tox .tox-sidebar{background-color:#fff;display:flex;flex-direction:row;justify-content:flex-end}.tox .tox-sidebar__slider{display:flex;overflow:hidden}.tox .tox-sidebar__pane-container{display:flex}.tox .tox-sidebar__pane{display:flex}.tox .tox-sidebar--sliding-closed{opacity:0}.tox .tox-sidebar--sliding-open{opacity:1}.tox .tox-sidebar--sliding-growing,.tox .tox-sidebar--sliding-shrinking{transition:width .5s ease,opacity .5s ease}.tox .tox-selector{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;display:inline-block;height:10px;position:absolute;width:10px}.tox.tox-platform-touch .tox-selector{height:12px;width:12px}.tox .tox-slider{align-items:center;display:flex;flex:1;-ms-flex-preferred-size:auto;height:24px;justify-content:center;position:relative}.tox .tox-slider__rail{background-color:transparent;border:1px solid #d9d9d9;border-radius:3px;height:10px;min-width:120px;width:100%}.tox .tox-slider__handle{background-color:#0a8fe9;border:2px solid #0871b8;border-radius:3px;box-shadow:none;height:24px;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:14px}.tox .tox-source-code{overflow:auto}.tox .tox-spinner{display:flex}.tox .tox-spinner>div{animation:tam-bouncing-dots 1.5s ease-in-out 0s infinite both;background-color:rgba(84,111,94,.7);border-radius:100%;height:5px;width:5px}.tox .tox-spinner>div:nth-child(1){animation-delay:-.32s}.tox .tox-spinner>div:nth-child(2){animation-delay:-.16s}@keyframes tam-bouncing-dots{0%,100%,80%{transform:scale(0)}40%{transform:scale(1)}}.tox:not([dir=rtl]) .tox-spinner>div:not(:first-child){margin-left:2.5px}.tox[dir=rtl] .tox-spinner>div:not(:first-child){margin-right:2.5px}.tox .tox-statusbar{align-items:center;background-color:#fff;border-top:1px solid #d9d9d9;color:rgba(84,111,94,.7);display:flex;flex:0 0 auto;font-size:12px;font-weight:400;height:18px;overflow:hidden;padding:0 5px;position:relative;text-transform:uppercase}.tox .tox-statusbar__text-container{display:flex;flex:1 1 auto;justify-content:flex-end;overflow:hidden}.tox .tox-statusbar__path{display:flex;flex:1 1 auto;margin-right:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tox .tox-statusbar__path>*{display:inline;white-space:nowrap}.tox .tox-statusbar__wordcount{flex:0 0 auto;margin-left:1ch}.tox .tox-statusbar a,.tox .tox-statusbar__path-item,.tox .tox-statusbar__wordcount{color:rgba(84,111,94,.7);text-decoration:none}.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]){cursor:pointer;text-decoration:underline}.tox .tox-statusbar__resize-handle{align-items:flex-end;align-self:stretch;cursor:nwse-resize;display:flex;flex:0 0 auto;justify-content:flex-end;margin-left:auto;margin-right:-5px;padding-left:1ch}.tox .tox-statusbar__resize-handle svg{display:block;fill:rgba(84,111,94,.7)}.tox .tox-statusbar__resize-handle:focus svg{background-color:#e5e9e7;border-radius:1px;box-shadow:0 0 0 2px #e5e9e7}.tox:not([dir=rtl]) .tox-statusbar__path>*{margin-right:2.5px}.tox:not([dir=rtl]) .tox-statusbar__branding{margin-left:1ch}.tox[dir=rtl] .tox-statusbar{flex-direction:row-reverse}.tox[dir=rtl] .tox-statusbar__path>*{margin-left:2.5px}.tox .tox-throbber{z-index:1299}.tox .tox-throbber__busy-spinner{align-items:center;background-color:rgba(255,255,255,.6);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0}.tox .tox-tbtn{align-items:center;background:0 0;border:0;border-radius:3px;box-shadow:none;color:#817f7c;display:flex;flex:0 0 auto;font-size:8.75px;font-style:normal;font-weight:400;height:34px;justify-content:center;margin:2px 0 3px 0;outline:0;overflow:hidden;padding:0;text-transform:none;width:34px}.tox .tox-tbtn svg{display:block;fill:#817f7c}.tox .tox-tbtn.tox-tbtn-more{padding-left:5px;padding-right:5px;width:inherit}.tox .tox-tbtn:focus{background:#e5e9e7;border:0;box-shadow:none}.tox .tox-tbtn:hover{background:#e5e9e7;border:0;box-shadow:none;color:#0a9fe5}.tox .tox-tbtn:hover svg{fill:#0a9fe5}.tox .tox-tbtn:active{background:#e5e9e7;border:0;box-shadow:none;color:rgba(41,159,250,.88)}.tox .tox-tbtn:active svg{fill:rgba(41,159,250,.88)}.tox .tox-tbtn--disabled,.tox .tox-tbtn--disabled:hover,.tox .tox-tbtn:disabled,.tox .tox-tbtn:disabled:hover{background:0 0;border:0;box-shadow:none;color:rgba(129,127,124,.5);cursor:not-allowed}.tox .tox-tbtn--disabled svg,.tox .tox-tbtn--disabled:hover svg,.tox .tox-tbtn:disabled svg,.tox .tox-tbtn:disabled:hover svg{fill:rgba(129,127,124,.5)}.tox .tox-tbtn--enabled,.tox .tox-tbtn--enabled:hover{background:#e5e9e7;border:0;box-shadow:none;color:rgba(41,159,250,.88)}.tox .tox-tbtn--enabled:hover>*,.tox .tox-tbtn--enabled>*{transform:none}.tox .tox-tbtn--enabled svg,.tox .tox-tbtn--enabled:hover svg{fill:rgba(41,159,250,.88)}.tox .tox-tbtn:focus:not(.tox-tbtn--disabled){color:#ee930e}.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg{fill:#ee930e}.tox .tox-tbtn:active>*{transform:none}.tox .tox-tbtn--md{height:51px;width:51px}.tox .tox-tbtn--lg{flex-direction:column;height:68px;width:68px}.tox .tox-tbtn--return{-ms-grid-row-align:stretch;align-self:stretch;height:unset;width:16px}.tox .tox-tbtn--labeled{padding:0 4px;width:unset}.tox .tox-tbtn__vlabel{display:block;font-size:10px;font-weight:400;letter-spacing:-.025em;margin-bottom:2.5px;white-space:nowrap}.tox .tox-tbtn--select{margin:2px 0 3px 0;padding:0 4px;width:auto}.tox .tox-tbtn__select-label{cursor:default;font-weight:400;margin:0 4px}.tox .tox-tbtn__select-chevron{align-items:center;display:flex;justify-content:center;width:10px}.tox .tox-tbtn__select-chevron svg{fill:rgba(129,127,124,.5)}.tox .tox-tbtn--bespoke .tox-tbtn__select-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:7em}.tox .tox-split-button{border:0;border-radius:3px;box-sizing:border-box;display:flex;margin:2px 0 3px 0;overflow:hidden}.tox .tox-split-button:hover{box-shadow:0 0 0 1px #e5e9e7 inset}.tox .tox-split-button:focus{background:#e5e9e7;box-shadow:none;color:#ee930e}.tox .tox-split-button>*{border-radius:0}.tox .tox-split-button__chevron{width:10px}.tox .tox-split-button__chevron svg{fill:rgba(129,127,124,.5)}.tox .tox-split-button .tox-tbtn{margin:0}.tox.tox-platform-touch .tox-split-button .tox-tbtn:first-child{width:30px}.tox.tox-platform-touch .tox-split-button__chevron{width:14px}.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus,.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover,.tox .tox-split-button.tox-tbtn--disabled:focus,.tox .tox-split-button.tox-tbtn--disabled:hover{background:0 0;box-shadow:none;color:rgba(129,127,124,.5)}.tox .tox-toolbar-overlord{background-color:#fff}.tox .tox-toolbar,.tox .tox-toolbar__overflow,.tox .tox-toolbar__primary{background:url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23d9d9d9'/%3E%3C/svg%3E") left 0 top 0 #fff;background-color:#fff;display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:wrap;padding:0 0}.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed{height:0;opacity:0;padding-bottom:0;padding-top:0;visibility:hidden}.tox .tox-toolbar__overflow--growing{transition:height .3s ease,opacity .2s linear .1s}.tox .tox-toolbar__overflow--shrinking{transition:opacity .3s ease,height .2s linear .1s,visibility 0s linear .3s}.tox .tox-menubar+.tox-toolbar,.tox .tox-menubar+.tox-toolbar-overlord .tox-toolbar__primary{border-top:1px solid #d9d9d9;margin-top:-1px}.tox .tox-toolbar--scrolling{flex-wrap:nowrap;overflow-x:auto}.tox .tox-pop .tox-toolbar{border-width:0}.tox .tox-toolbar--no-divider{background-image:none}.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar-overlord:first-child .tox-toolbar__primary,.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar:first-child{border-top:1px solid #d9d9d9}.tox.tox-tinymce-aux .tox-toolbar__overflow{background-color:#fff;border:1px solid #d9d9d9;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.15)}.tox .tox-toolbar__group{align-items:center;display:flex;flex-wrap:wrap;margin:0 0;padding:0 4px 0 4px}.tox .tox-toolbar__group--pull-right{margin-left:auto}.tox .tox-toolbar--scrolling .tox-toolbar__group{flex-shrink:0;flex-wrap:nowrap}.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type){border-right:1px solid #d9d9d9}.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type){border-left:1px solid #d9d9d9}.tox .tox-tooltip{display:inline-block;padding:5px;position:relative}.tox .tox-tooltip__body{background-color:rgba(84,111,94,.85);border-radius:3px;box-shadow:0 2px 4px rgba(84,111,94,.3);color:rgba(255,255,255,.75);font-size:8.75px;font-style:normal;font-weight:400;padding:2.5px 5px;text-transform:none}.tox .tox-tooltip__arrow{position:absolute}.tox .tox-tooltip--down .tox-tooltip__arrow{border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid rgba(84,111,94,.85);bottom:0;left:50%;position:absolute;transform:translateX(-50%)}.tox .tox-tooltip--up .tox-tooltip__arrow{border-bottom:5px solid rgba(84,111,94,.85);border-left:5px solid transparent;border-right:5px solid transparent;left:50%;position:absolute;top:0;transform:translateX(-50%)}.tox .tox-tooltip--right .tox-tooltip__arrow{border-bottom:5px solid transparent;border-left:5px solid rgba(84,111,94,.85);border-top:5px solid transparent;position:absolute;right:0;top:50%;transform:translateY(-50%)}.tox .tox-tooltip--left .tox-tooltip__arrow{border-bottom:5px solid transparent;border-right:5px solid rgba(84,111,94,.85);border-top:5px solid transparent;left:0;position:absolute;top:50%;transform:translateY(-50%)}.tox .tox-well{border:1px solid #d9d9d9;border-radius:3px;padding:5px;width:100%}.tox .tox-well>:first-child{margin-top:0}.tox .tox-well>:last-child{margin-bottom:0}.tox .tox-well>:only-child{margin:0}.tox .tox-custom-editor{border:1px solid #d9d9d9;border-radius:3px;display:flex;flex:1;position:relative}.tox .tox-dialog-loading::before{background-color:rgba(0,0,0,.5);content:"";height:100%;position:absolute;width:100%;z-index:1000}.tox .tox-tab{cursor:pointer}.tox .tox-dialog__content-js{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-dialog__body-content .tox-collection{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-image-tools-edit-panel{height:60px}.tox .tox-image-tools__sidebar{height:60px} \ No newline at end of file diff --git a/public/resource/tinymce/skins/ui/jeecg/skin.mobile.css b/public/resource/tinymce/skins/ui/jeecg/skin.mobile.css new file mode 100644 index 0000000..df458d5 --- /dev/null +++ b/public/resource/tinymce/skins/ui/jeecg/skin.mobile.css @@ -0,0 +1,677 @@ +/** +* Copyright (c) Tiny Technologies, Inc. All rights reserved. +* Licensed under the LGPL or a commercial license. +* For LGPL see License.txt in the project root for license information. +* For commercial licenses see https://www.tiny.cloud/ +*/ +/* RESET all the things! */ +.tinymce-mobile-outer-container { + all: initial; + display: block; +} +.tinymce-mobile-outer-container * { + border: 0; + box-sizing: initial; + cursor: inherit; + float: none; + line-height: 1; + margin: 0; + outline: 0; + padding: 0; + -webkit-tap-highlight-color: transparent; + /* TBIO-3691, stop the gray flicker on touch. */ + text-shadow: none; + white-space: nowrap; +} +.tinymce-mobile-icon-arrow-back::before { + content: "\e5cd"; +} +.tinymce-mobile-icon-image::before { + content: "\e412"; +} +.tinymce-mobile-icon-cancel-circle::before { + content: "\e5c9"; +} +.tinymce-mobile-icon-full-dot::before { + content: "\e061"; +} +.tinymce-mobile-icon-align-center::before { + content: "\e234"; +} +.tinymce-mobile-icon-align-left::before { + content: "\e236"; +} +.tinymce-mobile-icon-align-right::before { + content: "\e237"; +} +.tinymce-mobile-icon-bold::before { + content: "\e238"; +} +.tinymce-mobile-icon-italic::before { + content: "\e23f"; +} +.tinymce-mobile-icon-unordered-list::before { + content: "\e241"; +} +.tinymce-mobile-icon-ordered-list::before { + content: "\e242"; +} +.tinymce-mobile-icon-font-size::before { + content: "\e245"; +} +.tinymce-mobile-icon-underline::before { + content: "\e249"; +} +.tinymce-mobile-icon-link::before { + content: "\e157"; +} +.tinymce-mobile-icon-unlink::before { + content: "\eca2"; +} +.tinymce-mobile-icon-color::before { + content: "\e891"; +} +.tinymce-mobile-icon-previous::before { + content: "\e314"; +} +.tinymce-mobile-icon-next::before { + content: "\e315"; +} +.tinymce-mobile-icon-large-font::before, +.tinymce-mobile-icon-style-formats::before { + content: "\e264"; +} +.tinymce-mobile-icon-undo::before { + content: "\e166"; +} +.tinymce-mobile-icon-redo::before { + content: "\e15a"; +} +.tinymce-mobile-icon-removeformat::before { + content: "\e239"; +} +.tinymce-mobile-icon-small-font::before { + content: "\e906"; +} +.tinymce-mobile-icon-readonly-back::before, +.tinymce-mobile-format-matches::after { + content: "\e5ca"; +} +.tinymce-mobile-icon-small-heading::before { + content: "small"; +} +.tinymce-mobile-icon-large-heading::before { + content: "large"; +} +.tinymce-mobile-icon-small-heading::before, +.tinymce-mobile-icon-large-heading::before { + font-family: sans-serif; + font-size: 80%; +} +.tinymce-mobile-mask-edit-icon::before { + content: "\e254"; +} +.tinymce-mobile-icon-back::before { + content: "\e5c4"; +} +.tinymce-mobile-icon-heading::before { + /* TODO: Translate */ + content: "Headings"; + font-family: sans-serif; + font-size: 80%; + font-weight: bold; +} +.tinymce-mobile-icon-h1::before { + content: "H1"; + font-weight: bold; +} +.tinymce-mobile-icon-h2::before { + content: "H2"; + font-weight: bold; +} +.tinymce-mobile-icon-h3::before { + content: "H3"; + font-weight: bold; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask { + align-items: center; + display: flex; + justify-content: center; + background: rgba(51, 51, 51, 0.5); + height: 100%; + position: absolute; + top: 0; + width: 100%; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container { + align-items: center; + border-radius: 50%; + display: flex; + flex-direction: column; + font-family: sans-serif; + font-size: 1em; + justify-content: space-between; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .mixin-menu-item { + align-items: center; + display: flex; + justify-content: center; + border-radius: 50%; + height: 2.1em; + width: 2.1em; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section { + align-items: center; + display: flex; + justify-content: center; + flex-direction: column; + font-size: 1em; +} +@media only screen and (min-device-width:700px) { + .tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section { + font-size: 1.2em; + } +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon { + align-items: center; + display: flex; + justify-content: center; + border-radius: 50%; + height: 2.1em; + width: 2.1em; + background-color: white; + color: #207ab7; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon::before { + content: "\e900"; + font-family: 'tinymce-mobile', sans-serif; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section:not(.tinymce-mobile-mask-tap-icon-selected) .tinymce-mobile-mask-tap-icon { + z-index: 2; +} +.tinymce-mobile-android-container.tinymce-mobile-android-maximized { + background: #ffffff; + border: none; + bottom: 0; + display: flex; + flex-direction: column; + left: 0; + position: fixed; + right: 0; + top: 0; +} +.tinymce-mobile-android-container:not(.tinymce-mobile-android-maximized) { + position: relative; +} +.tinymce-mobile-android-container .tinymce-mobile-editor-socket { + display: flex; + flex-grow: 1; +} +.tinymce-mobile-android-container .tinymce-mobile-editor-socket iframe { + display: flex !important; + flex-grow: 1; + height: auto !important; +} +.tinymce-mobile-android-scroll-reload { + overflow: hidden; +} +:not(.tinymce-mobile-readonly-mode) > .tinymce-mobile-android-selection-context-toolbar { + margin-top: 23px; +} +.tinymce-mobile-toolstrip { + background: #fff; + display: flex; + flex: 0 0 auto; + z-index: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar { + align-items: center; + background-color: #fff; + border-bottom: 1px solid #cccccc; + display: flex; + flex: 1; + height: 2.5em; + width: 100%; + /* Make it no larger than the toolstrip, so that it needs to scroll */ +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group { + align-items: center; + display: flex; + height: 100%; + flex-shrink: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group > div { + align-items: center; + display: flex; + height: 100%; + flex: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-exit-container { + background: #f44336; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-toolbar-scrollable-group { + flex-grow: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item { + padding-left: 0.5em; + padding-right: 0.5em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button { + align-items: center; + display: flex; + height: 80%; + margin-left: 2px; + margin-right: 2px; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button.tinymce-mobile-toolbar-button-selected { + background: #d4dbd7; + color: #cccccc; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:first-of-type, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:last-of-type { + background: #207ab7; + color: #eceff1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar { + /* Note, this file is imported inside .tinymce-mobile-context-toolbar, so that prefix is on everything here. */ +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group { + align-items: center; + display: flex; + height: 100%; + flex: 1; + padding-bottom: 0.4em; + padding-top: 0.4em; + /* Make any buttons appearing on the left and right display in the centre (e.g. color edges) */ + /* For widgets like the colour picker, use the whole height */ +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog { + display: flex; + min-height: 1.5em; + overflow: hidden; + padding-left: 0; + padding-right: 0; + position: relative; + width: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain { + display: flex; + height: 100%; + transition: left cubic-bezier(0.4, 0, 1, 1) 0.15s; + width: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen { + display: flex; + flex: 0 0 auto; + justify-content: space-between; + width: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen input { + font-family: Sans-serif; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container { + display: flex; + flex-grow: 1; + position: relative; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container .tinymce-mobile-input-container-x { + -ms-grid-row-align: center; + align-self: center; + background: inherit; + border: none; + border-radius: 50%; + color: #888; + font-size: 0.6em; + font-weight: bold; + height: 100%; + padding-right: 2px; + position: absolute; + right: 0; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container.tinymce-mobile-input-container-empty .tinymce-mobile-input-container-x { + display: none; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next { + align-items: center; + display: flex; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous::before, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next::before { + align-items: center; + display: flex; + font-weight: bold; + height: 100%; + padding-left: 0.5em; + padding-right: 0.5em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous.tinymce-mobile-toolbar-navigation-disabled::before, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next.tinymce-mobile-toolbar-navigation-disabled::before { + visibility: hidden; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item { + color: #cccccc; + font-size: 10px; + line-height: 10px; + margin: 0 2px; + padding-top: 3px; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item.tinymce-mobile-dot-active { + color: #d4dbd7; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-font::before, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-heading::before { + margin-left: 0.5em; + margin-right: 0.9em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-font::before, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-heading::before { + margin-left: 0.9em; + margin-right: 0.5em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider { + display: flex; + flex: 1; + margin-left: 0; + margin-right: 0; + padding: 0.28em 0; + position: relative; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container { + align-items: center; + display: flex; + flex-grow: 1; + height: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container .tinymce-mobile-slider-size-line { + background: #cccccc; + display: flex; + flex: 1; + height: 0.2em; + margin-bottom: 0.3em; + margin-top: 0.3em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container { + padding-left: 2em; + padding-right: 2em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container { + align-items: center; + display: flex; + flex-grow: 1; + height: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container .tinymce-mobile-slider-gradient { + background: linear-gradient(to right, hsl(0, 100%, 50%) 0%, hsl(60, 100%, 50%) 17%, hsl(120, 100%, 50%) 33%, hsl(180, 100%, 50%) 50%, hsl(240, 100%, 50%) 67%, hsl(300, 100%, 50%) 83%, hsl(0, 100%, 50%) 100%); + display: flex; + flex: 1; + height: 0.2em; + margin-bottom: 0.3em; + margin-top: 0.3em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-black { + /* Not part of theming */ + background: black; + height: 0.2em; + margin-bottom: 0.3em; + margin-top: 0.3em; + width: 1.2em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-white { + /* Not part of theming */ + background: white; + height: 0.2em; + margin-bottom: 0.3em; + margin-top: 0.3em; + width: 1.2em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb { + /* vertically centering trick (margin: auto, top: 0, bottom: 0). On iOS and Safari, if you leave + * out these values, then it shows the thumb at the top of the spectrum. This is probably because it is + * absolutely positioned with only a left value, and not a top. Note, on Chrome it seems to be fine without + * this approach. + */ + align-items: center; + background-clip: padding-box; + background-color: #455a64; + border: 0.5em solid rgba(136, 136, 136, 0); + border-radius: 3em; + bottom: 0; + color: #fff; + display: flex; + height: 0.5em; + justify-content: center; + left: -10px; + margin: auto; + position: absolute; + top: 0; + transition: border 120ms cubic-bezier(0.39, 0.58, 0.57, 1); + width: 0.5em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb.tinymce-mobile-thumb-active { + border: 0.5em solid rgba(136, 136, 136, 0.39); +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group > div { + align-items: center; + display: flex; + height: 100%; + flex: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper { + flex-direction: column; + justify-content: center; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item { + align-items: center; + display: flex; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item:not(.tinymce-mobile-serialised-dialog) { + height: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-container { + display: flex; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input { + background: #ffffff; + border: none; + border-radius: 0; + color: #455a64; + flex-grow: 1; + font-size: 0.85em; + padding-bottom: 0.1em; + padding-left: 5px; + padding-top: 0.1em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::-webkit-input-placeholder { + /* WebKit, Blink, Edge */ + color: #888; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input:-ms-input-placeholder { + /* WebKit, Blink, Edge */ + color: #888; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::placeholder { + /* WebKit, Blink, Edge */ + color: #888; +} +/* dropup */ +.tinymce-mobile-dropup { + background: white; + display: flex; + overflow: hidden; + width: 100%; +} +.tinymce-mobile-dropup.tinymce-mobile-dropup-shrinking { + transition: height 0.3s ease-out; +} +.tinymce-mobile-dropup.tinymce-mobile-dropup-growing { + transition: height 0.3s ease-in; +} +.tinymce-mobile-dropup.tinymce-mobile-dropup-closed { + flex-grow: 0; +} +.tinymce-mobile-dropup.tinymce-mobile-dropup-open:not(.tinymce-mobile-dropup-growing) { + flex-grow: 1; +} +/* TODO min-height for device size and orientation */ +.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) { + min-height: 200px; +} +@media only screen and (orientation: landscape) { + .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) { + min-height: 200px; + } +} +@media only screen and (min-device-width : 320px) and (max-device-width : 568px) and (orientation : landscape) { + .tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) { + min-height: 150px; + } +} +/* styles menu */ +.tinymce-mobile-styles-menu { + font-family: sans-serif; + outline: 4px solid black; + overflow: hidden; + position: relative; + width: 100%; +} +.tinymce-mobile-styles-menu [role="menu"] { + display: flex; + flex-direction: column; + height: 100%; + position: absolute; + width: 100%; +} +.tinymce-mobile-styles-menu [role="menu"].transitioning { + transition: transform 0.5s ease-in-out; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item { + border-bottom: 1px solid #ddd; + color: #455a64; + cursor: pointer; + display: flex; + padding: 1em 1em; + position: relative; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser .tinymce-mobile-styles-collapse-icon::before { + color: #455a64; + content: "\e314"; + font-family: 'tinymce-mobile', sans-serif; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-styles-item-is-menu::after { + color: #455a64; + content: "\e315"; + font-family: 'tinymce-mobile', sans-serif; + padding-left: 1em; + padding-right: 1em; + position: absolute; + right: 0; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-format-matches::after { + font-family: 'tinymce-mobile', sans-serif; + padding-left: 1em; + padding-right: 1em; + position: absolute; + right: 0; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-separator, +.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser { + align-items: center; + background: #fff; + border-top: #455a64; + color: #455a64; + display: flex; + min-height: 2.5em; + padding-left: 1em; + padding-right: 1em; +} +.tinymce-mobile-styles-menu [data-transitioning-destination="before"][data-transitioning-state], +.tinymce-mobile-styles-menu [data-transitioning-state="before"] { + transform: translate(-100%); +} +.tinymce-mobile-styles-menu [data-transitioning-destination="current"][data-transitioning-state], +.tinymce-mobile-styles-menu [data-transitioning-state="current"] { + transform: translate(0%); +} +.tinymce-mobile-styles-menu [data-transitioning-destination="after"][data-transitioning-state], +.tinymce-mobile-styles-menu [data-transitioning-state="after"] { + transform: translate(100%); +} +@font-face { + font-family: 'tinymce-mobile'; + font-style: normal; + font-weight: normal; + src: url('fonts/tinymce-mobile.woff?8x92w3') format('woff'); +} +@media (min-device-width: 700px) { + .tinymce-mobile-outer-container, + .tinymce-mobile-outer-container input { + font-size: 25px; + } +} +@media (max-device-width: 700px) { + .tinymce-mobile-outer-container, + .tinymce-mobile-outer-container input { + font-size: 18px; + } +} +.tinymce-mobile-icon { + font-family: 'tinymce-mobile', sans-serif; +} +.mixin-flex-and-centre { + align-items: center; + display: flex; + justify-content: center; +} +.mixin-flex-bar { + align-items: center; + display: flex; + height: 100%; +} +.tinymce-mobile-outer-container .tinymce-mobile-editor-socket iframe { + background-color: #fff; + width: 100%; +} +.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon { + /* Note, on the iPod touch in landscape, this isn't visible when the navbar appears */ + background-color: #207ab7; + border-radius: 50%; + bottom: 1em; + color: white; + font-size: 1em; + height: 2.1em; + position: fixed; + right: 2em; + width: 2.1em; + align-items: center; + display: flex; + justify-content: center; +} +@media only screen and (min-device-width:700px) { + .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon { + font-size: 1.2em; + } +} +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket { + height: 300px; + overflow: hidden; +} +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket iframe { + height: 100%; +} +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-toolstrip { + display: none; +} +/* + Note, that if you don't include this (::-webkit-file-upload-button), the toolbar width gets + increased and the whole body becomes scrollable. It's important! + */ +input[type="file"]::-webkit-file-upload-button { + display: none; +} +@media only screen and (min-device-width : 320px) and (max-device-width : 568px) and (orientation : landscape) { + .tinymce-mobile-ios-container .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon { + bottom: 50%; + } +} diff --git a/public/resource/tinymce/skins/ui/jeecg/skin.mobile.min.css b/public/resource/tinymce/skins/ui/jeecg/skin.mobile.min.css new file mode 100644 index 0000000..eaad954 --- /dev/null +++ b/public/resource/tinymce/skins/ui/jeecg/skin.mobile.min.css @@ -0,0 +1,7 @@ +/** +* Copyright (c) Tiny Technologies, Inc. All rights reserved. +* Licensed under the LGPL or a commercial license. +* For LGPL see License.txt in the project root for license information. +* For commercial licenses see https://www.tiny.cloud/ +*/ +.tinymce-mobile-outer-container{all:initial;display:block}.tinymce-mobile-outer-container *{border:0;box-sizing:initial;cursor:inherit;float:none;line-height:1;margin:0;outline:0;padding:0;-webkit-tap-highlight-color:transparent;text-shadow:none;white-space:nowrap}.tinymce-mobile-icon-arrow-back::before{content:"\e5cd"}.tinymce-mobile-icon-image::before{content:"\e412"}.tinymce-mobile-icon-cancel-circle::before{content:"\e5c9"}.tinymce-mobile-icon-full-dot::before{content:"\e061"}.tinymce-mobile-icon-align-center::before{content:"\e234"}.tinymce-mobile-icon-align-left::before{content:"\e236"}.tinymce-mobile-icon-align-right::before{content:"\e237"}.tinymce-mobile-icon-bold::before{content:"\e238"}.tinymce-mobile-icon-italic::before{content:"\e23f"}.tinymce-mobile-icon-unordered-list::before{content:"\e241"}.tinymce-mobile-icon-ordered-list::before{content:"\e242"}.tinymce-mobile-icon-font-size::before{content:"\e245"}.tinymce-mobile-icon-underline::before{content:"\e249"}.tinymce-mobile-icon-link::before{content:"\e157"}.tinymce-mobile-icon-unlink::before{content:"\eca2"}.tinymce-mobile-icon-color::before{content:"\e891"}.tinymce-mobile-icon-previous::before{content:"\e314"}.tinymce-mobile-icon-next::before{content:"\e315"}.tinymce-mobile-icon-large-font::before,.tinymce-mobile-icon-style-formats::before{content:"\e264"}.tinymce-mobile-icon-undo::before{content:"\e166"}.tinymce-mobile-icon-redo::before{content:"\e15a"}.tinymce-mobile-icon-removeformat::before{content:"\e239"}.tinymce-mobile-icon-small-font::before{content:"\e906"}.tinymce-mobile-format-matches::after,.tinymce-mobile-icon-readonly-back::before{content:"\e5ca"}.tinymce-mobile-icon-small-heading::before{content:"small"}.tinymce-mobile-icon-large-heading::before{content:"large"}.tinymce-mobile-icon-large-heading::before,.tinymce-mobile-icon-small-heading::before{font-family:sans-serif;font-size:80%}.tinymce-mobile-mask-edit-icon::before{content:"\e254"}.tinymce-mobile-icon-back::before{content:"\e5c4"}.tinymce-mobile-icon-heading::before{content:"Headings";font-family:sans-serif;font-size:80%;font-weight:700}.tinymce-mobile-icon-h1::before{content:"H1";font-weight:700}.tinymce-mobile-icon-h2::before{content:"H2";font-weight:700}.tinymce-mobile-icon-h3::before{content:"H3";font-weight:700}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask{align-items:center;display:flex;justify-content:center;background:rgba(51,51,51,.5);height:100%;position:absolute;top:0;width:100%}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container{align-items:center;border-radius:50%;display:flex;flex-direction:column;font-family:sans-serif;font-size:1em;justify-content:space-between}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .mixin-menu-item{align-items:center;display:flex;justify-content:center;border-radius:50%;height:2.1em;width:2.1em}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section{align-items:center;display:flex;justify-content:center;flex-direction:column;font-size:1em}@media only screen and (min-device-width:700px){.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section{font-size:1.2em}}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon{align-items:center;display:flex;justify-content:center;border-radius:50%;height:2.1em;width:2.1em;background-color:#fff;color:#207ab7}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon::before{content:"\e900";font-family:tinymce-mobile,sans-serif}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section:not(.tinymce-mobile-mask-tap-icon-selected) .tinymce-mobile-mask-tap-icon{z-index:2}.tinymce-mobile-android-container.tinymce-mobile-android-maximized{background:#fff;border:none;bottom:0;display:flex;flex-direction:column;left:0;position:fixed;right:0;top:0}.tinymce-mobile-android-container:not(.tinymce-mobile-android-maximized){position:relative}.tinymce-mobile-android-container .tinymce-mobile-editor-socket{display:flex;flex-grow:1}.tinymce-mobile-android-container .tinymce-mobile-editor-socket iframe{display:flex!important;flex-grow:1;height:auto!important}.tinymce-mobile-android-scroll-reload{overflow:hidden}:not(.tinymce-mobile-readonly-mode)>.tinymce-mobile-android-selection-context-toolbar{margin-top:23px}.tinymce-mobile-toolstrip{background:#fff;display:flex;flex:0 0 auto;z-index:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar{align-items:center;background-color:#fff;border-bottom:1px solid #ccc;display:flex;flex:1;height:2.5em;width:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group{align-items:center;display:flex;height:100%;flex-shrink:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group>div{align-items:center;display:flex;height:100%;flex:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-exit-container{background:#f44336}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-toolbar-scrollable-group{flex-grow:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item{padding-left:.5em;padding-right:.5em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button{align-items:center;display:flex;height:80%;margin-left:2px;margin-right:2px}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button.tinymce-mobile-toolbar-button-selected{background:#d4dbd7;color:#ccc}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:first-of-type,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:last-of-type{background:#207ab7;color:#eceff1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group{align-items:center;display:flex;height:100%;flex:1;padding-bottom:.4em;padding-top:.4em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog{display:flex;min-height:1.5em;overflow:hidden;padding-left:0;padding-right:0;position:relative;width:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain{display:flex;height:100%;transition:left cubic-bezier(.4,0,1,1) .15s;width:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen{display:flex;flex:0 0 auto;justify-content:space-between;width:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen input{font-family:Sans-serif}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container{display:flex;flex-grow:1;position:relative}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container .tinymce-mobile-input-container-x{-ms-grid-row-align:center;align-self:center;background:inherit;border:none;border-radius:50%;color:#888;font-size:.6em;font-weight:700;height:100%;padding-right:2px;position:absolute;right:0}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container.tinymce-mobile-input-container-empty .tinymce-mobile-input-container-x{display:none}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous{align-items:center;display:flex}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous::before{align-items:center;display:flex;font-weight:700;height:100%;padding-left:.5em;padding-right:.5em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next.tinymce-mobile-toolbar-navigation-disabled::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous.tinymce-mobile-toolbar-navigation-disabled::before{visibility:hidden}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item{color:#ccc;font-size:10px;line-height:10px;margin:0 2px;padding-top:3px}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item.tinymce-mobile-dot-active{color:#d4dbd7}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-font::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-heading::before{margin-left:.5em;margin-right:.9em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-font::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-heading::before{margin-left:.9em;margin-right:.5em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider{display:flex;flex:1;margin-left:0;margin-right:0;padding:.28em 0;position:relative}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container{align-items:center;display:flex;flex-grow:1;height:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container .tinymce-mobile-slider-size-line{background:#ccc;display:flex;flex:1;height:.2em;margin-bottom:.3em;margin-top:.3em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container{padding-left:2em;padding-right:2em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container{align-items:center;display:flex;flex-grow:1;height:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container .tinymce-mobile-slider-gradient{background:linear-gradient(to right,red 0,#feff00 17%,#0f0 33%,#00feff 50%,#00f 67%,#ff00fe 83%,red 100%);display:flex;flex:1;height:.2em;margin-bottom:.3em;margin-top:.3em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-black{background:#000;height:.2em;margin-bottom:.3em;margin-top:.3em;width:1.2em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-white{background:#fff;height:.2em;margin-bottom:.3em;margin-top:.3em;width:1.2em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb{align-items:center;background-clip:padding-box;background-color:#455a64;border:.5em solid rgba(136,136,136,0);border-radius:3em;bottom:0;color:#fff;display:flex;height:.5em;justify-content:center;left:-10px;margin:auto;position:absolute;top:0;transition:border 120ms cubic-bezier(.39,.58,.57,1);width:.5em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb.tinymce-mobile-thumb-active{border:.5em solid rgba(136,136,136,.39)}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group>div{align-items:center;display:flex;height:100%;flex:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper{flex-direction:column;justify-content:center}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item{align-items:center;display:flex}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item:not(.tinymce-mobile-serialised-dialog){height:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-container{display:flex}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input{background:#fff;border:none;border-radius:0;color:#455a64;flex-grow:1;font-size:.85em;padding-bottom:.1em;padding-left:5px;padding-top:.1em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::-webkit-input-placeholder{color:#888}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input:-ms-input-placeholder{color:#888}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::placeholder{color:#888}.tinymce-mobile-dropup{background:#fff;display:flex;overflow:hidden;width:100%}.tinymce-mobile-dropup.tinymce-mobile-dropup-shrinking{transition:height .3s ease-out}.tinymce-mobile-dropup.tinymce-mobile-dropup-growing{transition:height .3s ease-in}.tinymce-mobile-dropup.tinymce-mobile-dropup-closed{flex-grow:0}.tinymce-mobile-dropup.tinymce-mobile-dropup-open:not(.tinymce-mobile-dropup-growing){flex-grow:1}.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height:200px}@media only screen and (orientation:landscape){.tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height:200px}}@media only screen and (min-device-width :320px) and (max-device-width :568px) and (orientation :landscape){.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height:150px}}.tinymce-mobile-styles-menu{font-family:sans-serif;outline:4px solid #000;overflow:hidden;position:relative;width:100%}.tinymce-mobile-styles-menu [role=menu]{display:flex;flex-direction:column;height:100%;position:absolute;width:100%}.tinymce-mobile-styles-menu [role=menu].transitioning{transition:transform .5s ease-in-out}.tinymce-mobile-styles-menu .tinymce-mobile-styles-item{border-bottom:1px solid #ddd;color:#455a64;cursor:pointer;display:flex;padding:1em 1em;position:relative}.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser .tinymce-mobile-styles-collapse-icon::before{color:#455a64;content:"\e314";font-family:tinymce-mobile,sans-serif}.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-styles-item-is-menu::after{color:#455a64;content:"\e315";font-family:tinymce-mobile,sans-serif;padding-left:1em;padding-right:1em;position:absolute;right:0}.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-format-matches::after{font-family:tinymce-mobile,sans-serif;padding-left:1em;padding-right:1em;position:absolute;right:0}.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser,.tinymce-mobile-styles-menu .tinymce-mobile-styles-separator{align-items:center;background:#fff;border-top:#455a64;color:#455a64;display:flex;min-height:2.5em;padding-left:1em;padding-right:1em}.tinymce-mobile-styles-menu [data-transitioning-destination=before][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=before]{transform:translate(-100%)}.tinymce-mobile-styles-menu [data-transitioning-destination=current][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=current]{transform:translate(0)}.tinymce-mobile-styles-menu [data-transitioning-destination=after][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=after]{transform:translate(100%)}@font-face{font-family:tinymce-mobile;font-style:normal;font-weight:400;src:url(fonts/tinymce-mobile.woff?8x92w3) format('woff')}@media (min-device-width:700px){.tinymce-mobile-outer-container,.tinymce-mobile-outer-container input{font-size:25px}}@media (max-device-width:700px){.tinymce-mobile-outer-container,.tinymce-mobile-outer-container input{font-size:18px}}.tinymce-mobile-icon{font-family:tinymce-mobile,sans-serif}.mixin-flex-and-centre{align-items:center;display:flex;justify-content:center}.mixin-flex-bar{align-items:center;display:flex;height:100%}.tinymce-mobile-outer-container .tinymce-mobile-editor-socket iframe{background-color:#fff;width:100%}.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{background-color:#207ab7;border-radius:50%;bottom:1em;color:#fff;font-size:1em;height:2.1em;position:fixed;right:2em;width:2.1em;align-items:center;display:flex;justify-content:center}@media only screen and (min-device-width:700px){.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{font-size:1.2em}}.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket{height:300px;overflow:hidden}.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket iframe{height:100%}.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-toolstrip{display:none}input[type=file]::-webkit-file-upload-button{display:none}@media only screen and (min-device-width :320px) and (max-device-width :568px) and (orientation :landscape){.tinymce-mobile-ios-container .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{bottom:50%}} \ No newline at end of file diff --git a/public/resource/tinymce/skins/ui/oxide-dark/content.inline.min.css b/public/resource/tinymce/skins/ui/oxide-dark/content.inline.min.css new file mode 100644 index 0000000..748f313 --- /dev/null +++ b/public/resource/tinymce/skins/ui/oxide-dark/content.inline.min.css @@ -0,0 +1,239 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.mce-content-body .mce-item-anchor{display: inline-block;width: 8px !important;height: 12px !important;padding: 0 2px;cursor: default;background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;-webkit-user-select: all;-moz-user-select: all;-ms-user-select: all;user-select: all;-webkit-user-modify: read-only;-moz-user-modify: read-only;} + +.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset: 1px;} + +.tox-comments-visible .tox-comment{background-color: #fff0b7;} + +.tox-comments-visible .tox-comment--active{background-color: #ffe168;} + +.tox-checklist>li:not(.tox-checklist--hidden){margin: .25em 0;list-style: none;} + +.tox-checklist>li:not(.tox-checklist--hidden)::before{position: absolute;width: 1em;height: 1em;margin-top: .125em;margin-left: -1.5em;cursor: pointer;background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A");background-size: 100%;content: '';} + +.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A");} + +[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-right: -1.5em;margin-left: 0;} + +code[class*=language-],pre[class*=language-]{font-family: Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size: .875rem;-webkit-hyphens: none;-ms-hyphens: none;hyphens: none;line-height: 1.5;word-spacing: normal;color: #000;text-shadow: 0 1px #fff;word-break: normal;word-wrap: normal;white-space: pre;-moz-tab-size: 4;tab-size: 4;} + +code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow: none;background: #b3d4fc;} + +code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow: none;background: #b3d4fc;}@media print{code[class*=language-],pre[class*=language-]{text-shadow: none;}} + +pre[class*=language-]{padding: 1em;margin: .5em 0;overflow: auto;} + +:not(pre)>code[class*=language-],pre[class*=language-]{background: 0 0 !important;border: 1px solid #ccc;} + +:not(pre)>code[class*=language-]{padding: .1em;border-radius: .3em;} + +.token.cdata,.token.comment,.token.doctype,.token.prolog{color: #708090;} + +.token.punctuation{color: #999;} + +.namespace{opacity: .7;} + +.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color: #905;} + +.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color: #690;} + +.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color: #a67f59;background: hsla(0,0%,100%,.5);} + +.token.atrule,.token.attr-value,.token.keyword{color: #07a;} + +.token.function{color: #dd4a68;} + +.token.important,.token.regex,.token.variable{color: #e90;} + +.token.bold,.token.important{font-weight: 700;} + +.token.italic{font-style: italic;} + +.token.entity{cursor: help;} + +:not([dir=rtl]) code[class*=language-],:not([dir=rtl]) pre[class*=language-]{text-align: left;direction: ltr;} + +[dir=rtl] code[class*=language-],[dir=rtl] pre[class*=language-]{text-align: right;direction: rtl;} + +.mce-content-body{overflow-wrap: break-word;word-wrap: break-word;} + +.mce-content-body .mce-visual-caret{position: absolute;background-color: #000;background-color: currentColor;} + +.mce-content-body .mce-visual-caret-hidden{display: none;} + +.mce-content-body [data-mce-caret]{position: absolute;top: 0;right: auto;left: -1000px;padding: 0;margin: 0;} + +.mce-content-body .mce-offscreen-selection{position: absolute;left: -9999999999px;max-width: 1000000px;} + +.mce-content-body [contentEditable=false]{cursor: default;} + +.mce-content-body [contentEditable=true]{cursor: text;} + +.tox-cursor-format-painter{cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"),default;} + +.mce-content-body figure.align-left{float: left;} + +.mce-content-body figure.align-right{float: right;} + +.mce-content-body figure.image.align-center{display: table;margin-right: auto;margin-left: auto;} + +.mce-preview-object{position: relative;display: inline-block;margin: 0 2px 0 2px;line-height: 0;border: 1px solid gray;} + +.mce-preview-object .mce-shim{position: absolute;top: 0;left: 0;width: 100%;height: 100%;background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);} + +.mce-preview-object[data-mce-selected="2"] .mce-shim{display: none;} + +.mce-object{background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;border: 1px dashed #aaa;} + +.mce-pagebreak{display: block;width: 100%;height: 5px;margin-top: 15px;cursor: default;border: 1px dashed #aaa;page-break-before: always;}@media print{.mce-pagebreak{border: 0;}} + +.tiny-pageembed .mce-shim{position: absolute;top: 0;left: 0;width: 100%;height: 100%;background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);} + +.tiny-pageembed[data-mce-selected="2"] .mce-shim{display: none;} + +.tiny-pageembed{position: relative;display: inline-block;} + +.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{position: relative;display: block;width: 100%;padding: 0;overflow: hidden;} + +.tiny-pageembed--16by9::before,.tiny-pageembed--1by1::before,.tiny-pageembed--21by9::before,.tiny-pageembed--4by3::before{display: block;content: "";} + +.tiny-pageembed--21by9::before{padding-top: 42.857143%;} + +.tiny-pageembed--16by9::before{padding-top: 56.25%;} + +.tiny-pageembed--4by3::before{padding-top: 75%;} + +.tiny-pageembed--1by1::before{padding-top: 100%;} + +.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{position: absolute;top: 0;left: 0;width: 100%;height: 100%;border: 0;} + +.mce-content-body div.mce-resizehandle{position: absolute;z-index: 10000;width: 10px;height: 10px;background-color: #4099ff;border-color: #4099ff;border-style: solid;border-width: 1px;box-sizing: border-box;} + +.mce-content-body div.mce-resizehandle:hover{background-color: #4099ff;} + +.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor: nwse-resize;} + +.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor: nesw-resize;} + +.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor: nwse-resize;} + +.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor: nesw-resize;} + +.mce-content-body .mce-clonedresizable{position: absolute;z-index: 10000;outline: 1px dashed #000;opacity: .5;} + +.mce-content-body .mce-resize-helper{position: absolute;z-index: 10001;display: none;padding: 5px;margin: 5px 10px;font-family: sans-serif;font-size: 12px;line-height: 14px;color: #fff;white-space: nowrap;background: #555;background: rgba(0,0,0,.75);border: 1px;border-radius: 3px;} + +.mce-match-marker{color: #fff;background: #aaa;} + +.mce-match-marker-selected{color: #fff;background: #39f;} + +.mce-content-body img[data-mce-selected],.mce-content-body table[data-mce-selected]{outline: 3px solid #b4d7ff;} + +.mce-content-body hr[data-mce-selected]{outline: 3px solid #b4d7ff;outline-offset: 1px;} + +.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline: 3px solid #b4d7ff;} + +.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline: 3px solid #b4d7ff;} + +.mce-content-body [contentEditable=false][data-mce-selected]{cursor: not-allowed;outline: 3px solid #b4d7ff;} + +.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline: 0;} + +.mce-content-body [data-mce-selected=inline-boundary]{background-color: #b4d7ff;} + +.mce-content-body .mce-edit-focus{outline: 3px solid #b4d7ff;} + +.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{background-color: #b4d7ff !important;} + +.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background: 0 0;} + +.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background: 0 0;} + +.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{-webkit-touch-callout: none;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;} + +.mce-content-body img::-moz-selection{background: 0 0;} + +.mce-content-body img::selection{background: 0 0;} + +.ephox-snooker-resizer-bar{background-color: #b4d7ff;opacity: 0;} + +.ephox-snooker-resizer-cols{cursor: col-resize;} + +.ephox-snooker-resizer-rows{cursor: row-resize;} + +.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity: 1;} + +.mce-spellchecker-word{height: 2rem;cursor: default;background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.5'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position: 0 calc(100% + 1px);background-repeat: repeat-x;background-size: auto 6px;} + +.mce-spellchecker-grammar{cursor: default;background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23008800'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position: 0 calc(100% + 1px);background-repeat: repeat-x;background-size: auto 6px;} + +.mce-toc{border: 1px solid gray;} + +.mce-toc h2{margin: 4px;} + +.mce-toc li{list-style-type: none;} + +.mce-item-table,.mce-item-table caption,.mce-item-table td,.mce-item-table th{border: 1px dashed #bbb;} + +.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{padding-top: 10px;margin-left: 3px;background-repeat: no-repeat;border: 1px dashed #bbb;} + +.mce-visualblocks p{background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7);} + +.mce-visualblocks h1{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==);} + +.mce-visualblocks h2{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==);} + +.mce-visualblocks h3{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7);} + +.mce-visualblocks h4{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==);} + +.mce-visualblocks h5{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==);} + +.mce-visualblocks h6{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==);} + +.mce-visualblocks div:not([data-mce-bogus]){background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7);} + +.mce-visualblocks section{background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=);} + +.mce-visualblocks article{background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7);} + +.mce-visualblocks blockquote{background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7);} + +.mce-visualblocks address{background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=);} + +.mce-visualblocks pre{background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==);} + +.mce-visualblocks figure{background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7);} + +.mce-visualblocks figcaption{border: 1px dashed #bbb;} + +.mce-visualblocks hgroup{background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7);} + +.mce-visualblocks aside{background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=);} + +.mce-visualblocks ul{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==);} + +.mce-visualblocks ol{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==);} + +.mce-visualblocks dl{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==);} + +.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left: 3px;} + +.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x: right;margin-right: 3px;} + +.mce-nbsp,.mce-shy{background: #aaa;} + +.mce-shy::after{content: '-';} + +.tox-toolbar-dock-fadeout{opacity: 0;visibility: hidden;} + +.tox-toolbar-dock-fadein{opacity: 1;visibility: visible;} + +.tox-toolbar-dock-transition{transition: visibility 0s linear .3s,opacity .3s ease;} + +.tox-toolbar-dock-transition.tox-toolbar-dock-fadein{transition-delay: 0s;} diff --git a/public/resource/tinymce/skins/ui/oxide-dark/content.min.css b/public/resource/tinymce/skins/ui/oxide-dark/content.min.css new file mode 100644 index 0000000..6e7165f --- /dev/null +++ b/public/resource/tinymce/skins/ui/oxide-dark/content.min.css @@ -0,0 +1,235 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.mce-content-body .mce-item-anchor{display: inline-block;width: 8px !important;height: 12px !important;padding: 0 2px;cursor: default;background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;-webkit-user-select: all;-moz-user-select: all;-ms-user-select: all;user-select: all;-webkit-user-modify: read-only;-moz-user-modify: read-only;} + +.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset: 1px;} + +.tox-comments-visible .tox-comment{background-color: #fff0b7;} + +.tox-comments-visible .tox-comment--active{background-color: #ffe168;} + +.tox-checklist>li:not(.tox-checklist--hidden){margin: .25em 0;list-style: none;} + +.tox-checklist>li:not(.tox-checklist--hidden)::before{position: absolute;width: 1em;height: 1em;margin-top: .125em;margin-left: -1.5em;cursor: pointer;background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A");background-size: 100%;content: '';} + +.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A");} + +[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-right: -1.5em;margin-left: 0;} + +code[class*=language-],pre[class*=language-]{font-family: Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size: .875rem;-webkit-hyphens: none;-ms-hyphens: none;hyphens: none;line-height: 1.5;word-spacing: normal;color: #000;text-shadow: 0 1px #fff;word-break: normal;word-wrap: normal;white-space: pre;-moz-tab-size: 4;tab-size: 4;} + +code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow: none;background: #b3d4fc;} + +code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow: none;background: #b3d4fc;}@media print{code[class*=language-],pre[class*=language-]{text-shadow: none;}} + +pre[class*=language-]{padding: 1em;margin: .5em 0;overflow: auto;} + +:not(pre)>code[class*=language-],pre[class*=language-]{background: 0 0 !important;border: 1px solid #ccc;} + +:not(pre)>code[class*=language-]{padding: .1em;border-radius: .3em;} + +.token.cdata,.token.comment,.token.doctype,.token.prolog{color: #708090;} + +.token.punctuation{color: #999;} + +.namespace{opacity: .7;} + +.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color: #905;} + +.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color: #690;} + +.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color: #a67f59;background: hsla(0,0%,100%,.5);} + +.token.atrule,.token.attr-value,.token.keyword{color: #07a;} + +.token.function{color: #dd4a68;} + +.token.important,.token.regex,.token.variable{color: #e90;} + +.token.bold,.token.important{font-weight: 700;} + +.token.italic{font-style: italic;} + +.token.entity{cursor: help;} + +:not([dir=rtl]) code[class*=language-],:not([dir=rtl]) pre[class*=language-]{text-align: left;direction: ltr;} + +[dir=rtl] code[class*=language-],[dir=rtl] pre[class*=language-]{text-align: right;direction: rtl;} + +.mce-content-body{overflow-wrap: break-word;word-wrap: break-word;} + +.mce-content-body .mce-visual-caret{position: absolute;background-color: #000;background-color: currentColor;} + +.mce-content-body .mce-visual-caret-hidden{display: none;} + +.mce-content-body [data-mce-caret]{position: absolute;top: 0;right: auto;left: -1000px;padding: 0;margin: 0;} + +.mce-content-body .mce-offscreen-selection{position: absolute;left: -9999999999px;max-width: 1000000px;} + +.mce-content-body [contentEditable=false]{cursor: default;} + +.mce-content-body [contentEditable=true]{cursor: text;} + +.tox-cursor-format-painter{cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"),default;} + +.mce-content-body figure.align-left{float: left;} + +.mce-content-body figure.align-right{float: right;} + +.mce-content-body figure.image.align-center{display: table;margin-right: auto;margin-left: auto;} + +.mce-preview-object{position: relative;display: inline-block;margin: 0 2px 0 2px;line-height: 0;border: 1px solid gray;} + +.mce-preview-object .mce-shim{position: absolute;top: 0;left: 0;width: 100%;height: 100%;background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);} + +.mce-preview-object[data-mce-selected="2"] .mce-shim{display: none;} + +.mce-object{background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;border: 1px dashed #aaa;} + +.mce-pagebreak{display: block;width: 100%;height: 5px;margin-top: 15px;cursor: default;border: 1px dashed #aaa;page-break-before: always;}@media print{.mce-pagebreak{border: 0;}} + +.tiny-pageembed .mce-shim{position: absolute;top: 0;left: 0;width: 100%;height: 100%;background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);} + +.tiny-pageembed[data-mce-selected="2"] .mce-shim{display: none;} + +.tiny-pageembed{position: relative;display: inline-block;} + +.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{position: relative;display: block;width: 100%;padding: 0;overflow: hidden;} + +.tiny-pageembed--16by9::before,.tiny-pageembed--1by1::before,.tiny-pageembed--21by9::before,.tiny-pageembed--4by3::before{display: block;content: "";} + +.tiny-pageembed--21by9::before{padding-top: 42.857143%;} + +.tiny-pageembed--16by9::before{padding-top: 56.25%;} + +.tiny-pageembed--4by3::before{padding-top: 75%;} + +.tiny-pageembed--1by1::before{padding-top: 100%;} + +.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{position: absolute;top: 0;left: 0;width: 100%;height: 100%;border: 0;} + +.mce-content-body div.mce-resizehandle{position: absolute;z-index: 10000;width: 10px;height: 10px;background-color: #4099ff;border-color: #4099ff;border-style: solid;border-width: 1px;box-sizing: border-box;} + +.mce-content-body div.mce-resizehandle:hover{background-color: #4099ff;} + +.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor: nwse-resize;} + +.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor: nesw-resize;} + +.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor: nwse-resize;} + +.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor: nesw-resize;} + +.mce-content-body .mce-clonedresizable{position: absolute;z-index: 10000;outline: 1px dashed #000;opacity: .5;} + +.mce-content-body .mce-resize-helper{position: absolute;z-index: 10001;display: none;padding: 5px;margin: 5px 10px;font-family: sans-serif;font-size: 12px;line-height: 14px;color: #fff;white-space: nowrap;background: #555;background: rgba(0,0,0,.75);border: 1px;border-radius: 3px;} + +.mce-match-marker{color: #fff;background: #aaa;} + +.mce-match-marker-selected{color: #fff;background: #39f;} + +.mce-content-body img[data-mce-selected],.mce-content-body table[data-mce-selected]{outline: 3px solid #b4d7ff;} + +.mce-content-body hr[data-mce-selected]{outline: 3px solid #b4d7ff;outline-offset: 1px;} + +.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline: 3px solid #b4d7ff;} + +.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline: 3px solid #b4d7ff;} + +.mce-content-body [contentEditable=false][data-mce-selected]{cursor: not-allowed;outline: 3px solid #b4d7ff;} + +.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline: 0;} + +.mce-content-body [data-mce-selected=inline-boundary]{background-color: #b4d7ff;} + +.mce-content-body .mce-edit-focus{outline: 3px solid #b4d7ff;} + +.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{background-color: #b4d7ff !important;} + +.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background: 0 0;} + +.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background: 0 0;} + +.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{-webkit-touch-callout: none;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;} + +.mce-content-body img::-moz-selection{background: 0 0;} + +.mce-content-body img::selection{background: 0 0;} + +.ephox-snooker-resizer-bar{background-color: #b4d7ff;opacity: 0;} + +.ephox-snooker-resizer-cols{cursor: col-resize;} + +.ephox-snooker-resizer-rows{cursor: row-resize;} + +.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity: 1;} + +.mce-spellchecker-word{height: 2rem;cursor: default;background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.5'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position: 0 calc(100% + 1px);background-repeat: repeat-x;background-size: auto 6px;} + +.mce-spellchecker-grammar{cursor: default;background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23008800'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position: 0 calc(100% + 1px);background-repeat: repeat-x;background-size: auto 6px;} + +.mce-toc{border: 1px solid gray;} + +.mce-toc h2{margin: 4px;} + +.mce-toc li{list-style-type: none;} + +.mce-item-table,.mce-item-table caption,.mce-item-table td,.mce-item-table th{border: 1px dashed #bbb;} + +.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{padding-top: 10px;margin-left: 3px;background-repeat: no-repeat;border: 1px dashed #bbb;} + +.mce-visualblocks p{background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7);} + +.mce-visualblocks h1{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==);} + +.mce-visualblocks h2{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==);} + +.mce-visualblocks h3{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7);} + +.mce-visualblocks h4{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==);} + +.mce-visualblocks h5{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==);} + +.mce-visualblocks h6{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==);} + +.mce-visualblocks div:not([data-mce-bogus]){background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7);} + +.mce-visualblocks section{background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=);} + +.mce-visualblocks article{background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7);} + +.mce-visualblocks blockquote{background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7);} + +.mce-visualblocks address{background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=);} + +.mce-visualblocks pre{background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==);} + +.mce-visualblocks figure{background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7);} + +.mce-visualblocks figcaption{border: 1px dashed #bbb;} + +.mce-visualblocks hgroup{background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7);} + +.mce-visualblocks aside{background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=);} + +.mce-visualblocks ul{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==);} + +.mce-visualblocks ol{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==);} + +.mce-visualblocks dl{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==);} + +.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left: 3px;} + +.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x: right;margin-right: 3px;} + +.mce-nbsp,.mce-shy{background: #aaa;} + +.mce-shy::after{content: '-';} + +body{font-family: sans-serif;} + +table{border-collapse: collapse;} diff --git a/public/resource/tinymce/skins/ui/oxide-dark/content.mobile.min.css b/public/resource/tinymce/skins/ui/oxide-dark/content.mobile.min.css new file mode 100644 index 0000000..c052252 --- /dev/null +++ b/public/resource/tinymce/skins/ui/oxide-dark/content.mobile.min.css @@ -0,0 +1,17 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{position: absolute;display: inline-block;background-color: green;opacity: .5;} + +body{-webkit-text-size-adjust: none;} + +body img{max-width: 96vw;} + +body table img{max-width: 95%;} + +body{font-family: sans-serif;} + +table{border-collapse: collapse;} diff --git a/public/resource/tinymce/skins/ui/oxide-dark/skin.min.css b/public/resource/tinymce/skins/ui/oxide-dark/skin.min.css new file mode 100644 index 0000000..d8dc9b2 --- /dev/null +++ b/public/resource/tinymce/skins/ui/oxide-dark/skin.min.css @@ -0,0 +1,875 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tox{font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size: 16px;font-style: normal;font-weight: 400;line-height: normal;color: #222f3e;text-decoration: none;text-shadow: none;text-transform: none;white-space: normal;vertical-align: initial;cursor: auto;box-sizing: content-box;-webkit-tap-highlight-color: transparent;} + +.tox :not(svg){font-family: inherit;font-size: inherit;font-style: inherit;font-weight: inherit;line-height: inherit;color: inherit;text-align: inherit;text-decoration: inherit;text-shadow: inherit;text-transform: inherit;white-space: inherit;vertical-align: inherit;cursor: inherit;box-sizing: inherit;direction: inherit;-webkit-tap-highlight-color: inherit;} + +.tox :not(svg){position: static;float: none;width: auto;height: auto;max-width: none;padding: 0;margin: 0;background: 0 0;border: 0;outline: 0;} + +.tox:not([dir=rtl]){text-align: left;direction: ltr;} + +.tox[dir=rtl]{text-align: right;direction: rtl;} + +.tox-tinymce{position: relative;display: flex;overflow: hidden;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;border: 1px solid #000;border-radius: 0;visibility: inherit !important;box-shadow: none;box-sizing: border-box;flex-direction: column;} + +.tox-editor-container{display: flex;flex: 1 1 auto;flex-direction: column;overflow: hidden;} + +.tox-editor-container>:first-child{border-top: none !important;} + +.tox-tinymce-aux{font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;} + +.tox-tinymce :focus,.tox-tinymce-aux :focus{outline: 0;} + +button::-moz-focus-inner{border: 0;} + +.tox-silver-sink{z-index: 1300;} + +.tox .tox-anchorbar{display: flex;flex: 0 0 auto;} + +.tox .tox-bar{display: flex;flex: 0 0 auto;} + +.tox .tox-button{display: inline-block;padding: 4px 16px;margin: 0;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size: 14px;font-weight: 700;line-height: 24px;letter-spacing: 1;color: #fff;text-align: center;text-decoration: none;text-transform: capitalize;white-space: nowrap;cursor: pointer;background-color: #207ab7;background-image: none;background-position: none;background-repeat: none;border-color: #207ab7;border-style: solid;border-width: 1px;border-radius: 3px;outline: 0;box-shadow: none;box-sizing: border-box;} + +.tox .tox-button[disabled]{color: rgba(255,255,255,.5);cursor: not-allowed;background-color: #207ab7;background-image: none;border-color: #207ab7;box-shadow: none;} + +.tox .tox-button:focus:not(:disabled){color: #fff;background-color: #1c6ca1;background-image: none;border-color: #1c6ca1;box-shadow: none;} + +.tox .tox-button:hover:not(:disabled){color: #fff;background-color: #1c6ca1;background-image: none;border-color: #1c6ca1;box-shadow: none;} + +.tox .tox-button:active:not(:disabled){color: #fff;background-color: #185d8c;background-image: none;border-color: #185d8c;box-shadow: none;} + +.tox .tox-button--secondary{padding: 4px 16px;color: #fff;text-decoration: none;text-transform: capitalize;background-color: #3d546f;background-image: none;background-position: none;background-repeat: none;border-color: #3d546f;border-style: solid;border-width: 1px;border-radius: 3px;outline: 0;box-shadow: none;} + +.tox .tox-button--secondary[disabled]{color: rgba(255,255,255,.5);background-color: #3d546f;background-image: none;border-color: #3d546f;box-shadow: none;} + +.tox .tox-button--secondary:focus:not(:disabled){color: #fff;background-color: #34485f;background-image: none;border-color: #34485f;box-shadow: none;} + +.tox .tox-button--secondary:hover:not(:disabled){color: #fff;background-color: #34485f;background-image: none;border-color: #34485f;box-shadow: none;} + +.tox .tox-button--secondary:active:not(:disabled){color: #fff;background-color: #2b3b4e;background-image: none;border-color: #2b3b4e;box-shadow: none;} + +.tox .tox-button--icon,.tox .tox-button.tox-button--icon,.tox .tox-button.tox-button--secondary.tox-button--icon{padding: 4px;} + +.tox .tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg{display: block;fill: currentColor;} + +.tox .tox-button-link{display: inline-block;padding: 0;margin: 0;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size: 16px;font-weight: 400;line-height: 1.3;white-space: nowrap;cursor: pointer;background: 0;border: none;box-sizing: border-box;} + +.tox .tox-button-link--sm{font-size: 14px;} + +.tox .tox-button--naked{color: #fff;background-color: transparent;border-color: transparent;box-shadow: unset;} + +.tox .tox-button--naked:hover:not(:disabled){color: #fff;background-color: #34485f;border-color: #34485f;box-shadow: none;} + +.tox .tox-button--naked:focus:not(:disabled){color: #fff;background-color: #34485f;border-color: #34485f;box-shadow: none;} + +.tox .tox-button--naked:active:not(:disabled){color: #fff;background-color: #2b3b4e;border-color: #2b3b4e;box-shadow: none;} + +.tox .tox-button--naked .tox-icon svg{fill: currentColor;} + +.tox .tox-button--naked.tox-button--icon{color: currentColor;} + +.tox .tox-button--naked.tox-button--icon:hover:not(:disabled){color: #fff;} + +.tox .tox-checkbox{display: flex;height: 36px;min-width: 36px;cursor: pointer;border-radius: 3px;align-items: center;} + +.tox .tox-checkbox__input{position: absolute;top: auto;left: -10000px;width: 1px;height: 1px;overflow: hidden;} + +.tox .tox-checkbox__icons{width: 24px;height: 24px;padding: calc(4px - 1px);border-radius: 3px;box-shadow: 0 0 0 2px transparent;box-sizing: content-box;} + +.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display: block;fill: rgba(255,255,255,.2);} + +.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display: none;fill: #207ab7;} + +.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg{display: none;fill: #207ab7;} + +.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display: none;} + +.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__checked svg{display: block;} + +.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display: none;} + +.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display: block;} + +.tox input.tox-checkbox__input:focus+.tox-checkbox__icons{padding: calc(4px - 1px);border-radius: 3px;box-shadow: inset 0 0 0 1px #207ab7;} + +.tox:not([dir=rtl]) .tox-checkbox__label{margin-left: 4px;} + +.tox:not([dir=rtl]) .tox-bar .tox-checkbox{margin-left: 4px;} + +.tox[dir=rtl] .tox-checkbox__label{margin-right: 4px;} + +.tox[dir=rtl] .tox-bar .tox-checkbox{margin-right: 4px;} + +.tox .tox-collection--toolbar .tox-collection__group{display: flex;padding: 0;} + +.tox .tox-collection--grid .tox-collection__group{display: flex;max-height: 208px;padding: 0;overflow-x: hidden;overflow-y: auto;flex-wrap: wrap;} + +.tox .tox-collection--list .tox-collection__group{padding: 4px 0;border-color: #1a1a1a;border-style: solid;border-top-width: 1px;border-right-width: 0;border-bottom-width: 0;border-left-width: 0;} + +.tox .tox-collection--list .tox-collection__group:first-child{border-top-width: 0;} + +.tox .tox-collection__group-heading{padding: 4px 8px;margin-top: -4px;margin-bottom: 4px;font-size: 12px;font-style: normal;font-weight: 400;color: #fff;text-transform: none;cursor: default;background-color: #333;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;-webkit-touch-callout: none;} + +.tox .tox-collection__item{display: flex;color: #fff;cursor: pointer;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;align-items: center;-webkit-touch-callout: none;} + +.tox .tox-collection--list .tox-collection__item{padding: 4px 8px;} + +.tox .tox-collection--toolbar .tox-collection__item{padding: 4px;border-radius: 3px;} + +.tox .tox-collection--grid .tox-collection__item{padding: 4px;border-radius: 3px;} + +.tox .tox-collection--list .tox-collection__item--enabled{color: contrast(inherit,#222f3e,#fff);background-color: inherit;} + +.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled){color: #fff;background-color: #434e5b;} + +.tox .tox-collection--toolbar .tox-collection__item--enabled{color: #fff;background-color: #6f7882;} + +.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled){color: #fff;background-color: #434e5b;} + +.tox .tox-collection--grid .tox-collection__item--enabled{color: #fff;background-color: #6f7882;} + +.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled){color: #fff;background-color: #434e5b;} + +.tox .tox-collection__item--state-disabled{color: rgba(255,255,255,.5);cursor: default;background-color: transparent;} + +.tox .tox-collection__item-icon{display: flex;width: 24px;height: 24px;align-items: center;justify-content: center;} + +.tox .tox-collection__item-icon svg{fill: currentColor;} + +.tox .tox-collection--toolbar-lg .tox-collection__item-icon{width: 48px;height: 48px;} + +.tox .tox-collection__item[role=menuitemcheckbox]:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg{display: none;} + +.tox .tox-collection__item-label{display: inline-block;font-size: 14px;font-style: normal;font-weight: 400;line-height: 24px;color: currentColor;text-transform: none;word-break: break-all;flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-collection__item-accessory{display: inline-block;height: 24px;font-size: 14px;line-height: 24px;color: rgba(255,255,255,.5);text-transform: normal;} + +.tox .tox-collection__item-caret{align-items: center;display: flex;min-height: 24px;} + +.tox .tox-collection__item-caret::after{min-height: inherit;font-size: 0;content: '';} + +.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>:not(:first-child){margin-left: 8px;} + +.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item-label:first-child{margin-left: 4px;} + +.tox:not([dir=rtl]) .tox-collection__item-accessory{margin-left: 16px;text-align: right;} + +.tox:not([dir=rtl]) .tox-collection__item-caret{margin-left: 16px;} + +.tox[dir=rtl] .tox-collection--list .tox-collection__item>:not(:first-child){margin-right: 8px;} + +.tox[dir=rtl] .tox-collection--list .tox-collection__item-label:first-child{margin-right: 4px;} + +.tox[dir=rtl] .tox-collection__item-icon-rtl .tox-collection__item-icon svg{transform: rotateY(180deg);} + +.tox[dir=rtl] .tox-collection__item-accessory{margin-right: 16px;text-align: left;} + +.tox[dir=rtl] .tox-collection__item-caret{margin-right: 16px;transform: rotateY(180deg);} + +.tox .tox-color-picker-container{display: flex;flex-direction: row;height: 225px;margin: 0;} + +.tox .tox-sv-palette{display: flex;height: 100%;box-sizing: border-box;} + +.tox .tox-sv-palette-spectrum{height: 100%;} + +.tox .tox-sv-palette,.tox .tox-sv-palette-spectrum{width: 225px;} + +.tox .tox-sv-palette-thumb{position: absolute;width: 12px;height: 12px;background: 0 0;border: 1px solid #000;border-radius: 50%;box-sizing: content-box;} + +.tox .tox-sv-palette-inner-thumb{position: absolute;width: 10px;height: 10px;border: 1px solid #fff;border-radius: 50%;} + +.tox .tox-hue-slider{width: 25px;height: 100%;box-sizing: border-box;} + +.tox .tox-hue-slider-spectrum{width: 100%;height: 100%;background: linear-gradient(to bottom,red,#ff0080,#f0f,#8000ff,#00f,#0080ff,#0ff,#00ff80,#0f0,#80ff00,#ff0,#ff8000,red);} + +.tox .tox-hue-slider,.tox .tox-hue-slider-spectrum{width: 20px;} + +.tox .tox-hue-slider-thumb{width: 100%;height: 4px;background: #fff;border: 1px solid #000;box-sizing: content-box;} + +.tox .tox-rgb-form{display: flex;flex-direction: column;justify-content: space-between;} + +.tox .tox-rgb-form div{display: flex;width: inherit;margin-bottom: 5px;align-items: center;justify-content: space-between;} + +.tox .tox-rgb-form input{width: 6em;} + +.tox .tox-rgb-form input.tox-invalid{border: 1px solid red !important;} + +.tox .tox-rgb-form .tox-rgba-preview{margin-bottom: 0;border: 1px solid #000;flex-grow: 2;} + +.tox:not([dir=rtl]) .tox-sv-palette{margin-right: 15px;} + +.tox:not([dir=rtl]) .tox-hue-slider{margin-right: 15px;} + +.tox:not([dir=rtl]) .tox-hue-slider-thumb{margin-left: -1px;} + +.tox:not([dir=rtl]) .tox-rgb-form label{margin-right: .5em;} + +.tox[dir=rtl] .tox-sv-palette{margin-left: 15px;} + +.tox[dir=rtl] .tox-hue-slider{margin-left: 15px;} + +.tox[dir=rtl] .tox-hue-slider-thumb{margin-right: -1px;} + +.tox[dir=rtl] .tox-rgb-form label{margin-left: .5em;} + +.tox .tox-toolbar .tox-swatches,.tox .tox-toolbar__overflow .tox-swatches,.tox .tox-toolbar__primary .tox-swatches{margin: 2px 0 3px 4px;} + +.tox .tox-collection--list .tox-collection__group .tox-swatches-menu{margin: -4px 0;border: 0;} + +.tox .tox-swatches__row{display: flex;} + +.tox .tox-swatch{width: 30px;height: 30px;transition: transform .15s,box-shadow .15s;} + +.tox .tox-swatch:focus,.tox .tox-swatch:hover{transform: scale(.8);box-shadow: 0 0 0 1px rgba(127,127,127,.3) inset;} + +.tox .tox-swatch--remove{align-items: center;display: flex;justify-content: center;} + +.tox .tox-swatch--remove svg path{stroke: #e74c3c;} + +.tox .tox-swatches__picker-btn{display: flex;width: 30px;height: 30px;padding: 0;cursor: pointer;background-color: transparent;border: 0;outline: 0;align-items: center;justify-content: center;} + +.tox .tox-swatches__picker-btn svg{width: 24px;height: 24px;} + +.tox .tox-swatches__picker-btn:hover{background: #434e5b;} + +.tox:not([dir=rtl]) .tox-swatches__picker-btn{margin-left: auto;} + +.tox[dir=rtl] .tox-swatches__picker-btn{margin-right: auto;} + +.tox .tox-comment-thread{position: relative;background: #2b3b4e;} + +.tox .tox-comment-thread>:not(:first-child){margin-top: 8px;} + +.tox .tox-comment{position: relative;padding: 8px 8px 16px 8px;background: #2b3b4e;border: 1px solid #000;border-radius: 3px;box-shadow: 0 4px 8px 0 rgba(34,47,62,.1);} + +.tox .tox-comment__header{display: flex;color: #fff;align-items: center;justify-content: space-between;} + +.tox .tox-comment__date{font-size: 12px;color: rgba(255,255,255,.5);} + +.tox .tox-comment__body{position: relative;margin-top: 8px;font-size: 14px;font-style: normal;font-weight: 400;line-height: 1.3;color: #fff;text-transform: initial;} + +.tox .tox-comment__body textarea{width: 100%;white-space: normal;resize: none;} + +.tox .tox-comment__expander{padding-top: 8px;} + +.tox .tox-comment__expander p{font-size: 14px;font-style: normal;color: rgba(255,255,255,.5);} + +.tox .tox-comment__body p{margin: 0;} + +.tox .tox-comment__buttonspacing{padding-top: 16px;text-align: center;} + +.tox .tox-comment-thread__overlay::after{position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 5;display: flex;background: #2b3b4e;content: "";opacity: .9;} + +.tox .tox-comment__reply{display: flex;flex-shrink: 0;flex-wrap: wrap;justify-content: flex-end;margin-top: 8px;} + +.tox .tox-comment__reply>:first-child{width: 100%;margin-bottom: 8px;} + +.tox .tox-comment__edit{display: flex;flex-wrap: wrap;justify-content: flex-end;margin-top: 16px;} + +.tox .tox-comment__gradient::after{position: absolute;bottom: 0;display: block;width: 100%;height: 5em;margin-top: -40px;background: linear-gradient(rgba(43,59,78,0),#2b3b4e);content: "";} + +.tox .tox-comment__overlay{position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 5;display: flex;text-align: center;background: #2b3b4e;opacity: .9;flex-direction: column;flex-grow: 1;} + +.tox .tox-comment__loading-text{position: relative;display: flex;color: #fff;align-items: center;flex-direction: column;} + +.tox .tox-comment__loading-text>div{padding-bottom: 16px;} + +.tox .tox-comment__overlaytext{position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 10;padding: 1em;font-size: 14px;flex-direction: column;} + +.tox .tox-comment__overlaytext p{color: #fff;text-align: center;background-color: #2b3b4e;box-shadow: 0 0 8px 8px #2b3b4e;} + +.tox .tox-comment__overlaytext div:nth-of-type(2){font-size: .8em;} + +.tox .tox-comment__busy-spinner{position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 1103;display: flex;background-color: #2b3b4e;align-items: center;justify-content: center;} + +.tox .tox-comment__scroll{display: flex;flex-direction: column;flex-shrink: 1;overflow: auto;} + +.tox .tox-conversations{margin: 8px;} + +.tox:not([dir=rtl]) .tox-comment__edit{margin-left: 8px;} + +.tox:not([dir=rtl]) .tox-comment__buttonspacing>:last-child,.tox:not([dir=rtl]) .tox-comment__edit>:last-child,.tox:not([dir=rtl]) .tox-comment__reply>:last-child{margin-left: 8px;} + +.tox[dir=rtl] .tox-comment__edit{margin-right: 8px;} + +.tox[dir=rtl] .tox-comment__buttonspacing>:last-child,.tox[dir=rtl] .tox-comment__edit>:last-child,.tox[dir=rtl] .tox-comment__reply>:last-child{margin-right: 8px;} + +.tox .tox-user{align-items: center;display: flex;} + +.tox .tox-user__avatar svg{fill: rgba(255,255,255,.5);} + +.tox .tox-user__name{font-size: 12px;font-style: normal;font-weight: 700;color: rgba(255,255,255,.5);text-transform: uppercase;} + +.tox:not([dir=rtl]) .tox-user__avatar svg{margin-right: 8px;} + +.tox:not([dir=rtl]) .tox-user__avatar+.tox-user__name{margin-left: 8px;} + +.tox[dir=rtl] .tox-user__avatar svg{margin-left: 8px;} + +.tox[dir=rtl] .tox-user__avatar+.tox-user__name{margin-right: 8px;} + +.tox .tox-dialog-wrap{position: fixed;top: 0;right: 0;bottom: 0;left: 0;z-index: 1100;display: flex;align-items: center;justify-content: center;} + +.tox .tox-dialog-wrap__backdrop{position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 1101;background-color: rgba(34,47,62,.75);} + +.tox .tox-dialog{position: relative;z-index: 1102;display: flex;width: 95vw;max-width: 480px;max-height: 100%;overflow: hidden;background-color: #2b3b4e;border-color: #000;border-style: solid;border-width: 1px;border-radius: 3px;box-shadow: 0 16px 16px -10px rgba(34,47,62,.15),0 0 40px 1px rgba(34,47,62,.15);flex-direction: column;} + +.tox .tox-dialog__header{position: relative;display: flex;padding: 8px 16px 0 16px;margin-bottom: 16px;font-size: 16px;color: #fff;background-color: #2b3b4e;border-bottom: none;align-items: center;justify-content: space-between;} + +.tox .tox-dialog__header .tox-button{z-index: 1;} + +.tox .tox-dialog__draghandle{position: absolute;top: 0;left: 0;width: 100%;height: 100%;cursor: grab;} + +.tox .tox-dialog__draghandle:active{cursor: grabbing;} + +.tox .tox-dialog__dismiss{margin-left: auto;} + +.tox .tox-dialog__title{margin: 0;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size: 20px;font-style: normal;font-weight: 400;line-height: 1.3;text-transform: normal;} + +.tox .tox-dialog__body{display: flex;min-width: 0;padding: 0 16px;font-size: 16px;font-style: normal;font-weight: 400;line-height: 1.3;color: #fff;text-align: left;text-transform: normal;flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-dialog__body-nav{align-items: flex-start;display: flex;flex-direction: column;} + +.tox .tox-dialog__body-nav-item{display: inline-block;margin-bottom: 8px;font-size: 14px;line-height: 1.3;color: rgba(255,255,255,.5);text-decoration: none;border-bottom: 2px solid transparent;} + +.tox .tox-dialog__body-nav-item--active{color: #207ab7;border-bottom: 2px solid #207ab7;} + +.tox .tox-dialog__body-content{display: flex;flex: 1;flex-direction: column;-ms-flex-preferred-size: auto;max-height: 650px;overflow: auto;} + +.tox .tox-dialog__body-content>*{margin-top: 16px;margin-bottom: 0;} + +.tox .tox-dialog__body-content>:first-child{margin-top: 0;} + +.tox .tox-dialog__body-content>:last-child{margin-bottom: 0;} + +.tox .tox-dialog__body-content>:only-child{margin-top: 0;margin-bottom: 0;} + +.tox .tox-dialog--width-lg{height: 650px;max-width: 1200px;} + +.tox .tox-dialog--width-md{max-width: 800px;} + +.tox .tox-dialog--width-md .tox-dialog__body-content{overflow: auto;} + +.tox .tox-dialog__body-content--centered{text-align: center;} + +.tox .tox-dialog__body-content--spacious{margin-bottom: 16px;} + +.tox .tox-dialog__footer{display: flex;padding: 8px 16px;margin-top: 16px;background-color: #2b3b4e;border-top: 1px solid #000;align-items: center;justify-content: space-between;} + +.tox .tox-dialog__busy-spinner{position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 1103;display: flex;background-color: rgba(34,47,62,.75);align-items: center;justify-content: center;} + +.tox .tox-dialog__table{width: 100%;border-collapse: collapse;} + +.tox .tox-dialog__table thead th{padding-bottom: 8px;font-weight: 700;} + +.tox .tox-dialog__table tbody tr{border-bottom: 1px solid #000;} + +.tox .tox-dialog__table tbody tr:last-child{border-bottom: none;} + +.tox .tox-dialog__table td{padding-top: 8px;padding-bottom: 8px;} + +.tox .tox-dialog__popups{position: absolute;z-index: 1100;width: 100%;} + +.tox .tox-dialog__body-iframe{display: flex;flex: 1;flex-direction: column;-ms-flex-preferred-size: auto;} + +.tox .tox-dialog__body-iframe .tox-navobj{display: flex;flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2){flex: 1;-ms-flex-preferred-size: auto;height: 100%;} + +body.tox-dialog__disable-scroll{overflow: hidden;} + +.tox.tox-platform-ie .tox-dialog-wrap{position: -ms-device-fixed;} + +.tox:not([dir=rtl]) .tox-dialog__body-nav{margin-right: 32px;} + +.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end>*,.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start>*{margin-left: 8px;} + +.tox[dir=rtl] .tox-dialog__body{text-align: right;} + +.tox[dir=rtl] .tox-dialog__body-nav{margin-left: 32px;} + +.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end>*,.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start>*{margin-right: 8px;} + +.tox .tox-dropzone-container{display: flex;flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-dropzone{display: flex;min-height: 100px;padding: 10px;background: #fff;border: 2px dashed #000;box-sizing: border-box;align-items: center;flex-direction: column;flex-grow: 1;justify-content: center;} + +.tox .tox-dropzone p{margin: 0 0 16px 0;color: rgba(255,255,255,.5);} + +.tox .tox-edit-area{position: relative;display: flex;overflow: hidden;border-top: 1px solid #000;flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-edit-area__iframe{position: absolute;width: 100%;height: 100%;background-color: #fff;border: 0;box-sizing: border-box;flex: 1;-ms-flex-preferred-size: auto;} + +.tox.tox-inline-edit-area{border: 1px dotted #000;} + +.tox .tox-control-wrap{flex: 1;position: relative;} + +.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid,.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown,.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid{display: none;} + +.tox .tox-control-wrap svg{display: block;} + +.tox .tox-control-wrap__status-icon-wrap{position: absolute;top: 50%;transform: translateY(-50%);} + +.tox .tox-control-wrap__status-icon-invalid svg{fill: #c00;} + +.tox .tox-control-wrap__status-icon-unknown svg{fill: orange;} + +.tox .tox-control-wrap__status-icon-valid svg{fill: green;} + +.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield{padding-right: 32px;} + +.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap{right: 4px;} + +.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield{padding-left: 32px;} + +.tox[dir=rtl] .tox-control-wrap__status-icon-wrap{left: 4px;} + +.tox .tox-autocompleter{max-width: 25em;} + +.tox .tox-autocompleter .tox-menu{max-width: 25em;} + +.tox .tox-color-input{display: flex;} + +.tox .tox-color-input .tox-textfield{display: flex;border-radius: 3px 0 0 3px;} + +.tox .tox-color-input span{display: flex;width: 35px;cursor: pointer;border-color: rgba(34,47,62,.2);border-style: solid;border-width: 1px 1px 1px 0;border-radius: 0 3px 3px 0;box-shadow: none;box-sizing: border-box;} + +.tox .tox-color-input span:focus{border-color: #207ab7;} + +.tox[dir=rtl] .tox-color-input .tox-textfield{border-radius: 0 3px 3px 0;} + +.tox[dir=rtl] .tox-color-input span{border-width: 1px 0 1px 1px;border-radius: 3px 0 0 3px;} + +.tox .tox-label,.tox .tox-toolbar-label{display: block;padding: 0 8px 0 0;font-size: 14px;font-style: normal;font-weight: 400;line-height: 1.3;color: rgba(255,255,255,.5);text-transform: normal;white-space: nowrap;} + +.tox .tox-toolbar-label{padding: 0 8px;} + +.tox[dir=rtl] .tox-label{padding: 0 0 0 8px;} + +.tox .tox-form{display: flex;flex: 1;flex-direction: column;-ms-flex-preferred-size: auto;} + +.tox .tox-form__group{margin-bottom: 4px;box-sizing: border-box;} + +.tox .tox-form__group--error{color: #c00;} + +.tox .tox-form__group--collection{display: flex;} + +.tox .tox-form__grid{display: flex;flex-direction: row;flex-wrap: wrap;justify-content: space-between;} + +.tox .tox-form__grid--2col>.tox-form__group{width: calc(50% - (8px / 2));} + +.tox .tox-form__grid--3col>.tox-form__group{width: calc(100% / 3 - (8px / 2));} + +.tox .tox-form__grid--4col>.tox-form__group{width: calc(25% - (8px / 2));} + +.tox .tox-form__controls-h-stack{align-items: center;display: flex;} + +.tox .tox-form__group--inline{align-items: center;display: flex;} + +.tox .tox-form__group--stretched{display: flex;flex: 1;flex-direction: column;-ms-flex-preferred-size: auto;} + +.tox .tox-form__group--stretched .tox-textarea{flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-form__group--stretched .tox-navobj{display: flex;flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-form__group--stretched .tox-navobj :nth-child(2){flex: 1;-ms-flex-preferred-size: auto;height: 100%;} + +.tox:not([dir=rtl]) .tox-form__controls-h-stack>:not(:first-child){margin-left: 4px;} + +.tox[dir=rtl] .tox-form__controls-h-stack>:not(:first-child){margin-right: 4px;} + +.tox .tox-lock.tox-locked .tox-lock-icon__unlock,.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock{display: none;} + +.tox .tox-textarea,.tox .tox-textfield,.tox .tox-toolbar-textfield,.tox:not([dir=rtl]) .tox-selectfield select,.tox[dir=rtl] .tox-selectfield select{width: 100%;padding: 5px 4.75px;margin: 0;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size: 16px;line-height: 24px;color: #fff;background-color: #2b3b4e;border-color: #000;border-style: solid;border-width: 1px;border-radius: 3px;outline: 0;box-shadow: none;box-sizing: border-box;resize: none;-webkit-appearance: none;-moz-appearance: none;appearance: none;} + +.tox .tox-selectfield select:focus,.tox .tox-textarea:focus,.tox .tox-textfield:focus{border-color: #207ab7;outline: 0;box-shadow: none;} + +.tox .tox-toolbar-textfield{max-width: 250px;margin-top: 2px;margin-bottom: 3px;border-width: 0;} + +.tox .tox-naked-btn{display: block;padding: 0;margin: 0;color: #207ab7;cursor: pointer;background-color: transparent;border: 0;border-color: transparent;box-shadow: unset;} + +.tox .tox-naked-btn svg{display: block;fill: #fff;} + +.tox:not([dir=rtl]) .tox-toolbar-textfield+*{margin-left: 4px;} + +.tox[dir=rtl] .tox-toolbar-textfield+*{margin-right: 4px;} + +.tox .tox-selectfield{position: relative;cursor: pointer;} + +.tox .tox-selectfield select::-ms-expand{display: none;} + +.tox .tox-selectfield svg{position: absolute;top: 50%;pointer-events: none;transform: translateY(-50%);} + +.tox:not([dir=rtl]) .tox-selectfield select{padding-right: 24px;} + +.tox:not([dir=rtl]) .tox-selectfield svg{right: 8px;} + +.tox[dir=rtl] .tox-selectfield select{padding-left: 24px;} + +.tox[dir=rtl] .tox-selectfield svg{left: 8px;} + +.tox .tox-textarea{white-space: pre-wrap;-webkit-appearance: textarea;-moz-appearance: textarea;appearance: textarea;} + +.tox-fullscreen{position: fixed;top: 0;left: 0;width: 100%;height: 100%;padding: 0;margin: 0;overflow: hidden;border: 0;} + +.tox-fullscreen .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display: none;} + +.tox-fullscreen .tox.tox-tinymce.tox-fullscreen{z-index: 1200;} + +.tox-fullscreen .tox.tox-tinymce-aux{z-index: 1201;} + +.tox .tox-image-tools{width: 100%;} + +.tox .tox-image-tools__toolbar{align-items: center;display: flex;justify-content: center;} + +.tox .tox-image-tools__image{position: relative;width: 100%;height: 380px;overflow: auto;background-color: #666;} + +.tox .tox-image-tools__image,.tox .tox-image-tools__image+.tox-image-tools__toolbar{margin-top: 8px;} + +.tox .tox-image-tools__image-bg{background: url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==);} + +.tox .tox-image-tools__toolbar>.tox-spacer{flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-croprect-block{position: absolute;background: #000;opacity: .5;zoom: 1;} + +.tox .tox-croprect-handle{position: absolute;top: 0;left: 0;width: 20px;height: 20px;border: 2px solid #fff;} + +.tox .tox-croprect-handle-move{position: absolute;cursor: move;border: 0;} + +.tox .tox-croprect-handle-nw{top: 100px;left: 100px;margin: -2px 0 0 -2px;cursor: nw-resize;border-width: 2px 0 0 2px;} + +.tox .tox-croprect-handle-ne{top: 100px;left: 200px;margin: -2px 0 0 -20px;cursor: ne-resize;border-width: 2px 2px 0 0;} + +.tox .tox-croprect-handle-sw{top: 200px;left: 100px;margin: -20px 2px 0 -2px;cursor: sw-resize;border-width: 0 0 2px 2px;} + +.tox .tox-croprect-handle-se{top: 200px;left: 200px;margin: -20px 0 0 -20px;cursor: se-resize;border-width: 0 2px 2px 0;} + +.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-slider:not(:first-of-type){margin-left: 8px;} + +.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-button+.tox-slider{margin-left: 32px;} + +.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-slider+.tox-button{margin-left: 32px;} + +.tox[dir=rtl] .tox-image-tools__toolbar>.tox-slider:not(:first-of-type){margin-right: 8px;} + +.tox[dir=rtl] .tox-image-tools__toolbar>.tox-button+.tox-slider{margin-right: 32px;} + +.tox[dir=rtl] .tox-image-tools__toolbar>.tox-slider+.tox-button{margin-right: 32px;} + +.tox .tox-insert-table-picker{display: flex;flex-wrap: wrap;width: 169px;} + +.tox .tox-insert-table-picker>div{width: 16px;height: 16px;border-color: #070a0d;border-style: solid;border-width: 0 1px 1px 0;box-sizing: content-box;} + +.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker{margin: -4px 0;} + +.tox .tox-insert-table-picker .tox-insert-table-picker__selected{background-color: rgba(32,122,183,.5);border-color: rgba(32,122,183,.5);} + +.tox .tox-insert-table-picker__label{display: block;width: 100%;padding: 4px;font-size: 14px;color: #fff;text-align: center;} + +.tox:not([dir=rtl]) .tox-insert-table-picker>div:nth-child(10n){border-right: 0;} + +.tox[dir=rtl] .tox-insert-table-picker>div:nth-child(10n+1){border-right: 0;} + +.tox .tox-menu{z-index: 1;display: inline-block;overflow: hidden;vertical-align: top;background-color: #2b3b4e;border: 1px solid #000;border-radius: 3px;box-shadow: 0 4px 8px 0 rgba(34,47,62,.1);} + +.tox .tox-menu.tox-collection.tox-collection--list{padding: 0;} + +.tox .tox-menu.tox-collection.tox-collection--toolbar{padding: 4px;} + +.tox .tox-menu.tox-collection.tox-collection--grid{padding: 4px;} + +.tox .tox-menu__label blockquote,.tox .tox-menu__label code,.tox .tox-menu__label h1,.tox .tox-menu__label h2,.tox .tox-menu__label h3,.tox .tox-menu__label h4,.tox .tox-menu__label h5,.tox .tox-menu__label h6,.tox .tox-menu__label p{margin: 0;} + +.tox .tox-menubar{display: flex;padding: 0 4px;margin-bottom: -1px;background: url("data:image/svg+xml;charset=utf8,%3Csvg height='43px' viewBox='0 0 40 43px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='42px' width='100' height='1' fill='%23000000'/%3E%3C/svg%3E") left 0 top 0 #222f3e;background-color: #222f3e;flex: 0 0 auto;flex-shrink: 0;flex-wrap: wrap;} + +.tox .tox-mbtn{display: flex;width: auto;height: 34px;padding: 0 4px;margin: 2px 0 3px 0;overflow: hidden;font-size: 14px;font-style: normal;font-weight: 400;color: #fff;text-transform: normal;background: 0 0;border: 0;border-radius: 3px;outline: 0;box-shadow: none;align-items: center;flex: 0 0 auto;justify-content: center;} + +.tox .tox-mbtn[disabled]{color: rgba(255,255,255,.5);cursor: not-allowed;background-color: none;border-color: none;box-shadow: none;} + +.tox .tox-mbtn:hover:not(:disabled){color: #fff;background: #434e5b;box-shadow: none;} + +.tox .tox-mbtn:focus:not(:disabled){color: #fff;background: #434e5b;box-shadow: none;} + +.tox .tox-mbtn--active{color: #fff;background: #6f7882;box-shadow: none;} + +.tox .tox-mbtn__select-label{margin: 0 4px;font-weight: 400;cursor: default;} + +.tox .tox-mbtn[disabled] .tox-mbtn__select-label{cursor: not-allowed;} + +.tox .tox-mbtn__select-chevron{display: flex;display: none;width: 16px;align-items: center;justify-content: center;} + +.tox .tox-notification{display: grid;padding: 5px;margin-top: 5px;background-color: #fffaea;border-color: #ffe89d;border-style: solid;border-width: 1px;opacity: 0;box-sizing: border-box;transition: transform .1s ease-in,opacity 150ms ease-in;grid-template-columns: minmax(40px,1fr) auto minmax(40px,1fr);} + +.tox .tox-notification--in{opacity: 1;} + +.tox .tox-notification--success{background-color: #dff0d8;border-color: #d6e9c6;} + +.tox .tox-notification--error{background-color: #f2dede;border-color: #ebccd1;} + +.tox .tox-notification--warn{background-color: #fcf8e3;border-color: #faebcc;} + +.tox .tox-notification--info{background-color: #d9edf7;border-color: #779ecb;} + +.tox .tox-notification__body{font-size: 14px;color: #fff;text-align: center;word-break: break-all;word-break: break-word;white-space: normal;align-self: center;grid-column-end: 3;-ms-grid-column-span: 1;grid-column-start: 2;grid-row-end: 2;grid-row-start: 1;} + +.tox .tox-notification__body>*{margin: 0;} + +.tox .tox-notification__body>*+*{margin-top: 1rem;} + +.tox .tox-notification__icon{align-self: center;-ms-grid-column-align: end;grid-column-end: 2;-ms-grid-column-span: 1;grid-column-start: 1;grid-row-end: 2;grid-row-start: 1;justify-self: end;} + +.tox .tox-notification__icon svg{display: block;} + +.tox .tox-notification__dismiss{align-self: start;-ms-grid-column-align: end;grid-column-end: 4;-ms-grid-column-span: 1;grid-column-start: 3;grid-row-end: 2;grid-row-start: 1;justify-self: end;} + +.tox .tox-notification .tox-progress-bar{-ms-grid-column-align: center;grid-column-end: 4;-ms-grid-column-span: 3;grid-column-start: 1;grid-row-end: 3;-ms-grid-row-span: 1;grid-row-start: 2;justify-self: center;} + +.tox .tox-pop{position: relative;display: inline-block;} + +.tox .tox-pop--resizing{transition: width .1s ease;} + +.tox .tox-pop--resizing .tox-toolbar{flex-wrap: nowrap;} + +.tox .tox-pop__dialog{min-width: 0;overflow: hidden;background-color: #222f3e;border: 1px solid #000;border-radius: 3px;box-shadow: 0 1px 3px rgba(0,0,0,.15);} + +.tox .tox-pop__dialog>:not(.tox-toolbar){margin: 4px 4px 4px 8px;} + +.tox .tox-pop__dialog .tox-toolbar{background-color: transparent;} + +.tox .tox-pop::after,.tox .tox-pop::before{position: absolute;display: block;width: 0;height: 0;border-style: solid;content: '';} + +.tox .tox-pop.tox-pop--bottom::after,.tox .tox-pop.tox-pop--bottom::before{top: 100%;left: 50%;} + +.tox .tox-pop.tox-pop--bottom::after{margin-top: -1px;margin-left: -8px;border-color: #222f3e transparent transparent transparent;border-width: 8px;} + +.tox .tox-pop.tox-pop--bottom::before{margin-left: -9px;border-color: #000 transparent transparent transparent;border-width: 9px;} + +.tox .tox-pop.tox-pop--top::after,.tox .tox-pop.tox-pop--top::before{top: 0;left: 50%;transform: translateY(-100%);} + +.tox .tox-pop.tox-pop--top::after{margin-top: 1px;margin-left: -8px;border-color: transparent transparent #222f3e transparent;border-width: 8px;} + +.tox .tox-pop.tox-pop--top::before{margin-left: -9px;border-color: transparent transparent #000 transparent;border-width: 9px;} + +.tox .tox-pop.tox-pop--left::after,.tox .tox-pop.tox-pop--left::before{top: calc(50% - 1px);left: 0;transform: translateY(-50%);} + +.tox .tox-pop.tox-pop--left::after{margin-left: -15px;border-color: transparent #222f3e transparent transparent;border-width: 8px;} + +.tox .tox-pop.tox-pop--left::before{margin-left: -19px;border-color: transparent #000 transparent transparent;border-width: 10px;} + +.tox .tox-pop.tox-pop--right::after,.tox .tox-pop.tox-pop--right::before{top: calc(50% + 1px);left: 100%;transform: translateY(-50%);} + +.tox .tox-pop.tox-pop--right::after{margin-left: -1px;border-color: transparent transparent transparent #222f3e;border-width: 8px;} + +.tox .tox-pop.tox-pop--right::before{margin-left: -1px;border-color: transparent transparent transparent #000;border-width: 10px;} + +.tox .tox-pop.tox-pop--align-left::after,.tox .tox-pop.tox-pop--align-left::before{left: 20px;} + +.tox .tox-pop.tox-pop--align-right::after,.tox .tox-pop.tox-pop--align-right::before{left: calc(100% - 20px);} + +.tox .tox-sidebar-wrap{display: flex;flex-direction: row;flex-grow: 1;min-height: 0;} + +.tox .tox-sidebar{display: flex;flex-direction: row;justify-content: flex-end;} + +.tox .tox-sidebar__slider{display: flex;overflow: hidden;} + +.tox .tox-sidebar__pane-container{display: flex;} + +.tox .tox-sidebar__pane{display: flex;} + +.tox .tox-sidebar--sliding-closed{opacity: 0;} + +.tox .tox-sidebar--sliding-open{opacity: 1;} + +.tox .tox-sidebar--sliding-growing,.tox .tox-sidebar--sliding-shrinking{transition: width .5s ease,opacity .5s ease;} + +.tox .tox-slider{position: relative;display: flex;height: 24px;align-items: center;flex: 1;-ms-flex-preferred-size: auto;justify-content: center;} + +.tox .tox-slider__rail{width: 100%;height: 10px;min-width: 120px;background-color: transparent;border: 1px solid #000;border-radius: 3px;} + +.tox .tox-slider__handle{position: absolute;top: 50%;left: 50%;width: 14px;height: 24px;background-color: #207ab7;border: 2px solid #185d8c;border-radius: 3px;transform: translateX(-50%) translateY(-50%);box-shadow: none;} + +.tox .tox-source-code{overflow: auto;} + +.tox .tox-spinner{display: flex;} + +.tox .tox-spinner>div{width: 8px;height: 8px;background-color: rgba(255,255,255,.5);border-radius: 100%;animation: tam-bouncing-dots 1.5s ease-in-out 0s infinite both;} + +.tox .tox-spinner>div:nth-child(1){animation-delay: -.32s;} + +.tox .tox-spinner>div:nth-child(2){animation-delay: -.16s;}@keyframes tam-bouncing-dots{0%,100%,80%{transform: scale(0);} + +40%{transform: scale(1);}} + +.tox:not([dir=rtl]) .tox-spinner>div:not(:first-child){margin-left: 4px;} + +.tox[dir=rtl] .tox-spinner>div:not(:first-child){margin-right: 4px;} + +.tox .tox-statusbar{position: relative;display: flex;height: 18px;padding: 0 8px;overflow: hidden;font-size: 12px;color: rgba(255,255,255,.5);text-transform: uppercase;background-color: #222f3e;border-top: 1px solid #000;align-items: center;flex: 0 0 auto;} + +.tox .tox-statusbar a{color: rgba(255,255,255,.5);text-decoration: none;} + +.tox .tox-statusbar a:hover{text-decoration: underline;} + +.tox .tox-statusbar__text-container{display: flex;flex: 1 1 auto;justify-content: flex-end;overflow: hidden;} + +.tox .tox-statusbar__path{display: flex;flex: 1 1 auto;margin-right: auto;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;} + +.tox .tox-statusbar__path>*{display: inline;white-space: nowrap;} + +.tox .tox-statusbar__wordcount{flex: 0 0 auto;margin-left: 1ch;} + +.tox .tox-statusbar__resize-handle{display: flex;padding-left: 1ch;margin-right: -8px;margin-left: auto;cursor: nwse-resize;align-items: flex-end;align-self: stretch;flex: 0 0 auto;justify-content: flex-end;} + +.tox .tox-statusbar__resize-handle svg{display: block;fill: rgba(255,255,255,.5);} + +.tox:not([dir=rtl]) .tox-statusbar__path>*{margin-right: 4px;} + +.tox:not([dir=rtl]) .tox-statusbar__branding{margin-left: 1ch;} + +.tox[dir=rtl] .tox-statusbar{flex-direction: row-reverse;} + +.tox[dir=rtl] .tox-statusbar__path>*{margin-left: 4px;} + +.tox .tox-throbber{z-index: 1400;} + +.tox .tox-throbber__busy-spinner{position: absolute;top: 0;right: 0;bottom: 0;left: 0;display: flex;background-color: rgba(34,47,62,.6);align-items: center;justify-content: center;} + +.tox .tox-tbtn{display: flex;width: 34px;height: 34px;padding: 0;margin: 2px 0 3px 0;overflow: hidden;font-size: 14px;font-style: normal;font-weight: 400;color: #fff;text-transform: normal;background: 0 0;border: 0;border-radius: 3px;outline: 0;box-shadow: none;align-items: center;flex: 0 0 auto;justify-content: center;} + +.tox .tox-tbtn svg{display: block;fill: #fff;} + +.tox .tox-tbtn.tox-tbtn-more{width: inherit;padding-right: 5px;padding-left: 5px;} + +.tox .tox-tbtn--enabled{color: #fff;background: #6f7882;box-shadow: none;} + +.tox .tox-tbtn--enabled>*{transform: none;} + +.tox .tox-tbtn--enabled svg{fill: #fff;} + +.tox .tox-tbtn:hover{color: #fff;background: #434e5b;box-shadow: none;} + +.tox .tox-tbtn:hover svg{fill: #fff;} + +.tox .tox-tbtn:focus{color: #fff;background: #434e5b;box-shadow: none;} + +.tox .tox-tbtn:focus svg{fill: #fff;} + +.tox .tox-tbtn:active{color: #fff;background: #6f7882;box-shadow: none;} + +.tox .tox-tbtn:active svg{fill: #fff;} + +.tox .tox-tbtn--disabled,.tox .tox-tbtn--disabled:hover,.tox .tox-tbtn:disabled,.tox .tox-tbtn:disabled:hover{color: rgba(255,255,255,.5);cursor: not-allowed;background: 0 0;box-shadow: none;} + +.tox .tox-tbtn--disabled svg,.tox .tox-tbtn--disabled:hover svg,.tox .tox-tbtn:disabled svg,.tox .tox-tbtn:disabled:hover svg{fill: rgba(255,255,255,.5);} + +.tox .tox-tbtn:active>*{transform: none;} + +.tox .tox-tbtn--md{width: 51px;height: 51px;} + +.tox .tox-tbtn--lg{width: 68px;height: 68px;flex-direction: column;} + +.tox .tox-tbtn--return{width: 16px;height: unset;align-self: stretch;} + +.tox .tox-tbtn--labeled{width: unset;padding: 0 4px;} + +.tox .tox-tbtn__vlabel{display: block;margin-bottom: 4px;font-size: 10px;font-weight: 400;letter-spacing: -.025em;white-space: nowrap;} + +.tox .tox-tbtn--select{width: auto;padding: 0 4px;margin: 2px 0 3px 0;} + +.tox .tox-tbtn__select-label{margin: 0 4px;font-weight: 400;cursor: default;} + +.tox .tox-tbtn__select-chevron{align-items: center;display: flex;justify-content: center;width: 16px;} + +.tox .tox-tbtn__select-chevron svg{fill: rgba(255,255,255,.5);} + +.tox .tox-tbtn--bespoke .tox-tbtn__select-label{width: 7em;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;} + +.tox .tox-split-button{display: flex;margin: 2px 0 3px 0;overflow: hidden;border: 0;border-radius: 3px;box-sizing: border-box;} + +.tox .tox-split-button:hover{box-shadow: 0 0 0 1px #434e5b inset;} + +.tox .tox-split-button:focus{color: #fff;background: #434e5b;box-shadow: none;} + +.tox .tox-split-button>*{border-radius: 0;} + +.tox .tox-split-button__chevron{width: 16px;} + +.tox .tox-split-button__chevron svg{fill: rgba(255,255,255,.5);} + +.tox .tox-pop .tox-split-button__chevron svg{transform: rotate(-90deg);} + +.tox .tox-split-button .tox-tbtn{margin: 0;} + +.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus,.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover,.tox .tox-split-button.tox-tbtn--disabled:focus,.tox .tox-split-button.tox-tbtn--disabled:hover{color: rgba(255,255,255,.5);background: 0 0;box-shadow: none;} + +.tox .tox-toolbar,.tox .tox-toolbar__overflow,.tox .tox-toolbar__primary{display: flex;padding: 0 0;margin-bottom: -1px;background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23000000'/%3E%3C/svg%3E") left 0 top 0 #222f3e;background-color: #222f3e;border-top: 1px solid #000;flex: 0 0 auto;flex-shrink: 0;flex-wrap: wrap;} + +.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed{height: 0;opacity: 0;visibility: hidden;} + +.tox .tox-toolbar__overflow--growing{transition: height .3s ease,opacity .2s linear .1s;} + +.tox .tox-toolbar__overflow--shrinking{transition: opacity .3s ease,height .2s linear .1s,visibility 0s linear .3s;} + +.tox .tox-pop .tox-toolbar{border-width: 0;} + +.tox .tox-toolbar--no-divider{background-image: none;} + +.tox.tox-tinymce-aux .tox-toolbar__overflow{background-color: #222f3e;border: 1px solid #000;border-radius: 3px;box-shadow: 0 1px 3px rgba(0,0,0,.15);} + +.tox.tox-tinymce-aux:not([dir=rtl]) .tox-toolbar__overflow{margin-left: 4px;} + +.tox[dir=rtl] .tox-tbtn__icon-rtl svg{transform: rotateY(180deg);} + +.tox[dir=rtl].tox-tinymce-aux .tox-toolbar__overflow{margin-right: 4px;} + +.tox .tox-toolbar__group{display: flex;padding: 0 4px;margin: 0 0;align-items: center;flex-wrap: wrap;} + +.tox .tox-toolbar__group--pull-right{margin-left: auto;} + +.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type){border-right: 1px solid #000;} + +.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type){border-left: 1px solid #000;} + +.tox .tox-tooltip{position: relative;display: inline-block;padding: 8px;} + +.tox .tox-tooltip__body{padding: 4px 8px;font-size: 14px;font-style: normal;font-weight: 400;color: rgba(255,255,255,.75);text-transform: normal;background-color: #3d546f;border-radius: 3px;box-shadow: 0 2px 4px rgba(34,47,62,.3);} + +.tox .tox-tooltip__arrow{position: absolute;} + +.tox .tox-tooltip--down .tox-tooltip__arrow{position: absolute;bottom: 0;left: 50%;border-top: 8px solid #3d546f;border-right: 8px solid transparent;border-left: 8px solid transparent;transform: translateX(-50%);} + +.tox .tox-tooltip--up .tox-tooltip__arrow{position: absolute;top: 0;left: 50%;border-right: 8px solid transparent;border-bottom: 8px solid #3d546f;border-left: 8px solid transparent;transform: translateX(-50%);} + +.tox .tox-tooltip--right .tox-tooltip__arrow{position: absolute;top: 50%;right: 0;border-top: 8px solid transparent;border-bottom: 8px solid transparent;border-left: 8px solid #3d546f;transform: translateY(-50%);} + +.tox .tox-tooltip--left .tox-tooltip__arrow{position: absolute;top: 50%;left: 0;border-top: 8px solid transparent;border-right: 8px solid #3d546f;border-bottom: 8px solid transparent;transform: translateY(-50%);} + +.tox .tox-well{width: 100%;padding: 8px;border: 1px solid #000;border-radius: 3px;} + +.tox .tox-well>:first-child{margin-top: 0;} + +.tox .tox-well>:last-child{margin-bottom: 0;} + +.tox .tox-well>:only-child{margin: 0;} + +.tox .tox-custom-editor{display: flex;height: 525px;border: 1px solid #000;border-radius: 3px;} + +.tox .tox-dialog-loading::before{position: absolute;z-index: 1000;width: 100%;height: 100%;background-color: rgba(0,0,0,.5);content: "";} + +.tox .tox-tab{cursor: pointer;} + +.tox .tox-dialog__content-js{display: flex;flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-dialog__body-content .tox-collection{display: flex;flex: 1;-ms-flex-preferred-size: auto;} + +.tox ul{display: block;list-style-type: disc;-webkit-margin-before: 1em;margin-block-start: 1em;-webkit-margin-after: 1em;margin-block-end: 1em;-webkit-margin-start: 0;margin-inline-start: 0;-webkit-margin-end: 0;margin-inline-end: 0;-webkit-padding-start: 40px;padding-inline-start: 40px;} + +.tox a{color: #2276d2;cursor: pointer;} + +.tox .tox-image-tools-edit-panel{height: 60px;} + +.tox .tox-image-tools__sidebar{height: 60px;} diff --git a/public/resource/tinymce/skins/ui/oxide-dark/skin.mobile.min.css b/public/resource/tinymce/skins/ui/oxide-dark/skin.mobile.min.css new file mode 100644 index 0000000..14847d0 --- /dev/null +++ b/public/resource/tinymce/skins/ui/oxide-dark/skin.mobile.min.css @@ -0,0 +1,239 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tinymce-mobile-outer-container{all: initial;display: block;} + +.tinymce-mobile-outer-container *{float: none;padding: 0;margin: 0;line-height: 1;text-shadow: none;white-space: nowrap;cursor: inherit;border: 0;outline: 0;box-sizing: initial;-webkit-tap-highlight-color: transparent;} + +.tinymce-mobile-icon-arrow-back::before{content: "\e5cd";} + +.tinymce-mobile-icon-image::before{content: "\e412";} + +.tinymce-mobile-icon-cancel-circle::before{content: "\e5c9";} + +.tinymce-mobile-icon-full-dot::before{content: "\e061";} + +.tinymce-mobile-icon-align-center::before{content: "\e234";} + +.tinymce-mobile-icon-align-left::before{content: "\e236";} + +.tinymce-mobile-icon-align-right::before{content: "\e237";} + +.tinymce-mobile-icon-bold::before{content: "\e238";} + +.tinymce-mobile-icon-italic::before{content: "\e23f";} + +.tinymce-mobile-icon-unordered-list::before{content: "\e241";} + +.tinymce-mobile-icon-ordered-list::before{content: "\e242";} + +.tinymce-mobile-icon-font-size::before{content: "\e245";} + +.tinymce-mobile-icon-underline::before{content: "\e249";} + +.tinymce-mobile-icon-link::before{content: "\e157";} + +.tinymce-mobile-icon-unlink::before{content: "\eca2";} + +.tinymce-mobile-icon-color::before{content: "\e891";} + +.tinymce-mobile-icon-previous::before{content: "\e314";} + +.tinymce-mobile-icon-next::before{content: "\e315";} + +.tinymce-mobile-icon-large-font::before,.tinymce-mobile-icon-style-formats::before{content: "\e264";} + +.tinymce-mobile-icon-undo::before{content: "\e166";} + +.tinymce-mobile-icon-redo::before{content: "\e15a";} + +.tinymce-mobile-icon-removeformat::before{content: "\e239";} + +.tinymce-mobile-icon-small-font::before{content: "\e906";} + +.tinymce-mobile-format-matches::after,.tinymce-mobile-icon-readonly-back::before{content: "\e5ca";} + +.tinymce-mobile-icon-small-heading::before{content: "small";} + +.tinymce-mobile-icon-large-heading::before{content: "large";} + +.tinymce-mobile-icon-large-heading::before,.tinymce-mobile-icon-small-heading::before{font-family: sans-serif;font-size: 80%;} + +.tinymce-mobile-mask-edit-icon::before{content: "\e254";} + +.tinymce-mobile-icon-back::before{content: "\e5c4";} + +.tinymce-mobile-icon-heading::before{font-family: sans-serif;font-size: 80%;font-weight: 700;content: "Headings";} + +.tinymce-mobile-icon-h1::before{font-weight: 700;content: "H1";} + +.tinymce-mobile-icon-h2::before{font-weight: 700;content: "H2";} + +.tinymce-mobile-icon-h3::before{font-weight: 700;content: "H3";} + +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask{position: absolute;top: 0;display: flex;width: 100%;height: 100%;background: rgba(51,51,51,.5);align-items: center;justify-content: center;} + +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container{display: flex;font-family: sans-serif;font-size: 1em;border-radius: 50%;align-items: center;flex-direction: column;justify-content: space-between;} + +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .mixin-menu-item{display: flex;width: 2.1em;height: 2.1em;border-radius: 50%;align-items: center;justify-content: center;} + +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section{align-items: center;display: flex;justify-content: center;flex-direction: column;font-size: 1em;}@media only screen and (min-device-width: 700px){.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section{font-size: 1.2em;}} + +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon{display: flex;width: 2.1em;height: 2.1em;color: #207ab7;background-color: #fff;border-radius: 50%;align-items: center;justify-content: center;} + +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon::before{font-family: tinymce-mobile,sans-serif;content: "\e900";} + +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section:not(.tinymce-mobile-mask-tap-icon-selected) .tinymce-mobile-mask-tap-icon{z-index: 2;} + +.tinymce-mobile-android-container.tinymce-mobile-android-maximized{position: fixed;top: 0;right: 0;bottom: 0;left: 0;display: flex;background: #fff;border: none;flex-direction: column;} + +.tinymce-mobile-android-container:not(.tinymce-mobile-android-maximized){position: relative;} + +.tinymce-mobile-android-container .tinymce-mobile-editor-socket{display: flex;flex-grow: 1;} + +.tinymce-mobile-android-container .tinymce-mobile-editor-socket iframe{display: flex !important;flex-grow: 1;height: auto !important;} + +.tinymce-mobile-android-scroll-reload{overflow: hidden;} + +:not(.tinymce-mobile-readonly-mode)>.tinymce-mobile-android-selection-context-toolbar{margin-top: 23px;} + +.tinymce-mobile-toolstrip{z-index: 1;display: flex;background: #fff;flex: 0 0 auto;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar{display: flex;width: 100%;height: 2.5em;background-color: #fff;border-bottom: 1px solid #ccc;align-items: center;flex: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group{align-items: center;display: flex;height: 100%;flex-shrink: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group>div{align-items: center;display: flex;height: 100%;flex: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-exit-container{background: #f44336;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-toolbar-scrollable-group{flex-grow: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item{padding-right: .5em;padding-left: .5em;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button{display: flex;height: 80%;margin-right: 2px;margin-left: 2px;align-items: center;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button.tinymce-mobile-toolbar-button-selected{color: #ccc;background: #c8cbcf;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:first-of-type,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:last-of-type{color: #eceff1;background: #207ab7;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group{display: flex;height: 100%;padding-top: .4em;padding-bottom: .4em;align-items: center;flex: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog{position: relative;display: flex;width: 100%;min-height: 1.5em;padding-right: 0;padding-left: 0;overflow: hidden;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain{display: flex;width: 100%;height: 100%;transition: left cubic-bezier(.4,0,1,1) .15s;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen{display: flex;flex: 0 0 auto;justify-content: space-between;width: 100%;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen input{font-family: sans-serif;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container{position: relative;display: flex;flex-grow: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container .tinymce-mobile-input-container-x{position: absolute;right: 0;height: 100%;padding-right: 2px;font-size: .6em;font-weight: 700;color: #888;background: inherit;border: none;border-radius: 50%;align-self: center;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container.tinymce-mobile-input-container-empty .tinymce-mobile-input-container-x{display: none;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous{align-items: center;display: flex;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous::before{display: flex;height: 100%;padding-right: .5em;padding-left: .5em;font-weight: 700;align-items: center;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next.tinymce-mobile-toolbar-navigation-disabled::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous.tinymce-mobile-toolbar-navigation-disabled::before{visibility: hidden;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item{padding-top: 3px;margin: 0 2px;font-size: 10px;line-height: 10px;color: #ccc;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item.tinymce-mobile-dot-active{color: #c8cbcf;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-font::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-heading::before{margin-right: .9em;margin-left: .5em;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-font::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-heading::before{margin-right: .5em;margin-left: .9em;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider{position: relative;display: flex;padding: .28em 0;margin-right: 0;margin-left: 0;flex: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container{align-items: center;display: flex;flex-grow: 1;height: 100%;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container .tinymce-mobile-slider-size-line{display: flex;height: .2em;margin-top: .3em;margin-bottom: .3em;background: #ccc;flex: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container{padding-right: 2em;padding-left: 2em;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container{align-items: center;display: flex;flex-grow: 1;height: 100%;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container .tinymce-mobile-slider-gradient{display: flex;height: .2em;margin-top: .3em;margin-bottom: .3em;background: linear-gradient(to right,red 0,#feff00 17%,#0f0 33%,#00feff 50%,#00f 67%,#ff00fe 83%,red 100%);flex: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-black{width: 1.2em;height: .2em;margin-top: .3em;margin-bottom: .3em;background: #000;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-white{width: 1.2em;height: .2em;margin-top: .3em;margin-bottom: .3em;background: #fff;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb{position: absolute;top: 0;bottom: 0;left: -10px;display: flex;width: .5em;height: .5em;margin: auto;color: #fff;background-color: #455a64;border: .5em solid rgba(136,136,136,0);border-radius: 3em;transition: border 120ms cubic-bezier(.39,.58,.57,1);background-clip: padding-box;align-items: center;justify-content: center;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb.tinymce-mobile-thumb-active{border: .5em solid rgba(136,136,136,.39);} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group>div{align-items: center;display: flex;height: 100%;flex: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper{flex-direction: column;justify-content: center;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item{align-items: center;display: flex;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item:not(.tinymce-mobile-serialised-dialog){height: 100%;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-container{display: flex;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input{padding-top: .1em;padding-bottom: .1em;padding-left: 5px;font-size: .85em;color: #455a64;background: #fff;border: none;border-radius: 0;flex-grow: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::-webkit-input-placeholder{color: #888;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::placeholder{color: #888;} + +.tinymce-mobile-dropup{display: flex;width: 100%;overflow: hidden;background: #fff;} + +.tinymce-mobile-dropup.tinymce-mobile-dropup-shrinking{transition: height .3s ease-out;} + +.tinymce-mobile-dropup.tinymce-mobile-dropup-growing{transition: height .3s ease-in;} + +.tinymce-mobile-dropup.tinymce-mobile-dropup-closed{flex-grow: 0;} + +.tinymce-mobile-dropup.tinymce-mobile-dropup-open:not(.tinymce-mobile-dropup-growing){flex-grow: 1;} + +.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height: 200px;}@media only screen and (orientation: landscape){.tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height: 200px;}}@media only screen and (min-device-width: 320px) and (max-device-width: 568px) and (orientation: landscape){.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height: 150px;}} + +.tinymce-mobile-styles-menu{position: relative;width: 100%;overflow: hidden;font-family: sans-serif;outline: 4px solid #000;} + +.tinymce-mobile-styles-menu [role=menu]{position: absolute;display: flex;width: 100%;height: 100%;flex-direction: column;} + +.tinymce-mobile-styles-menu [role=menu].transitioning{transition: transform .5s ease-in-out;} + +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item{position: relative;display: flex;padding: 1em 1em;color: #455a64;cursor: pointer;border-bottom: 1px solid #ddd;} + +.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser .tinymce-mobile-styles-collapse-icon::before{font-family: tinymce-mobile,sans-serif;color: #455a64;content: "\e314";} + +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-styles-item-is-menu::after{position: absolute;right: 0;padding-right: 1em;padding-left: 1em;font-family: tinymce-mobile,sans-serif;color: #455a64;content: "\e315";} + +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-format-matches::after{position: absolute;right: 0;padding-right: 1em;padding-left: 1em;font-family: tinymce-mobile,sans-serif;} + +.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser,.tinymce-mobile-styles-menu .tinymce-mobile-styles-separator{display: flex;min-height: 2.5em;padding-right: 1em;padding-left: 1em;color: #455a64;background: #fff;border-top: #455a64;align-items: center;} + +.tinymce-mobile-styles-menu [data-transitioning-destination=before][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=before]{transform: translate(-100%);} + +.tinymce-mobile-styles-menu [data-transitioning-destination=current][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=current]{transform: translate(0);} + +.tinymce-mobile-styles-menu [data-transitioning-destination=after][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=after]{transform: translate(100%);}@font-face{font-family: tinymce-mobile;font-style: normal;font-weight: 400;src: url(fonts/tinymce-mobile.woff?8x92w3) format('woff');}@media (min-device-width: 700px){.tinymce-mobile-outer-container,.tinymce-mobile-outer-container input{font-size: 25px;}}@media (max-device-width: 700px){.tinymce-mobile-outer-container,.tinymce-mobile-outer-container input{font-size: 18px;}} + +.tinymce-mobile-icon{font-family: tinymce-mobile,sans-serif;} + +.mixin-flex-and-centre{align-items: center;display: flex;justify-content: center;} + +.mixin-flex-bar{align-items: center;display: flex;height: 100%;} + +.tinymce-mobile-outer-container .tinymce-mobile-editor-socket iframe{width: 100%;background-color: #fff;} + +.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{position: fixed;right: 2em;bottom: 1em;display: flex;width: 2.1em;height: 2.1em;font-size: 1em;color: #fff;background-color: #207ab7;border-radius: 50%;align-items: center;justify-content: center;}@media only screen and (min-device-width: 700px){.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{font-size: 1.2em;}} + +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket{height: 300px;overflow: hidden;} + +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket iframe{height: 100%;} + +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-toolstrip{display: none;} + +input[type=file]::-webkit-file-upload-button{display: none;}@media only screen and (min-device-width: 320px) and (max-device-width: 568px) and (orientation: landscape){.tinymce-mobile-ios-container .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{bottom: 50%;}} diff --git a/public/resource/tinymce/skins/ui/oxide/content.inline.min.css b/public/resource/tinymce/skins/ui/oxide/content.inline.min.css new file mode 100644 index 0000000..748f313 --- /dev/null +++ b/public/resource/tinymce/skins/ui/oxide/content.inline.min.css @@ -0,0 +1,239 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.mce-content-body .mce-item-anchor{display: inline-block;width: 8px !important;height: 12px !important;padding: 0 2px;cursor: default;background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;-webkit-user-select: all;-moz-user-select: all;-ms-user-select: all;user-select: all;-webkit-user-modify: read-only;-moz-user-modify: read-only;} + +.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset: 1px;} + +.tox-comments-visible .tox-comment{background-color: #fff0b7;} + +.tox-comments-visible .tox-comment--active{background-color: #ffe168;} + +.tox-checklist>li:not(.tox-checklist--hidden){margin: .25em 0;list-style: none;} + +.tox-checklist>li:not(.tox-checklist--hidden)::before{position: absolute;width: 1em;height: 1em;margin-top: .125em;margin-left: -1.5em;cursor: pointer;background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A");background-size: 100%;content: '';} + +.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A");} + +[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-right: -1.5em;margin-left: 0;} + +code[class*=language-],pre[class*=language-]{font-family: Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size: .875rem;-webkit-hyphens: none;-ms-hyphens: none;hyphens: none;line-height: 1.5;word-spacing: normal;color: #000;text-shadow: 0 1px #fff;word-break: normal;word-wrap: normal;white-space: pre;-moz-tab-size: 4;tab-size: 4;} + +code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow: none;background: #b3d4fc;} + +code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow: none;background: #b3d4fc;}@media print{code[class*=language-],pre[class*=language-]{text-shadow: none;}} + +pre[class*=language-]{padding: 1em;margin: .5em 0;overflow: auto;} + +:not(pre)>code[class*=language-],pre[class*=language-]{background: 0 0 !important;border: 1px solid #ccc;} + +:not(pre)>code[class*=language-]{padding: .1em;border-radius: .3em;} + +.token.cdata,.token.comment,.token.doctype,.token.prolog{color: #708090;} + +.token.punctuation{color: #999;} + +.namespace{opacity: .7;} + +.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color: #905;} + +.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color: #690;} + +.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color: #a67f59;background: hsla(0,0%,100%,.5);} + +.token.atrule,.token.attr-value,.token.keyword{color: #07a;} + +.token.function{color: #dd4a68;} + +.token.important,.token.regex,.token.variable{color: #e90;} + +.token.bold,.token.important{font-weight: 700;} + +.token.italic{font-style: italic;} + +.token.entity{cursor: help;} + +:not([dir=rtl]) code[class*=language-],:not([dir=rtl]) pre[class*=language-]{text-align: left;direction: ltr;} + +[dir=rtl] code[class*=language-],[dir=rtl] pre[class*=language-]{text-align: right;direction: rtl;} + +.mce-content-body{overflow-wrap: break-word;word-wrap: break-word;} + +.mce-content-body .mce-visual-caret{position: absolute;background-color: #000;background-color: currentColor;} + +.mce-content-body .mce-visual-caret-hidden{display: none;} + +.mce-content-body [data-mce-caret]{position: absolute;top: 0;right: auto;left: -1000px;padding: 0;margin: 0;} + +.mce-content-body .mce-offscreen-selection{position: absolute;left: -9999999999px;max-width: 1000000px;} + +.mce-content-body [contentEditable=false]{cursor: default;} + +.mce-content-body [contentEditable=true]{cursor: text;} + +.tox-cursor-format-painter{cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"),default;} + +.mce-content-body figure.align-left{float: left;} + +.mce-content-body figure.align-right{float: right;} + +.mce-content-body figure.image.align-center{display: table;margin-right: auto;margin-left: auto;} + +.mce-preview-object{position: relative;display: inline-block;margin: 0 2px 0 2px;line-height: 0;border: 1px solid gray;} + +.mce-preview-object .mce-shim{position: absolute;top: 0;left: 0;width: 100%;height: 100%;background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);} + +.mce-preview-object[data-mce-selected="2"] .mce-shim{display: none;} + +.mce-object{background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;border: 1px dashed #aaa;} + +.mce-pagebreak{display: block;width: 100%;height: 5px;margin-top: 15px;cursor: default;border: 1px dashed #aaa;page-break-before: always;}@media print{.mce-pagebreak{border: 0;}} + +.tiny-pageembed .mce-shim{position: absolute;top: 0;left: 0;width: 100%;height: 100%;background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);} + +.tiny-pageembed[data-mce-selected="2"] .mce-shim{display: none;} + +.tiny-pageembed{position: relative;display: inline-block;} + +.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{position: relative;display: block;width: 100%;padding: 0;overflow: hidden;} + +.tiny-pageembed--16by9::before,.tiny-pageembed--1by1::before,.tiny-pageembed--21by9::before,.tiny-pageembed--4by3::before{display: block;content: "";} + +.tiny-pageembed--21by9::before{padding-top: 42.857143%;} + +.tiny-pageembed--16by9::before{padding-top: 56.25%;} + +.tiny-pageembed--4by3::before{padding-top: 75%;} + +.tiny-pageembed--1by1::before{padding-top: 100%;} + +.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{position: absolute;top: 0;left: 0;width: 100%;height: 100%;border: 0;} + +.mce-content-body div.mce-resizehandle{position: absolute;z-index: 10000;width: 10px;height: 10px;background-color: #4099ff;border-color: #4099ff;border-style: solid;border-width: 1px;box-sizing: border-box;} + +.mce-content-body div.mce-resizehandle:hover{background-color: #4099ff;} + +.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor: nwse-resize;} + +.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor: nesw-resize;} + +.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor: nwse-resize;} + +.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor: nesw-resize;} + +.mce-content-body .mce-clonedresizable{position: absolute;z-index: 10000;outline: 1px dashed #000;opacity: .5;} + +.mce-content-body .mce-resize-helper{position: absolute;z-index: 10001;display: none;padding: 5px;margin: 5px 10px;font-family: sans-serif;font-size: 12px;line-height: 14px;color: #fff;white-space: nowrap;background: #555;background: rgba(0,0,0,.75);border: 1px;border-radius: 3px;} + +.mce-match-marker{color: #fff;background: #aaa;} + +.mce-match-marker-selected{color: #fff;background: #39f;} + +.mce-content-body img[data-mce-selected],.mce-content-body table[data-mce-selected]{outline: 3px solid #b4d7ff;} + +.mce-content-body hr[data-mce-selected]{outline: 3px solid #b4d7ff;outline-offset: 1px;} + +.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline: 3px solid #b4d7ff;} + +.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline: 3px solid #b4d7ff;} + +.mce-content-body [contentEditable=false][data-mce-selected]{cursor: not-allowed;outline: 3px solid #b4d7ff;} + +.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline: 0;} + +.mce-content-body [data-mce-selected=inline-boundary]{background-color: #b4d7ff;} + +.mce-content-body .mce-edit-focus{outline: 3px solid #b4d7ff;} + +.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{background-color: #b4d7ff !important;} + +.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background: 0 0;} + +.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background: 0 0;} + +.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{-webkit-touch-callout: none;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;} + +.mce-content-body img::-moz-selection{background: 0 0;} + +.mce-content-body img::selection{background: 0 0;} + +.ephox-snooker-resizer-bar{background-color: #b4d7ff;opacity: 0;} + +.ephox-snooker-resizer-cols{cursor: col-resize;} + +.ephox-snooker-resizer-rows{cursor: row-resize;} + +.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity: 1;} + +.mce-spellchecker-word{height: 2rem;cursor: default;background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.5'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position: 0 calc(100% + 1px);background-repeat: repeat-x;background-size: auto 6px;} + +.mce-spellchecker-grammar{cursor: default;background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23008800'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position: 0 calc(100% + 1px);background-repeat: repeat-x;background-size: auto 6px;} + +.mce-toc{border: 1px solid gray;} + +.mce-toc h2{margin: 4px;} + +.mce-toc li{list-style-type: none;} + +.mce-item-table,.mce-item-table caption,.mce-item-table td,.mce-item-table th{border: 1px dashed #bbb;} + +.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{padding-top: 10px;margin-left: 3px;background-repeat: no-repeat;border: 1px dashed #bbb;} + +.mce-visualblocks p{background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7);} + +.mce-visualblocks h1{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==);} + +.mce-visualblocks h2{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==);} + +.mce-visualblocks h3{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7);} + +.mce-visualblocks h4{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==);} + +.mce-visualblocks h5{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==);} + +.mce-visualblocks h6{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==);} + +.mce-visualblocks div:not([data-mce-bogus]){background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7);} + +.mce-visualblocks section{background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=);} + +.mce-visualblocks article{background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7);} + +.mce-visualblocks blockquote{background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7);} + +.mce-visualblocks address{background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=);} + +.mce-visualblocks pre{background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==);} + +.mce-visualblocks figure{background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7);} + +.mce-visualblocks figcaption{border: 1px dashed #bbb;} + +.mce-visualblocks hgroup{background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7);} + +.mce-visualblocks aside{background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=);} + +.mce-visualblocks ul{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==);} + +.mce-visualblocks ol{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==);} + +.mce-visualblocks dl{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==);} + +.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left: 3px;} + +.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x: right;margin-right: 3px;} + +.mce-nbsp,.mce-shy{background: #aaa;} + +.mce-shy::after{content: '-';} + +.tox-toolbar-dock-fadeout{opacity: 0;visibility: hidden;} + +.tox-toolbar-dock-fadein{opacity: 1;visibility: visible;} + +.tox-toolbar-dock-transition{transition: visibility 0s linear .3s,opacity .3s ease;} + +.tox-toolbar-dock-transition.tox-toolbar-dock-fadein{transition-delay: 0s;} diff --git a/public/resource/tinymce/skins/ui/oxide/content.min.css b/public/resource/tinymce/skins/ui/oxide/content.min.css new file mode 100644 index 0000000..6e7165f --- /dev/null +++ b/public/resource/tinymce/skins/ui/oxide/content.min.css @@ -0,0 +1,235 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.mce-content-body .mce-item-anchor{display: inline-block;width: 8px !important;height: 12px !important;padding: 0 2px;cursor: default;background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;-webkit-user-select: all;-moz-user-select: all;-ms-user-select: all;user-select: all;-webkit-user-modify: read-only;-moz-user-modify: read-only;} + +.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset: 1px;} + +.tox-comments-visible .tox-comment{background-color: #fff0b7;} + +.tox-comments-visible .tox-comment--active{background-color: #ffe168;} + +.tox-checklist>li:not(.tox-checklist--hidden){margin: .25em 0;list-style: none;} + +.tox-checklist>li:not(.tox-checklist--hidden)::before{position: absolute;width: 1em;height: 1em;margin-top: .125em;margin-left: -1.5em;cursor: pointer;background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A");background-size: 100%;content: '';} + +.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A");} + +[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-right: -1.5em;margin-left: 0;} + +code[class*=language-],pre[class*=language-]{font-family: Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size: .875rem;-webkit-hyphens: none;-ms-hyphens: none;hyphens: none;line-height: 1.5;word-spacing: normal;color: #000;text-shadow: 0 1px #fff;word-break: normal;word-wrap: normal;white-space: pre;-moz-tab-size: 4;tab-size: 4;} + +code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow: none;background: #b3d4fc;} + +code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow: none;background: #b3d4fc;}@media print{code[class*=language-],pre[class*=language-]{text-shadow: none;}} + +pre[class*=language-]{padding: 1em;margin: .5em 0;overflow: auto;} + +:not(pre)>code[class*=language-],pre[class*=language-]{background: 0 0 !important;border: 1px solid #ccc;} + +:not(pre)>code[class*=language-]{padding: .1em;border-radius: .3em;} + +.token.cdata,.token.comment,.token.doctype,.token.prolog{color: #708090;} + +.token.punctuation{color: #999;} + +.namespace{opacity: .7;} + +.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color: #905;} + +.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color: #690;} + +.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color: #a67f59;background: hsla(0,0%,100%,.5);} + +.token.atrule,.token.attr-value,.token.keyword{color: #07a;} + +.token.function{color: #dd4a68;} + +.token.important,.token.regex,.token.variable{color: #e90;} + +.token.bold,.token.important{font-weight: 700;} + +.token.italic{font-style: italic;} + +.token.entity{cursor: help;} + +:not([dir=rtl]) code[class*=language-],:not([dir=rtl]) pre[class*=language-]{text-align: left;direction: ltr;} + +[dir=rtl] code[class*=language-],[dir=rtl] pre[class*=language-]{text-align: right;direction: rtl;} + +.mce-content-body{overflow-wrap: break-word;word-wrap: break-word;} + +.mce-content-body .mce-visual-caret{position: absolute;background-color: #000;background-color: currentColor;} + +.mce-content-body .mce-visual-caret-hidden{display: none;} + +.mce-content-body [data-mce-caret]{position: absolute;top: 0;right: auto;left: -1000px;padding: 0;margin: 0;} + +.mce-content-body .mce-offscreen-selection{position: absolute;left: -9999999999px;max-width: 1000000px;} + +.mce-content-body [contentEditable=false]{cursor: default;} + +.mce-content-body [contentEditable=true]{cursor: text;} + +.tox-cursor-format-painter{cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"),default;} + +.mce-content-body figure.align-left{float: left;} + +.mce-content-body figure.align-right{float: right;} + +.mce-content-body figure.image.align-center{display: table;margin-right: auto;margin-left: auto;} + +.mce-preview-object{position: relative;display: inline-block;margin: 0 2px 0 2px;line-height: 0;border: 1px solid gray;} + +.mce-preview-object .mce-shim{position: absolute;top: 0;left: 0;width: 100%;height: 100%;background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);} + +.mce-preview-object[data-mce-selected="2"] .mce-shim{display: none;} + +.mce-object{background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;border: 1px dashed #aaa;} + +.mce-pagebreak{display: block;width: 100%;height: 5px;margin-top: 15px;cursor: default;border: 1px dashed #aaa;page-break-before: always;}@media print{.mce-pagebreak{border: 0;}} + +.tiny-pageembed .mce-shim{position: absolute;top: 0;left: 0;width: 100%;height: 100%;background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);} + +.tiny-pageembed[data-mce-selected="2"] .mce-shim{display: none;} + +.tiny-pageembed{position: relative;display: inline-block;} + +.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{position: relative;display: block;width: 100%;padding: 0;overflow: hidden;} + +.tiny-pageembed--16by9::before,.tiny-pageembed--1by1::before,.tiny-pageembed--21by9::before,.tiny-pageembed--4by3::before{display: block;content: "";} + +.tiny-pageembed--21by9::before{padding-top: 42.857143%;} + +.tiny-pageembed--16by9::before{padding-top: 56.25%;} + +.tiny-pageembed--4by3::before{padding-top: 75%;} + +.tiny-pageembed--1by1::before{padding-top: 100%;} + +.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{position: absolute;top: 0;left: 0;width: 100%;height: 100%;border: 0;} + +.mce-content-body div.mce-resizehandle{position: absolute;z-index: 10000;width: 10px;height: 10px;background-color: #4099ff;border-color: #4099ff;border-style: solid;border-width: 1px;box-sizing: border-box;} + +.mce-content-body div.mce-resizehandle:hover{background-color: #4099ff;} + +.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor: nwse-resize;} + +.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor: nesw-resize;} + +.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor: nwse-resize;} + +.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor: nesw-resize;} + +.mce-content-body .mce-clonedresizable{position: absolute;z-index: 10000;outline: 1px dashed #000;opacity: .5;} + +.mce-content-body .mce-resize-helper{position: absolute;z-index: 10001;display: none;padding: 5px;margin: 5px 10px;font-family: sans-serif;font-size: 12px;line-height: 14px;color: #fff;white-space: nowrap;background: #555;background: rgba(0,0,0,.75);border: 1px;border-radius: 3px;} + +.mce-match-marker{color: #fff;background: #aaa;} + +.mce-match-marker-selected{color: #fff;background: #39f;} + +.mce-content-body img[data-mce-selected],.mce-content-body table[data-mce-selected]{outline: 3px solid #b4d7ff;} + +.mce-content-body hr[data-mce-selected]{outline: 3px solid #b4d7ff;outline-offset: 1px;} + +.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline: 3px solid #b4d7ff;} + +.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline: 3px solid #b4d7ff;} + +.mce-content-body [contentEditable=false][data-mce-selected]{cursor: not-allowed;outline: 3px solid #b4d7ff;} + +.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline: 0;} + +.mce-content-body [data-mce-selected=inline-boundary]{background-color: #b4d7ff;} + +.mce-content-body .mce-edit-focus{outline: 3px solid #b4d7ff;} + +.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{background-color: #b4d7ff !important;} + +.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background: 0 0;} + +.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background: 0 0;} + +.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{-webkit-touch-callout: none;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;} + +.mce-content-body img::-moz-selection{background: 0 0;} + +.mce-content-body img::selection{background: 0 0;} + +.ephox-snooker-resizer-bar{background-color: #b4d7ff;opacity: 0;} + +.ephox-snooker-resizer-cols{cursor: col-resize;} + +.ephox-snooker-resizer-rows{cursor: row-resize;} + +.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity: 1;} + +.mce-spellchecker-word{height: 2rem;cursor: default;background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.5'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position: 0 calc(100% + 1px);background-repeat: repeat-x;background-size: auto 6px;} + +.mce-spellchecker-grammar{cursor: default;background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23008800'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position: 0 calc(100% + 1px);background-repeat: repeat-x;background-size: auto 6px;} + +.mce-toc{border: 1px solid gray;} + +.mce-toc h2{margin: 4px;} + +.mce-toc li{list-style-type: none;} + +.mce-item-table,.mce-item-table caption,.mce-item-table td,.mce-item-table th{border: 1px dashed #bbb;} + +.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{padding-top: 10px;margin-left: 3px;background-repeat: no-repeat;border: 1px dashed #bbb;} + +.mce-visualblocks p{background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7);} + +.mce-visualblocks h1{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==);} + +.mce-visualblocks h2{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==);} + +.mce-visualblocks h3{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7);} + +.mce-visualblocks h4{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==);} + +.mce-visualblocks h5{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==);} + +.mce-visualblocks h6{background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==);} + +.mce-visualblocks div:not([data-mce-bogus]){background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7);} + +.mce-visualblocks section{background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=);} + +.mce-visualblocks article{background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7);} + +.mce-visualblocks blockquote{background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7);} + +.mce-visualblocks address{background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=);} + +.mce-visualblocks pre{background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==);} + +.mce-visualblocks figure{background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7);} + +.mce-visualblocks figcaption{border: 1px dashed #bbb;} + +.mce-visualblocks hgroup{background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7);} + +.mce-visualblocks aside{background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=);} + +.mce-visualblocks ul{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==);} + +.mce-visualblocks ol{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==);} + +.mce-visualblocks dl{background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==);} + +.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left: 3px;} + +.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x: right;margin-right: 3px;} + +.mce-nbsp,.mce-shy{background: #aaa;} + +.mce-shy::after{content: '-';} + +body{font-family: sans-serif;} + +table{border-collapse: collapse;} diff --git a/public/resource/tinymce/skins/ui/oxide/content.mobile.min.css b/public/resource/tinymce/skins/ui/oxide/content.mobile.min.css new file mode 100644 index 0000000..c052252 --- /dev/null +++ b/public/resource/tinymce/skins/ui/oxide/content.mobile.min.css @@ -0,0 +1,17 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{position: absolute;display: inline-block;background-color: green;opacity: .5;} + +body{-webkit-text-size-adjust: none;} + +body img{max-width: 96vw;} + +body table img{max-width: 95%;} + +body{font-family: sans-serif;} + +table{border-collapse: collapse;} diff --git a/public/resource/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff b/public/resource/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff new file mode 100644 index 0000000..1e3be03 Binary files /dev/null and b/public/resource/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff differ diff --git a/public/resource/tinymce/skins/ui/oxide/skin.min.css b/public/resource/tinymce/skins/ui/oxide/skin.min.css new file mode 100644 index 0000000..5886c59 --- /dev/null +++ b/public/resource/tinymce/skins/ui/oxide/skin.min.css @@ -0,0 +1,875 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tox{font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size: 16px;font-style: normal;font-weight: 400;line-height: normal;color: #222f3e;text-decoration: none;text-shadow: none;text-transform: none;white-space: normal;vertical-align: initial;cursor: auto;box-sizing: content-box;-webkit-tap-highlight-color: transparent;} + +.tox :not(svg){font-family: inherit;font-size: inherit;font-style: inherit;font-weight: inherit;line-height: inherit;color: inherit;text-align: inherit;text-decoration: inherit;text-shadow: inherit;text-transform: inherit;white-space: inherit;vertical-align: inherit;cursor: inherit;box-sizing: inherit;direction: inherit;-webkit-tap-highlight-color: inherit;} + +.tox :not(svg){position: static;float: none;width: auto;height: auto;max-width: none;padding: 0;margin: 0;background: 0 0;border: 0;outline: 0;} + +.tox:not([dir=rtl]){text-align: left;direction: ltr;} + +.tox[dir=rtl]{text-align: right;direction: rtl;} + +.tox-tinymce{position: relative;display: flex;overflow: hidden;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;border: 1px solid #ccc;border-radius: 0;visibility: inherit !important;box-shadow: none;box-sizing: border-box;flex-direction: column;} + +.tox-editor-container{display: flex;flex: 1 1 auto;flex-direction: column;overflow: hidden;} + +.tox-editor-container>:first-child{border-top: none !important;} + +.tox-tinymce-aux{font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;} + +.tox-tinymce :focus,.tox-tinymce-aux :focus{outline: 0;} + +button::-moz-focus-inner{border: 0;} + +.tox-silver-sink{z-index: 1300;} + +.tox .tox-anchorbar{display: flex;flex: 0 0 auto;} + +.tox .tox-bar{display: flex;flex: 0 0 auto;} + +.tox .tox-button{display: inline-block;padding: 4px 16px;margin: 0;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size: 14px;font-weight: 700;line-height: 24px;letter-spacing: 1;color: #fff;text-align: center;text-decoration: none;text-transform: capitalize;white-space: nowrap;cursor: pointer;background-color: #207ab7;background-image: none;background-position: none;background-repeat: none;border-color: #207ab7;border-style: solid;border-width: 1px;border-radius: 3px;outline: 0;box-shadow: none;box-sizing: border-box;} + +.tox .tox-button[disabled]{color: rgba(255,255,255,.5);cursor: not-allowed;background-color: #207ab7;background-image: none;border-color: #207ab7;box-shadow: none;} + +.tox .tox-button:focus:not(:disabled){color: #fff;background-color: #1c6ca1;background-image: none;border-color: #1c6ca1;box-shadow: none;} + +.tox .tox-button:hover:not(:disabled){color: #fff;background-color: #1c6ca1;background-image: none;border-color: #1c6ca1;box-shadow: none;} + +.tox .tox-button:active:not(:disabled){color: #fff;background-color: #185d8c;background-image: none;border-color: #185d8c;box-shadow: none;} + +.tox .tox-button--secondary{padding: 4px 16px;color: #222f3e;text-decoration: none;text-transform: capitalize;background-color: #f0f0f0;background-image: none;background-position: none;background-repeat: none;border-color: #f0f0f0;border-style: solid;border-width: 1px;border-radius: 3px;outline: 0;box-shadow: none;} + +.tox .tox-button--secondary[disabled]{color: rgba(34,47,62,.5);background-color: #f0f0f0;background-image: none;border-color: #f0f0f0;box-shadow: none;} + +.tox .tox-button--secondary:focus:not(:disabled){color: #222f3e;background-color: #e3e3e3;background-image: none;border-color: #e3e3e3;box-shadow: none;} + +.tox .tox-button--secondary:hover:not(:disabled){color: #222f3e;background-color: #e3e3e3;background-image: none;border-color: #e3e3e3;box-shadow: none;} + +.tox .tox-button--secondary:active:not(:disabled){color: #222f3e;background-color: #d6d6d6;background-image: none;border-color: #d6d6d6;box-shadow: none;} + +.tox .tox-button--icon,.tox .tox-button.tox-button--icon,.tox .tox-button.tox-button--secondary.tox-button--icon{padding: 4px;} + +.tox .tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg{display: block;fill: currentColor;} + +.tox .tox-button-link{display: inline-block;padding: 0;margin: 0;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size: 16px;font-weight: 400;line-height: 1.3;white-space: nowrap;cursor: pointer;background: 0;border: none;box-sizing: border-box;} + +.tox .tox-button-link--sm{font-size: 14px;} + +.tox .tox-button--naked{color: #222f3e;background-color: transparent;border-color: transparent;box-shadow: unset;} + +.tox .tox-button--naked:hover:not(:disabled){color: #222f3e;background-color: #e3e3e3;border-color: #e3e3e3;box-shadow: none;} + +.tox .tox-button--naked:focus:not(:disabled){color: #222f3e;background-color: #e3e3e3;border-color: #e3e3e3;box-shadow: none;} + +.tox .tox-button--naked:active:not(:disabled){color: #222f3e;background-color: #d6d6d6;border-color: #d6d6d6;box-shadow: none;} + +.tox .tox-button--naked .tox-icon svg{fill: currentColor;} + +.tox .tox-button--naked.tox-button--icon{color: currentColor;} + +.tox .tox-button--naked.tox-button--icon:hover:not(:disabled){color: #222f3e;} + +.tox .tox-checkbox{display: flex;height: 36px;min-width: 36px;cursor: pointer;border-radius: 3px;align-items: center;} + +.tox .tox-checkbox__input{position: absolute;top: auto;left: -10000px;width: 1px;height: 1px;overflow: hidden;} + +.tox .tox-checkbox__icons{width: 24px;height: 24px;padding: calc(4px - 1px);border-radius: 3px;box-shadow: 0 0 0 2px transparent;box-sizing: content-box;} + +.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display: block;fill: rgba(34,47,62,.3);} + +.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display: none;fill: #207ab7;} + +.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg{display: none;fill: #207ab7;} + +.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display: none;} + +.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__checked svg{display: block;} + +.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display: none;} + +.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display: block;} + +.tox input.tox-checkbox__input:focus+.tox-checkbox__icons{padding: calc(4px - 1px);border-radius: 3px;box-shadow: inset 0 0 0 1px #207ab7;} + +.tox:not([dir=rtl]) .tox-checkbox__label{margin-left: 4px;} + +.tox:not([dir=rtl]) .tox-bar .tox-checkbox{margin-left: 4px;} + +.tox[dir=rtl] .tox-checkbox__label{margin-right: 4px;} + +.tox[dir=rtl] .tox-bar .tox-checkbox{margin-right: 4px;} + +.tox .tox-collection--toolbar .tox-collection__group{display: flex;padding: 0;} + +.tox .tox-collection--grid .tox-collection__group{display: flex;max-height: 208px;padding: 0;overflow-x: hidden;overflow-y: auto;flex-wrap: wrap;} + +.tox .tox-collection--list .tox-collection__group{padding: 4px 0;border-color: #ccc;border-style: solid;border-top-width: 1px;border-right-width: 0;border-bottom-width: 0;border-left-width: 0;} + +.tox .tox-collection--list .tox-collection__group:first-child{border-top-width: 0;} + +.tox .tox-collection__group-heading{padding: 4px 8px;margin-top: -4px;margin-bottom: 4px;font-size: 12px;font-style: normal;font-weight: 400;color: rgba(34,47,62,.7);text-transform: none;cursor: default;background-color: #e6e6e6;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;-webkit-touch-callout: none;} + +.tox .tox-collection__item{display: flex;color: #222f3e;cursor: pointer;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;align-items: center;-webkit-touch-callout: none;} + +.tox .tox-collection--list .tox-collection__item{padding: 4px 8px;} + +.tox .tox-collection--toolbar .tox-collection__item{padding: 4px;border-radius: 3px;} + +.tox .tox-collection--grid .tox-collection__item{padding: 4px;border-radius: 3px;} + +.tox .tox-collection--list .tox-collection__item--enabled{color: contrast(inherit,#222f3e,#fff);background-color: inherit;} + +.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled){color: #222f3e;background-color: #dee0e2;} + +.tox .tox-collection--toolbar .tox-collection__item--enabled{color: #222f3e;background-color: #c8cbcf;} + +.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled){color: #222f3e;background-color: #dee0e2;} + +.tox .tox-collection--grid .tox-collection__item--enabled{color: #222f3e;background-color: #c8cbcf;} + +.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled){color: #222f3e;background-color: #dee0e2;} + +.tox .tox-collection__item--state-disabled{color: rgba(34,47,62,.5);cursor: default;background-color: transparent;} + +.tox .tox-collection__item-icon{display: flex;width: 24px;height: 24px;align-items: center;justify-content: center;} + +.tox .tox-collection__item-icon svg{fill: currentColor;} + +.tox .tox-collection--toolbar-lg .tox-collection__item-icon{width: 48px;height: 48px;} + +.tox .tox-collection__item[role=menuitemcheckbox]:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg{display: none;} + +.tox .tox-collection__item-label{display: inline-block;font-size: 14px;font-style: normal;font-weight: 400;line-height: 24px;color: currentColor;text-transform: none;word-break: break-all;flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-collection__item-accessory{display: inline-block;height: 24px;font-size: 14px;line-height: 24px;color: rgba(34,47,62,.7);text-transform: normal;} + +.tox .tox-collection__item-caret{align-items: center;display: flex;min-height: 24px;} + +.tox .tox-collection__item-caret::after{min-height: inherit;font-size: 0;content: '';} + +.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>:not(:first-child){margin-left: 8px;} + +.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item-label:first-child{margin-left: 4px;} + +.tox:not([dir=rtl]) .tox-collection__item-accessory{margin-left: 16px;text-align: right;} + +.tox:not([dir=rtl]) .tox-collection__item-caret{margin-left: 16px;} + +.tox[dir=rtl] .tox-collection--list .tox-collection__item>:not(:first-child){margin-right: 8px;} + +.tox[dir=rtl] .tox-collection--list .tox-collection__item-label:first-child{margin-right: 4px;} + +.tox[dir=rtl] .tox-collection__item-icon-rtl .tox-collection__item-icon svg{transform: rotateY(180deg);} + +.tox[dir=rtl] .tox-collection__item-accessory{margin-right: 16px;text-align: left;} + +.tox[dir=rtl] .tox-collection__item-caret{margin-right: 16px;transform: rotateY(180deg);} + +.tox .tox-color-picker-container{display: flex;flex-direction: row;height: 225px;margin: 0;} + +.tox .tox-sv-palette{display: flex;height: 100%;box-sizing: border-box;} + +.tox .tox-sv-palette-spectrum{height: 100%;} + +.tox .tox-sv-palette,.tox .tox-sv-palette-spectrum{width: 225px;} + +.tox .tox-sv-palette-thumb{position: absolute;width: 12px;height: 12px;background: 0 0;border: 1px solid #000;border-radius: 50%;box-sizing: content-box;} + +.tox .tox-sv-palette-inner-thumb{position: absolute;width: 10px;height: 10px;border: 1px solid #fff;border-radius: 50%;} + +.tox .tox-hue-slider{width: 25px;height: 100%;box-sizing: border-box;} + +.tox .tox-hue-slider-spectrum{width: 100%;height: 100%;background: linear-gradient(to bottom,red,#ff0080,#f0f,#8000ff,#00f,#0080ff,#0ff,#00ff80,#0f0,#80ff00,#ff0,#ff8000,red);} + +.tox .tox-hue-slider,.tox .tox-hue-slider-spectrum{width: 20px;} + +.tox .tox-hue-slider-thumb{width: 100%;height: 4px;background: #fff;border: 1px solid #000;box-sizing: content-box;} + +.tox .tox-rgb-form{display: flex;flex-direction: column;justify-content: space-between;} + +.tox .tox-rgb-form div{display: flex;width: inherit;margin-bottom: 5px;align-items: center;justify-content: space-between;} + +.tox .tox-rgb-form input{width: 6em;} + +.tox .tox-rgb-form input.tox-invalid{border: 1px solid red !important;} + +.tox .tox-rgb-form .tox-rgba-preview{margin-bottom: 0;border: 1px solid #000;flex-grow: 2;} + +.tox:not([dir=rtl]) .tox-sv-palette{margin-right: 15px;} + +.tox:not([dir=rtl]) .tox-hue-slider{margin-right: 15px;} + +.tox:not([dir=rtl]) .tox-hue-slider-thumb{margin-left: -1px;} + +.tox:not([dir=rtl]) .tox-rgb-form label{margin-right: .5em;} + +.tox[dir=rtl] .tox-sv-palette{margin-left: 15px;} + +.tox[dir=rtl] .tox-hue-slider{margin-left: 15px;} + +.tox[dir=rtl] .tox-hue-slider-thumb{margin-right: -1px;} + +.tox[dir=rtl] .tox-rgb-form label{margin-left: .5em;} + +.tox .tox-toolbar .tox-swatches,.tox .tox-toolbar__overflow .tox-swatches,.tox .tox-toolbar__primary .tox-swatches{margin: 2px 0 3px 4px;} + +.tox .tox-collection--list .tox-collection__group .tox-swatches-menu{margin: -4px 0;border: 0;} + +.tox .tox-swatches__row{display: flex;} + +.tox .tox-swatch{width: 30px;height: 30px;transition: transform .15s,box-shadow .15s;} + +.tox .tox-swatch:focus,.tox .tox-swatch:hover{transform: scale(.8);box-shadow: 0 0 0 1px rgba(127,127,127,.3) inset;} + +.tox .tox-swatch--remove{align-items: center;display: flex;justify-content: center;} + +.tox .tox-swatch--remove svg path{stroke: #e74c3c;} + +.tox .tox-swatches__picker-btn{display: flex;width: 30px;height: 30px;padding: 0;cursor: pointer;background-color: transparent;border: 0;outline: 0;align-items: center;justify-content: center;} + +.tox .tox-swatches__picker-btn svg{width: 24px;height: 24px;} + +.tox .tox-swatches__picker-btn:hover{background: #dee0e2;} + +.tox:not([dir=rtl]) .tox-swatches__picker-btn{margin-left: auto;} + +.tox[dir=rtl] .tox-swatches__picker-btn{margin-right: auto;} + +.tox .tox-comment-thread{position: relative;background: #fff;} + +.tox .tox-comment-thread>:not(:first-child){margin-top: 8px;} + +.tox .tox-comment{position: relative;padding: 8px 8px 16px 8px;background: #fff;border: 1px solid #ccc;border-radius: 3px;box-shadow: 0 4px 8px 0 rgba(34,47,62,.1);} + +.tox .tox-comment__header{display: flex;color: #222f3e;align-items: center;justify-content: space-between;} + +.tox .tox-comment__date{font-size: 12px;color: rgba(34,47,62,.7);} + +.tox .tox-comment__body{position: relative;margin-top: 8px;font-size: 14px;font-style: normal;font-weight: 400;line-height: 1.3;color: #222f3e;text-transform: initial;} + +.tox .tox-comment__body textarea{width: 100%;white-space: normal;resize: none;} + +.tox .tox-comment__expander{padding-top: 8px;} + +.tox .tox-comment__expander p{font-size: 14px;font-style: normal;color: rgba(34,47,62,.7);} + +.tox .tox-comment__body p{margin: 0;} + +.tox .tox-comment__buttonspacing{padding-top: 16px;text-align: center;} + +.tox .tox-comment-thread__overlay::after{position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 5;display: flex;background: #fff;content: "";opacity: .9;} + +.tox .tox-comment__reply{display: flex;flex-shrink: 0;flex-wrap: wrap;justify-content: flex-end;margin-top: 8px;} + +.tox .tox-comment__reply>:first-child{width: 100%;margin-bottom: 8px;} + +.tox .tox-comment__edit{display: flex;flex-wrap: wrap;justify-content: flex-end;margin-top: 16px;} + +.tox .tox-comment__gradient::after{position: absolute;bottom: 0;display: block;width: 100%;height: 5em;margin-top: -40px;background: linear-gradient(rgba(255,255,255,0),#fff);content: "";} + +.tox .tox-comment__overlay{position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 5;display: flex;text-align: center;background: #fff;opacity: .9;flex-direction: column;flex-grow: 1;} + +.tox .tox-comment__loading-text{position: relative;display: flex;color: #222f3e;align-items: center;flex-direction: column;} + +.tox .tox-comment__loading-text>div{padding-bottom: 16px;} + +.tox .tox-comment__overlaytext{position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 10;padding: 1em;font-size: 14px;flex-direction: column;} + +.tox .tox-comment__overlaytext p{color: #222f3e;text-align: center;background-color: #fff;box-shadow: 0 0 8px 8px #fff;} + +.tox .tox-comment__overlaytext div:nth-of-type(2){font-size: .8em;} + +.tox .tox-comment__busy-spinner{position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 1103;display: flex;background-color: #fff;align-items: center;justify-content: center;} + +.tox .tox-comment__scroll{display: flex;flex-direction: column;flex-shrink: 1;overflow: auto;} + +.tox .tox-conversations{margin: 8px;} + +.tox:not([dir=rtl]) .tox-comment__edit{margin-left: 8px;} + +.tox:not([dir=rtl]) .tox-comment__buttonspacing>:last-child,.tox:not([dir=rtl]) .tox-comment__edit>:last-child,.tox:not([dir=rtl]) .tox-comment__reply>:last-child{margin-left: 8px;} + +.tox[dir=rtl] .tox-comment__edit{margin-right: 8px;} + +.tox[dir=rtl] .tox-comment__buttonspacing>:last-child,.tox[dir=rtl] .tox-comment__edit>:last-child,.tox[dir=rtl] .tox-comment__reply>:last-child{margin-right: 8px;} + +.tox .tox-user{align-items: center;display: flex;} + +.tox .tox-user__avatar svg{fill: rgba(34,47,62,.7);} + +.tox .tox-user__name{font-size: 12px;font-style: normal;font-weight: 700;color: rgba(34,47,62,.7);text-transform: uppercase;} + +.tox:not([dir=rtl]) .tox-user__avatar svg{margin-right: 8px;} + +.tox:not([dir=rtl]) .tox-user__avatar+.tox-user__name{margin-left: 8px;} + +.tox[dir=rtl] .tox-user__avatar svg{margin-left: 8px;} + +.tox[dir=rtl] .tox-user__avatar+.tox-user__name{margin-right: 8px;} + +.tox .tox-dialog-wrap{position: fixed;top: 0;right: 0;bottom: 0;left: 0;z-index: 1100;display: flex;align-items: center;justify-content: center;} + +.tox .tox-dialog-wrap__backdrop{position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 1101;background-color: rgba(255,255,255,.75);} + +.tox .tox-dialog{position: relative;z-index: 1102;display: flex;width: 95vw;max-width: 480px;max-height: 100%;overflow: hidden;background-color: #fff;border-color: #ccc;border-style: solid;border-width: 1px;border-radius: 3px;box-shadow: 0 16px 16px -10px rgba(34,47,62,.15),0 0 40px 1px rgba(34,47,62,.15);flex-direction: column;} + +.tox .tox-dialog__header{position: relative;display: flex;padding: 8px 16px 0 16px;margin-bottom: 16px;font-size: 16px;color: #222f3e;background-color: #fff;border-bottom: none;align-items: center;justify-content: space-between;} + +.tox .tox-dialog__header .tox-button{z-index: 1;} + +.tox .tox-dialog__draghandle{position: absolute;top: 0;left: 0;width: 100%;height: 100%;cursor: grab;} + +.tox .tox-dialog__draghandle:active{cursor: grabbing;} + +.tox .tox-dialog__dismiss{margin-left: auto;} + +.tox .tox-dialog__title{margin: 0;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size: 20px;font-style: normal;font-weight: 400;line-height: 1.3;text-transform: normal;} + +.tox .tox-dialog__body{display: flex;min-width: 0;padding: 0 16px;font-size: 16px;font-style: normal;font-weight: 400;line-height: 1.3;color: #222f3e;text-align: left;text-transform: normal;flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-dialog__body-nav{align-items: flex-start;display: flex;flex-direction: column;} + +.tox .tox-dialog__body-nav-item{display: inline-block;margin-bottom: 8px;font-size: 14px;line-height: 1.3;color: rgba(34,47,62,.7);text-decoration: none;border-bottom: 2px solid transparent;} + +.tox .tox-dialog__body-nav-item--active{color: #207ab7;border-bottom: 2px solid #207ab7;} + +.tox .tox-dialog__body-content{display: flex;flex: 1;flex-direction: column;-ms-flex-preferred-size: auto;max-height: 650px;overflow: auto;} + +.tox .tox-dialog__body-content>*{margin-top: 16px;margin-bottom: 0;} + +.tox .tox-dialog__body-content>:first-child{margin-top: 0;} + +.tox .tox-dialog__body-content>:last-child{margin-bottom: 0;} + +.tox .tox-dialog__body-content>:only-child{margin-top: 0;margin-bottom: 0;} + +.tox .tox-dialog--width-lg{height: 650px;max-width: 1200px;} + +.tox .tox-dialog--width-md{max-width: 800px;} + +.tox .tox-dialog--width-md .tox-dialog__body-content{overflow: auto;} + +.tox .tox-dialog__body-content--centered{text-align: center;} + +.tox .tox-dialog__body-content--spacious{margin-bottom: 16px;} + +.tox .tox-dialog__footer{display: flex;padding: 8px 16px;margin-top: 16px;background-color: #fff;border-top: 1px solid #ccc;align-items: center;justify-content: space-between;} + +.tox .tox-dialog__busy-spinner{position: absolute;top: 0;right: 0;bottom: 0;left: 0;z-index: 1103;display: flex;background-color: rgba(255,255,255,.75);align-items: center;justify-content: center;} + +.tox .tox-dialog__table{width: 100%;border-collapse: collapse;} + +.tox .tox-dialog__table thead th{padding-bottom: 8px;font-weight: 700;} + +.tox .tox-dialog__table tbody tr{border-bottom: 1px solid #ccc;} + +.tox .tox-dialog__table tbody tr:last-child{border-bottom: none;} + +.tox .tox-dialog__table td{padding-top: 8px;padding-bottom: 8px;} + +.tox .tox-dialog__popups{position: absolute;z-index: 1100;width: 100%;} + +.tox .tox-dialog__body-iframe{display: flex;flex: 1;flex-direction: column;-ms-flex-preferred-size: auto;} + +.tox .tox-dialog__body-iframe .tox-navobj{display: flex;flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2){flex: 1;-ms-flex-preferred-size: auto;height: 100%;} + +body.tox-dialog__disable-scroll{overflow: hidden;} + +.tox.tox-platform-ie .tox-dialog-wrap{position: -ms-device-fixed;} + +.tox:not([dir=rtl]) .tox-dialog__body-nav{margin-right: 32px;} + +.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end>*,.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start>*{margin-left: 8px;} + +.tox[dir=rtl] .tox-dialog__body{text-align: right;} + +.tox[dir=rtl] .tox-dialog__body-nav{margin-left: 32px;} + +.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end>*,.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start>*{margin-right: 8px;} + +.tox .tox-dropzone-container{display: flex;flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-dropzone{display: flex;min-height: 100px;padding: 10px;background: #fff;border: 2px dashed #ccc;box-sizing: border-box;align-items: center;flex-direction: column;flex-grow: 1;justify-content: center;} + +.tox .tox-dropzone p{margin: 0 0 16px 0;color: rgba(34,47,62,.7);} + +.tox .tox-edit-area{position: relative;display: flex;overflow: hidden;border-top: 1px solid #ccc;flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-edit-area__iframe{position: absolute;width: 100%;height: 100%;background-color: #fff;border: 0;box-sizing: border-box;flex: 1;-ms-flex-preferred-size: auto;} + +.tox.tox-inline-edit-area{border: 1px dotted #ccc;} + +.tox .tox-control-wrap{flex: 1;position: relative;} + +.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid,.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown,.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid{display: none;} + +.tox .tox-control-wrap svg{display: block;} + +.tox .tox-control-wrap__status-icon-wrap{position: absolute;top: 50%;transform: translateY(-50%);} + +.tox .tox-control-wrap__status-icon-invalid svg{fill: #c00;} + +.tox .tox-control-wrap__status-icon-unknown svg{fill: orange;} + +.tox .tox-control-wrap__status-icon-valid svg{fill: green;} + +.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield{padding-right: 32px;} + +.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap{right: 4px;} + +.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield{padding-left: 32px;} + +.tox[dir=rtl] .tox-control-wrap__status-icon-wrap{left: 4px;} + +.tox .tox-autocompleter{max-width: 25em;} + +.tox .tox-autocompleter .tox-menu{max-width: 25em;} + +.tox .tox-color-input{display: flex;} + +.tox .tox-color-input .tox-textfield{display: flex;border-radius: 3px 0 0 3px;} + +.tox .tox-color-input span{display: flex;width: 35px;cursor: pointer;border-color: rgba(34,47,62,.2);border-style: solid;border-width: 1px 1px 1px 0;border-radius: 0 3px 3px 0;box-shadow: none;box-sizing: border-box;} + +.tox .tox-color-input span:focus{border-color: #207ab7;} + +.tox[dir=rtl] .tox-color-input .tox-textfield{border-radius: 0 3px 3px 0;} + +.tox[dir=rtl] .tox-color-input span{border-width: 1px 0 1px 1px;border-radius: 3px 0 0 3px;} + +.tox .tox-label,.tox .tox-toolbar-label{display: block;padding: 0 8px 0 0;font-size: 14px;font-style: normal;font-weight: 400;line-height: 1.3;color: rgba(34,47,62,.7);text-transform: normal;white-space: nowrap;} + +.tox .tox-toolbar-label{padding: 0 8px;} + +.tox[dir=rtl] .tox-label{padding: 0 0 0 8px;} + +.tox .tox-form{display: flex;flex: 1;flex-direction: column;-ms-flex-preferred-size: auto;} + +.tox .tox-form__group{margin-bottom: 4px;box-sizing: border-box;} + +.tox .tox-form__group--error{color: #c00;} + +.tox .tox-form__group--collection{display: flex;} + +.tox .tox-form__grid{display: flex;flex-direction: row;flex-wrap: wrap;justify-content: space-between;} + +.tox .tox-form__grid--2col>.tox-form__group{width: calc(50% - (8px / 2));} + +.tox .tox-form__grid--3col>.tox-form__group{width: calc(100% / 3 - (8px / 2));} + +.tox .tox-form__grid--4col>.tox-form__group{width: calc(25% - (8px / 2));} + +.tox .tox-form__controls-h-stack{align-items: center;display: flex;} + +.tox .tox-form__group--inline{align-items: center;display: flex;} + +.tox .tox-form__group--stretched{display: flex;flex: 1;flex-direction: column;-ms-flex-preferred-size: auto;} + +.tox .tox-form__group--stretched .tox-textarea{flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-form__group--stretched .tox-navobj{display: flex;flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-form__group--stretched .tox-navobj :nth-child(2){flex: 1;-ms-flex-preferred-size: auto;height: 100%;} + +.tox:not([dir=rtl]) .tox-form__controls-h-stack>:not(:first-child){margin-left: 4px;} + +.tox[dir=rtl] .tox-form__controls-h-stack>:not(:first-child){margin-right: 4px;} + +.tox .tox-lock.tox-locked .tox-lock-icon__unlock,.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock{display: none;} + +.tox .tox-textarea,.tox .tox-textfield,.tox .tox-toolbar-textfield,.tox:not([dir=rtl]) .tox-selectfield select,.tox[dir=rtl] .tox-selectfield select{width: 100%;padding: 5px 4.75px;margin: 0;font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size: 16px;line-height: 24px;color: #222f3e;background-color: #fff;border-color: #ccc;border-style: solid;border-width: 1px;border-radius: 3px;outline: 0;box-shadow: none;box-sizing: border-box;resize: none;-webkit-appearance: none;-moz-appearance: none;appearance: none;} + +.tox .tox-selectfield select:focus,.tox .tox-textarea:focus,.tox .tox-textfield:focus{border-color: #207ab7;outline: 0;box-shadow: none;} + +.tox .tox-toolbar-textfield{max-width: 250px;margin-top: 2px;margin-bottom: 3px;border-width: 0;} + +.tox .tox-naked-btn{display: block;padding: 0;margin: 0;color: #207ab7;cursor: pointer;background-color: transparent;border: 0;border-color: transparent;box-shadow: unset;} + +.tox .tox-naked-btn svg{display: block;fill: #222f3e;} + +.tox:not([dir=rtl]) .tox-toolbar-textfield+*{margin-left: 4px;} + +.tox[dir=rtl] .tox-toolbar-textfield+*{margin-right: 4px;} + +.tox .tox-selectfield{position: relative;cursor: pointer;} + +.tox .tox-selectfield select::-ms-expand{display: none;} + +.tox .tox-selectfield svg{position: absolute;top: 50%;pointer-events: none;transform: translateY(-50%);} + +.tox:not([dir=rtl]) .tox-selectfield select{padding-right: 24px;} + +.tox:not([dir=rtl]) .tox-selectfield svg{right: 8px;} + +.tox[dir=rtl] .tox-selectfield select{padding-left: 24px;} + +.tox[dir=rtl] .tox-selectfield svg{left: 8px;} + +.tox .tox-textarea{white-space: pre-wrap;-webkit-appearance: textarea;-moz-appearance: textarea;appearance: textarea;} + +.tox-fullscreen{position: fixed;top: 0;left: 0;width: 100%;height: 100%;padding: 0;margin: 0;overflow: hidden;border: 0;} + +.tox-fullscreen .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display: none;} + +.tox-fullscreen .tox.tox-tinymce.tox-fullscreen{z-index: 1200;} + +.tox-fullscreen .tox.tox-tinymce-aux{z-index: 1201;} + +.tox .tox-image-tools{width: 100%;} + +.tox .tox-image-tools__toolbar{align-items: center;display: flex;justify-content: center;} + +.tox .tox-image-tools__image{position: relative;width: 100%;height: 380px;overflow: auto;background-color: #666;} + +.tox .tox-image-tools__image,.tox .tox-image-tools__image+.tox-image-tools__toolbar{margin-top: 8px;} + +.tox .tox-image-tools__image-bg{background: url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==);} + +.tox .tox-image-tools__toolbar>.tox-spacer{flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-croprect-block{position: absolute;background: #000;opacity: .5;zoom: 1;} + +.tox .tox-croprect-handle{position: absolute;top: 0;left: 0;width: 20px;height: 20px;border: 2px solid #fff;} + +.tox .tox-croprect-handle-move{position: absolute;cursor: move;border: 0;} + +.tox .tox-croprect-handle-nw{top: 100px;left: 100px;margin: -2px 0 0 -2px;cursor: nw-resize;border-width: 2px 0 0 2px;} + +.tox .tox-croprect-handle-ne{top: 100px;left: 200px;margin: -2px 0 0 -20px;cursor: ne-resize;border-width: 2px 2px 0 0;} + +.tox .tox-croprect-handle-sw{top: 200px;left: 100px;margin: -20px 2px 0 -2px;cursor: sw-resize;border-width: 0 0 2px 2px;} + +.tox .tox-croprect-handle-se{top: 200px;left: 200px;margin: -20px 0 0 -20px;cursor: se-resize;border-width: 0 2px 2px 0;} + +.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-slider:not(:first-of-type){margin-left: 8px;} + +.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-button+.tox-slider{margin-left: 32px;} + +.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-slider+.tox-button{margin-left: 32px;} + +.tox[dir=rtl] .tox-image-tools__toolbar>.tox-slider:not(:first-of-type){margin-right: 8px;} + +.tox[dir=rtl] .tox-image-tools__toolbar>.tox-button+.tox-slider{margin-right: 32px;} + +.tox[dir=rtl] .tox-image-tools__toolbar>.tox-slider+.tox-button{margin-right: 32px;} + +.tox .tox-insert-table-picker{display: flex;flex-wrap: wrap;width: 169px;} + +.tox .tox-insert-table-picker>div{width: 16px;height: 16px;border-color: #ccc;border-style: solid;border-width: 0 1px 1px 0;box-sizing: content-box;} + +.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker{margin: -4px 0;} + +.tox .tox-insert-table-picker .tox-insert-table-picker__selected{background-color: rgba(32,122,183,.5);border-color: rgba(32,122,183,.5);} + +.tox .tox-insert-table-picker__label{display: block;width: 100%;padding: 4px;font-size: 14px;color: rgba(34,47,62,.7);text-align: center;} + +.tox:not([dir=rtl]) .tox-insert-table-picker>div:nth-child(10n){border-right: 0;} + +.tox[dir=rtl] .tox-insert-table-picker>div:nth-child(10n+1){border-right: 0;} + +.tox .tox-menu{z-index: 1;display: inline-block;overflow: hidden;vertical-align: top;background-color: #fff;border: 1px solid #ccc;border-radius: 3px;box-shadow: 0 4px 8px 0 rgba(34,47,62,.1);} + +.tox .tox-menu.tox-collection.tox-collection--list{padding: 0;} + +.tox .tox-menu.tox-collection.tox-collection--toolbar{padding: 4px;} + +.tox .tox-menu.tox-collection.tox-collection--grid{padding: 4px;} + +.tox .tox-menu__label blockquote,.tox .tox-menu__label code,.tox .tox-menu__label h1,.tox .tox-menu__label h2,.tox .tox-menu__label h3,.tox .tox-menu__label h4,.tox .tox-menu__label h5,.tox .tox-menu__label h6,.tox .tox-menu__label p{margin: 0;} + +.tox .tox-menubar{display: flex;padding: 0 4px;margin-bottom: -1px;background: url("data:image/svg+xml;charset=utf8,%3Csvg height='43px' viewBox='0 0 40 43px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='42px' width='100' height='1' fill='%23cccccc'/%3E%3C/svg%3E") left 0 top 0 #fff;background-color: #fff;flex: 0 0 auto;flex-shrink: 0;flex-wrap: wrap;} + +.tox .tox-mbtn{display: flex;width: auto;height: 34px;padding: 0 4px;margin: 2px 0 3px 0;overflow: hidden;font-size: 14px;font-style: normal;font-weight: 400;color: #222f3e;text-transform: normal;background: 0 0;border: 0;border-radius: 3px;outline: 0;box-shadow: none;align-items: center;flex: 0 0 auto;justify-content: center;} + +.tox .tox-mbtn[disabled]{color: rgba(34,47,62,.5);cursor: not-allowed;background-color: none;border-color: none;box-shadow: none;} + +.tox .tox-mbtn:hover:not(:disabled){color: #222f3e;background: #dee0e2;box-shadow: none;} + +.tox .tox-mbtn:focus:not(:disabled){color: #222f3e;background: #dee0e2;box-shadow: none;} + +.tox .tox-mbtn--active{color: #222f3e;background: #c8cbcf;box-shadow: none;} + +.tox .tox-mbtn__select-label{margin: 0 4px;font-weight: 400;cursor: default;} + +.tox .tox-mbtn[disabled] .tox-mbtn__select-label{cursor: not-allowed;} + +.tox .tox-mbtn__select-chevron{display: flex;display: none;width: 16px;align-items: center;justify-content: center;} + +.tox .tox-notification{display: grid;padding: 5px;margin-top: 5px;background-color: #fffaea;border-color: #ffe89d;border-style: solid;border-width: 1px;opacity: 0;box-sizing: border-box;transition: transform .1s ease-in,opacity 150ms ease-in;grid-template-columns: minmax(40px,1fr) auto minmax(40px,1fr);} + +.tox .tox-notification--in{opacity: 1;} + +.tox .tox-notification--success{background-color: #dff0d8;border-color: #d6e9c6;} + +.tox .tox-notification--error{background-color: #f2dede;border-color: #ebccd1;} + +.tox .tox-notification--warn{background-color: #fcf8e3;border-color: #faebcc;} + +.tox .tox-notification--info{background-color: #d9edf7;border-color: #779ecb;} + +.tox .tox-notification__body{font-size: 14px;color: #222f3e;text-align: center;word-break: break-all;word-break: break-word;white-space: normal;align-self: center;grid-column-end: 3;-ms-grid-column-span: 1;grid-column-start: 2;grid-row-end: 2;grid-row-start: 1;} + +.tox .tox-notification__body>*{margin: 0;} + +.tox .tox-notification__body>*+*{margin-top: 1rem;} + +.tox .tox-notification__icon{align-self: center;-ms-grid-column-align: end;grid-column-end: 2;-ms-grid-column-span: 1;grid-column-start: 1;grid-row-end: 2;grid-row-start: 1;justify-self: end;} + +.tox .tox-notification__icon svg{display: block;} + +.tox .tox-notification__dismiss{align-self: start;-ms-grid-column-align: end;grid-column-end: 4;-ms-grid-column-span: 1;grid-column-start: 3;grid-row-end: 2;grid-row-start: 1;justify-self: end;} + +.tox .tox-notification .tox-progress-bar{-ms-grid-column-align: center;grid-column-end: 4;-ms-grid-column-span: 3;grid-column-start: 1;grid-row-end: 3;-ms-grid-row-span: 1;grid-row-start: 2;justify-self: center;} + +.tox .tox-pop{position: relative;display: inline-block;} + +.tox .tox-pop--resizing{transition: width .1s ease;} + +.tox .tox-pop--resizing .tox-toolbar{flex-wrap: nowrap;} + +.tox .tox-pop__dialog{min-width: 0;overflow: hidden;background-color: #fff;border: 1px solid #ccc;border-radius: 3px;box-shadow: 0 1px 3px rgba(0,0,0,.15);} + +.tox .tox-pop__dialog>:not(.tox-toolbar){margin: 4px 4px 4px 8px;} + +.tox .tox-pop__dialog .tox-toolbar{background-color: transparent;} + +.tox .tox-pop::after,.tox .tox-pop::before{position: absolute;display: block;width: 0;height: 0;border-style: solid;content: '';} + +.tox .tox-pop.tox-pop--bottom::after,.tox .tox-pop.tox-pop--bottom::before{top: 100%;left: 50%;} + +.tox .tox-pop.tox-pop--bottom::after{margin-top: -1px;margin-left: -8px;border-color: #fff transparent transparent transparent;border-width: 8px;} + +.tox .tox-pop.tox-pop--bottom::before{margin-left: -9px;border-color: #ccc transparent transparent transparent;border-width: 9px;} + +.tox .tox-pop.tox-pop--top::after,.tox .tox-pop.tox-pop--top::before{top: 0;left: 50%;transform: translateY(-100%);} + +.tox .tox-pop.tox-pop--top::after{margin-top: 1px;margin-left: -8px;border-color: transparent transparent #fff transparent;border-width: 8px;} + +.tox .tox-pop.tox-pop--top::before{margin-left: -9px;border-color: transparent transparent #ccc transparent;border-width: 9px;} + +.tox .tox-pop.tox-pop--left::after,.tox .tox-pop.tox-pop--left::before{top: calc(50% - 1px);left: 0;transform: translateY(-50%);} + +.tox .tox-pop.tox-pop--left::after{margin-left: -15px;border-color: transparent #fff transparent transparent;border-width: 8px;} + +.tox .tox-pop.tox-pop--left::before{margin-left: -19px;border-color: transparent #ccc transparent transparent;border-width: 10px;} + +.tox .tox-pop.tox-pop--right::after,.tox .tox-pop.tox-pop--right::before{top: calc(50% + 1px);left: 100%;transform: translateY(-50%);} + +.tox .tox-pop.tox-pop--right::after{margin-left: -1px;border-color: transparent transparent transparent #fff;border-width: 8px;} + +.tox .tox-pop.tox-pop--right::before{margin-left: -1px;border-color: transparent transparent transparent #ccc;border-width: 10px;} + +.tox .tox-pop.tox-pop--align-left::after,.tox .tox-pop.tox-pop--align-left::before{left: 20px;} + +.tox .tox-pop.tox-pop--align-right::after,.tox .tox-pop.tox-pop--align-right::before{left: calc(100% - 20px);} + +.tox .tox-sidebar-wrap{display: flex;flex-direction: row;flex-grow: 1;min-height: 0;} + +.tox .tox-sidebar{display: flex;flex-direction: row;justify-content: flex-end;} + +.tox .tox-sidebar__slider{display: flex;overflow: hidden;} + +.tox .tox-sidebar__pane-container{display: flex;} + +.tox .tox-sidebar__pane{display: flex;} + +.tox .tox-sidebar--sliding-closed{opacity: 0;} + +.tox .tox-sidebar--sliding-open{opacity: 1;} + +.tox .tox-sidebar--sliding-growing,.tox .tox-sidebar--sliding-shrinking{transition: width .5s ease,opacity .5s ease;} + +.tox .tox-slider{position: relative;display: flex;height: 24px;align-items: center;flex: 1;-ms-flex-preferred-size: auto;justify-content: center;} + +.tox .tox-slider__rail{width: 100%;height: 10px;min-width: 120px;background-color: transparent;border: 1px solid #ccc;border-radius: 3px;} + +.tox .tox-slider__handle{position: absolute;top: 50%;left: 50%;width: 14px;height: 24px;background-color: #207ab7;border: 2px solid #185d8c;border-radius: 3px;transform: translateX(-50%) translateY(-50%);box-shadow: none;} + +.tox .tox-source-code{overflow: auto;} + +.tox .tox-spinner{display: flex;} + +.tox .tox-spinner>div{width: 8px;height: 8px;background-color: rgba(34,47,62,.7);border-radius: 100%;animation: tam-bouncing-dots 1.5s ease-in-out 0s infinite both;} + +.tox .tox-spinner>div:nth-child(1){animation-delay: -.32s;} + +.tox .tox-spinner>div:nth-child(2){animation-delay: -.16s;}@keyframes tam-bouncing-dots{0%,100%,80%{transform: scale(0);} + +40%{transform: scale(1);}} + +.tox:not([dir=rtl]) .tox-spinner>div:not(:first-child){margin-left: 4px;} + +.tox[dir=rtl] .tox-spinner>div:not(:first-child){margin-right: 4px;} + +.tox .tox-statusbar{position: relative;display: flex;height: 18px;padding: 0 8px;overflow: hidden;font-size: 12px;color: rgba(34,47,62,.7);text-transform: uppercase;background-color: #fff;border-top: 1px solid #ccc;align-items: center;flex: 0 0 auto;} + +.tox .tox-statusbar a{color: rgba(34,47,62,.7);text-decoration: none;} + +.tox .tox-statusbar a:hover{text-decoration: underline;} + +.tox .tox-statusbar__text-container{display: flex;flex: 1 1 auto;justify-content: flex-end;overflow: hidden;} + +.tox .tox-statusbar__path{display: flex;flex: 1 1 auto;margin-right: auto;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;} + +.tox .tox-statusbar__path>*{display: inline;white-space: nowrap;} + +.tox .tox-statusbar__wordcount{flex: 0 0 auto;margin-left: 1ch;} + +.tox .tox-statusbar__resize-handle{display: flex;padding-left: 1ch;margin-right: -8px;margin-left: auto;cursor: nwse-resize;align-items: flex-end;align-self: stretch;flex: 0 0 auto;justify-content: flex-end;} + +.tox .tox-statusbar__resize-handle svg{display: block;fill: rgba(34,47,62,.7);} + +.tox:not([dir=rtl]) .tox-statusbar__path>*{margin-right: 4px;} + +.tox:not([dir=rtl]) .tox-statusbar__branding{margin-left: 1ch;} + +.tox[dir=rtl] .tox-statusbar{flex-direction: row-reverse;} + +.tox[dir=rtl] .tox-statusbar__path>*{margin-left: 4px;} + +.tox .tox-throbber{z-index: 1400;} + +.tox .tox-throbber__busy-spinner{position: absolute;top: 0;right: 0;bottom: 0;left: 0;display: flex;background-color: rgba(255,255,255,.6);align-items: center;justify-content: center;} + +.tox .tox-tbtn{display: flex;width: 34px;height: 34px;padding: 0;margin: 2px 0 3px 0;overflow: hidden;font-size: 14px;font-style: normal;font-weight: 400;color: #222f3e;text-transform: normal;background: 0 0;border: 0;border-radius: 3px;outline: 0;box-shadow: none;align-items: center;flex: 0 0 auto;justify-content: center;} + +.tox .tox-tbtn svg{display: block;fill: #222f3e;} + +.tox .tox-tbtn.tox-tbtn-more{width: inherit;padding-right: 5px;padding-left: 5px;} + +.tox .tox-tbtn--enabled{color: #222f3e;background: #c8cbcf;box-shadow: none;} + +.tox .tox-tbtn--enabled>*{transform: none;} + +.tox .tox-tbtn--enabled svg{fill: #222f3e;} + +.tox .tox-tbtn:hover{color: #222f3e;background: #dee0e2;box-shadow: none;} + +.tox .tox-tbtn:hover svg{fill: #222f3e;} + +.tox .tox-tbtn:focus{color: #222f3e;background: #dee0e2;box-shadow: none;} + +.tox .tox-tbtn:focus svg{fill: #222f3e;} + +.tox .tox-tbtn:active{color: #222f3e;background: #c8cbcf;box-shadow: none;} + +.tox .tox-tbtn:active svg{fill: #222f3e;} + +.tox .tox-tbtn--disabled,.tox .tox-tbtn--disabled:hover,.tox .tox-tbtn:disabled,.tox .tox-tbtn:disabled:hover{color: rgba(34,47,62,.5);cursor: not-allowed;background: 0 0;box-shadow: none;} + +.tox .tox-tbtn--disabled svg,.tox .tox-tbtn--disabled:hover svg,.tox .tox-tbtn:disabled svg,.tox .tox-tbtn:disabled:hover svg{fill: rgba(34,47,62,.5);} + +.tox .tox-tbtn:active>*{transform: none;} + +.tox .tox-tbtn--md{width: 51px;height: 51px;} + +.tox .tox-tbtn--lg{width: 68px;height: 68px;flex-direction: column;} + +.tox .tox-tbtn--return{width: 16px;height: unset;align-self: stretch;} + +.tox .tox-tbtn--labeled{width: unset;padding: 0 4px;} + +.tox .tox-tbtn__vlabel{display: block;margin-bottom: 4px;font-size: 10px;font-weight: 400;letter-spacing: -.025em;white-space: nowrap;} + +.tox .tox-tbtn--select{width: auto;padding: 0 4px;margin: 2px 0 3px 0;} + +.tox .tox-tbtn__select-label{margin: 0 4px;font-weight: 400;cursor: default;} + +.tox .tox-tbtn__select-chevron{align-items: center;display: flex;justify-content: center;width: 16px;} + +.tox .tox-tbtn__select-chevron svg{fill: rgba(34,47,62,.7);} + +.tox .tox-tbtn--bespoke .tox-tbtn__select-label{width: 7em;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;} + +.tox .tox-split-button{display: flex;margin: 2px 0 3px 0;overflow: hidden;border: 0;border-radius: 3px;box-sizing: border-box;} + +.tox .tox-split-button:hover{box-shadow: 0 0 0 1px #dee0e2 inset;} + +.tox .tox-split-button:focus{color: #222f3e;background: #dee0e2;box-shadow: none;} + +.tox .tox-split-button>*{border-radius: 0;} + +.tox .tox-split-button__chevron{width: 16px;} + +.tox .tox-split-button__chevron svg{fill: rgba(34,47,62,.7);} + +.tox .tox-pop .tox-split-button__chevron svg{transform: rotate(-90deg);} + +.tox .tox-split-button .tox-tbtn{margin: 0;} + +.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus,.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover,.tox .tox-split-button.tox-tbtn--disabled:focus,.tox .tox-split-button.tox-tbtn--disabled:hover{color: rgba(34,47,62,.5);background: 0 0;box-shadow: none;} + +.tox .tox-toolbar,.tox .tox-toolbar__overflow,.tox .tox-toolbar__primary{display: flex;padding: 0 0;margin-bottom: -1px;background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23cccccc'/%3E%3C/svg%3E") left 0 top 0 #fff;background-color: #fff;border-top: 1px solid #ccc;flex: 0 0 auto;flex-shrink: 0;flex-wrap: wrap;} + +.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed{height: 0;opacity: 0;visibility: hidden;} + +.tox .tox-toolbar__overflow--growing{transition: height .3s ease,opacity .2s linear .1s;} + +.tox .tox-toolbar__overflow--shrinking{transition: opacity .3s ease,height .2s linear .1s,visibility 0s linear .3s;} + +.tox .tox-pop .tox-toolbar{border-width: 0;} + +.tox .tox-toolbar--no-divider{background-image: none;} + +.tox.tox-tinymce-aux .tox-toolbar__overflow{background-color: #fff;border: 1px solid #ccc;border-radius: 3px;box-shadow: 0 1px 3px rgba(0,0,0,.15);} + +.tox.tox-tinymce-aux:not([dir=rtl]) .tox-toolbar__overflow{margin-left: 4px;} + +.tox[dir=rtl] .tox-tbtn__icon-rtl svg{transform: rotateY(180deg);} + +.tox[dir=rtl].tox-tinymce-aux .tox-toolbar__overflow{margin-right: 4px;} + +.tox .tox-toolbar__group{display: flex;padding: 0 4px;margin: 0 0;align-items: center;flex-wrap: wrap;} + +.tox .tox-toolbar__group--pull-right{margin-left: auto;} + +.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type){border-right: 1px solid #ccc;} + +.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type){border-left: 1px solid #ccc;} + +.tox .tox-tooltip{position: relative;display: inline-block;padding: 8px;} + +.tox .tox-tooltip__body{padding: 4px 8px;font-size: 14px;font-style: normal;font-weight: 400;color: rgba(255,255,255,.75);text-transform: normal;background-color: #222f3e;border-radius: 3px;box-shadow: 0 2px 4px rgba(34,47,62,.3);} + +.tox .tox-tooltip__arrow{position: absolute;} + +.tox .tox-tooltip--down .tox-tooltip__arrow{position: absolute;bottom: 0;left: 50%;border-top: 8px solid #222f3e;border-right: 8px solid transparent;border-left: 8px solid transparent;transform: translateX(-50%);} + +.tox .tox-tooltip--up .tox-tooltip__arrow{position: absolute;top: 0;left: 50%;border-right: 8px solid transparent;border-bottom: 8px solid #222f3e;border-left: 8px solid transparent;transform: translateX(-50%);} + +.tox .tox-tooltip--right .tox-tooltip__arrow{position: absolute;top: 50%;right: 0;border-top: 8px solid transparent;border-bottom: 8px solid transparent;border-left: 8px solid #222f3e;transform: translateY(-50%);} + +.tox .tox-tooltip--left .tox-tooltip__arrow{position: absolute;top: 50%;left: 0;border-top: 8px solid transparent;border-right: 8px solid #222f3e;border-bottom: 8px solid transparent;transform: translateY(-50%);} + +.tox .tox-well{width: 100%;padding: 8px;border: 1px solid #ccc;border-radius: 3px;} + +.tox .tox-well>:first-child{margin-top: 0;} + +.tox .tox-well>:last-child{margin-bottom: 0;} + +.tox .tox-well>:only-child{margin: 0;} + +.tox .tox-custom-editor{display: flex;height: 525px;border: 1px solid #ccc;border-radius: 3px;} + +.tox .tox-dialog-loading::before{position: absolute;z-index: 1000;width: 100%;height: 100%;background-color: rgba(0,0,0,.5);content: "";} + +.tox .tox-tab{cursor: pointer;} + +.tox .tox-dialog__content-js{display: flex;flex: 1;-ms-flex-preferred-size: auto;} + +.tox .tox-dialog__body-content .tox-collection{display: flex;flex: 1;-ms-flex-preferred-size: auto;} + +.tox ul{display: block;list-style-type: disc;-webkit-margin-before: 1em;margin-block-start: 1em;-webkit-margin-after: 1em;margin-block-end: 1em;-webkit-margin-start: 0;margin-inline-start: 0;-webkit-margin-end: 0;margin-inline-end: 0;-webkit-padding-start: 40px;padding-inline-start: 40px;} + +.tox a{color: #2276d2;cursor: pointer;} + +.tox .tox-image-tools-edit-panel{height: 60px;} + +.tox .tox-image-tools__sidebar{height: 60px;} diff --git a/public/resource/tinymce/skins/ui/oxide/skin.mobile.min.css b/public/resource/tinymce/skins/ui/oxide/skin.mobile.min.css new file mode 100644 index 0000000..14847d0 --- /dev/null +++ b/public/resource/tinymce/skins/ui/oxide/skin.mobile.min.css @@ -0,0 +1,239 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tinymce-mobile-outer-container{all: initial;display: block;} + +.tinymce-mobile-outer-container *{float: none;padding: 0;margin: 0;line-height: 1;text-shadow: none;white-space: nowrap;cursor: inherit;border: 0;outline: 0;box-sizing: initial;-webkit-tap-highlight-color: transparent;} + +.tinymce-mobile-icon-arrow-back::before{content: "\e5cd";} + +.tinymce-mobile-icon-image::before{content: "\e412";} + +.tinymce-mobile-icon-cancel-circle::before{content: "\e5c9";} + +.tinymce-mobile-icon-full-dot::before{content: "\e061";} + +.tinymce-mobile-icon-align-center::before{content: "\e234";} + +.tinymce-mobile-icon-align-left::before{content: "\e236";} + +.tinymce-mobile-icon-align-right::before{content: "\e237";} + +.tinymce-mobile-icon-bold::before{content: "\e238";} + +.tinymce-mobile-icon-italic::before{content: "\e23f";} + +.tinymce-mobile-icon-unordered-list::before{content: "\e241";} + +.tinymce-mobile-icon-ordered-list::before{content: "\e242";} + +.tinymce-mobile-icon-font-size::before{content: "\e245";} + +.tinymce-mobile-icon-underline::before{content: "\e249";} + +.tinymce-mobile-icon-link::before{content: "\e157";} + +.tinymce-mobile-icon-unlink::before{content: "\eca2";} + +.tinymce-mobile-icon-color::before{content: "\e891";} + +.tinymce-mobile-icon-previous::before{content: "\e314";} + +.tinymce-mobile-icon-next::before{content: "\e315";} + +.tinymce-mobile-icon-large-font::before,.tinymce-mobile-icon-style-formats::before{content: "\e264";} + +.tinymce-mobile-icon-undo::before{content: "\e166";} + +.tinymce-mobile-icon-redo::before{content: "\e15a";} + +.tinymce-mobile-icon-removeformat::before{content: "\e239";} + +.tinymce-mobile-icon-small-font::before{content: "\e906";} + +.tinymce-mobile-format-matches::after,.tinymce-mobile-icon-readonly-back::before{content: "\e5ca";} + +.tinymce-mobile-icon-small-heading::before{content: "small";} + +.tinymce-mobile-icon-large-heading::before{content: "large";} + +.tinymce-mobile-icon-large-heading::before,.tinymce-mobile-icon-small-heading::before{font-family: sans-serif;font-size: 80%;} + +.tinymce-mobile-mask-edit-icon::before{content: "\e254";} + +.tinymce-mobile-icon-back::before{content: "\e5c4";} + +.tinymce-mobile-icon-heading::before{font-family: sans-serif;font-size: 80%;font-weight: 700;content: "Headings";} + +.tinymce-mobile-icon-h1::before{font-weight: 700;content: "H1";} + +.tinymce-mobile-icon-h2::before{font-weight: 700;content: "H2";} + +.tinymce-mobile-icon-h3::before{font-weight: 700;content: "H3";} + +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask{position: absolute;top: 0;display: flex;width: 100%;height: 100%;background: rgba(51,51,51,.5);align-items: center;justify-content: center;} + +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container{display: flex;font-family: sans-serif;font-size: 1em;border-radius: 50%;align-items: center;flex-direction: column;justify-content: space-between;} + +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .mixin-menu-item{display: flex;width: 2.1em;height: 2.1em;border-radius: 50%;align-items: center;justify-content: center;} + +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section{align-items: center;display: flex;justify-content: center;flex-direction: column;font-size: 1em;}@media only screen and (min-device-width: 700px){.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section{font-size: 1.2em;}} + +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon{display: flex;width: 2.1em;height: 2.1em;color: #207ab7;background-color: #fff;border-radius: 50%;align-items: center;justify-content: center;} + +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon::before{font-family: tinymce-mobile,sans-serif;content: "\e900";} + +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section:not(.tinymce-mobile-mask-tap-icon-selected) .tinymce-mobile-mask-tap-icon{z-index: 2;} + +.tinymce-mobile-android-container.tinymce-mobile-android-maximized{position: fixed;top: 0;right: 0;bottom: 0;left: 0;display: flex;background: #fff;border: none;flex-direction: column;} + +.tinymce-mobile-android-container:not(.tinymce-mobile-android-maximized){position: relative;} + +.tinymce-mobile-android-container .tinymce-mobile-editor-socket{display: flex;flex-grow: 1;} + +.tinymce-mobile-android-container .tinymce-mobile-editor-socket iframe{display: flex !important;flex-grow: 1;height: auto !important;} + +.tinymce-mobile-android-scroll-reload{overflow: hidden;} + +:not(.tinymce-mobile-readonly-mode)>.tinymce-mobile-android-selection-context-toolbar{margin-top: 23px;} + +.tinymce-mobile-toolstrip{z-index: 1;display: flex;background: #fff;flex: 0 0 auto;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar{display: flex;width: 100%;height: 2.5em;background-color: #fff;border-bottom: 1px solid #ccc;align-items: center;flex: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group{align-items: center;display: flex;height: 100%;flex-shrink: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group>div{align-items: center;display: flex;height: 100%;flex: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-exit-container{background: #f44336;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-toolbar-scrollable-group{flex-grow: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item{padding-right: .5em;padding-left: .5em;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button{display: flex;height: 80%;margin-right: 2px;margin-left: 2px;align-items: center;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button.tinymce-mobile-toolbar-button-selected{color: #ccc;background: #c8cbcf;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:first-of-type,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:last-of-type{color: #eceff1;background: #207ab7;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group{display: flex;height: 100%;padding-top: .4em;padding-bottom: .4em;align-items: center;flex: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog{position: relative;display: flex;width: 100%;min-height: 1.5em;padding-right: 0;padding-left: 0;overflow: hidden;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain{display: flex;width: 100%;height: 100%;transition: left cubic-bezier(.4,0,1,1) .15s;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen{display: flex;flex: 0 0 auto;justify-content: space-between;width: 100%;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen input{font-family: sans-serif;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container{position: relative;display: flex;flex-grow: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container .tinymce-mobile-input-container-x{position: absolute;right: 0;height: 100%;padding-right: 2px;font-size: .6em;font-weight: 700;color: #888;background: inherit;border: none;border-radius: 50%;align-self: center;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container.tinymce-mobile-input-container-empty .tinymce-mobile-input-container-x{display: none;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous{align-items: center;display: flex;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous::before{display: flex;height: 100%;padding-right: .5em;padding-left: .5em;font-weight: 700;align-items: center;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next.tinymce-mobile-toolbar-navigation-disabled::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous.tinymce-mobile-toolbar-navigation-disabled::before{visibility: hidden;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item{padding-top: 3px;margin: 0 2px;font-size: 10px;line-height: 10px;color: #ccc;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item.tinymce-mobile-dot-active{color: #c8cbcf;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-font::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-heading::before{margin-right: .9em;margin-left: .5em;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-font::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-heading::before{margin-right: .5em;margin-left: .9em;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider{position: relative;display: flex;padding: .28em 0;margin-right: 0;margin-left: 0;flex: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container{align-items: center;display: flex;flex-grow: 1;height: 100%;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container .tinymce-mobile-slider-size-line{display: flex;height: .2em;margin-top: .3em;margin-bottom: .3em;background: #ccc;flex: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container{padding-right: 2em;padding-left: 2em;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container{align-items: center;display: flex;flex-grow: 1;height: 100%;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container .tinymce-mobile-slider-gradient{display: flex;height: .2em;margin-top: .3em;margin-bottom: .3em;background: linear-gradient(to right,red 0,#feff00 17%,#0f0 33%,#00feff 50%,#00f 67%,#ff00fe 83%,red 100%);flex: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-black{width: 1.2em;height: .2em;margin-top: .3em;margin-bottom: .3em;background: #000;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-white{width: 1.2em;height: .2em;margin-top: .3em;margin-bottom: .3em;background: #fff;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb{position: absolute;top: 0;bottom: 0;left: -10px;display: flex;width: .5em;height: .5em;margin: auto;color: #fff;background-color: #455a64;border: .5em solid rgba(136,136,136,0);border-radius: 3em;transition: border 120ms cubic-bezier(.39,.58,.57,1);background-clip: padding-box;align-items: center;justify-content: center;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb.tinymce-mobile-thumb-active{border: .5em solid rgba(136,136,136,.39);} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group>div{align-items: center;display: flex;height: 100%;flex: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper{flex-direction: column;justify-content: center;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item{align-items: center;display: flex;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item:not(.tinymce-mobile-serialised-dialog){height: 100%;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-container{display: flex;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input{padding-top: .1em;padding-bottom: .1em;padding-left: 5px;font-size: .85em;color: #455a64;background: #fff;border: none;border-radius: 0;flex-grow: 1;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::-webkit-input-placeholder{color: #888;} + +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::placeholder{color: #888;} + +.tinymce-mobile-dropup{display: flex;width: 100%;overflow: hidden;background: #fff;} + +.tinymce-mobile-dropup.tinymce-mobile-dropup-shrinking{transition: height .3s ease-out;} + +.tinymce-mobile-dropup.tinymce-mobile-dropup-growing{transition: height .3s ease-in;} + +.tinymce-mobile-dropup.tinymce-mobile-dropup-closed{flex-grow: 0;} + +.tinymce-mobile-dropup.tinymce-mobile-dropup-open:not(.tinymce-mobile-dropup-growing){flex-grow: 1;} + +.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height: 200px;}@media only screen and (orientation: landscape){.tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height: 200px;}}@media only screen and (min-device-width: 320px) and (max-device-width: 568px) and (orientation: landscape){.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height: 150px;}} + +.tinymce-mobile-styles-menu{position: relative;width: 100%;overflow: hidden;font-family: sans-serif;outline: 4px solid #000;} + +.tinymce-mobile-styles-menu [role=menu]{position: absolute;display: flex;width: 100%;height: 100%;flex-direction: column;} + +.tinymce-mobile-styles-menu [role=menu].transitioning{transition: transform .5s ease-in-out;} + +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item{position: relative;display: flex;padding: 1em 1em;color: #455a64;cursor: pointer;border-bottom: 1px solid #ddd;} + +.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser .tinymce-mobile-styles-collapse-icon::before{font-family: tinymce-mobile,sans-serif;color: #455a64;content: "\e314";} + +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-styles-item-is-menu::after{position: absolute;right: 0;padding-right: 1em;padding-left: 1em;font-family: tinymce-mobile,sans-serif;color: #455a64;content: "\e315";} + +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-format-matches::after{position: absolute;right: 0;padding-right: 1em;padding-left: 1em;font-family: tinymce-mobile,sans-serif;} + +.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser,.tinymce-mobile-styles-menu .tinymce-mobile-styles-separator{display: flex;min-height: 2.5em;padding-right: 1em;padding-left: 1em;color: #455a64;background: #fff;border-top: #455a64;align-items: center;} + +.tinymce-mobile-styles-menu [data-transitioning-destination=before][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=before]{transform: translate(-100%);} + +.tinymce-mobile-styles-menu [data-transitioning-destination=current][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=current]{transform: translate(0);} + +.tinymce-mobile-styles-menu [data-transitioning-destination=after][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=after]{transform: translate(100%);}@font-face{font-family: tinymce-mobile;font-style: normal;font-weight: 400;src: url(fonts/tinymce-mobile.woff?8x92w3) format('woff');}@media (min-device-width: 700px){.tinymce-mobile-outer-container,.tinymce-mobile-outer-container input{font-size: 25px;}}@media (max-device-width: 700px){.tinymce-mobile-outer-container,.tinymce-mobile-outer-container input{font-size: 18px;}} + +.tinymce-mobile-icon{font-family: tinymce-mobile,sans-serif;} + +.mixin-flex-and-centre{align-items: center;display: flex;justify-content: center;} + +.mixin-flex-bar{align-items: center;display: flex;height: 100%;} + +.tinymce-mobile-outer-container .tinymce-mobile-editor-socket iframe{width: 100%;background-color: #fff;} + +.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{position: fixed;right: 2em;bottom: 1em;display: flex;width: 2.1em;height: 2.1em;font-size: 1em;color: #fff;background-color: #207ab7;border-radius: 50%;align-items: center;justify-content: center;}@media only screen and (min-device-width: 700px){.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{font-size: 1.2em;}} + +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket{height: 300px;overflow: hidden;} + +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket iframe{height: 100%;} + +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-toolstrip{display: none;} + +input[type=file]::-webkit-file-upload-button{display: none;}@media only screen and (min-device-width: 320px) and (max-device-width: 568px) and (orientation: landscape){.tinymce-mobile-ios-container .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{bottom: 50%;}} diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..fa3fec8 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,107 @@ + + + + diff --git a/src/api/common/api.ts b/src/api/common/api.ts new file mode 100644 index 0000000..fee7386 --- /dev/null +++ b/src/api/common/api.ts @@ -0,0 +1,190 @@ +import { defHttp } from '/@/utils/http/axios'; +import { message } from 'ant-design-vue'; +import { useGlobSetting } from '/@/hooks/setting'; +const globSetting = useGlobSetting(); +const baseUploadUrl = globSetting.uploadUrl; +enum Api { + positionList = '/sys/position/list', + userList = '/sys/user/list', + roleList = '/sys/role/list', + queryDepartTreeSync = '/sys/sysDepart/queryDepartTreeSync', + queryTreeList = '/sys/sysDepart/queryTreeList', + loadTreeData = '/sys/category/loadTreeData', + loadDictItem = '/sys/category/loadDictItem', + getDictItems = '/sys/dict/getDictItems/', + getTableList = '/sys/user/queryUserComponentData', + getCategoryData = '/sys/category/loadAllData', + refreshDragCache = '/drag/page/refreshCache', + refreshDefaultIndexCache = '/sys/sysRoleIndex/cleanDefaultIndexCache', + //异步获取部门和岗位 + queryDepartAndPostTreeSync = '/sys/sysDepart/queryDepartAndPostTreeSync', + //查询部门岗位下的用户 + queryDepartPostUserPageList = '/sys/user/queryDepartPostUserPageList', + //查询所选部门的所有父节点ID + queryAllParentId = '/sys/sysDepart/queryAllParentId', +} + +/** + * 上传父路径 + */ +export const uploadUrl = `${baseUploadUrl}/sys/common/upload`; + +/** + * 职务列表 + * @param params + */ +export const getPositionList = (params) => { + return defHttp.get({ url: Api.positionList, params }); +}; + +/** + * 用户列表 + * @param params + */ +export const getUserList = (params) => { + return defHttp.get({ url: Api.userList, params }); +}; + +/** + * 角色列表 + * @param params + */ +export const getRoleList = (params) => { + return defHttp.get({ url: Api.roleList, params }); +}; + +/** + * 异步获取部门树列表 + */ +export const queryDepartTreeSync = (params?) => { + return defHttp.get({ url: Api.queryDepartTreeSync, params }); +}; +/** + * 异步获取部门职位树列表 + */ +export const queryDepartAndPostTreeSync = (params?) => { + return defHttp.get({ url: Api.queryDepartAndPostTreeSync, params }); +}; + +/** + * 获取部门树列表 + */ +export const queryTreeList = (params?) => { + return defHttp.get({ url: Api.queryTreeList, params }); +}; + +/** + * 分类字典树控件 加载节点 + */ +export const loadTreeData = (params?) => { + return defHttp.get({ url: Api.loadTreeData, params }); +}; + +/** + * 根据字典code加载字典text + */ +export const loadDictItem = (params?) => { + return defHttp.get({ url: Api.loadDictItem, params }); +}; + +/** + * 根据字典code加载字典text + */ +export const getDictItems = (dictCode) => { + return defHttp.get({ url: Api.getDictItems + dictCode }, { joinTime: false }); +}; +/** + * 部门用户modal选择列表加载list + */ +export const getTableList = (params) => { + return defHttp.get({ url: Api.getTableList, params }); +}; + +/** + * 部门岗位用户modal【查询部门岗位下的用户】 + */ +export const queryDepartPostUserPageList = (params) => { + return defHttp.get({ url: Api.queryDepartPostUserPageList, params }); +}; + +/** + * 查询所选部门的所有父节点ID + */ +export const queryAllParentId = (params) => { + return defHttp.get({ url: Api.queryAllParentId, params }); +}; + +/** + * 加载全部分类字典数据 + */ +export const loadCategoryData = (params) => { + return defHttp.get({ url: Api.getCategoryData, params }); +}; +/** + * 文件上传 + */ +export const uploadFile = (params, success) => { + return defHttp.uploadFile({ url: uploadUrl }, params, { success }); +}; +/** + * 下载文件 + * @param url 文件路径 + * @param fileName 文件名 + * @param parameter + * @returns {*} + */ +export const downloadFile = (url, fileName?, parameter?) => { + return getFileblob(url, parameter).then((data) => { + if (!data || data.size === 0) { + message.warning('文件下载失败'); + return; + } + if (typeof window.navigator.msSaveBlob !== 'undefined') { + window.navigator.msSaveBlob(new Blob([data]), fileName); + } else { + let url = window.URL.createObjectURL(new Blob([data])); + let link = document.createElement('a'); + link.style.display = 'none'; + link.href = url; + link.setAttribute('download', fileName); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); //下载完成移除元素 + window.URL.revokeObjectURL(url); //释放掉blob对象 + } + }); +}; + +/** + * 下载文件 用于excel导出 + * @param url + * @param parameter + * @returns {*} + */ +export const getFileblob = (url, parameter) => { + return defHttp.get( + { + url: url, + params: parameter, + responseType: 'blob', + }, + { isTransformResponse: false } + ); +}; + +/** + * 【用于评论功能】自定义文件上传-方法 + */ +export const uploadMyFile = (url, data) => { + return defHttp.uploadMyFile(url, data); +}; +/** + * 刷新仪表盘缓存 + * @param params + */ +export const refreshDragCache = () => defHttp.get({ url: Api.refreshDragCache }, { isTransformResponse: false }); +/** + * 刷新默认首页缓存 + * @param params + */ +export const refreshHomeCache = () => defHttp.get({ url: Api.refreshDefaultIndexCache }, { isTransformResponse: false }); diff --git a/src/api/demo/error.ts b/src/api/demo/error.ts new file mode 100644 index 0000000..fcf089d --- /dev/null +++ b/src/api/demo/error.ts @@ -0,0 +1,5 @@ +import { defHttp } from '/@/utils/http/axios'; + +export function fireErrorApi() { + return defHttp.get({ url: '/demo/error' }); +} diff --git a/src/api/model/baseModel.ts b/src/api/model/baseModel.ts new file mode 100644 index 0000000..7a4d797 --- /dev/null +++ b/src/api/model/baseModel.ts @@ -0,0 +1,14 @@ +export interface BasicPageParams { + page: number; + pageSize: number; +} + +export interface BasicFetchResult { + items: T[]; + total: number; +} + +export interface BasicResult { + records: T[]; + total: number; +} diff --git a/src/api/sys/menu.ts b/src/api/sys/menu.ts new file mode 100644 index 0000000..ddd8d85 --- /dev/null +++ b/src/api/sys/menu.ts @@ -0,0 +1,61 @@ +import { defHttp } from '/@/utils/http/axios'; +import { getMenuListResultModel } from './model/menuModel'; +import { useUserStoreWithOut } from '@/store/modules/user'; +import { setAuthCache } from '@/utils/auth'; +import { TOKEN_KEY } from '@/enums/cacheEnum'; +import { router } from '@/router'; +import { PageEnum } from '@/enums/pageEnum'; + +enum Api { + GetMenuList = '/sys/permission/getUserPermissionByToken', + // 【QQYUN-8487】 + // SwitchVue3Menu = '/sys/switchVue3Menu', +} + +/** + * @description: Get user menu based on id + */ + +export const getMenuList = () => { + return new Promise((resolve) => { + //为了兼容mock和接口数据 + defHttp.get({ url: Api.GetMenuList }).then((res) => { + if (Array.isArray(res)) { + resolve(res); + } else { + resolve(res['menu']); + } + }); + }); +}; + +/** + * @description: 获取后台菜单权限和按钮权限 + */ +export function getBackMenuAndPerms() { + return defHttp.get({ url: Api.GetMenuList }).catch((e) => { + console.log('接口 getBackMenuAndPerms 异常错误信息:', e); + // Token过期失效,直接跳转登录页面 2025-09-08 scott + if (e && (e.message.includes('timeout') || e.message.includes('401') || e.message.includes('500'))) { + const userStore = useUserStoreWithOut(); + userStore.setToken(''); + setAuthCache(TOKEN_KEY, null); + router.push({ + path: PageEnum.BASE_LOGIN, + query: { + // 传入当前的路由,登录成功后跳转到当前路由 + redirect: router.currentRoute.value.fullPath, + } + }); + } + }); +} + +/** + * 切换成vue3菜单 + */ +// export const switchVue3Menu = () => { +// return new Promise((resolve) => { +// defHttp.get({ url: Api.SwitchVue3Menu }); +// }); +// }; diff --git a/src/api/sys/model/menuModel.ts b/src/api/sys/model/menuModel.ts new file mode 100644 index 0000000..8d19eea --- /dev/null +++ b/src/api/sys/model/menuModel.ts @@ -0,0 +1,16 @@ +import type { RouteMeta } from 'vue-router'; +export interface RouteItem { + path: string; + component: any; + meta: RouteMeta; + name?: string; + alias?: string | string[]; + redirect?: string; + caseSensitive?: boolean; + children?: RouteItem[]; +} + +/** + * @description: Get menu return value + */ +export type getMenuListResultModel = RouteItem[]; diff --git a/src/api/sys/model/uploadModel.ts b/src/api/sys/model/uploadModel.ts new file mode 100644 index 0000000..d770c64 --- /dev/null +++ b/src/api/sys/model/uploadModel.ts @@ -0,0 +1,5 @@ +export interface UploadApiResult { + message: string; + code: number; + url: string; +} diff --git a/src/api/sys/model/userModel.ts b/src/api/sys/model/userModel.ts new file mode 100644 index 0000000..f1d9be7 --- /dev/null +++ b/src/api/sys/model/userModel.ts @@ -0,0 +1,58 @@ +/** + * @description: Login interface parameters + */ +export interface LoginParams { + username: string; + password: string; +} + +export interface ThirdLoginParams { + token: string; + thirdType: string; +} + +export interface RoleInfo { + roleName: string; + value: string; +} + +/** + * @description: Login interface return value + */ +export interface LoginResultModel { + userId: string | number; + token: string; + role: RoleInfo; + userInfo?: any +} + +/** + * @description: Get user information return value + */ +export interface GetUserInfoModel { + roles: RoleInfo[]; + // 用户id + userId: string | number; + // 用户名 + username: string; + // 真实名字 + realname: string; + // 头像 + avatar: string; + // 介绍 + desc?: string; + // 用户信息 + userInfo?: any; + // 缓存字典项 + sysAllDictItems?: any; +} + +/** + * @description: Get user information return value + */ +export interface GetResultModel { + code: number; + message: string; + result: object; + success: Boolean; +} diff --git a/src/api/sys/upload.ts b/src/api/sys/upload.ts new file mode 100644 index 0000000..1a83e93 --- /dev/null +++ b/src/api/sys/upload.ts @@ -0,0 +1,32 @@ +import { UploadApiResult } from './model/uploadModel'; +import { defHttp } from '/@/utils/http/axios'; +import { UploadFileParams } from '/#/axios'; +import { useGlobSetting } from '/@/hooks/setting'; + +const { uploadUrl = '' } = useGlobSetting(); + +/** + * @description: Upload interface + */ +export function uploadApi(params: UploadFileParams, onUploadProgress: (progressEvent: ProgressEvent) => void) { + return defHttp.uploadFile( + { + url: uploadUrl, + onUploadProgress, + }, + params + ); +} +/** + * @description: Upload interface + */ +export function uploadImg(params: UploadFileParams, onUploadProgress: (progressEvent: ProgressEvent) => void) { + return defHttp.uploadFile( + { + url: `${uploadUrl}/sys/common/upload`, + onUploadProgress, + }, + params, + { isReturnResponse: true } + ); +} diff --git a/src/api/sys/user.ts b/src/api/sys/user.ts new file mode 100644 index 0000000..af355ea --- /dev/null +++ b/src/api/sys/user.ts @@ -0,0 +1,216 @@ +import { defHttp } from '/@/utils/http/axios'; +import { LoginParams, LoginResultModel, GetUserInfoModel } from './model/userModel'; + +import { ErrorMessageMode } from '/#/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { useUserStoreWithOut } from '/@/store/modules/user'; +import { setAuthCache } from '/@/utils/auth'; +import { TOKEN_KEY } from '/@/enums/cacheEnum'; +import { router } from '/@/router'; +import { PageEnum } from '/@/enums/pageEnum'; +import { ExceptionEnum } from "@/enums/exceptionEnum"; + +const { createErrorModal } = useMessage(); +enum Api { + Login = '/sys/login', + phoneLogin = '/sys/phoneLogin', + Logout = '/sys/logout', + GetUserInfo = '/sys/user/getUserInfo', + // 获取系统权限 + // 1、查询用户拥有的按钮/表单访问权限 + // 2、所有权限 + // 3、系统安全模式 + GetPermCode = '/sys/permission/getPermCode', + //新加的获取图形验证码的接口 + getInputCode = '/sys/randomImage', + //获取短信验证码的接口 + getCaptcha = '/sys/sms', + //注册接口 + registerApi = '/sys/user/register', + //校验用户接口 + checkOnlyUser = '/sys/user/checkOnlyUser', + //SSO登录校验 + validateCasLogin = '/sys/cas/client/validateLogin', + //校验手机号 + phoneVerify = '/sys/user/phoneVerification', + //修改密码 + passwordChange = '/sys/user/passwordChange', + //第三方登录 + thirdLogin = '/sys/thirdLogin/getLoginUser', + //第三方登录 + getThirdCaptcha = '/sys/thirdSms', + //获取二维码信息 + getLoginQrcode = '/sys/getLoginQrcode', + //监控二维码扫描状态 + getQrcodeToken = '/sys/getQrcodeToken', +} + +/** + * @description: user login api + */ +export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal') { + return defHttp.post( + { + url: Api.Login, + params, + }, + { + errorMessageMode: mode, + } + ); +} + +/** + * @description: user phoneLogin api + */ +export function phoneLoginApi(params: LoginParams, mode: ErrorMessageMode = 'modal') { + return defHttp.post( + { + url: Api.phoneLogin, + params, + }, + { + errorMessageMode: mode, + } + ); +} + +/** + * @description: getUserInfo + */ +export function getUserInfo() { + return defHttp.get({ url: Api.GetUserInfo }, {}).catch((e) => { + // Token过期失效,直接跳转登录页面 + if (e && (e.message.includes('timeout') || e.message.includes('401'))) { + //接口不通时跳转到登录界面 + const userStore = useUserStoreWithOut(); + userStore.setToken(''); + setAuthCache(TOKEN_KEY, null); + router.push({ + path: PageEnum.BASE_LOGIN, + query: { + // 传入当前的路由,登录成功后跳转到当前路由 + redirect: router.currentRoute.value.fullPath, + } + }); + } + }); +} + +export function getPermCode() { + return defHttp.get({ url: Api.GetPermCode }); +} + +export function doLogout() { + return defHttp.get({ url: Api.Logout }); +} + +export function getCodeInfo(currdatetime) { + let url = Api.getInputCode + `/${currdatetime}`; + return defHttp.get({ url: url }); +} +/** + * @description: 获取短信验证码 + */ +export function getCaptcha(params) { + return new Promise((resolve, reject) => { + defHttp.post({ url: Api.getCaptcha, params }, { isTransformResponse: false }).then((res) => { + console.log(res); + if (res.success) { + resolve(true); + } else { + // 代码逻辑说明: 【QQYUN-9005】同一个IP,1分钟超过5次短信,则提示需要验证码--- + if(res.code != ExceptionEnum.PHONE_SMS_FAIL_CODE){ + createErrorModal({ title: '错误提示', content: res.message || '未知问题' }); + reject(); + } + reject(res); + } + }).catch((res)=>{ + createErrorModal({ title: '错误提示', content: res.message || '未知问题' }); + reject(); + }); + }); +} + +/** + * @description: 注册接口 + */ +export function register(params) { + return defHttp.post({ url: Api.registerApi, params }, { isReturnNativeResponse: true }); +} + +/** + *校验用户是否存在 + * @param params + */ +export const checkOnlyUser = (params) => defHttp.get({ url: Api.checkOnlyUser, params }, { isTransformResponse: false }); +/** + *校验手机号码 + * @param params + */ +export const phoneVerify = (params) => defHttp.post({ url: Api.phoneVerify, params }, { isTransformResponse: false }); +/** + *密码修改 + * @param params + */ +export const passwordChange = (params) => defHttp.get({ url: Api.passwordChange, params }, { isTransformResponse: false }); +/** + * @description: 第三方登录 + */ +export function thirdLogin(params, mode: ErrorMessageMode = 'modal') { + //==========begin 第三方登录/auth2登录需要传递租户id=========== + let tenantId = "0"; + if(!params.tenantId){ + tenantId = params.tenantId; + } + //==========end 第三方登录/auth2登录需要传递租户id=========== + return defHttp.get( + { + url: `${Api.thirdLogin}/${params.token}/${params.thirdType}/${tenantId}`, + }, + { + errorMessageMode: mode, + } + ); +} +/** + * @description: 获取第三方短信验证码 + */ +export function setThirdCaptcha(params) { + return new Promise((resolve, reject) => { + defHttp.post({ url: Api.getThirdCaptcha, params }, { isTransformResponse: false }).then((res) => { + console.log(res); + if (res.success) { + resolve(true); + } else { + createErrorModal({ title: '错误提示', content: res.message || '未知问题' }); + reject(); + } + }); + }); +} + +/** + * 获取登录二维码信息 + */ +export function getLoginQrcode() { + let url = Api.getLoginQrcode; + return defHttp.get({ url: url }); +} + +/** + * 监控扫码状态 + */ +export function getQrcodeToken(params) { + let url = Api.getQrcodeToken; + return defHttp.get({ url: url, params }); +} + +/** + * SSO登录校验 + */ +export async function validateCasLogin(params) { + let url = Api.validateCasLogin; + return defHttp.get({ url: url, params }); +} diff --git a/src/assets/icons/calendarNotice.png b/src/assets/icons/calendarNotice.png new file mode 100644 index 0000000..41eb8f1 Binary files /dev/null and b/src/assets/icons/calendarNotice.png differ diff --git a/src/assets/icons/collaborationNotice.png b/src/assets/icons/collaborationNotice.png new file mode 100644 index 0000000..931c29c Binary files /dev/null and b/src/assets/icons/collaborationNotice.png differ diff --git a/src/assets/icons/download-count.svg b/src/assets/icons/download-count.svg new file mode 100644 index 0000000..1c95195 --- /dev/null +++ b/src/assets/icons/download-count.svg @@ -0,0 +1 @@ +Asset 91 \ No newline at end of file diff --git a/src/assets/icons/dynamic-avatar-1.svg b/src/assets/icons/dynamic-avatar-1.svg new file mode 100644 index 0000000..e1553e5 --- /dev/null +++ b/src/assets/icons/dynamic-avatar-1.svg @@ -0,0 +1 @@ +Asset 15 \ No newline at end of file diff --git a/src/assets/icons/dynamic-avatar-2.svg b/src/assets/icons/dynamic-avatar-2.svg new file mode 100644 index 0000000..c4c1722 --- /dev/null +++ b/src/assets/icons/dynamic-avatar-2.svg @@ -0,0 +1 @@ +Asset 16 \ No newline at end of file diff --git a/src/assets/icons/dynamic-avatar-3.svg b/src/assets/icons/dynamic-avatar-3.svg new file mode 100644 index 0000000..81145f9 --- /dev/null +++ b/src/assets/icons/dynamic-avatar-3.svg @@ -0,0 +1 @@ +Asset 17 \ No newline at end of file diff --git a/src/assets/icons/dynamic-avatar-4.svg b/src/assets/icons/dynamic-avatar-4.svg new file mode 100644 index 0000000..e586ed4 --- /dev/null +++ b/src/assets/icons/dynamic-avatar-4.svg @@ -0,0 +1 @@ +Asset 120 \ No newline at end of file diff --git a/src/assets/icons/dynamic-avatar-5.svg b/src/assets/icons/dynamic-avatar-5.svg new file mode 100644 index 0000000..746e4b8 --- /dev/null +++ b/src/assets/icons/dynamic-avatar-5.svg @@ -0,0 +1 @@ +Asset 110 \ No newline at end of file diff --git a/src/assets/icons/dynamic-avatar-6.svg b/src/assets/icons/dynamic-avatar-6.svg new file mode 100644 index 0000000..b2432f2 --- /dev/null +++ b/src/assets/icons/dynamic-avatar-6.svg @@ -0,0 +1 @@ +Asset 100 \ No newline at end of file diff --git a/src/assets/icons/flowNotice.png b/src/assets/icons/flowNotice.png new file mode 100644 index 0000000..ae5d6cd Binary files /dev/null and b/src/assets/icons/flowNotice.png differ diff --git a/src/assets/icons/folderNotice.png b/src/assets/icons/folderNotice.png new file mode 100644 index 0000000..6f25faf Binary files /dev/null and b/src/assets/icons/folderNotice.png differ diff --git a/src/assets/icons/js/iconfont.js b/src/assets/icons/js/iconfont.js new file mode 100644 index 0000000..dead26b --- /dev/null +++ b/src/assets/icons/js/iconfont.js @@ -0,0 +1 @@ +window._iconfont_svg_string_3814468='',function(l){var c=(c=document.getElementsByTagName("script"))[c.length-1],h=c.getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var a,t,o,z,i,v=function(c,h){h.parentNode.insertBefore(c,h)};if(h&&!l.__iconfont__svg__cssinject__){l.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}a=function(){var c,h=document.createElement("div");h.innerHTML=l._iconfont_svg_string_3814468,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(c=document.body).firstChild?v(h,c.firstChild):c.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(a,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),a()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(o=a,z=l.document,i=!1,m(),z.onreadystatechange=function(){"complete"==z.readyState&&(z.onreadystatechange=null,s())})}function s(){i||(i=!0,o())}function m(){try{z.documentElement.doScroll("left")}catch(c){return void setTimeout(m,50)}s()}}(window); \ No newline at end of file diff --git a/src/assets/icons/js/iconfont2.js b/src/assets/icons/js/iconfont2.js new file mode 100644 index 0000000..b098acf --- /dev/null +++ b/src/assets/icons/js/iconfont2.js @@ -0,0 +1 @@ +!function(c){var a,t,e,i,l,n,o='',d=(d=document.getElementsByTagName("script"))[d.length-1].getAttribute("data-injectcss");if(d&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}function h(){l||(l=!0,e())}a=function(){var c,a,t,e;(e=document.createElement("div")).innerHTML=o,o=null,(t=e.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",c=t,(a=document.body).firstChild?(e=c,(t=a.firstChild).parentNode.insertBefore(e,t)):a.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(a,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),a()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(e=a,i=c.document,l=!1,(n=function(){try{i.documentElement.doScroll("left")}catch(c){return void setTimeout(n,50)}h()})(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,h())})}(window); \ No newline at end of file diff --git a/src/assets/icons/lock.svg b/src/assets/icons/lock.svg new file mode 100644 index 0000000..c55da4d --- /dev/null +++ b/src/assets/icons/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/moon.svg b/src/assets/icons/moon.svg new file mode 100644 index 0000000..e6667f0 --- /dev/null +++ b/src/assets/icons/moon.svg @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/src/assets/icons/reload-01.svg b/src/assets/icons/reload-01.svg new file mode 100644 index 0000000..2f73719 --- /dev/null +++ b/src/assets/icons/reload-01.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/robot.svg b/src/assets/icons/robot.svg new file mode 100644 index 0000000..a1f035a --- /dev/null +++ b/src/assets/icons/robot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/sun.svg b/src/assets/icons/sun.svg new file mode 100644 index 0000000..a3997cb --- /dev/null +++ b/src/assets/icons/sun.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/icons/superviseNotice.png b/src/assets/icons/superviseNotice.png new file mode 100644 index 0000000..66ceee8 Binary files /dev/null and b/src/assets/icons/superviseNotice.png differ diff --git a/src/assets/icons/systemNotice.png b/src/assets/icons/systemNotice.png new file mode 100644 index 0000000..7ed5c72 Binary files /dev/null and b/src/assets/icons/systemNotice.png differ diff --git a/src/assets/icons/test.svg b/src/assets/icons/test.svg new file mode 100644 index 0000000..244252d --- /dev/null +++ b/src/assets/icons/test.svg @@ -0,0 +1,21 @@ + + + + Icon1@3x + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/total-sales.svg b/src/assets/icons/total-sales.svg new file mode 100644 index 0000000..eff7964 --- /dev/null +++ b/src/assets/icons/total-sales.svg @@ -0,0 +1 @@ +Asset 500 \ No newline at end of file diff --git a/src/assets/icons/transaction.svg b/src/assets/icons/transaction.svg new file mode 100644 index 0000000..7ba9e2f --- /dev/null +++ b/src/assets/icons/transaction.svg @@ -0,0 +1 @@ +Asset 480% \ No newline at end of file diff --git a/src/assets/icons/visit-count.svg b/src/assets/icons/visit-count.svg new file mode 100644 index 0000000..ba2a306 --- /dev/null +++ b/src/assets/icons/visit-count.svg @@ -0,0 +1 @@ +Asset 510 \ No newline at end of file diff --git a/src/assets/images/ai/aiflow.png b/src/assets/images/ai/aiflow.png new file mode 100644 index 0000000..49ffd3d Binary files /dev/null and b/src/assets/images/ai/aiflow.png differ diff --git a/src/assets/images/ai/avatar.jpg b/src/assets/images/ai/avatar.jpg new file mode 100644 index 0000000..9be9587 Binary files /dev/null and b/src/assets/images/ai/avatar.jpg differ diff --git a/src/assets/images/checkcode.png b/src/assets/images/checkcode.png new file mode 100644 index 0000000..844fa70 Binary files /dev/null and b/src/assets/images/checkcode.png differ diff --git a/src/assets/images/cms_bpm.png b/src/assets/images/cms_bpm.png new file mode 100644 index 0000000..afac2cf Binary files /dev/null and b/src/assets/images/cms_bpm.png differ diff --git a/src/assets/images/cms_oa.png b/src/assets/images/cms_oa.png new file mode 100644 index 0000000..727d0c0 Binary files /dev/null and b/src/assets/images/cms_oa.png differ diff --git a/src/assets/images/daiban.png b/src/assets/images/daiban.png new file mode 100644 index 0000000..19002c1 Binary files /dev/null and b/src/assets/images/daiban.png differ diff --git a/src/assets/images/demo.png b/src/assets/images/demo.png new file mode 100644 index 0000000..1a45c98 Binary files /dev/null and b/src/assets/images/demo.png differ diff --git a/src/assets/images/department.png b/src/assets/images/department.png new file mode 100644 index 0000000..07bfac2 Binary files /dev/null and b/src/assets/images/department.png differ diff --git a/src/assets/images/drag_cover.png b/src/assets/images/drag_cover.png new file mode 100644 index 0000000..b95fa73 Binary files /dev/null and b/src/assets/images/drag_cover.png differ diff --git a/src/assets/images/duban.png b/src/assets/images/duban.png new file mode 100644 index 0000000..1597486 Binary files /dev/null and b/src/assets/images/duban.png differ diff --git a/src/assets/images/ghb-login.svg b/src/assets/images/ghb-login.svg new file mode 100644 index 0000000..d176d3e --- /dev/null +++ b/src/assets/images/ghb-login.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/ghb-logo.svg b/src/assets/images/ghb-logo.svg new file mode 100644 index 0000000..947f6c3 --- /dev/null +++ b/src/assets/images/ghb-logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + TEST + diff --git a/src/assets/images/guaz.png b/src/assets/images/guaz.png new file mode 100644 index 0000000..7ba480f Binary files /dev/null and b/src/assets/images/guaz.png differ diff --git a/src/assets/images/header.jpg b/src/assets/images/header.jpg new file mode 100644 index 0000000..977584b Binary files /dev/null and b/src/assets/images/header.jpg differ diff --git a/src/assets/images/link.png b/src/assets/images/link.png new file mode 100644 index 0000000..4a0319c Binary files /dev/null and b/src/assets/images/link.png differ diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png new file mode 100644 index 0000000..8072ced Binary files /dev/null and b/src/assets/images/logo.png differ diff --git a/src/assets/images/logo_duty.png b/src/assets/images/logo_duty.png new file mode 100644 index 0000000..8072ced Binary files /dev/null and b/src/assets/images/logo_duty.png differ diff --git a/src/assets/images/nodata.png b/src/assets/images/nodata.png new file mode 100644 index 0000000..2cebdb3 Binary files /dev/null and b/src/assets/images/nodata.png differ diff --git a/src/assets/images/panel_cover.png b/src/assets/images/panel_cover.png new file mode 100644 index 0000000..faf0065 Binary files /dev/null and b/src/assets/images/panel_cover.png differ diff --git a/src/assets/images/pdf4.jpg b/src/assets/images/pdf4.jpg new file mode 100644 index 0000000..10166e0 Binary files /dev/null and b/src/assets/images/pdf4.jpg differ diff --git a/src/assets/images/people.png b/src/assets/images/people.png new file mode 100644 index 0000000..19ed1be Binary files /dev/null and b/src/assets/images/people.png differ diff --git a/src/assets/images/placeholderImage.png b/src/assets/images/placeholderImage.png new file mode 100644 index 0000000..65fbf52 Binary files /dev/null and b/src/assets/images/placeholderImage.png differ diff --git a/src/assets/images/process_no_form.png b/src/assets/images/process_no_form.png new file mode 100644 index 0000000..46e3cab Binary files /dev/null and b/src/assets/images/process_no_form.png differ diff --git a/src/assets/images/setting.png b/src/assets/images/setting.png new file mode 100644 index 0000000..8e2e11c Binary files /dev/null and b/src/assets/images/setting.png differ diff --git a/src/assets/images/template_cover.jpg b/src/assets/images/template_cover.jpg new file mode 100644 index 0000000..ee2e502 Binary files /dev/null and b/src/assets/images/template_cover.jpg differ diff --git a/src/assets/images/wallet.png b/src/assets/images/wallet.png new file mode 100644 index 0000000..ec10bea Binary files /dev/null and b/src/assets/images/wallet.png differ diff --git a/src/assets/images/wordtpl/images/alignment.svg b/src/assets/images/wordtpl/images/alignment.svg new file mode 100644 index 0000000..08b66e3 --- /dev/null +++ b/src/assets/images/wordtpl/images/alignment.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/arrow-left.svg b/src/assets/images/wordtpl/images/arrow-left.svg new file mode 100644 index 0000000..b55538b --- /dev/null +++ b/src/assets/images/wordtpl/images/arrow-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/arrow-right.svg b/src/assets/images/wordtpl/images/arrow-right.svg new file mode 100644 index 0000000..1aadb00 --- /dev/null +++ b/src/assets/images/wordtpl/images/arrow-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/barcode.svg b/src/assets/images/wordtpl/images/barcode.svg new file mode 100644 index 0000000..655b3ef --- /dev/null +++ b/src/assets/images/wordtpl/images/barcode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/bold.svg b/src/assets/images/wordtpl/images/bold.svg new file mode 100644 index 0000000..80728d0 --- /dev/null +++ b/src/assets/images/wordtpl/images/bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/catalog.svg b/src/assets/images/wordtpl/images/catalog.svg new file mode 100644 index 0000000..90d45c2 --- /dev/null +++ b/src/assets/images/wordtpl/images/catalog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/cellcolor.svg b/src/assets/images/wordtpl/images/cellcolor.svg new file mode 100644 index 0000000..4a44be1 --- /dev/null +++ b/src/assets/images/wordtpl/images/cellcolor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/center.svg b/src/assets/images/wordtpl/images/center.svg new file mode 100644 index 0000000..28dc13c --- /dev/null +++ b/src/assets/images/wordtpl/images/center.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/chart.svg b/src/assets/images/wordtpl/images/chart.svg new file mode 100644 index 0000000..8705b76 --- /dev/null +++ b/src/assets/images/wordtpl/images/chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/close.svg b/src/assets/images/wordtpl/images/close.svg new file mode 100644 index 0000000..e5b1c23 --- /dev/null +++ b/src/assets/images/wordtpl/images/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/color.svg b/src/assets/images/wordtpl/images/color.svg new file mode 100644 index 0000000..2b84e88 --- /dev/null +++ b/src/assets/images/wordtpl/images/color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/download.svg b/src/assets/images/wordtpl/images/download.svg new file mode 100644 index 0000000..1c7ea4a --- /dev/null +++ b/src/assets/images/wordtpl/images/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/exit-fullscreen.svg b/src/assets/images/wordtpl/images/exit-fullscreen.svg new file mode 100644 index 0000000..7999e25 --- /dev/null +++ b/src/assets/images/wordtpl/images/exit-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/format.svg b/src/assets/images/wordtpl/images/format.svg new file mode 100644 index 0000000..aae6e6b --- /dev/null +++ b/src/assets/images/wordtpl/images/format.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/highlight.svg b/src/assets/images/wordtpl/images/highlight.svg new file mode 100644 index 0000000..c4b2e8b --- /dev/null +++ b/src/assets/images/wordtpl/images/highlight.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/hyperlink.svg b/src/assets/images/wordtpl/images/hyperlink.svg new file mode 100644 index 0000000..45090f6 --- /dev/null +++ b/src/assets/images/wordtpl/images/hyperlink.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/image.svg b/src/assets/images/wordtpl/images/image.svg new file mode 100644 index 0000000..7b43678 --- /dev/null +++ b/src/assets/images/wordtpl/images/image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/italic.svg b/src/assets/images/wordtpl/images/italic.svg new file mode 100644 index 0000000..73b2af5 --- /dev/null +++ b/src/assets/images/wordtpl/images/italic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/left.svg b/src/assets/images/wordtpl/images/left.svg new file mode 100644 index 0000000..b41d2b2 --- /dev/null +++ b/src/assets/images/wordtpl/images/left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/line-dash-dot-dot.svg b/src/assets/images/wordtpl/images/line-dash-dot-dot.svg new file mode 100644 index 0000000..30ab5ac --- /dev/null +++ b/src/assets/images/wordtpl/images/line-dash-dot-dot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/line-dash-dot.svg b/src/assets/images/wordtpl/images/line-dash-dot.svg new file mode 100644 index 0000000..1958671 --- /dev/null +++ b/src/assets/images/wordtpl/images/line-dash-dot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/line-dash-large-gap.svg b/src/assets/images/wordtpl/images/line-dash-large-gap.svg new file mode 100644 index 0000000..2e38e60 --- /dev/null +++ b/src/assets/images/wordtpl/images/line-dash-large-gap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/line-dash-small-gap.svg b/src/assets/images/wordtpl/images/line-dash-small-gap.svg new file mode 100644 index 0000000..88d6082 --- /dev/null +++ b/src/assets/images/wordtpl/images/line-dash-small-gap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/line-dot.svg b/src/assets/images/wordtpl/images/line-dot.svg new file mode 100644 index 0000000..c08b564 --- /dev/null +++ b/src/assets/images/wordtpl/images/line-dot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/line-double.svg b/src/assets/images/wordtpl/images/line-double.svg new file mode 100644 index 0000000..2efe69f --- /dev/null +++ b/src/assets/images/wordtpl/images/line-double.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/line-single.svg b/src/assets/images/wordtpl/images/line-single.svg new file mode 100644 index 0000000..453d4fa --- /dev/null +++ b/src/assets/images/wordtpl/images/line-single.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/line-wavy.svg b/src/assets/images/wordtpl/images/line-wavy.svg new file mode 100644 index 0000000..bc0f47c --- /dev/null +++ b/src/assets/images/wordtpl/images/line-wavy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/list.svg b/src/assets/images/wordtpl/images/list.svg new file mode 100644 index 0000000..564897c --- /dev/null +++ b/src/assets/images/wordtpl/images/list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/option.svg b/src/assets/images/wordtpl/images/option.svg new file mode 100644 index 0000000..53b6cae --- /dev/null +++ b/src/assets/images/wordtpl/images/option.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/page-break.svg b/src/assets/images/wordtpl/images/page-break.svg new file mode 100644 index 0000000..c40ec93 --- /dev/null +++ b/src/assets/images/wordtpl/images/page-break.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/page-mode.svg b/src/assets/images/wordtpl/images/page-mode.svg new file mode 100644 index 0000000..516f3a8 --- /dev/null +++ b/src/assets/images/wordtpl/images/page-mode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/page-scale-add.svg b/src/assets/images/wordtpl/images/page-scale-add.svg new file mode 100644 index 0000000..bcfa9a3 --- /dev/null +++ b/src/assets/images/wordtpl/images/page-scale-add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/page-scale-minus.svg b/src/assets/images/wordtpl/images/page-scale-minus.svg new file mode 100644 index 0000000..f85bf77 --- /dev/null +++ b/src/assets/images/wordtpl/images/page-scale-minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/painter.svg b/src/assets/images/wordtpl/images/painter.svg new file mode 100644 index 0000000..a865d1d --- /dev/null +++ b/src/assets/images/wordtpl/images/painter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/paper-direction.svg b/src/assets/images/wordtpl/images/paper-direction.svg new file mode 100644 index 0000000..ee90234 --- /dev/null +++ b/src/assets/images/wordtpl/images/paper-direction.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/paper-margin.svg b/src/assets/images/wordtpl/images/paper-margin.svg new file mode 100644 index 0000000..6188f36 --- /dev/null +++ b/src/assets/images/wordtpl/images/paper-margin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/paper-size.svg b/src/assets/images/wordtpl/images/paper-size.svg new file mode 100644 index 0000000..205a6aa --- /dev/null +++ b/src/assets/images/wordtpl/images/paper-size.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/preview.svg b/src/assets/images/wordtpl/images/preview.svg new file mode 100644 index 0000000..9e36cb1 --- /dev/null +++ b/src/assets/images/wordtpl/images/preview.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/print.svg b/src/assets/images/wordtpl/images/print.svg new file mode 100644 index 0000000..5ee44a0 --- /dev/null +++ b/src/assets/images/wordtpl/images/print.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/qrcode.svg b/src/assets/images/wordtpl/images/qrcode.svg new file mode 100644 index 0000000..3677506 --- /dev/null +++ b/src/assets/images/wordtpl/images/qrcode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/redo.svg b/src/assets/images/wordtpl/images/redo.svg new file mode 100644 index 0000000..fc88331 --- /dev/null +++ b/src/assets/images/wordtpl/images/redo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/request-fullscreen.svg b/src/assets/images/wordtpl/images/request-fullscreen.svg new file mode 100644 index 0000000..cf47c4a --- /dev/null +++ b/src/assets/images/wordtpl/images/request-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/right.svg b/src/assets/images/wordtpl/images/right.svg new file mode 100644 index 0000000..eca4643 --- /dev/null +++ b/src/assets/images/wordtpl/images/right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/row-margin.svg b/src/assets/images/wordtpl/images/row-margin.svg new file mode 100644 index 0000000..97f2baa --- /dev/null +++ b/src/assets/images/wordtpl/images/row-margin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/save.svg b/src/assets/images/wordtpl/images/save.svg new file mode 100644 index 0000000..0d0fb13 --- /dev/null +++ b/src/assets/images/wordtpl/images/save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/search.svg b/src/assets/images/wordtpl/images/search.svg new file mode 100644 index 0000000..9d515dc --- /dev/null +++ b/src/assets/images/wordtpl/images/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/separator.svg b/src/assets/images/wordtpl/images/separator.svg new file mode 100644 index 0000000..58225e9 --- /dev/null +++ b/src/assets/images/wordtpl/images/separator.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/size-add.svg b/src/assets/images/wordtpl/images/size-add.svg new file mode 100644 index 0000000..aa1073c --- /dev/null +++ b/src/assets/images/wordtpl/images/size-add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/size-minus.svg b/src/assets/images/wordtpl/images/size-minus.svg new file mode 100644 index 0000000..7bfa958 --- /dev/null +++ b/src/assets/images/wordtpl/images/size-minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/strikeout.svg b/src/assets/images/wordtpl/images/strikeout.svg new file mode 100644 index 0000000..c2c83ca --- /dev/null +++ b/src/assets/images/wordtpl/images/strikeout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/subscript.svg b/src/assets/images/wordtpl/images/subscript.svg new file mode 100644 index 0000000..9ec06b7 --- /dev/null +++ b/src/assets/images/wordtpl/images/subscript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/superscript.svg b/src/assets/images/wordtpl/images/superscript.svg new file mode 100644 index 0000000..053bd3e --- /dev/null +++ b/src/assets/images/wordtpl/images/superscript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/table.svg b/src/assets/images/wordtpl/images/table.svg new file mode 100644 index 0000000..6a9eb22 --- /dev/null +++ b/src/assets/images/wordtpl/images/table.svg @@ -0,0 +1,14 @@ + + + 编组 10 + + + + + + + + + + + diff --git a/src/assets/images/wordtpl/images/title.svg b/src/assets/images/wordtpl/images/title.svg new file mode 100644 index 0000000..c131320 --- /dev/null +++ b/src/assets/images/wordtpl/images/title.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/underline.svg b/src/assets/images/wordtpl/images/underline.svg new file mode 100644 index 0000000..dcd81b0 --- /dev/null +++ b/src/assets/images/wordtpl/images/underline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/undo.svg b/src/assets/images/wordtpl/images/undo.svg new file mode 100644 index 0000000..820f852 --- /dev/null +++ b/src/assets/images/wordtpl/images/undo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/upload.svg b/src/assets/images/wordtpl/images/upload.svg new file mode 100644 index 0000000..7e2f518 --- /dev/null +++ b/src/assets/images/wordtpl/images/upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/wordtpl/images/watermark.svg b/src/assets/images/wordtpl/images/watermark.svg new file mode 100644 index 0000000..68de565 --- /dev/null +++ b/src/assets/images/wordtpl/images/watermark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/zaiban.png b/src/assets/images/zaiban.png new file mode 100644 index 0000000..46b1f6e Binary files /dev/null and b/src/assets/images/zaiban.png differ diff --git a/src/assets/less/JAreaLinkage.less b/src/assets/less/JAreaLinkage.less new file mode 100644 index 0000000..4fea722 --- /dev/null +++ b/src/assets/less/JAreaLinkage.less @@ -0,0 +1,258 @@ +.area-zoom-in-top-enter-active, +.area-zoom-in-top-leave-active { + opacity: 1; + transform: scaleY(1); +} + +.area-zoom-in-top-enter, +.area-zoom-in-top-leave-active { + opacity: 0; + transform: scaleY(0); +} + +.area-select { + box-sizing: border-box; + margin: 0; + padding: 0; + color: rgba(0, 0, 0, 0.65); + font-size: 14px; + font-variant: tabular-nums; + line-height: 1.5; + list-style: none; + font-feature-settings: 'tnum'; + position: relative; + outline: 0; + display: block; + background-color: #fff; + border: 1px solid #d9d9d9; + border-top-width: 1.02px; + border-radius: 4px; + outline: none; + transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.area-select-wrap .area-select { + display: inline-block; +} + +.area-select * { + box-sizing: border-box; +} + +.area-select:hover { + border-color: #40a9ff; + border-right-width: 1px !important; + outline: 0; +} + +.area-select:active { + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); +} + +.area-select.small { + width: 126px; +} + +.area-select.medium { + width: 160px; +} + +.area-select.large { + width: 194px; +} + +.area-select.is-disabled { + background: #eceff5; + cursor: not-allowed; +} + +.area-select.is-disabled:hover { + border-color: #e1e2e6; +} + +.area-select.is-disabled .area-selected-trigger { + cursor: not-allowed; +} + +.area-select .area-selected-trigger { + position: relative; + display: block; + font-size: 14px; + cursor: pointer; + margin: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + height: 100%; + padding: 8px 20px 7px 12px; +} + +.area-select .area-select-icon { + position: absolute; + top: 50%; + margin-top: -2px; + right: 6px; + content: ''; + width: 0; + height: 0; + border: 6px solid transparent; + border-top-color: rgba(0, 0, 0, 0.25); + transition: all 0.3s linear; + transform-origin: center; +} + +.area-select .area-select-icon.active { + margin-top: -8px; + transform: rotate(180deg); +} + +.area-selectable-list-wrap { + position: absolute; + width: 100%; + max-height: 275px; + z-index: 15000; + background-color: #fff; + box-sizing: border-box; + overflow-x: auto; + margin: 2px 0; + border-radius: 4px; + outline: none; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + + transition: opacity 0.15s, transform 0.3s !important; + transform-origin: center top !important; +} + +.area-selectable-list { + position: relative; + margin: 0; + padding: 6px 0; + width: 100%; + font-size: 14px; + color: #565656; + list-style: none; +} + +.area-selectable-list .area-select-option { + position: relative; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + padding: 0 15px 0 10px; + height: 32px; + line-height: 32px; +} + +.area-selectable-list .area-select-option.hover { + background-color: #e6f7ff; +} + +.area-selectable-list .area-select-option.selected { + color: rgba(0, 0, 0, 0.65); + font-weight: 600; + background-color: #efefef; +} + +.cascader-menu-list-wrap { + position: absolute; + white-space: nowrap; + z-index: 15000; + background-color: #fff; + box-sizing: border-box; + overflow: hidden; + font-size: 0; + margin: 2px 0; + border-radius: 4px; + outline: none; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + + transition: opacity 0.15s, transform 0.3s !important; + transform-origin: center top !important; +} + +.cascader-menu-list { + position: relative; + margin: 0; + font-size: 14px; + color: #565656; + padding: 6px 0; + list-style: none; + display: inline-block; + height: 204px; + overflow-x: hidden; + overflow-y: auto; + min-width: 160px; + vertical-align: top; + background-color: #fff; + border-right: 1px solid #e4e7ed; +} + +.cascader-menu-list:last-child { + border-right: none; +} + +.cascader-menu-list .cascader-menu-option { + position: relative; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + padding: 0 15px 0 10px; + height: 32px; + line-height: 32px; +} + +.cascader-menu-list .cascader-menu-option.hover, +.cascader-menu-list .cascader-menu-option:hover { + background-color: #e6f7ff; +} + +.cascader-menu-list .cascader-menu-option.selected { + color: rgba(0, 0, 0, 0.65); + font-weight: 600; + background-color: #efefef; +} + +.cascader-menu-list .cascader-menu-option.cascader-menu-extensible:after { + position: absolute; + top: 50%; + margin-top: -4px; + right: 5px; + content: ''; + width: 0; + height: 0; + border: 4px solid transparent; + border-left-color: #a1a4ad; +} + +.cascader-menu-list::-webkit-scrollbar, +.area-selectable-list-wrap::-webkit-scrollbar { + width: 8px; + background: transparent; +} + +.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:decremen, +.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:end:decrement, +.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:increment, +.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:start:increment, +.cascader-menu-list::-webkit-scrollbar-button:vertical:decremen, +.cascader-menu-list::-webkit-scrollbar-button:vertical:end:decrement, +.cascader-menu-list::-webkit-scrollbar-button:vertical:increment, +.cascader-menu-list::-webkit-scrollbar-button:vertical:start:increment { + display: none; +} + +.cascader-menu-list::-webkit-scrollbar-thumb:vertical, +.area-selectable-list-wrap::-webkit-scrollbar-thumb:vertical { + background-color: #b8b8b8; + border-radius: 4px; +} + +.cascader-menu-list::-webkit-scrollbar-thumb:vertical:hover, +.area-selectable-list-wrap::-webkit-scrollbar-thumb:vertical:hover { + background-color: #777; +} diff --git a/src/assets/loginmini/icon/ghb_ad_text.svg b/src/assets/loginmini/icon/ghb_ad_text.svg new file mode 100644 index 0000000..6cef4bb --- /dev/null +++ b/src/assets/loginmini/icon/ghb_ad_text.svg @@ -0,0 +1,5 @@ + + test 管理系统 + 高效 · 稳定 · 安全的企业级后台管理平台 + 助力业务快速落地 + diff --git a/src/assets/loginmini/icon/icon-code.png b/src/assets/loginmini/icon/icon-code.png new file mode 100644 index 0000000..142d3ab Binary files /dev/null and b/src/assets/loginmini/icon/icon-code.png differ diff --git a/src/assets/loginmini/icon/icon-depart.png b/src/assets/loginmini/icon/icon-depart.png new file mode 100644 index 0000000..ed1a416 Binary files /dev/null and b/src/assets/loginmini/icon/icon-depart.png differ diff --git a/src/assets/loginmini/icon/icon-eye-g.png b/src/assets/loginmini/icon/icon-eye-g.png new file mode 100644 index 0000000..8cf83d6 Binary files /dev/null and b/src/assets/loginmini/icon/icon-eye-g.png differ diff --git a/src/assets/loginmini/icon/icon-eye-k.png b/src/assets/loginmini/icon/icon-eye-k.png new file mode 100644 index 0000000..e1cacb0 Binary files /dev/null and b/src/assets/loginmini/icon/icon-eye-k.png differ diff --git a/src/assets/loginmini/icon/icon-line-msg.png b/src/assets/loginmini/icon/icon-line-msg.png new file mode 100644 index 0000000..08e7bea Binary files /dev/null and b/src/assets/loginmini/icon/icon-line-msg.png differ diff --git a/src/assets/loginmini/icon/icon-line-pad.png b/src/assets/loginmini/icon/icon-line-pad.png new file mode 100644 index 0000000..2aaccec Binary files /dev/null and b/src/assets/loginmini/icon/icon-line-pad.png differ diff --git a/src/assets/loginmini/icon/icon-line-tel.png b/src/assets/loginmini/icon/icon-line-tel.png new file mode 100644 index 0000000..c3efe1c Binary files /dev/null and b/src/assets/loginmini/icon/icon-line-tel.png differ diff --git a/src/assets/loginmini/icon/icon-line-user.png b/src/assets/loginmini/icon/icon-line-user.png new file mode 100644 index 0000000..30a280c Binary files /dev/null and b/src/assets/loginmini/icon/icon-line-user.png differ diff --git a/src/assets/loginmini/icon/icon-password.png b/src/assets/loginmini/icon/icon-password.png new file mode 100644 index 0000000..edf6d31 Binary files /dev/null and b/src/assets/loginmini/icon/icon-password.png differ diff --git a/src/assets/loginmini/icon/icon-success.png b/src/assets/loginmini/icon/icon-success.png new file mode 100644 index 0000000..aa3a233 Binary files /dev/null and b/src/assets/loginmini/icon/icon-success.png differ diff --git a/src/assets/loginmini/icon/icon-user.png b/src/assets/loginmini/icon/icon-user.png new file mode 100644 index 0000000..968fc1a Binary files /dev/null and b/src/assets/loginmini/icon/icon-user.png differ diff --git a/src/assets/loginmini/icon/icon_dow.png b/src/assets/loginmini/icon/icon_dow.png new file mode 100644 index 0000000..03d928d Binary files /dev/null and b/src/assets/loginmini/icon/icon_dow.png differ diff --git a/src/assets/loginmini/icon/jeecg_ad_text.png b/src/assets/loginmini/icon/jeecg_ad_text.png new file mode 100644 index 0000000..8725482 Binary files /dev/null and b/src/assets/loginmini/icon/jeecg_ad_text.png differ diff --git a/src/assets/loginmini/icon/jeecg_logo.png b/src/assets/loginmini/icon/jeecg_logo.png new file mode 100644 index 0000000..109687f Binary files /dev/null and b/src/assets/loginmini/icon/jeecg_logo.png differ diff --git a/src/assets/loginmini/icon/logo.png b/src/assets/loginmini/icon/logo.png new file mode 100644 index 0000000..ad1cb65 Binary files /dev/null and b/src/assets/loginmini/icon/logo.png differ diff --git a/src/assets/loginmini/style/base.less b/src/assets/loginmini/style/base.less new file mode 100644 index 0000000..0eb0ba4 --- /dev/null +++ b/src/assets/loginmini/style/base.less @@ -0,0 +1,365 @@ +::-webkit-input-placeholder { + /* WebKit browsers */ + color: #868686; + font-size: 15px; +} + +::-moz-placeholder { + /* Mozilla Firefox 19+ */ + color: #868686; + font-size: 15px; +} + +:-ms-input-placeholder { + /* Internet Explorer 10+ */ + color: #868686; + font-size: 15px; +} + +input:-webkit-autofill { + transition: background-color 5000s ease-in-out 0s; +} + +html { + scroll-behavior: smooth; +} + +html, +body { + color: #333; + margin: 0; + height: 100%; + font-family: 'Myriad Set Pro', 'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: normal; +} + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +a { + text-decoration: none; + color: #000; +} + +a, +label, +button, +input, +select { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +img { + max-width: 100%; + height: auto; + display: block; + border: 0; +} + +body { + background: #e3f0ff; + color: #666; +} + +html, +body, +div, +dl, +dt, +dd, +ol, +ul, +li, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +button, +fieldset, +form, +input, +legend, +textarea, +th, +td { + margin: 0; + padding: 0; +} + +a { + text-decoration: none; + color: #08acee; +} + +button { + outline: 0; +} + +button, +input, +optgroup, +select, +textarea { + margin: 0; + font: inherit; + color: inherit; + outline: none; +} + +li { + list-style: none; +} + +a { + color: #666; +} + +.clearfix::after { + clear: both; + content: '.'; + display: block; + height: 0; + visibility: hidden; +} + +.clearfix { +} + +.divHeight { + width: 100%; + height: 10px; + background: #f5f5f5; + position: relative; + overflow: hidden; +} + +.r-line { + position: relative; +} + +.r-line:after { + content: ''; + position: absolute; + z-index: 0; + top: 0; + right: 0; + height: 100%; + border-right: 1px solid #d9d9d9; + -webkit-transform: scaleX(0.5); + transform: scaleX(0.5); + -webkit-transform-origin: 100% 0; + transform-origin: 100% 0; +} + +.b-line { + position: relative; +} + +.b-line:after { + content: ''; + position: absolute; + z-index: 2; + bottom: 0; + left: 0; + width: 100%; + height: 1px; + border-bottom: 1px solid #dedede; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); + -webkit-transform-origin: 0 100%; + transform-origin: 0 100%; +} + +.aui-arrow { + position: relative; + padding-right: 0.8rem; +} + +.aui-arrow span { + font-size: 0.8rem; + color: #9b9b9b; +} + +.aui-arrow:after { + content: ' '; + display: inline-block; + height: 6px; + width: 6px; + border-width: 2px 2px 0 0; + border-color: #848484; + border-style: solid; + -webkit-transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0); + transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0); + position: relative; + position: absolute; + top: 50%; + margin-top: -4px; + right: 2px; + border-radius: 1px; +} + +.aui-flex { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + position: relative; +} + +.aui-flex-box { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + min-width: 0; + font-size: 14px; + color: #333; +} + +/* 必要布局样式css */ +.aui-flexView { + width: 100%; + height: 100%; + margin: 0 auto; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.aui-scrollView { + width: 100%; + height: 100%; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + overflow-y: auto; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + position: relative; + padding-bottom: 53px; +} + +.aui-navBar { + height: 44px; + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + z-index: 102; + background-color: #5064eb; +} + +.aui-navBar-item { + height: 44px; + min-width: 15%; + -webkit-box-flex: 0; + -webkit-flex: 0 0 15%; + -ms-flex: 0 0 15%; + flex: 0 0 15%; + padding: 0 0.9rem; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + font-size: 0.7rem; + white-space: nowrap; + overflow: hidden; + color: #808080; + position: relative; +} + +.aui-navBar-item:first-child { + -webkit-box-ordinal-group: 2; + -webkit-order: 1; + -ms-flex-order: 1; + order: 1; + margin-right: -25%; + font-size: 0.9rem; + font-weight: bold; +} + +.aui-navBar-item:last-child { + -webkit-box-ordinal-group: 4; + -webkit-order: 3; + -ms-flex-order: 3; + order: 3; + -webkit-box-pack: end; + -webkit-justify-content: flex-end; + -ms-flex-pack: end; + justify-content: flex-end; +} + +.aui-center { + -webkit-box-ordinal-group: 3; + -webkit-order: 2; + -ms-flex-order: 2; + order: 2; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + height: 44px; + width: 80%; + margin-left: 22%; +} + +.aui-center-title { + text-align: center; + width: 100%; + white-space: nowrap; + overflow: hidden; + display: block; + text-overflow: ellipsis; + font-size: 0.95rem; + color: #fff; + font-weight: 500; +} + +.icon { + width: 20px; + height: 20px; + display: block; + border: none; + float: left; + background-size: 20px; + background-repeat: no-repeat; + position: relative; +} + +.login-background-img { + background-image: linear-gradient(160deg, #eaf2ff 0%, #d6e6ff 100%); + background-size: cover; + background-position: top center; + background-repeat: no-repeat; +} diff --git a/src/assets/loginmini/style/home.less b/src/assets/loginmini/style/home.less new file mode 100644 index 0000000..306bd24 --- /dev/null +++ b/src/assets/loginmini/style/home.less @@ -0,0 +1,615 @@ +.aui-content { + padding: 40px 60px; + min-height: 100vh; +} + +.aui-container { + max-width: 1000px; + margin: 0 auto; + box-shadow: 0 4px 8px 1px rgba(0, 0, 0, 0.2); + position: fixed; + top: 50%; + left: 50%; + width: 92%; + height: auto; + -webkit-transform: translateX(-50%) translateY(-50%); + -moz-transform: translateX(-50%) translateY(-50%); + -ms-transform: translateX(-50%) translateY(-50%); + transform: translateX(-50%) translateY(-50%); + -webkit-transform: translateX(-50%) translateY(-50%); +} + +.aui-form { + width: 100%; + background: #eee; + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; +} + +.aui-image { + padding: 180px 80px; + flex-basis: 60%; + -webkit-flex-basis: 60%; + background-color: #1677ff; + background-image: linear-gradient(135deg, #1677ff 0%, #0e4fb0 100%); + background-size: cover; +} + +.aui-image-text { + top: 50%; + left: 50%; + width: 100%; +} + +.aui-formBox { + flex-basis: 40%; + -webkit-flex-basis: 40%; + box-sizing: border-box; + padding: 30px 20px; + background: #fff; + box-shadow: 2px 9px 49px -17px rgba(0, 0, 0, 0.1); +} + +.aui-logo { + width: 180px; + height: 80px; + position: absolute; + top: 2%; + left: 8%; + z-index: 4; +} + +.aui-account-line { + padding-top: 20px; + padding-bottom: 40px; +} + +.aui-code-line { + position: absolute; + right: 0; + top: 0; + border-left: 3px solid #fff; + height: 42px; + padding: 0 15px; + line-height: 40px; + font-size: 14px; + cursor: pointer; +} + +.aui-eye { + position: absolute; + right: 20px; + top: 10px; + width: 20px; + cursor: pointer; +} + +.aui-input-line { + background: #f5f5f9; + border-radius: 2px; + position: relative; + margin: 12px 0; +} + +.aui-input-line input { + width: 100%; + padding: 12px 10px; + border: none; + color: #333333; + font-size: 14px; + background: unset; + padding-left: 40px; +} + +.aui-input-line .icon { + position: absolute; + top: 10px; + left: 10px; +} + +.icon-line-user { + background-image: url(../icon/icon-line-user.png); +} + +.icon-line-tel { + background-image: url(../icon/icon-line-tel.png); +} + +.icon-line-msg { + background-image: url(../icon/icon-line-msg.png); +} + +.icon-line-pad { + background-image: url(../icon/icon-line-pad.png); +} + +.aui-forgot .aui-input-line input { + padding-left: 20px; +} + +.aui-forgot .aui-input-line { + background: none; + border: 1px solid #dbdbdb; + border-radius: 2px; +} + +.aui-forgot .aui-input-line:focus { + border-color: #1b90ff; +} + +.aui-forgot .aui-input-line:hover { + border-color: #1b90ff; +} + +.aui-forgot .aui-input-line .aui-code-line { + border-left: 1px solid #dbdbdb; + height: 40px; + color: #1b90ff; +} + +.aui-step-box { + width: 100%; + height: auto; + position: relative; + overflow: hidden; + margin-top: 50px; + margin-bottom: 20px; +} + +.aui-step-box::after { + position: absolute; + top: 20px; + left: 50%; + width: 76%; + margin-left: -38%; + height: 1px; + background: #bcbcbc; + content: ''; +} + +.aui-step-item { + width: 33.333%; + float: left; + text-align: center; + position: relative; + z-index: 2; +} + +.aui-step-tags em { + width: 40px; + height: 40px; + border: 8px solid #fff; + line-height: 1.3; + border-radius: 100px; + background: #bcbcbc; + display: block; + margin: 0 auto; + font-style: normal; + color: #fff; + font-size: 19px; + font-weight: 500; +} + +.aui-step-tags p { + font-size: 14px; + color: #bcbcbc; +} + +.activeStep .aui-step-tags em { + background: #1b90ff; +} + +.activeStep .aui-step-tags p { + color: #1b90ff; +} + +.aui-success { + position: absolute; + top: 50%; + left: 50%; + height: 80px; + width: 100%; + margin-top: -40px; + margin-left: -50%; +} + +.aui-success-icon { + width: 40px; + margin: 0 auto; +} + +.aui-success h3 { + width: 100%; + text-align: center; + color: #515151; + font-size: 18px; + padding-top: 20px; +} + +.aui-form-nav { + text-align: center; + padding-bottom: 20px; +} + +.aui-form-nav .aui-flex-box { + color: #040404; + font-size: 18px; + font-weight: 500; + cursor: pointer; +} + +.aui-clear-left { + text-align: left; +} + +.aui-clear-left .activeNav::after { + left: 18px; +} + +.activeNav { + position: relative; +} + +.activeNav::after { + content: ''; + position: absolute; + z-index: 0; + bottom: -10px; + left: 50%; + margin-left: -15px; + width: 30px; + height: 4px; + background: #1b90ff; + border-radius: 100px; +} + +.phone .aui-inputClear { + padding-left: 0; +} + +.phone .aui-inputClear input { + //padding-left: 1px; +} + +.phone .aui-inputClear .aui-code { + text-align: right; + width: auto; + bottom: 10px; +} + +.phone .aui-inputClear .aui-code a { + color: #1b90ff; + font-size: 14px; +} + +.phoneChina { + position: absolute; + bottom: 10px; + left: 0; + font-size: 14px; + color: #040404; +} + +.phoneChina::after { + position: absolute; + right: -25px; + bottom: 0; + content: ''; + background-image: url(../icon/icon_dow.png); + background-size: 18px; + width: 18px; + height: 18px; +} + +.phoneChina:before { + position: absolute; + right: -42px; + bottom: -15px; + content: ' '; + background: #fff; + width: 18px; + height: 18px; +} + +.aui-ewm { + width: 280px; + margin: 0 auto; +} + +.aui-formEwm { + padding: 50px 40px 55px 40px; +} + +.aui-inputClear { + width: 100%; + border-bottom: 1px solid #cccccc; + position: relative; + padding-left: 20px; + background: #fff; + margin-bottom: 8px; + margin-top: 20px; +} + +.aui-inputClear .icon { + position: absolute; + top: 10px; + left: 0; +} + +.aui-inputClear input { + width: 100%; + padding: 10px; + border: none; + color: #333333; + font-size: 14px; + background: none; +} + +.aui-code { + position: absolute; + right: 8px; + bottom: 0; + width: 115px; + cursor: pointer; +} + +.icon-code { + background-image: url(../icon/icon-user.png); +} + +.icon-password { + background-image: url(../icon/icon-password.png); +} +.icon-depart { + top: 5px !important; + background-image: url(../icon/icon-depart.png); +} +.icon-code { + background-image: url(../icon/icon-code.png); +} + +.aui-inputClear:focus { + border-bottom: 1px solid #1b90ff; +} + +.aui-inputClear:hover { + border-bottom: 1px solid #1b90ff; +} + +.aui-choice { + position: relative; + font-size: 12px; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + position: relative; + color: #040404; +} + +.aui-choice input { + width: 14px; + height: 14px; + cursor: pointer; +} + +.aui-forget a { + color: #1b90ff; + font-size: 12px; +} + +.aui-forget a:hover { + text-decoration: underline; +} + +.aui-formButton { + padding-top: 10px; +} + +.aui-formButton a { + height: 42px; + padding: 10px 15px; + font-size: 14px; + border-radius: 8px; + border-color: #67b5ff; + background: #1b90ff; + width: 100%; + cursor: pointer; + border: none; + color: #fff; + margin: 8px 0; + display: block; + text-align: center; +} + +.aui-formButton a:focus { + opacity: 0.9; +} + +.aui-formButton a:hover { + opacity: 0.9; +} + +.aui-formButton .aui-linek-code { + background: #fff; + color: #3c3c3c; + border: 1px solid #dbdbdb; +} + +.aui-formButton .aui-linek-code:hover { + color: #1b90ff; + border: 1px solid #1b90ff; +} + +.aui-third-text { + font-size: 12px; + color: #3c3c3c; + margin-top: 25px; + margin-bottom: 25px; +} + +.aui-third-text span { + color: #afafaf; + display: block; + width: 38%; + margin: 0 auto; + text-align: center; + position: relative; + background: #fff; + z-index: 100; + font-size: 12px; +} + +.aui-third-border { + position: relative; +} + +.aui-third-border::after { + content: ''; + position: absolute; + z-index: 0; + top: 8px; + left: 0; + width: 100%; + height: 1px; + border-top: 1px solid #d9d9d9; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); + -webkit-transform-origin: 0 100%; + transform-origin: 0 100%; +} + +.aui-third-login { + width: 30px; + height: 30px; + margin: 0 auto; + border-radius: 100px; +} + +.aui-third-login a { + font-size: 22px; + margin: 0 auto; + border-radius: 100px; + display: inline-block; + color: #888; +} + +.aui-third-login a:hover { + color: #1b90ff; + cursor: pointer; +} + +.aui-third-login:hover { + cursor: pointer; +} + +@media (max-width: 320px) { + .aui-form { + flex-direction: column; + } + + .aui-image { + order: 2; + display: none; + } + + .aui-container { + width: 100%; + max-width: 550px; + margin-top: 10px; + } + + .aui-content { + justify-content: initial; + width: 100%; + padding: 20px; + } +} + +@media (min-width: 321px) and (max-width: 375px) { + .aui-form { + flex-direction: column; + } + + .aui-image { + order: 2; + display: none; + } + + .aui-container { + width: 90%; + max-width: 550px; + } + + .aui-content { + justify-content: initial; + width: 100%; + padding: 20px; + } +} + +@media (min-width: 375px) and (max-width: 425px) { + .aui-form { + flex-direction: column; + } + + .aui-image { + order: 2; + display: none; + } + + .aui-container { + width: 90%; + max-width: 550px; + } + + .aui-content { + justify-content: initial; + width: 100%; + padding: 40px; + } +} + +@media (min-width: 425px) and (max-width: 768px) { + .aui-form { + flex-direction: column; + } + + .aui-image { + order: 2; + display: none; + } + + .aui-container { + width: 90%; + max-width: 550px; + } + + .aui-content { + justify-content: initial; + width: 100%; + padding: 40px; + } + + .aui-step-box::after { + width: 70%; + margin-left: -35%; + } +} + +@media only screen and (max-width: 767px) { + .aui-logo { + top: 3%; + } +} + +@media screen and (max-width: 300px) { + .aui-logo { + top: 3%; + } +} diff --git a/src/assets/svg/fileType/excel.svg b/src/assets/svg/fileType/excel.svg new file mode 100644 index 0000000..debdb36 --- /dev/null +++ b/src/assets/svg/fileType/excel.svg @@ -0,0 +1 @@ +MACWIN图形/印刷产品/思维导图影视/广告代码通用工业/建筑 \ No newline at end of file diff --git a/src/assets/svg/fileType/image.png b/src/assets/svg/fileType/image.png new file mode 100644 index 0000000..0284ad7 Binary files /dev/null and b/src/assets/svg/fileType/image.png differ diff --git a/src/assets/svg/fileType/other.svg b/src/assets/svg/fileType/other.svg new file mode 100644 index 0000000..59b0c41 --- /dev/null +++ b/src/assets/svg/fileType/other.svg @@ -0,0 +1 @@ +MACWIN图形/印刷产品/思维导图影视/广告代码通用工业/建筑 \ No newline at end of file diff --git a/src/assets/svg/fileType/pdf.svg b/src/assets/svg/fileType/pdf.svg new file mode 100644 index 0000000..67c0f10 --- /dev/null +++ b/src/assets/svg/fileType/pdf.svg @@ -0,0 +1 @@ +MACWIN图形/印刷产品/思维导图影视/广告代码通用工业/建筑 \ No newline at end of file diff --git a/src/assets/svg/fileType/txt.svg b/src/assets/svg/fileType/txt.svg new file mode 100644 index 0000000..602c3b9 --- /dev/null +++ b/src/assets/svg/fileType/txt.svg @@ -0,0 +1 @@ +MACWIN图形/印刷产品/思维导图影视/广告代码通用工业/建筑 \ No newline at end of file diff --git a/src/assets/svg/fileType/word.svg b/src/assets/svg/fileType/word.svg new file mode 100644 index 0000000..16aa1a4 --- /dev/null +++ b/src/assets/svg/fileType/word.svg @@ -0,0 +1 @@ +MACWIN图形/印刷产品/思维导图影视/广告代码通用工业/建筑 \ No newline at end of file diff --git a/src/assets/svg/illustration.svg b/src/assets/svg/illustration.svg new file mode 100644 index 0000000..b45215b --- /dev/null +++ b/src/assets/svg/illustration.svg @@ -0,0 +1 @@ +Asset 336 \ No newline at end of file diff --git a/src/assets/svg/login-bg-dark.svg b/src/assets/svg/login-bg-dark.svg new file mode 100644 index 0000000..888da7a --- /dev/null +++ b/src/assets/svg/login-bg-dark.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/svg/login-bg.svg b/src/assets/svg/login-bg.svg new file mode 100644 index 0000000..7b66baf --- /dev/null +++ b/src/assets/svg/login-bg.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/assets/svg/net-error.svg b/src/assets/svg/net-error.svg new file mode 100644 index 0000000..81f2004 --- /dev/null +++ b/src/assets/svg/net-error.svg @@ -0,0 +1 @@ +personal settings \ No newline at end of file diff --git a/src/assets/svg/no-data.svg b/src/assets/svg/no-data.svg new file mode 100644 index 0000000..2b9f257 --- /dev/null +++ b/src/assets/svg/no-data.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/preview/p-rotate.svg b/src/assets/svg/preview/p-rotate.svg new file mode 100644 index 0000000..5153a81 --- /dev/null +++ b/src/assets/svg/preview/p-rotate.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/preview/resume.svg b/src/assets/svg/preview/resume.svg new file mode 100644 index 0000000..0e86c5f --- /dev/null +++ b/src/assets/svg/preview/resume.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/preview/scale.svg b/src/assets/svg/preview/scale.svg new file mode 100644 index 0000000..1f7adae --- /dev/null +++ b/src/assets/svg/preview/scale.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/preview/unrotate.svg b/src/assets/svg/preview/unrotate.svg new file mode 100644 index 0000000..e4708be --- /dev/null +++ b/src/assets/svg/preview/unrotate.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/preview/unscale.svg b/src/assets/svg/preview/unscale.svg new file mode 100644 index 0000000..1359b34 --- /dev/null +++ b/src/assets/svg/preview/unscale.svg @@ -0,0 +1 @@ + diff --git a/src/components/Application/index.ts b/src/components/Application/index.ts new file mode 100644 index 0000000..01c02bf --- /dev/null +++ b/src/components/Application/index.ts @@ -0,0 +1,10 @@ +import { withInstall } from '/@/utils'; +import { defineAsyncComponent } from 'vue'; + +export { useAppProviderContext } from './src/useAppContext'; + +export const AppLogo = withInstall(defineAsyncComponent(() => import('./src/AppLogo.vue'))); +export const AppProvider = withInstall(defineAsyncComponent(() => import('./src/AppProvider.vue'))); +export const AppSearch = withInstall(defineAsyncComponent(() => import('./src/search/AppSearch.vue'))); +export const AppLocalePicker = withInstall(defineAsyncComponent(() => import('./src/AppLocalePicker.vue'))); +export const AppDarkModeToggle = withInstall(defineAsyncComponent(() => import('./src/AppDarkModeToggle.vue'))); diff --git a/src/components/Application/src/AppDarkModeToggle.vue b/src/components/Application/src/AppDarkModeToggle.vue new file mode 100644 index 0000000..6b07d92 --- /dev/null +++ b/src/components/Application/src/AppDarkModeToggle.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/components/Application/src/AppLocalePicker.vue b/src/components/Application/src/AppLocalePicker.vue new file mode 100644 index 0000000..d600bba --- /dev/null +++ b/src/components/Application/src/AppLocalePicker.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/src/components/Application/src/AppLogo.vue b/src/components/Application/src/AppLogo.vue new file mode 100644 index 0000000..7075ef4 --- /dev/null +++ b/src/components/Application/src/AppLogo.vue @@ -0,0 +1,93 @@ + + + + diff --git a/src/components/Application/src/AppProvider.vue b/src/components/Application/src/AppProvider.vue new file mode 100644 index 0000000..4c277bd --- /dev/null +++ b/src/components/Application/src/AppProvider.vue @@ -0,0 +1,77 @@ + diff --git a/src/components/Application/src/search/AppSearch.vue b/src/components/Application/src/search/AppSearch.vue new file mode 100644 index 0000000..63d346e --- /dev/null +++ b/src/components/Application/src/search/AppSearch.vue @@ -0,0 +1,33 @@ + diff --git a/src/components/Application/src/search/AppSearchFooter.vue b/src/components/Application/src/search/AppSearchFooter.vue new file mode 100644 index 0000000..06e1372 --- /dev/null +++ b/src/components/Application/src/search/AppSearchFooter.vue @@ -0,0 +1,55 @@ + + + + diff --git a/src/components/Application/src/search/AppSearchKeyItem.vue b/src/components/Application/src/search/AppSearchKeyItem.vue new file mode 100644 index 0000000..aba36a5 --- /dev/null +++ b/src/components/Application/src/search/AppSearchKeyItem.vue @@ -0,0 +1,11 @@ + + diff --git a/src/components/Application/src/search/AppSearchModal.vue b/src/components/Application/src/search/AppSearchModal.vue new file mode 100644 index 0000000..d5f2290 --- /dev/null +++ b/src/components/Application/src/search/AppSearchModal.vue @@ -0,0 +1,260 @@ + + + + diff --git a/src/components/Application/src/search/useMenuSearch.ts b/src/components/Application/src/search/useMenuSearch.ts new file mode 100644 index 0000000..2795dc5 --- /dev/null +++ b/src/components/Application/src/search/useMenuSearch.ts @@ -0,0 +1,181 @@ +import type { Menu } from '/@/router/types'; +import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue'; +import { getMenus } from '/@/router/menus'; +import { cloneDeep } from 'lodash-es'; +import { filter, forEach } from '/@/utils/helper/treeHelper'; +import { useGo } from '/@/hooks/web/usePage'; +import { useScrollTo } from '/@/hooks/event/useScrollTo'; +import { onKeyStroke, useDebounceFn } from '@vueuse/core'; +import { useI18n } from '/@/hooks/web/useI18n'; +import { URL_HASH_TAB } from '/@/utils'; + +export interface SearchResult { + name: string; + path: string; + icon?: string; + internalOrExternal: boolean; +} + +// Translate special characters +function transform(c: string) { + const code: string[] = ['$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|']; + return code.includes(c) ? `\\${c}` : c; +} + +function createSearchReg(key: string) { + const keys = [...key].map((item) => transform(item)); + const str = ['', ...keys, ''].join('.*'); + return new RegExp(str, 'i'); +} + +export function useMenuSearch(refs: Ref, scrollWrap: Ref, emit: EmitType) { + const searchResult = ref([]); + const keyword = ref(''); + const activeIndex = ref(-1); + + let menuList: Menu[] = []; + + const { t } = useI18n(); + const go = useGo(); + const handleSearch = useDebounceFn(search, 200); + + onBeforeMount(async () => { + const list = await getMenus(); + menuList = cloneDeep(list); + forEach(menuList, (item) => { + item.name = t(item.name); + }); + }); + + function search(e: ChangeEvent) { + e?.stopPropagation(); + const key = e.target.value; + keyword.value = key.trim(); + if (!key) { + searchResult.value = []; + return; + } + const reg = createSearchReg(unref(keyword)); + const filterMenu = filter(menuList, (item) => { + // 【issues/33】包含子菜单时,不添加到搜索队列 + if (Array.isArray(item.children)) { + return false; + } + return reg.test(item.name) && !item.hideMenu; + }); + searchResult.value = handlerSearchResult(filterMenu, reg); + activeIndex.value = 0; + } + + function handlerSearchResult(filterMenu: Menu[], reg: RegExp, parent?: Menu) { + const ret: SearchResult[] = []; + filterMenu.forEach((item) => { + const { name, path, icon, children, hideMenu, meta, internalOrExternal } = item; + if (!hideMenu && reg.test(name) && (!children?.length || meta?.hideChildrenInMenu)) { + ret.push({ + name: parent?.name ? `${parent.name} > ${name}` : name, + path, + icon, + internalOrExternal + }); + } + if (!meta?.hideChildrenInMenu && Array.isArray(children) && children.length) { + ret.push(...handlerSearchResult(children, reg, item)); + } + }); + return ret; + } + + // Activate when the mouse moves to a certain line + function handleMouseenter(e: any) { + const index = e.target.dataset.index; + activeIndex.value = Number(index); + } + + // Arrow key up + function handleUp() { + if (!searchResult.value.length) return; + activeIndex.value--; + if (activeIndex.value < 0) { + activeIndex.value = searchResult.value.length - 1; + } + handleScroll(); + } + + // Arrow key down + function handleDown() { + if (!searchResult.value.length) return; + activeIndex.value++; + if (activeIndex.value > searchResult.value.length - 1) { + activeIndex.value = 0; + } + handleScroll(); + } + + // When the keyboard up and down keys move to an invisible place + // the scroll bar needs to scroll automatically + function handleScroll() { + const refList = unref(refs); + if (!refList || !Array.isArray(refList) || refList.length === 0 || !unref(scrollWrap)) { + return; + } + + const index = unref(activeIndex); + const currentRef = refList[index]; + if (!currentRef) { + return; + } + const wrapEl = unref(scrollWrap); + if (!wrapEl) { + return; + } + const scrollHeight = currentRef.offsetTop + currentRef.offsetHeight; + const wrapHeight = wrapEl.offsetHeight; + const { start } = useScrollTo({ + el: wrapEl, + duration: 100, + to: scrollHeight - wrapHeight, + }); + start(); + } + + // enter keyboard event + async function handleEnter() { + if (!searchResult.value.length) { + return; + } + const result = unref(searchResult); + const index = unref(activeIndex); + if (result.length === 0 || index < 0) { + return; + } + const to = result[index]; + handleClose(); + await nextTick(); + + // 代码逻辑说明: 【QQYUN-8369】搜索区分大小写,外部链接新页打开 + if (to.internalOrExternal) { + // 代码逻辑说明: 【QQYUN-8773】配置外部网址在顶部菜单模式和搜索打不开 + const path = to.path.replace(URL_HASH_TAB, '#'); + window.open(path, '_blank'); + } else { + go(to.path); + } + } + + // close search modal + function handleClose() { + searchResult.value = []; + emit('close'); + } + + // enter search + onKeyStroke('Enter', handleEnter); + // Monitor keyboard arrow keys + onKeyStroke('ArrowUp', handleUp); + onKeyStroke('ArrowDown', handleDown); + // esc close + onKeyStroke('Escape', handleClose); + + return { handleSearch, searchResult, keyword, activeIndex, handleMouseenter, handleEnter }; +} diff --git a/src/components/Application/src/useAppContext.ts b/src/components/Application/src/useAppContext.ts new file mode 100644 index 0000000..8bdfb4f --- /dev/null +++ b/src/components/Application/src/useAppContext.ts @@ -0,0 +1,17 @@ +import { InjectionKey, Ref } from 'vue'; +import { createContext, useContext } from '/@/hooks/core/useContext'; + +export interface AppProviderContextProps { + prefixCls: Ref; + isMobile: Ref; +} + +const key: InjectionKey = Symbol(); + +export function createAppProviderContext(context: AppProviderContextProps) { + return createContext(context, key); +} + +export function useAppProviderContext() { + return useContext(key); +} diff --git a/src/components/Authority/index.ts b/src/components/Authority/index.ts new file mode 100644 index 0000000..2f0eab7 --- /dev/null +++ b/src/components/Authority/index.ts @@ -0,0 +1,4 @@ +import { withInstall } from '/@/utils'; +import authority from './src/Authority.vue'; + +export const Authority = withInstall(authority); diff --git a/src/components/Authority/src/Authority.vue b/src/components/Authority/src/Authority.vue new file mode 100644 index 0000000..0d35938 --- /dev/null +++ b/src/components/Authority/src/Authority.vue @@ -0,0 +1,45 @@ + + diff --git a/src/components/Basic/index.ts b/src/components/Basic/index.ts new file mode 100644 index 0000000..97a53a1 --- /dev/null +++ b/src/components/Basic/index.ts @@ -0,0 +1,8 @@ +import { withInstall } from '/@/utils'; +import basicArrow from './src/BasicArrow.vue'; +import basicTitle from './src/BasicTitle.vue'; +import basicHelp from './src/BasicHelp.vue'; + +export const BasicArrow = withInstall(basicArrow); +export const BasicTitle = withInstall(basicTitle); +export const BasicHelp = withInstall(basicHelp); diff --git a/src/components/Basic/src/BasicArrow.vue b/src/components/Basic/src/BasicArrow.vue new file mode 100644 index 0000000..6a4cd01 --- /dev/null +++ b/src/components/Basic/src/BasicArrow.vue @@ -0,0 +1,84 @@ + + + + diff --git a/src/components/Basic/src/BasicHelp.vue b/src/components/Basic/src/BasicHelp.vue new file mode 100644 index 0000000..396bd75 --- /dev/null +++ b/src/components/Basic/src/BasicHelp.vue @@ -0,0 +1,112 @@ + + diff --git a/src/components/Basic/src/BasicTitle.vue b/src/components/Basic/src/BasicTitle.vue new file mode 100644 index 0000000..7a796cb --- /dev/null +++ b/src/components/Basic/src/BasicTitle.vue @@ -0,0 +1,80 @@ + + + diff --git a/src/components/Button/index.ts b/src/components/Button/index.ts new file mode 100644 index 0000000..71bd2e4 --- /dev/null +++ b/src/components/Button/index.ts @@ -0,0 +1,11 @@ +import { withInstall } from '/@/utils'; +import type { ExtractPropTypes } from 'vue'; +import button from './src/BasicButton.vue'; +import jUploadButton from './src/JUploadButton.vue'; +import popConfirmButton from './src/PopConfirmButton.vue'; +import { buttonProps } from './src/props'; + +export const Button = withInstall(button); +export const JUploadButton = withInstall(jUploadButton); +export const PopConfirmButton = withInstall(popConfirmButton); +export declare type ButtonProps = Partial>; diff --git a/src/components/Button/src/BasicButton.vue b/src/components/Button/src/BasicButton.vue new file mode 100644 index 0000000..efbb7c3 --- /dev/null +++ b/src/components/Button/src/BasicButton.vue @@ -0,0 +1,41 @@ + + + + diff --git a/src/components/Button/src/JUploadButton.vue b/src/components/Button/src/JUploadButton.vue new file mode 100644 index 0000000..19dddde --- /dev/null +++ b/src/components/Button/src/JUploadButton.vue @@ -0,0 +1,41 @@ + + + + diff --git a/src/components/Button/src/PopConfirmButton.vue b/src/components/Button/src/PopConfirmButton.vue new file mode 100644 index 0000000..05d0f9a --- /dev/null +++ b/src/components/Button/src/PopConfirmButton.vue @@ -0,0 +1,56 @@ + diff --git a/src/components/Button/src/props.ts b/src/components/Button/src/props.ts new file mode 100644 index 0000000..b5026d6 --- /dev/null +++ b/src/components/Button/src/props.ts @@ -0,0 +1,21 @@ +export const buttonProps = { + color: { type: String, validator: (v) => ['error', 'warning', 'success', ''].includes(v) }, + loading: { type: Boolean }, + disabled: { type: Boolean }, + /** + * Text before icon. + */ + preIcon: { type: String }, + /** + * Text after icon. + */ + postIcon: { type: String }, + type: { type: String }, + /** + * preIcon and postIcon icon size. + * @default: 15 + */ + iconSize: { type: Number, default: 15 }, + isUpload: { type: Boolean, default: false }, + onClick: { type: Function as PropType<(...args) => any>, default: null }, +}; diff --git a/src/components/CardList/index.ts b/src/components/CardList/index.ts new file mode 100644 index 0000000..b977c1b --- /dev/null +++ b/src/components/CardList/index.ts @@ -0,0 +1,4 @@ +import { withInstall } from '/@/utils'; +import cardList from './src/CardList.vue'; + +export const CardList = withInstall(cardList); diff --git a/src/components/CardList/src/CardList.vue b/src/components/CardList/src/CardList.vue new file mode 100644 index 0000000..e2d369c --- /dev/null +++ b/src/components/CardList/src/CardList.vue @@ -0,0 +1,164 @@ + + diff --git a/src/components/CardList/src/data.ts b/src/components/CardList/src/data.ts new file mode 100644 index 0000000..ac56cad --- /dev/null +++ b/src/components/CardList/src/data.ts @@ -0,0 +1,25 @@ +import { ref } from 'vue'; +//每行个数 +export const grid = ref(12); +// slider属性 +export const useSlider = (min = 6, max = 12) => { + // 每行显示个数滑动条 + const getMarks = () => { + const l = {}; + for (let i = min; i < max + 1; i++) { + l[i] = { + style: { + color: '#fff', + }, + label: i, + }; + } + return l; + }; + return { + min, + max, + marks: getMarks(), + step: 1, + }; +}; diff --git a/src/components/ClickOutSide/index.ts b/src/components/ClickOutSide/index.ts new file mode 100644 index 0000000..5e7dd2d --- /dev/null +++ b/src/components/ClickOutSide/index.ts @@ -0,0 +1,4 @@ +import { withInstall } from '/@/utils'; +import clickOutSide from './src/ClickOutSide.vue'; + +export const ClickOutSide = withInstall(clickOutSide); diff --git a/src/components/ClickOutSide/src/ClickOutSide.vue b/src/components/ClickOutSide/src/ClickOutSide.vue new file mode 100644 index 0000000..c043cc1 --- /dev/null +++ b/src/components/ClickOutSide/src/ClickOutSide.vue @@ -0,0 +1,19 @@ + + diff --git a/src/components/CodeEditor/index.ts b/src/components/CodeEditor/index.ts new file mode 100644 index 0000000..3255431 --- /dev/null +++ b/src/components/CodeEditor/index.ts @@ -0,0 +1,4 @@ +import { withInstall } from '/@/utils'; +import codeEditor from './src/CodeEditor.vue'; + +export const CodeEditor = withInstall(codeEditor); diff --git a/src/components/CodeEditor/src/CodeEditor.vue b/src/components/CodeEditor/src/CodeEditor.vue new file mode 100644 index 0000000..28abd7b --- /dev/null +++ b/src/components/CodeEditor/src/CodeEditor.vue @@ -0,0 +1,49 @@ + + + + diff --git a/src/components/CodeEditor/src/codemirror/CodeMirror.vue b/src/components/CodeEditor/src/codemirror/CodeMirror.vue new file mode 100644 index 0000000..2d3bca1 --- /dev/null +++ b/src/components/CodeEditor/src/codemirror/CodeMirror.vue @@ -0,0 +1,102 @@ + + + diff --git a/src/components/CodeEditor/src/codemirror/codeMirror.ts b/src/components/CodeEditor/src/codemirror/codeMirror.ts new file mode 100644 index 0000000..e04f51b --- /dev/null +++ b/src/components/CodeEditor/src/codemirror/codeMirror.ts @@ -0,0 +1,21 @@ +import CodeMirror from 'codemirror'; +import './codemirror.css'; +import 'codemirror/theme/idea.css'; +import 'codemirror/theme/material-palenight.css'; +// import 'codemirror/addon/lint/lint.css'; + +// modes +import 'codemirror/mode/javascript/javascript'; +import 'codemirror/mode/css/css'; +import 'codemirror/mode/htmlmixed/htmlmixed'; +// addons +// import 'codemirror/addon/edit/closebrackets'; +// import 'codemirror/addon/edit/closetag'; +// import 'codemirror/addon/comment/comment'; +// import 'codemirror/addon/fold/foldcode'; +// import 'codemirror/addon/fold/foldgutter'; +// import 'codemirror/addon/fold/brace-fold'; +// import 'codemirror/addon/fold/indent-fold'; +// import 'codemirror/addon/lint/json-lint'; +// import 'codemirror/addon/fold/comment-fold'; +export { CodeMirror }; diff --git a/src/components/CodeEditor/src/codemirror/codemirror.css b/src/components/CodeEditor/src/codemirror/codemirror.css new file mode 100644 index 0000000..dc7c681 --- /dev/null +++ b/src/components/CodeEditor/src/codemirror/codemirror.css @@ -0,0 +1,539 @@ +/* BASICS */ + +.CodeMirror { + --base: #545281; + --comment: hsl(210, 25%, 60%); + --keyword: #af4ab1; + --variable: #0055d1; + --function: #c25205; + --string: #2ba46d; + --number: #c25205; + --tags: #d00; + --qualifier: #ff6032; + --important: var(--string); + + position: relative; + height: auto; + height: 100%; + overflow: hidden; + font-family: var(--font-code); + background: white; + direction: ltr; +} + +/* PADDING */ + +.CodeMirror-lines { + min-height: 1px; /* prevents collapsing before first draw */ + padding: 4px 0; /* Vertical padding around content */ + cursor: text; +} + +.CodeMirror-scrollbar-filler, +.CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + position: absolute; + top: 0; + left: 0; + z-index: 3; + min-height: 100%; + white-space: nowrap; + background-color: transparent; + border-right: 1px solid #ddd; +} + +.CodeMirror-linenumber { + min-width: 20px; + padding: 0 3px 0 5px; + color: var(--comment); + text-align: right; + white-space: nowrap; + opacity: 0.6; +} + +.CodeMirror-guttermarker { + color: black; +} + +.CodeMirror-guttermarker-subtle { + color: #999; +} + +/* FOLD GUTTER */ + +.CodeMirror-foldmarker { + font-family: arial; + line-height: 0.3; + color: #414141; + text-shadow: #f96 1px 1px 2px, #f96 -1px -1px 2px, #f96 1px -1px 2px, #f96 -1px 1px 2px; + cursor: pointer; +} + +.CodeMirror-foldgutter { + width: 0.7em; +} + +.CodeMirror-foldgutter-open, +.CodeMirror-foldgutter-folded { + cursor: pointer; +} + +.CodeMirror-foldgutter-open::after, +.CodeMirror-foldgutter-folded::after { + position: relative; + top: -0.1em; + display: inline-block; + font-size: 0.8em; + content: '>'; + opacity: 0.8; + transform: rotate(90deg); + transition: transform 0.2s; +} + +.CodeMirror-foldgutter-folded::after { + transform: none; +} + +/* CURSOR */ + +.CodeMirror-cursor { + position: absolute; + width: 0; + pointer-events: none; + border-right: none; + border-left: 1px solid black; +} + +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} + +.cm-fat-cursor .CodeMirror-cursor { + width: auto; + background: #7e7; + border: 0 !important; +} + +.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} + +.cm-fat-cursor-mark { + background-color: rgba(20, 255, 20, 0.5); + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; +} + +.cm-animate-fat-cursor { + width: auto; + background-color: #7e7; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; +} +@-moz-keyframes blink { + 50% { + background-color: transparent; + } +} +@-webkit-keyframes blink { + 50% { + background-color: transparent; + } +} +@keyframes blink { + 50% { + background-color: transparent; + } +} + +.cm-tab { + display: inline-block; + text-decoration: inherit; +} + +.CodeMirror-rulers { + position: absolute; + top: -50px; + right: 0; + bottom: -20px; + left: 0; + overflow: hidden; +} + +.CodeMirror-ruler { + position: absolute; + top: 0; + bottom: 0; + border-left: 1px solid #ccc; +} + +/* DEFAULT THEME */ +.cm-s-default.CodeMirror { + background-color: transparent; +} + +.cm-s-default .cm-header { + color: blue; +} + +.cm-s-default .cm-quote { + color: #090; +} + +.cm-negative { + color: #d44; +} + +.cm-positive { + color: #292; +} + +.cm-header, +.cm-strong { + font-weight: bold; +} + +.cm-em { + font-style: italic; +} + +.cm-link { + text-decoration: underline; +} + +.cm-strikethrough { + text-decoration: line-through; +} + +.cm-s-default .cm-atom, +.cm-s-default .cm-def, +.cm-s-default .cm-property, +.cm-s-default .cm-variable-2, +.cm-s-default .cm-variable-3, +.cm-s-default .cm-punctuation { + color: var(--base); +} + +.cm-s-default .cm-hr, +.cm-s-default .cm-comment { + color: var(--comment); +} + +.cm-s-default .cm-attribute, +.cm-s-default .cm-keyword { + color: var(--keyword); +} + +.cm-s-default .cm-variable { + color: var(--variable); +} + +.cm-s-default .cm-bracket, +.cm-s-default .cm-tag { + color: var(--tags); +} + +.cm-s-default .cm-number { + color: var(--number); +} + +.cm-s-default .cm-string, +.cm-s-default .cm-string-2 { + color: var(--string); +} + +.cm-s-default .cm-type { + color: #085; +} + +.cm-s-default .cm-meta { + color: #555; +} + +.cm-s-default .cm-qualifier { + color: var(--qualifier); +} + +.cm-s-default .cm-builtin { + color: #7539ff; +} + +.cm-s-default .cm-link { + color: var(--flash); +} + +.cm-s-default .cm-error { + color: #ff008c; +} + +.cm-invalidchar { + color: #ff008c; +} + +.CodeMirror-composing { + border-bottom: 2px solid; +} + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket { + color: #0b0; +} + +div.CodeMirror span.CodeMirror-nonmatchingbracket { + color: #a22; +} + +.CodeMirror-matchingtag { + background: rgba(255, 150, 0, 0.3); +} + +.CodeMirror-activeline-background { + background: #e8f2ff; +} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror-scroll { + position: relative; + height: 100%; + padding-bottom: 30px; + margin-right: -30px; + + /* 30px is the magic margin used to hide the element's real scrollbars */ + + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; + overflow: scroll !important; /* Things will break if this is overridden */ + outline: none; /* Prevent dragging from highlighting the element */ +} + +.CodeMirror-sizer { + position: relative; + margin-bottom: 20px !important; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actual scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, +.CodeMirror-hscrollbar, +.CodeMirror-scrollbar-filler, +.CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} + +.CodeMirror-vscrollbar { + top: 0; + right: 0; + overflow-x: hidden; + overflow-y: scroll; +} + +.CodeMirror-hscrollbar { + bottom: 0; + left: 0; + overflow-x: scroll; + overflow-y: hidden; +} + +.CodeMirror-scrollbar-filler { + right: 0; + bottom: 0; +} + +.CodeMirror-gutter-filler { + bottom: 0; + left: 0; +} + +.CodeMirror-gutter { + display: inline-block; + height: 100%; + margin-bottom: -30px; + white-space: normal; + vertical-align: top; +} + +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + background: none !important; + border: none !important; +} + +.CodeMirror-gutter-background { + position: absolute; + top: 0; + bottom: 0; + z-index: 4; +} + +.CodeMirror-gutter-elt { + position: absolute; + z-index: 4; + cursor: default; +} + +.CodeMirror-gutter-wrapper ::selection { + background-color: transparent; +} + +.CodeMirror-gutter-wrapper ::-moz-selection { + background-color: transparent; +} + +.CodeMirror pre { + position: relative; + z-index: 2; + padding: 0 4px; /* Horizontal padding of content */ + margin: 0; + overflow: visible; + font-family: inherit; + font-size: inherit; + line-height: inherit; + color: inherit; + word-wrap: normal; + white-space: pre; + background: transparent; + border-width: 0; + + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + -webkit-tap-highlight-color: transparent; + -webkit-font-variant-ligatures: contextual; + font-variant-ligatures: contextual; +} + +.CodeMirror-wrap pre { + word-break: normal; + word-wrap: break-word; + white-space: pre-wrap; +} + +.CodeMirror-linebackground { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + padding: 0.1px; /* Force widget margins to stay inside of the container */ +} + +.CodeMirror-rtl pre { + direction: rtl; +} + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.CodeMirror-measure pre { + position: static; +} + +div.CodeMirror-cursors { + position: relative; + z-index: 3; + visibility: hidden; +} + +div.CodeMirror-dragcursors { + visibility: visible; +} + +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { + background: #d9d9d9; +} + +.CodeMirror-focused .CodeMirror-selected { + background: #d7d4f0; +} + +.CodeMirror-crosshair { + cursor: crosshair; +} + +.CodeMirror-line::selection, +.CodeMirror-line > span::selection, +.CodeMirror-line > span > span::selection { + background: #d7d4f0; +} + +.CodeMirror-line::-moz-selection, +.CodeMirror-line > span::-moz-selection, +.CodeMirror-line > span > span::-moz-selection { + background: #d7d4f0; +} + +.cm-searching { + background-color: #ffa; + background-color: rgba(255, 255, 0, 0.4); +} + +/* Used to force a border model for a node */ +.cm-force-border { + padding-right: 0.1px; +} + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack::after { + content: ''; +} + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { + background: none; +} diff --git a/src/components/CodeEditor/src/typing.ts b/src/components/CodeEditor/src/typing.ts new file mode 100644 index 0000000..34b5ed1 --- /dev/null +++ b/src/components/CodeEditor/src/typing.ts @@ -0,0 +1,5 @@ +export enum MODE { + JSON = 'application/json', + HTML = 'htmlmixed', + JS = 'javascript', +} diff --git a/src/components/Container/index.ts b/src/components/Container/index.ts new file mode 100644 index 0000000..e1230a0 --- /dev/null +++ b/src/components/Container/index.ts @@ -0,0 +1,10 @@ +import { withInstall } from '/@/utils'; +import collapseContainer from './src/collapse/CollapseContainer.vue'; +import scrollContainer from './src/ScrollContainer.vue'; +import lazyContainer from './src/LazyContainer.vue'; + +export const CollapseContainer = withInstall(collapseContainer); +export const ScrollContainer = withInstall(scrollContainer); +export const LazyContainer = withInstall(lazyContainer); + +export * from './src/typing'; diff --git a/src/components/Container/src/LazyContainer.vue b/src/components/Container/src/LazyContainer.vue new file mode 100644 index 0000000..4e26242 --- /dev/null +++ b/src/components/Container/src/LazyContainer.vue @@ -0,0 +1,138 @@ + + diff --git a/src/components/Container/src/ScrollContainer.vue b/src/components/Container/src/ScrollContainer.vue new file mode 100644 index 0000000..65c71ed --- /dev/null +++ b/src/components/Container/src/ScrollContainer.vue @@ -0,0 +1,93 @@ + + + + diff --git a/src/components/Container/src/collapse/CollapseContainer.vue b/src/components/Container/src/collapse/CollapseContainer.vue new file mode 100644 index 0000000..8e22046 --- /dev/null +++ b/src/components/Container/src/collapse/CollapseContainer.vue @@ -0,0 +1,106 @@ + + + diff --git a/src/components/Container/src/collapse/CollapseHeader.vue b/src/components/Container/src/collapse/CollapseHeader.vue new file mode 100644 index 0000000..4196c0a --- /dev/null +++ b/src/components/Container/src/collapse/CollapseHeader.vue @@ -0,0 +1,38 @@ + + diff --git a/src/components/Container/src/typing.ts b/src/components/Container/src/typing.ts new file mode 100644 index 0000000..86c03be --- /dev/null +++ b/src/components/Container/src/typing.ts @@ -0,0 +1,17 @@ +export type ScrollType = 'default' | 'main'; + +export interface CollapseContainerOptions { + canExpand?: boolean; + title?: string; + helpMessage?: Array | string; +} +export interface ScrollContainerOptions { + enableScroll?: boolean; + type?: ScrollType; +} + +export type ScrollActionType = RefType<{ + scrollBottom: () => void; + getScrollWrap: () => Nullable; + scrollTo: (top: number) => void; +}>; diff --git a/src/components/ContextMenu/index.ts b/src/components/ContextMenu/index.ts new file mode 100644 index 0000000..ed294d7 --- /dev/null +++ b/src/components/ContextMenu/index.ts @@ -0,0 +1,3 @@ +export { createContextMenu, destroyContextMenu } from './src/createContextMenu'; + +export * from './src/typing'; diff --git a/src/components/ContextMenu/src/ContextMenu.vue b/src/components/ContextMenu/src/ContextMenu.vue new file mode 100644 index 0000000..57dc14f --- /dev/null +++ b/src/components/ContextMenu/src/ContextMenu.vue @@ -0,0 +1,197 @@ + + diff --git a/src/components/ContextMenu/src/createContextMenu.ts b/src/components/ContextMenu/src/createContextMenu.ts new file mode 100644 index 0000000..8f7a1c8 --- /dev/null +++ b/src/components/ContextMenu/src/createContextMenu.ts @@ -0,0 +1,75 @@ +import contextMenuVue from './ContextMenu.vue'; +import { isClient } from '/@/utils/is'; +import { CreateContextOptions, ContextMenuProps } from './typing'; +import { createVNode, render } from 'vue'; + +const menuManager: { + domList: Element[]; + resolve: Fn; +} = { + domList: [], + resolve: () => {}, +}; + +export const createContextMenu = function (options: CreateContextOptions) { + const { event } = options || {}; + + event && event?.preventDefault(); + + if (!isClient) { + return; + } + return new Promise((resolve) => { + const body = document.body; + + const container = document.createElement('div'); + const propsData: Partial = {}; + if (options.styles) { + propsData.styles = options.styles; + } + + if (options.items) { + propsData.items = options.items; + } + + if (options.event) { + propsData.customEvent = event; + propsData.axis = { x: event.clientX, y: event.clientY }; + } + + const vm = createVNode(contextMenuVue, propsData); + render(vm, container); + + const handleClick = function () { + menuManager.resolve(''); + }; + + menuManager.domList.push(container); + + const remove = function () { + menuManager.domList.forEach((dom: Element) => { + try { + dom && body.removeChild(dom); + } catch (error) {} + }); + body.removeEventListener('click', handleClick); + body.removeEventListener('scroll', handleClick); + }; + + menuManager.resolve = function (arg) { + remove(); + resolve(arg); + }; + remove(); + body.appendChild(container); + body.addEventListener('click', handleClick); + body.addEventListener('scroll', handleClick); + }); +}; + +export const destroyContextMenu = function () { + if (menuManager) { + menuManager.resolve(''); + menuManager.domList = []; + } +}; diff --git a/src/components/ContextMenu/src/typing.ts b/src/components/ContextMenu/src/typing.ts new file mode 100644 index 0000000..899d36b --- /dev/null +++ b/src/components/ContextMenu/src/typing.ts @@ -0,0 +1,35 @@ +export interface Axis { + x: number; + y: number; +} + +export interface ContextMenuItem { + label: string; + icon?: string; + disabled?: boolean; + handler?: Fn; + divider?: boolean; + children?: ContextMenuItem[]; +} +export interface CreateContextOptions { + event: MouseEvent; + icon?: string; + styles?: any; + items?: ContextMenuItem[]; +} + +export interface ContextMenuProps { + event?: MouseEvent; + styles?: any; + items: ContextMenuItem[]; + customEvent?: MouseEvent; + axis?: Axis; + width?: number; + showIcon?: boolean; +} + +export interface ItemContentProps { + showIcon: boolean | undefined; + item: ContextMenuItem; + handler: Fn; +} diff --git a/src/components/CountDown/index.ts b/src/components/CountDown/index.ts new file mode 100644 index 0000000..9809416 --- /dev/null +++ b/src/components/CountDown/index.ts @@ -0,0 +1,6 @@ +import { withInstall } from '/@/utils'; +import countButton from './src/CountButton.vue'; +import countdownInput from './src/CountdownInput.vue'; + +export const CountdownInput = withInstall(countdownInput); +export const CountButton = withInstall(countButton); diff --git a/src/components/CountDown/src/CountButton.vue b/src/components/CountDown/src/CountButton.vue new file mode 100644 index 0000000..b842bb5 --- /dev/null +++ b/src/components/CountDown/src/CountButton.vue @@ -0,0 +1,72 @@ + + diff --git a/src/components/CountDown/src/CountdownInput.vue b/src/components/CountDown/src/CountdownInput.vue new file mode 100644 index 0000000..6a49f77 --- /dev/null +++ b/src/components/CountDown/src/CountdownInput.vue @@ -0,0 +1,54 @@ + + + diff --git a/src/components/CountDown/src/useCountdown.ts b/src/components/CountDown/src/useCountdown.ts new file mode 100644 index 0000000..316d69a --- /dev/null +++ b/src/components/CountDown/src/useCountdown.ts @@ -0,0 +1,51 @@ +import { ref, unref } from 'vue'; +import { tryOnUnmounted } from '@vueuse/core'; + +export function useCountdown(count: number) { + const currentCount = ref(count); + + const isStart = ref(false); + + let timerId: ReturnType | null; + + function clear() { + timerId && window.clearInterval(timerId); + } + + function stop() { + isStart.value = false; + clear(); + timerId = null; + } + + function start() { + if (unref(isStart) || !!timerId) { + return; + } + isStart.value = true; + timerId = setInterval(() => { + if (unref(currentCount) === 1) { + stop(); + currentCount.value = count; + } else { + currentCount.value -= 1; + } + }, 1000); + } + + function reset() { + currentCount.value = count; + stop(); + } + + function restart() { + reset(); + start(); + } + + tryOnUnmounted(() => { + reset(); + }); + + return { start, reset, restart, clear, stop, currentCount, isStart }; +} diff --git a/src/components/CountTo/index.ts b/src/components/CountTo/index.ts new file mode 100644 index 0000000..36a4e65 --- /dev/null +++ b/src/components/CountTo/index.ts @@ -0,0 +1,4 @@ +import { withInstall } from '/@/utils'; +import countTo from './src/CountTo.vue'; + +export const CountTo = withInstall(countTo); diff --git a/src/components/CountTo/src/CountTo.vue b/src/components/CountTo/src/CountTo.vue new file mode 100644 index 0000000..7de3361 --- /dev/null +++ b/src/components/CountTo/src/CountTo.vue @@ -0,0 +1,110 @@ + + diff --git a/src/components/Cropper/index.ts b/src/components/Cropper/index.ts new file mode 100644 index 0000000..88d6d1d --- /dev/null +++ b/src/components/Cropper/index.ts @@ -0,0 +1,7 @@ +import { withInstall } from '/@/utils'; +import cropperImage from './src/Cropper.vue'; +import avatarCropper from './src/CropperAvatar.vue'; + +export * from './src/typing'; +export const CropperImage = withInstall(cropperImage); +export const CropperAvatar = withInstall(avatarCropper); diff --git a/src/components/Cropper/src/CopperModal.vue b/src/components/Cropper/src/CopperModal.vue new file mode 100644 index 0000000..33a6fef --- /dev/null +++ b/src/components/Cropper/src/CopperModal.vue @@ -0,0 +1,237 @@ + + + + diff --git a/src/components/Cropper/src/Cropper.vue b/src/components/Cropper/src/Cropper.vue new file mode 100644 index 0000000..99176d7 --- /dev/null +++ b/src/components/Cropper/src/Cropper.vue @@ -0,0 +1,181 @@ + + + diff --git a/src/components/Cropper/src/CropperAvatar.vue b/src/components/Cropper/src/CropperAvatar.vue new file mode 100644 index 0000000..e95329a --- /dev/null +++ b/src/components/Cropper/src/CropperAvatar.vue @@ -0,0 +1,136 @@ + + + + diff --git a/src/components/Cropper/src/typing.ts b/src/components/Cropper/src/typing.ts new file mode 100644 index 0000000..e76cc6f --- /dev/null +++ b/src/components/Cropper/src/typing.ts @@ -0,0 +1,8 @@ +import type Cropper from 'cropperjs'; + +export interface CropendResult { + imgBase64: string; + imgInfo: Cropper.Data; +} + +export type { Cropper }; diff --git a/src/components/Description/index.ts b/src/components/Description/index.ts new file mode 100644 index 0000000..58277d0 --- /dev/null +++ b/src/components/Description/index.ts @@ -0,0 +1,6 @@ +import { withInstall } from '/@/utils'; +import description from './src/Description.vue'; + +export * from './src/typing'; +export { useDescription } from './src/useDescription'; +export const Description = withInstall(description); diff --git a/src/components/Description/src/Description.vue b/src/components/Description/src/Description.vue new file mode 100644 index 0000000..17b8c74 --- /dev/null +++ b/src/components/Description/src/Description.vue @@ -0,0 +1,181 @@ + diff --git a/src/components/Description/src/typing.ts b/src/components/Description/src/typing.ts new file mode 100644 index 0000000..897b7d2 --- /dev/null +++ b/src/components/Description/src/typing.ts @@ -0,0 +1,47 @@ +import type { VNode, CSSProperties } from 'vue'; +import type { CollapseContainerOptions } from '/@/components/Container/index'; +import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index'; + +export interface DescItem { + labelMinWidth?: number; + contentMinWidth?: number; + labelStyle?: CSSProperties; + field: string; + label: string | VNode | JSX.Element; + // Merge column + span?: number; + show?: (...arg: any) => boolean; + // render + render?: (val: any, data: Recordable) => VNode | undefined | JSX.Element | Element | string | number; +} + +export interface DescriptionProps extends DescriptionsProps { + // Whether to include the collapse component + useCollapse?: boolean; + /** + * item configuration + * @type DescItem + */ + schema: DescItem[]; + /** + * 数据 + * @type object + */ + data: Recordable; + /** + * Built-in CollapseContainer component configuration + * @type CollapseContainerOptions + */ + collapseOptions?: CollapseContainerOptions; +} + +export interface DescInstance { + setDescProps(descProps: Partial): void; +} + +export type Register = (descInstance: DescInstance) => void; + +/** + * @description: + */ +export type UseDescReturnType = [Register, DescInstance]; diff --git a/src/components/Description/src/useDescription.ts b/src/components/Description/src/useDescription.ts new file mode 100644 index 0000000..d1b3241 --- /dev/null +++ b/src/components/Description/src/useDescription.ts @@ -0,0 +1,33 @@ +import type { DescriptionProps, DescInstance, UseDescReturnType } from './typing'; +import { ref, getCurrentInstance, unref, onUnmounted } from 'vue'; +import { isProdMode } from '/@/utils/env'; + +export function useDescription(props?: Partial): UseDescReturnType { + if (!getCurrentInstance()) { + throw new Error('useDescription() can only be used inside setup() or functional components!'); + } + const desc = ref>(null); + const loaded = ref(false); + + function register(instance: DescInstance) { + // update-begin--author:liaozhiyang---date:20251223---for:【pull/9125】在抽屉中配置destroy-on-close,再次打开未正确渲染 + isProdMode() && + onUnmounted(() => { + desc.value = null; + loaded.value = false; + }); + if (unref(loaded) && isProdMode() && instance === unref(desc)) return; + // update-end--author:liaozhiyang---date:20251223---for:【pull/9125】在抽屉中配置destroy-on-close,再次打开未正确渲染 + desc.value = instance; + props && instance.setDescProps(props); + loaded.value = true; + } + + const methods: DescInstance = { + setDescProps: (descProps: Partial): void => { + unref(desc)?.setDescProps(descProps); + }, + }; + + return [register, methods]; +} diff --git a/src/components/Drawer/index.ts b/src/components/Drawer/index.ts new file mode 100644 index 0000000..820ade5 --- /dev/null +++ b/src/components/Drawer/index.ts @@ -0,0 +1,6 @@ +import { withInstall } from '/@/utils'; +import basicDrawer from './src/BasicDrawer.vue'; + +export const BasicDrawer = withInstall(basicDrawer); +export * from './src/typing'; +export { useDrawer, useDrawerInner } from './src/useDrawer'; diff --git a/src/components/Drawer/src/BasicDrawer.vue b/src/components/Drawer/src/BasicDrawer.vue new file mode 100644 index 0000000..eff27b2 --- /dev/null +++ b/src/components/Drawer/src/BasicDrawer.vue @@ -0,0 +1,252 @@ + + + diff --git a/src/components/Drawer/src/components/DrawerFooter.vue b/src/components/Drawer/src/components/DrawerFooter.vue new file mode 100644 index 0000000..9e6d322 --- /dev/null +++ b/src/components/Drawer/src/components/DrawerFooter.vue @@ -0,0 +1,75 @@ + + + + diff --git a/src/components/Drawer/src/components/DrawerHeader.vue b/src/components/Drawer/src/components/DrawerHeader.vue new file mode 100644 index 0000000..5eaa44f --- /dev/null +++ b/src/components/Drawer/src/components/DrawerHeader.vue @@ -0,0 +1,74 @@ + + + + diff --git a/src/components/Drawer/src/props.ts b/src/components/Drawer/src/props.ts new file mode 100644 index 0000000..398f251 --- /dev/null +++ b/src/components/Drawer/src/props.ts @@ -0,0 +1,47 @@ +import type { PropType } from 'vue'; + +import { useI18n } from '/@/hooks/web/useI18n'; +const { t } = useI18n(); + +export const footerProps = { + confirmLoading: { type: Boolean }, + /** + * @description: Show close button + */ + showCancelBtn: { type: Boolean, default: true }, + cancelButtonProps: Object as PropType, + cancelText: { type: String, default: t('common.cancelText') }, + /** + * @description: Show confirmation button + */ + showOkBtn: { type: Boolean, default: true }, + okButtonProps: Object as PropType, + okText: { type: String, default: t('common.okText') }, + okType: { type: String, default: 'primary' }, + showFooter: { type: Boolean }, + footerHeight: { + type: [String, Number] as PropType, + default: 60, + }, +}; +export const basicProps = { + class: {type: [String, Object, Array]}, + isDetail: { type: Boolean }, + title: { type: String, default: '' }, + loadingText: { type: String }, + showDetailBack: { type: Boolean, default: true }, + visible: { type: Boolean }, + open: { type: Boolean }, + loading: { type: Boolean }, + maskClosable: { type: Boolean, default: true }, + getContainer: { + type: [Object, String, Function, Boolean] as PropType, + default: () => 'body', + }, + closeFunc: { + type: [Function, Object] as PropType, + default: null, + }, + destroyOnClose: { type: Boolean }, + ...footerProps, +}; diff --git a/src/components/Drawer/src/typing.ts b/src/components/Drawer/src/typing.ts new file mode 100644 index 0000000..b129073 --- /dev/null +++ b/src/components/Drawer/src/typing.ts @@ -0,0 +1,199 @@ +import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes'; +import type { CSSProperties, VNodeChild, ComputedRef } from 'vue'; +import type { ScrollContainerOptions } from '/@/components/Container/index'; + +export interface DrawerInstance { + setDrawerProps: (props: Partial | boolean) => void; + emitVisible?: (visible: boolean, uid: number) => void; +} + +export interface ReturnMethods extends DrawerInstance { + openDrawer: (visible?: boolean, data?: T, openOnSet?: boolean) => void; + closeDrawer: () => void; + getVisible?: ComputedRef; + getOpen?: ComputedRef; +} + +export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void; + +export interface ReturnInnerMethods extends DrawerInstance { + closeDrawer: () => void; + changeLoading: (loading: boolean) => void; + changeOkLoading: (loading: boolean) => void; + getVisible?: ComputedRef; + getOpen?: ComputedRef; +} + +export type UseDrawerReturnType = [RegisterFn, ReturnMethods]; + +export type UseDrawerInnerReturnType = [RegisterFn, ReturnInnerMethods]; + +export interface DrawerFooterProps { + showOkBtn: boolean; + showCancelBtn: boolean; + /** + * Text of the Cancel button + * @default 'cancel' + * @type string + */ + cancelText: string; + /** + * Text of the OK button + * @default 'OK' + * @type string + */ + okText: string; + + /** + * Button type of the OK button + * @default 'primary' + * @type string + */ + okType: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default'; + /** + * The ok button props, follow jsx rules + * @type object + */ + okButtonProps: { props: ButtonProps; on: {} }; + + /** + * The cancel button props, follow jsx rules + * @type object + */ + cancelButtonProps: { props: ButtonProps; on: {} }; + /** + * Whether to apply loading visual effect for OK button or not + * @default false + * @type boolean + */ + confirmLoading: boolean; + + showFooter: boolean; + footerHeight: string | number; +} +export interface DrawerProps extends DrawerFooterProps { + isDetail?: boolean; + loading?: boolean; + showDetailBack?: boolean; + visible?: boolean; + open?: boolean; + /** + * Built-in ScrollContainer component configuration + * @type ScrollContainerOptions + */ + scrollOptions?: ScrollContainerOptions; + closeFunc?: () => Promise; + triggerWindowResize?: boolean; + /** + * Whether a close (x) button is visible on top right of the Drawer dialog or not. + * @default true + * @type boolean + */ + closable?: boolean; + + /** + * Whether to unmount child components on closing drawer or not. + * @default false + * @type boolean + */ + destroyOnClose?: boolean; + + /** + * Return the mounted node for Drawer. + * @default 'body' + * @type any ( HTMLElement| () => HTMLElement | string) + */ + getContainer?: () => HTMLElement | string; + + /** + * Whether to show mask or not. + * @default true + * @type boolean + */ + mask?: boolean; + + /** + * Clicking on the mask (area outside the Drawer) to close the Drawer or not. + * @default true + * @type boolean + */ + maskClosable?: boolean; + + /** + * Style for Drawer's mask element. + * @default {} + * @type object + */ + maskStyle?: CSSProperties; + + /** + * The title for Drawer. + * @type any (string | slot) + */ + title?: VNodeChild | JSX.Element; + + /** + * The class name of the container of the Drawer dialog. + * @type string + */ + class?: string; + // 兼容老版本的写法(后续可能会删除,优先写class) + wrapClassName?: string; + + /** + * Style of wrapper element which **contains mask** compare to `drawerStyle` + * @type object + */ + wrapStyle?: CSSProperties; + + /** + * Style of the popup layer element + * @type object + */ + drawerStyle?: CSSProperties; + + /** + * Style of floating layer, typically used for adjusting its position. + * @type object + */ + bodyStyle?: CSSProperties; + headerStyle?: CSSProperties; + + /** + * Width of the Drawer dialog. + * @default 256 + * @type string | number + */ + width?: string | number; + + /** + * placement is top or bottom, height of the Drawer dialog. + * @type string | number + */ + height?: string | number; + + /** + * The z-index of the Drawer. + * @default 1000 + * @type number + */ + zIndex?: number; + + /** + * The placement of the Drawer. + * @default 'right' + * @type string + */ + placement?: 'top' | 'right' | 'bottom' | 'left'; + afterVisibleChange?: (visible?: boolean) => void; + keyboard?: boolean; + /** + * Specify a callback that will be called when a user clicks mask, close button or Cancel button. + */ + onClose?: (e?: Event) => void; +} +export interface DrawerActionType { + scrollBottom: () => void; + scrollTo: (to: number) => void; + getScrollWrap: () => Element | null; +} diff --git a/src/components/Drawer/src/useDrawer.ts b/src/components/Drawer/src/useDrawer.ts new file mode 100644 index 0000000..2b86352 --- /dev/null +++ b/src/components/Drawer/src/useDrawer.ts @@ -0,0 +1,154 @@ +import type { UseDrawerReturnType, DrawerInstance, ReturnMethods, DrawerProps, UseDrawerInnerReturnType } from './typing'; +import { ref, getCurrentInstance, unref, reactive, watchEffect, nextTick, toRaw, computed } from 'vue'; +import { isProdMode } from '/@/utils/env'; +import { isFunction } from '/@/utils/is'; +import { tryOnUnmounted } from '@vueuse/core'; +import { isEqual } from 'lodash-es'; +import { error } from '/@/utils/log'; + +const dataTransferRef = reactive({}); + +const visibleData = reactive<{ [key: number]: boolean }>({}); + +/** + * @description: Applicable to separate drawer and call outside + */ +export function useDrawer(): UseDrawerReturnType { + if (!getCurrentInstance()) { + throw new Error('useDrawer() can only be used inside setup() or functional components!'); + } + const drawer = ref(null); + const loaded = ref>(false); + const uid = ref(''); + + function register(drawerInstance: DrawerInstance, uuid: string) { + isProdMode() && + tryOnUnmounted(() => { + drawer.value = null; + loaded.value = null; + dataTransferRef[unref(uid)] = null; + }); + + if (unref(loaded) && isProdMode() && drawerInstance === unref(drawer)) { + return; + } + uid.value = uuid; + drawer.value = drawerInstance; + loaded.value = true; + + drawerInstance.emitVisible = (visible: boolean, uid: number) => { + visibleData[uid] = visible; + }; + } + + const getInstance = () => { + const instance = unref(drawer); + if (!instance) { + error('useDrawer instance is undefined!'); + } + return instance; + }; + + const methods: ReturnMethods = { + setDrawerProps: (props: Partial): void => { + getInstance()?.setDrawerProps(props); + }, + + getVisible: computed((): boolean => { + return visibleData[~~unref(uid)]; + }), + + getOpen: computed((): boolean => { + return visibleData[~~unref(uid)]; + }), + + openDrawer: (visible = true, data?: T, openOnSet = true): void => { + // 代码逻辑说明: 【QQYUN-6366】升级到antd4.x + getInstance()?.setDrawerProps({ + open: visible, + }); + if (!data) return; + + if (openOnSet) { + dataTransferRef[unref(uid)] = null; + dataTransferRef[unref(uid)] = toRaw(data); + return; + } + const equal = isEqual(toRaw(dataTransferRef[unref(uid)]), toRaw(data)); + if (!equal) { + dataTransferRef[unref(uid)] = toRaw(data); + } + }, + closeDrawer: () => { + // 代码逻辑说明: 【QQYUN-6366】升级到antd4.x + getInstance()?.setDrawerProps({ open: false }); + }, + }; + + return [register, methods]; +} + +export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { + const drawerInstanceRef = ref>(null); + const currentInstance = getCurrentInstance(); + const uidRef = ref(''); + + if (!getCurrentInstance()) { + throw new Error('useDrawerInner() can only be used inside setup() or functional components!'); + } + + const getInstance = () => { + const instance = unref(drawerInstanceRef); + if (!instance) { + error('useDrawerInner instance is undefined!'); + return; + } + return instance; + }; + + const register = (modalInstance: DrawerInstance, uuid: string) => { + isProdMode() && + tryOnUnmounted(() => { + drawerInstanceRef.value = null; + }); + + uidRef.value = uuid; + drawerInstanceRef.value = modalInstance; + currentInstance?.emit('register', modalInstance, uuid); + }; + + watchEffect(() => { + const data = dataTransferRef[unref(uidRef)]; + if (!data) return; + if (!callbackFn || !isFunction(callbackFn)) return; + nextTick(() => { + callbackFn(data); + }); + }); + + return [ + register, + { + changeLoading: (loading = true) => { + getInstance()?.setDrawerProps({ loading }); + }, + + changeOkLoading: (loading = true) => { + getInstance()?.setDrawerProps({ confirmLoading: loading }); + }, + getVisible: computed((): boolean => { + return visibleData[~~unref(uidRef)]; + }), + getOpen: computed((): boolean => { + return visibleData[~~unref(uidRef)]; + }), + closeDrawer: () => { + getInstance()?.setDrawerProps({ open: false }); + }, + + setDrawerProps: (props: Partial) => { + getInstance()?.setDrawerProps(props); + }, + }, + ]; +}; diff --git a/src/components/Dropdown/index.ts b/src/components/Dropdown/index.ts new file mode 100644 index 0000000..80439e5 --- /dev/null +++ b/src/components/Dropdown/index.ts @@ -0,0 +1,5 @@ +import { withInstall } from '/@/utils'; +import dropdown from './src/Dropdown.vue'; + +export * from './src/typing'; +export const Dropdown = withInstall(dropdown); diff --git a/src/components/Dropdown/src/Dropdown.vue b/src/components/Dropdown/src/Dropdown.vue new file mode 100644 index 0000000..27f0910 --- /dev/null +++ b/src/components/Dropdown/src/Dropdown.vue @@ -0,0 +1,117 @@ + + + + + diff --git a/src/components/Dropdown/src/typing.ts b/src/components/Dropdown/src/typing.ts new file mode 100644 index 0000000..94a8f8a --- /dev/null +++ b/src/components/Dropdown/src/typing.ts @@ -0,0 +1,11 @@ +export interface DropMenu { + onClick?: Fn; + to?: string; + icon?: string; + event: string | number; + text: string; + disabled?: boolean; + // 是否隐藏 + hide?: boolean; + divider?: boolean; +} diff --git a/src/components/Form/index.ts b/src/components/Form/index.ts new file mode 100644 index 0000000..486016d --- /dev/null +++ b/src/components/Form/index.ts @@ -0,0 +1,43 @@ +import BasicForm from './src/BasicForm.vue'; +import { defineAsyncComponent } from 'vue'; +export * from './src/types/form'; +export * from './src/types/formItem'; + +export { useComponentRegister } from './src/hooks/useComponentRegister'; +export { useForm } from './src/hooks/useForm'; + +export const ApiSelect = defineAsyncComponent(() => import('./src/components/ApiSelect.vue')); +export const RadioButtonGroup = defineAsyncComponent(() => import('./src/components/RadioButtonGroup.vue')); +export const ApiTreeSelect = defineAsyncComponent(() => import('./src/components/ApiTreeSelect.vue')); +export const ApiRadioGroup = defineAsyncComponent(() => import('./src/components/ApiRadioGroup.vue')); +//Jeecg自定义组件 +export const JAreaLinkage = defineAsyncComponent(() => import('./src/jeecg/components/JAreaLinkage.vue')); +export const JSelectUser = defineAsyncComponent(() => import('./src/jeecg/components/JSelectUser.vue')); +export const JSelectDept = defineAsyncComponent(() => import('./src/jeecg/components/JSelectDept.vue')); +export const JSelectDepartPost = defineAsyncComponent(() => import('./src/jeecg/components/JSelectDepartPost.vue')); +export const JSelectUserByDeptPost = defineAsyncComponent(() => import('./src/jeecg/components/JSelectUserByDeptPost.vue')); +export const JCodeEditor = defineAsyncComponent(() => import('./src/jeecg/components/JCodeEditor.vue')); +export const JCategorySelect = defineAsyncComponent(() => import('./src/jeecg/components/JCategorySelect.vue')); +export const JSelectMultiple = defineAsyncComponent(() => import('./src/jeecg/components/JSelectMultiple.vue')); +export const JPopup = defineAsyncComponent(() => import('./src/jeecg/components/JPopup.vue')); +export const JAreaSelect = defineAsyncComponent(() => import('./src/jeecg/components/JAreaSelect.vue')); +export const JEasyCron = defineAsyncComponent(() => import('./src/jeecg/components/JEasyCron/EasyCronInput.vue')); +export const JEasyCronInner = defineAsyncComponent(() => import('./src/jeecg/components/JEasyCron/EasyCronInner.vue')); +export const JEasyCronModal = defineAsyncComponent(() => import('./src/jeecg/components/JEasyCron/EasyCronModal.vue')); +export const JCheckbox = defineAsyncComponent(() => import('./src/jeecg/components/JCheckbox.vue')); +export const JInput = defineAsyncComponent(() => import('./src/jeecg/components/JInput.vue')); +export const JEllipsis = defineAsyncComponent(() => import('./src/jeecg/components/JEllipsis.vue')); +export const JDictSelectTag = defineAsyncComponent(() => import('./src/jeecg/components/JDictSelectTag.vue')); +export const JTreeSelect = defineAsyncComponent(() => import('./src/jeecg/components/JTreeSelect.vue')); +export const JSearchSelect = defineAsyncComponent(() => import('./src/jeecg/components/JSearchSelect.vue')); +export const JSelectUserByDept = defineAsyncComponent(() => import('./src/jeecg/components/JSelectUserByDept.vue')); +export const JSelectUserByDepartment = defineAsyncComponent(() => import('./src/jeecg/components/JSelectUserByDepartment.vue')); +// update-begin--author:liaozhiyang---date:20260227---for:【QQYUN-14751】tinymce富文本、JEasyCron、JLinkTableCard异步加载 +export const JEditor = defineAsyncComponent(() => import('./src/jeecg/components/JEditor.vue')); +// update-end--author:liaozhiyang---date:20260227---for:【QQYUN-14751】tinymce富文本、JEasyCron、JLinkTableCard异步加载 +export const JImageUpload = defineAsyncComponent(() => import('./src/jeecg/components/JImageUpload.vue')); +// Jeecg自定义校验 +// update-begin--author:liaozhiyang---date:20260303---for:【QQYUN-14815】JCronValidator从Form中注释,防止首页加载,改为业务中直接导入 +// export { default as JCronValidator } from '/@/components/Form/src/jeecg/components/JEasyCron/validator'; +// update-end--author:liaozhiyang---date:20260303---for:【QQYUN-14815】JCronValidator从Form中注释,防止首页加载,改为业务中直接导入 +export { BasicForm }; diff --git a/src/components/Form/src/BasicForm.vue b/src/components/Form/src/BasicForm.vue new file mode 100644 index 0000000..3b9f44e --- /dev/null +++ b/src/components/Form/src/BasicForm.vue @@ -0,0 +1,455 @@ + + + diff --git a/src/components/Form/src/componentMap.ts b/src/components/Form/src/componentMap.ts new file mode 100644 index 0000000..e579762 --- /dev/null +++ b/src/components/Form/src/componentMap.ts @@ -0,0 +1,106 @@ +/** + * 目前实现了异步加载的组件清单(antd 组件已改为 createAsyncComponent 异步加载) + */ +import type { Component } from 'vue'; +import type { ComponentType } from './types/index'; +import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; +/** + * Component list, register here to setting it in the form + */ + +const componentMap = new Map(); + +componentMap.set('Time', createAsyncComponent(() => import('/@/components/Time/src/Time.vue'))); +componentMap.set('Input', createAsyncComponent(() => import('ant-design-vue/es/input'))); +componentMap.set('InputGroup', createAsyncComponent(() => import('ant-design-vue/es/input/Group'))); +componentMap.set('InputPassword', createAsyncComponent(() => import('ant-design-vue/es/input/Password'))); +componentMap.set('InputSearch', createAsyncComponent(() => import('ant-design-vue/es/input/Search'))); +componentMap.set('InputTextArea', createAsyncComponent(() => import('ant-design-vue/es/input/TextArea'))); +componentMap.set('InputNumber', createAsyncComponent(() => import('ant-design-vue/es/input-number'))); +componentMap.set('AutoComplete', createAsyncComponent(() => import('ant-design-vue/es/auto-complete'))); + +componentMap.set('Select', createAsyncComponent(() => import('ant-design-vue/es/select'))); +componentMap.set('ApiSelect', createAsyncComponent(() => import('./components/ApiSelect.vue'))); +componentMap.set('JTabsSelectUser', createAsyncComponent(() => import('@/components/jeecg/JTabsSelectUser/index.vue'))); +componentMap.set('TreeSelect', createAsyncComponent(() => import('ant-design-vue/es/tree-select'))); +componentMap.set('ApiTreeSelect', createAsyncComponent(() => import('./components/ApiTreeSelect.vue'))); +componentMap.set('ApiRadioGroup', createAsyncComponent(() => import('./components/ApiRadioGroup.vue'))); +componentMap.set('Switch', createAsyncComponent(() => import('ant-design-vue/es/switch'))); +componentMap.set('RadioButtonGroup', createAsyncComponent(() => import('./components/RadioButtonGroup.vue'))); +componentMap.set('RadioGroup', createAsyncComponent(() => import('ant-design-vue/es/radio/Group'))); +componentMap.set('Checkbox', createAsyncComponent(() => import('ant-design-vue/es/checkbox'))); +componentMap.set('CheckboxGroup', createAsyncComponent(() => import('ant-design-vue/es/checkbox/Group'))); +componentMap.set('Cascader', createAsyncComponent(() => import('ant-design-vue/es/cascader'))); +componentMap.set('Slider', createAsyncComponent(() => import('ant-design-vue/es/slider'))); +componentMap.set('Rate', createAsyncComponent(() => import('ant-design-vue/es/rate'))); + +componentMap.set('DatePicker', createAsyncComponent(() => import('ant-design-vue/es/date-picker'))); +componentMap.set('MonthPicker', createAsyncComponent(() => import('ant-design-vue/es/date-picker').then((m) => m.default.MonthPicker))); +componentMap.set('RangePicker', createAsyncComponent(() => import('ant-design-vue/es/date-picker').then((m) => m.default.RangePicker))); +componentMap.set('WeekPicker', createAsyncComponent(() => import('ant-design-vue/es/date-picker').then((m) => m.default.WeekPicker))); +componentMap.set('TimePicker', createAsyncComponent(() => import('ant-design-vue/es/time-picker'))); +componentMap.set('DatePickerInFilter', createAsyncComponent(() => import('@/components/InFilter/DatePickerInFilter.vue'))); +componentMap.set('JDatePickerMultiple', createAsyncComponent(() => import('./jeecg/components/JDatePickerMultiple.vue'))); +componentMap.set('StrengthMeter', createAsyncComponent(() => import('/@/components/StrengthMeter/src/StrengthMeter.vue'))); +componentMap.set('IconPicker', createAsyncComponent(() => import('/@/components/Icon/src/IconPicker.vue'))); +componentMap.set('InputCountDown', createAsyncComponent(() => import('/@/components/CountDown/src/CountdownInput.vue'))); + +componentMap.set('Upload', createAsyncComponent(() => import('/@/components/Upload/src/BasicUpload.vue'))); +componentMap.set('Divider', createAsyncComponent(() => import('ant-design-vue/es/divider'))); + +//注册自定义组件 + +componentMap.set('JAreaLinkage', createAsyncComponent(() => import('./jeecg/components/JAreaLinkage.vue'))); +componentMap.set('JSelectPosition', createAsyncComponent(() => import('./jeecg/components/JSelectPosition.vue'))); +componentMap.set('JSelectUser', createAsyncComponent(() => import('./jeecg/components/JSelectUser.vue'))); +componentMap.set('JSelectRole', createAsyncComponent(() => import('./jeecg/components/JSelectRole.vue'))); +componentMap.set('JImageUpload', createAsyncComponent(() => import('./jeecg/components/JImageUpload.vue'))); +componentMap.set('JDictSelectTag', createAsyncComponent(() => import('./jeecg/components/JDictSelectTag.vue'))); +componentMap.set('JSelectDept', createAsyncComponent(() => import('./jeecg/components/JSelectDept.vue'))); +componentMap.set('JAreaSelect', createAsyncComponent(() => import('./jeecg/components/JAreaSelect.vue'))); +// update-begin--author:liaozhiyang---date:20260227---for:【QQYUN-14751】tinymce富文本、JEasyCron、JLinkTableCard异步加载 +componentMap.set('JLinkTableCard', createAsyncComponent(() => import('./jeecg/components/JLinkTableCard/JLinkTableCard.vue'), { loading: true })); +componentMap.set('JEditor', createAsyncComponent(() => import('./jeecg/components/JEditor.vue'))); +// update-end--author:liaozhiyang---date:20260227---for:【QQYUN-14751】tinymce富文本、JEasyCron、JLinkTableCard异步加载 +componentMap.set('JMarkdownEditor', createAsyncComponent(() => import('./jeecg/components/JMarkdownEditor.vue'))); +componentMap.set('JSelectInput', createAsyncComponent(() => import('./jeecg/components/JSelectInput.vue'))); +componentMap.set('JCodeEditor', createAsyncComponent(() => import('./jeecg/components/JCodeEditor.vue'))); +componentMap.set('JCategorySelect', createAsyncComponent(() => import('./jeecg/components/JCategorySelect.vue'))); +componentMap.set('JSelectMultiple', createAsyncComponent(() => import('./jeecg/components/JSelectMultiple.vue'))); +componentMap.set('JSelectSingle', createAsyncComponent(() => import('./jeecg/components/JSelectSingle.vue'))); +componentMap.set('JPopup', createAsyncComponent(() => import('./jeecg/components/JPopup.vue'))); +// 代码逻辑说明: 【QQYUN-7961】popupDict字典 +componentMap.set('JPopupDict', createAsyncComponent(() => import('./jeecg/components/JPopupDict.vue'))); +componentMap.set('JSwitch', createAsyncComponent(() => import('./jeecg/components/JSwitch.vue'))); +componentMap.set('JTreeDict', createAsyncComponent(() => import('./jeecg/components/JTreeDict.vue'))); +componentMap.set('JInputPop', createAsyncComponent(() => import('./jeecg/components/JInputPop.vue'))); +componentMap.set('JEasyCron', createAsyncComponent(() => import('./jeecg/components/JEasyCron/EasyCronInput.vue'))); +componentMap.set('JCheckbox', createAsyncComponent(() => import('./jeecg/components/JCheckbox.vue'))); +componentMap.set('JInput', createAsyncComponent(() => import('./jeecg/components/JInput.vue'))); +componentMap.set('JTreeSelect', createAsyncComponent(() => import('./jeecg/components/JTreeSelect.vue'))); +componentMap.set('JEllipsis', createAsyncComponent(() => import('./jeecg/components/JEllipsis.vue'))); +componentMap.set('JSelectUserByDept', createAsyncComponent(() => import('./jeecg/components/JSelectUserByDept.vue'))); +componentMap.set('JSelectUserByDepartment', createAsyncComponent(() => import('./jeecg/components/JSelectUserByDepartment.vue'))); +componentMap.set('JUpload', createAsyncComponent(() => import('./jeecg/components/JUpload/JUpload.vue'))); +componentMap.set('JSearchSelect', createAsyncComponent(() => import('./jeecg/components/JSearchSelect.vue'))); +componentMap.set('JAddInput', createAsyncComponent(() => import('./jeecg/components/JAddInput.vue'))); +componentMap.set('JRangeNumber', createAsyncComponent(() => import('./jeecg/components/JRangeNumber.vue'))); +componentMap.set('CascaderPcaInFilter', createAsyncComponent(() => import('@/components/InFilter/CascaderPcaInFilter.vue'))); +componentMap.set('UserSelect', createAsyncComponent(() => import('./jeecg/components/userSelect/index.vue'))); +componentMap.set('RangeDate', createAsyncComponent(() => import('./jeecg/components/JRangeDate.vue'))); +componentMap.set('RangeTime', createAsyncComponent(() => import('./jeecg/components/JRangeTime.vue'))); +componentMap.set('RoleSelect', createAsyncComponent(() => import('./jeecg/components/roleSelect/RoleSelectInput.vue'))); +componentMap.set('JInputSelect', createAsyncComponent(() => import('./jeecg/components/JInputSelect.vue'))); +componentMap.set('JSelectDepartPost', createAsyncComponent(() => import('./jeecg/components/JSelectDepartPost.vue'))); +componentMap.set('JSelectUserByDeptPost', createAsyncComponent(() => import('./jeecg/components/JSelectUserByDeptPost.vue'))); + + +export function add(compName: ComponentType, component: Component) { + componentMap.set(compName, component); +} + +export function del(compName: ComponentType) { + componentMap.delete(compName); +} + +export { componentMap }; diff --git a/src/components/Form/src/components/ApiRadioGroup.vue b/src/components/Form/src/components/ApiRadioGroup.vue new file mode 100644 index 0000000..b58b421 --- /dev/null +++ b/src/components/Form/src/components/ApiRadioGroup.vue @@ -0,0 +1,130 @@ + + + diff --git a/src/components/Form/src/components/ApiSelect.vue b/src/components/Form/src/components/ApiSelect.vue new file mode 100644 index 0000000..58ce857 --- /dev/null +++ b/src/components/Form/src/components/ApiSelect.vue @@ -0,0 +1,242 @@ + + diff --git a/src/components/Form/src/components/ApiTreeSelect.vue b/src/components/Form/src/components/ApiTreeSelect.vue new file mode 100644 index 0000000..158eb03 --- /dev/null +++ b/src/components/Form/src/components/ApiTreeSelect.vue @@ -0,0 +1,87 @@ + + + diff --git a/src/components/Form/src/components/FormAction.vue b/src/components/Form/src/components/FormAction.vue new file mode 100644 index 0000000..cf67c4d --- /dev/null +++ b/src/components/Form/src/components/FormAction.vue @@ -0,0 +1,137 @@ + + + diff --git a/src/components/Form/src/components/FormItem.vue b/src/components/Form/src/components/FormItem.vue new file mode 100644 index 0000000..af05918 --- /dev/null +++ b/src/components/Form/src/components/FormItem.vue @@ -0,0 +1,527 @@ + diff --git a/src/components/Form/src/components/Middleware.vue b/src/components/Form/src/components/Middleware.vue new file mode 100644 index 0000000..ee4f5f1 --- /dev/null +++ b/src/components/Form/src/components/Middleware.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/src/components/Form/src/components/RadioButtonGroup.vue b/src/components/Form/src/components/RadioButtonGroup.vue new file mode 100644 index 0000000..c2c7b22 --- /dev/null +++ b/src/components/Form/src/components/RadioButtonGroup.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/components/Form/src/container/JFormContainer.vue b/src/components/Form/src/container/JFormContainer.vue new file mode 100644 index 0000000..270731c --- /dev/null +++ b/src/components/Form/src/container/JFormContainer.vue @@ -0,0 +1,221 @@ + + + + diff --git a/src/components/Form/src/helper.ts b/src/components/Form/src/helper.ts new file mode 100644 index 0000000..616b49c --- /dev/null +++ b/src/components/Form/src/helper.ts @@ -0,0 +1,85 @@ +import type { ValidationRule } from 'ant-design-vue/lib/form/Form'; +import type { ComponentType } from './types/index'; +import { useI18n } from '/@/hooks/web/useI18n'; +import { dateUtil } from '/@/utils/dateUtil'; +import { isNumber, isObject } from '/@/utils/is'; + +const { t } = useI18n(); + +/** + * @description: 生成placeholder + */ +export function createPlaceholderMessage(component: ComponentType) { + if (component.includes('Input') || component.includes('Complete')) { + return t('common.inputText'); + } + if (component.includes('Picker')) { + return t('common.chooseText'); + } + if ( + component.includes('Select') || + component.includes('Cascader') || + component.includes('Checkbox') || + component.includes('Radio') || + component.includes('Switch') + ) { + // return `请选择${label}`; + return t('common.chooseText'); + } + return ''; +} + +const DATE_TYPE = ['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker']; + +function genType() { + return [...DATE_TYPE, 'RangePicker']; +} + +export function setComponentRuleType(rule: ValidationRule, component: ComponentType, valueFormat: string) { + //https://github.com/vbenjs/vue-vben-admin/pull/3082 github修复原文 + if (Reflect.has(rule, 'type')) { + return; + } + if (['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'].includes(component)) { + rule.type = valueFormat ? 'string' : 'object'; + } else if (['RangePicker', 'Upload', 'CheckboxGroup', 'TimePicker'].includes(component)) { + rule.type = 'array'; + } else if (['InputNumber'].includes(component)) { + rule.type = 'number'; + } +} + +export function processDateValue(attr: Recordable, component: string) { + const { valueFormat, value } = attr; + if (valueFormat) { + attr.value = isObject(value) ? dateUtil(value).format(valueFormat) : value; + } else if (DATE_TYPE.includes(component) && value) { + attr.value = dateUtil(attr.value); + } +} + +export function handleInputNumberValue(component?: ComponentType, val?: any) { + if (!component) return val; + if (['Input', 'InputPassword', 'InputSearch', 'InputTextArea'].includes(component)) { + return val && isNumber(val) ? `${val}` : val; + } + return val; +} +/** +*liaozhiyang +*2023-12-26 +*某些组件的传值需要把字符串类型转成数值类型 +*/ +export function handleInputStringValue(component?: ComponentType, val?: any) { + if (!component) return val; + // 代码逻辑说明: 【TV360X-13】InputNumber设置精确3位小数传入''变成了0.00 + if (['InputNumber'].includes(component) && typeof val === 'string' && val != '') { + return Number(val); + } + return val; +} + +/** + * 时间字段 + */ +export const dateItemType = genType(); diff --git a/src/components/Form/src/hooks/useAdvanced.ts b/src/components/Form/src/hooks/useAdvanced.ts new file mode 100644 index 0000000..156cecb --- /dev/null +++ b/src/components/Form/src/hooks/useAdvanced.ts @@ -0,0 +1,163 @@ +import type { ColEx } from '../types'; +import type { AdvanceState } from '../types/hooks'; +import type { ComputedRef, Ref } from 'vue'; +import type { FormProps, FormSchema } from '../types/form'; +import { computed, unref, watch } from 'vue'; +import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is'; +import { useBreakpoint } from '/@/hooks/event/useBreakpoint'; +import { useDebounceFn } from '@vueuse/core'; + +const BASIC_COL_LEN = 24; + +interface UseAdvancedContext { + advanceState: AdvanceState; + emit: EmitType; + getProps: ComputedRef; + getSchema: ComputedRef; + formModel: Recordable; + defaultValueRef: Ref; +} + +export default function ({ advanceState, emit, getProps, getSchema, formModel, defaultValueRef }: UseAdvancedContext) { + const { realWidthRef, screenEnum, screenRef } = useBreakpoint(); + + const getEmptySpan = computed((): number => { + if (!advanceState.isAdvanced) { + return 0; + } + // For some special cases, you need to manually specify additional blank lines + const emptySpan = unref(getProps).emptySpan || 0; + + if (isNumber(emptySpan)) { + return emptySpan; + } + if (isObject(emptySpan)) { + const { span = 0 } = emptySpan; + const screen = unref(screenRef) as string; + + const screenSpan = (emptySpan as any)[screen.toLowerCase()]; + return screenSpan || span || 0; + } + return 0; + }); + + const debounceUpdateAdvanced = useDebounceFn(updateAdvanced, 30); + + watch( + [() => unref(getSchema), () => advanceState.isAdvanced, () => unref(realWidthRef)], + () => { + const { showAdvancedButton } = unref(getProps); + if (showAdvancedButton) { + debounceUpdateAdvanced(); + } + }, + { immediate: true } + ); + + function getAdvanced(itemCol: Partial, itemColSum = 0, isLastAction = false, index = 0) { + const width = unref(realWidthRef); + + const mdWidth = + parseInt(itemCol.md as string) || parseInt(itemCol.xs as string) || parseInt(itemCol.sm as string) || (itemCol.span as number) || BASIC_COL_LEN; + + const lgWidth = parseInt(itemCol.lg as string) || mdWidth; + const xlWidth = parseInt(itemCol.xl as string) || lgWidth; + const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth; + if (width <= screenEnum.LG) { + itemColSum += mdWidth; + } else if (width < screenEnum.XL) { + itemColSum += lgWidth; + } else if (width < screenEnum.XXL) { + itemColSum += xlWidth; + } else { + itemColSum += xxlWidth; + } + + let autoAdvancedCol = unref(getProps).autoAdvancedCol ?? 3; + + if (isLastAction) { + advanceState.hideAdvanceBtn = unref(getSchema).length <= autoAdvancedCol; + // 代码逻辑说明: 注释掉该逻辑,使小于等于2行时,也显示展开收起按钮 + /* if (itemColSum <= BASIC_COL_LEN * 2) { + // 小于等于2行时,不显示折叠和展开按钮 + advanceState.hideAdvanceBtn = true; + advanceState.isAdvanced = true; + } else */ + /*if (itemColSum > BASIC_COL_LEN * 2 && itemColSum <= BASIC_COL_LEN * (unref(getProps).autoAdvancedLine || 3)) { + advanceState.hideAdvanceBtn = false; + + // 默认超过 3 行折叠 + } else*/ + if (!advanceState.isLoad) { + advanceState.isLoad = true; + advanceState.isAdvanced = !advanceState.isAdvanced; + // 代码逻辑说明: 如果总列数大于 autoAdvancedCol,就默认折叠 + if (unref(getSchema).length > autoAdvancedCol) { + advanceState.hideAdvanceBtn = false; + advanceState.isAdvanced = false; + } + } + return { isAdvanced: advanceState.isAdvanced, itemColSum }; + } + if (itemColSum > BASIC_COL_LEN * (unref(getProps).alwaysShowLines || 1)) { + return { isAdvanced: advanceState.isAdvanced, itemColSum }; + } else if (!advanceState.isAdvanced && index + 1 > autoAdvancedCol) { + // 如果当前是收起状态,并且当前列下标 > autoAdvancedCol,就隐藏 + return { isAdvanced: false, itemColSum }; + } else { + // The first line is always displayed + return { isAdvanced: true, itemColSum }; + } + } + + function updateAdvanced() { + let itemColSum = 0; + let realItemColSum = 0; + const { baseColProps = {} } = unref(getProps); + + const schemas = unref(getSchema); + for (let i = 0; i < schemas.length; i++) { + const schema = schemas[i]; + const { show, colProps } = schema; + let isShow = true; + + if (isBoolean(show)) { + isShow = show; + } + + if (isFunction(show)) { + isShow = show({ + schema: schema, + model: formModel, + field: schema.field, + values: { + ...unref(defaultValueRef), + ...formModel, + }, + }); + } + + if (isShow && (colProps || baseColProps)) { + const { itemColSum: sum, isAdvanced } = getAdvanced({ ...baseColProps, ...colProps }, itemColSum, false, i); + + itemColSum = sum || 0; + if (isAdvanced) { + realItemColSum = itemColSum; + } + schema.isAdvanced = isAdvanced; + } + } + + advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpan); + + getAdvanced(unref(getProps).actionColOptions || { span: BASIC_COL_LEN }, itemColSum, true); + + emit('advanced-change'); + } + + function handleToggleAdvanced() { + advanceState.isAdvanced = !advanceState.isAdvanced; + } + + return { handleToggleAdvanced }; +} diff --git a/src/components/Form/src/hooks/useAutoFocus.ts b/src/components/Form/src/hooks/useAutoFocus.ts new file mode 100644 index 0000000..85dcc2f --- /dev/null +++ b/src/components/Form/src/hooks/useAutoFocus.ts @@ -0,0 +1,35 @@ +import type { ComputedRef, Ref } from 'vue'; +import type { FormSchema, FormActionType, FormProps } from '../types/form'; + +import { unref, nextTick, watchEffect } from 'vue'; + +interface UseAutoFocusContext { + getSchema: ComputedRef; + getProps: ComputedRef; + isInitedDefault: Ref; + formElRef: Ref; +} +export async function useAutoFocus({ getSchema, getProps, formElRef, isInitedDefault }: UseAutoFocusContext) { + watchEffect(async () => { + if (unref(isInitedDefault) || !unref(getProps).autoFocusFirstItem) { + return; + } + await nextTick(); + const schemas = unref(getSchema); + const formEl = unref(formElRef); + const el = (formEl as any)?.$el as HTMLElement; + if (!formEl || !el || !schemas || schemas.length === 0) { + return; + } + + const firstItem = schemas[0]; + // Only open when the first form item is input type + if (!firstItem.component.includes('Input')) { + return; + } + + const inputEl = el.querySelector('.ant-row:first-child input') as Nullable; + if (!inputEl) return; + inputEl?.focus(); + }); +} diff --git a/src/components/Form/src/hooks/useComponentRegister.ts b/src/components/Form/src/hooks/useComponentRegister.ts new file mode 100644 index 0000000..218aaa9 --- /dev/null +++ b/src/components/Form/src/hooks/useComponentRegister.ts @@ -0,0 +1,11 @@ +import type { ComponentType } from '../types/index'; +import { tryOnUnmounted } from '@vueuse/core'; +import { add, del } from '../componentMap'; +import type { Component } from 'vue'; + +export function useComponentRegister(compName: ComponentType, comp: Component) { + add(compName, comp); + tryOnUnmounted(() => { + del(compName); + }); +} diff --git a/src/components/Form/src/hooks/useForm.ts b/src/components/Form/src/hooks/useForm.ts new file mode 100644 index 0000000..a260be8 --- /dev/null +++ b/src/components/Form/src/hooks/useForm.ts @@ -0,0 +1,148 @@ +import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form'; +import type { NamePath, ValidateOptions } from 'ant-design-vue/lib/form/interface'; +import type { DynamicProps } from '/#/utils'; +import { handleRangeValue } from '../utils/formUtils'; +import { ref, onUnmounted, unref, nextTick, watch } from 'vue'; +import { isProdMode } from '/@/utils/env'; +import { error } from '/@/utils/log'; +import { getDynamicProps, getValueType, getValueTypeBySchema } from '/@/utils'; +export declare type ValidateFields = (nameList?: NamePath[], options?: ValidateOptions) => Promise; + +type Props = Partial>; + +export function useForm(props?: Props): UseFormReturnType { + const formRef = ref>(null); + const loadedRef = ref>(false); + + async function getForm() { + const form = unref(formRef); + if (!form) { + error('The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!'); + } + await nextTick(); + return form as FormActionType; + } + + function register(instance: FormActionType) { + isProdMode() && + onUnmounted(() => { + formRef.value = null; + loadedRef.value = null; + }); + if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return; + + formRef.value = instance; + loadedRef.value = true; + + watch( + () => props, + () => { + props && instance.setProps(getDynamicProps(props)); + }, + { + immediate: true, + deep: true, + } + ); + } + + const methods: FormActionType = { + scrollToField: async (name: NamePath, options?: ScrollOptions | undefined) => { + const form = await getForm(); + form.scrollToField(name, options); + }, + setProps: async (formProps: Partial) => { + const form = await getForm(); + form.setProps(formProps); + }, + + updateSchema: async (data: Partial | Partial[]) => { + const form = await getForm(); + form.updateSchema(data); + }, + + resetSchema: async (data: Partial | Partial[]) => { + const form = await getForm(); + form.resetSchema(data); + }, + + clearValidate: async (name?: string | string[]) => { + const form = await getForm(); + form.clearValidate(name); + }, + + resetFields: async () => { + getForm().then(async (form) => { + await form.resetFields(); + }); + }, + + removeSchemaByFiled: async (field: string | string[]) => { + unref(formRef)?.removeSchemaByFiled(field); + }, + + // TODO promisify + getFieldsValue: () => { + // 代码逻辑说明: VUEN-1341【流程】编码方式 流程节点编辑表单时,填写数据报错 包括用户组件、部门组件、省市区 + let values = unref(formRef)?.getFieldsValue() as T; + if(values){ + Object.keys(values).map(key=>{ + if (values[key] instanceof Array) { + // 代码逻辑说明: 【issues/4330】判断如果是对象数组,则不拼接 + let isObject = typeof (values[key][0] || '') === 'object'; + if (!isObject) { + values[key] = values[key].join(','); + } + } + }); + } + return values; + }, + + setFieldsValue: async (values: T) => { + const form = await getForm(); + form.setFieldsValue(values); + }, + + appendSchemaByField: async (schema: FormSchema, prefixField: string | undefined, first: boolean) => { + const form = await getForm(); + form.appendSchemaByField(schema, prefixField, first); + }, + + submit: async (): Promise => { + const form = await getForm(); + return form.submit(); + }, + + /** + * 表单验证并返回表单值 + * @update:添加表单值转换逻辑 + * @updateBy:zyf + * @updateDate:2021-09-02 + */ + validate: async (nameList?: NamePath[]): Promise => { + const form = await getForm(); + let getProps = props || form.getProps; + let values = form.validate(nameList).then((values) => { + for (let key in values) { + if (values[key] instanceof Array) { + let valueType = getValueTypeBySchema(form.getSchemaByField(key)!, form); + if (valueType === 'string') { + values[key] = values[key].join(','); + } + } + } + //--@updateBy-begin----author:liusq---date:20210916------for:处理区域事件字典信息------ + return handleRangeValue(getProps, values); + //--@updateBy-end----author:liusq---date:20210916------for:处理区域事件字典信息------ + }); + return values; + }, + validateFields: async (nameList?: NamePath[], options?: ValidateOptions): Promise => { + const form = await getForm(); + return form.validateFields(nameList, options); + }, + }; + + return [register, methods]; +} diff --git a/src/components/Form/src/hooks/useFormContext.ts b/src/components/Form/src/hooks/useFormContext.ts new file mode 100644 index 0000000..01dfadd --- /dev/null +++ b/src/components/Form/src/hooks/useFormContext.ts @@ -0,0 +1,17 @@ +import type { InjectionKey } from 'vue'; +import { createContext, useContext } from '/@/hooks/core/useContext'; + +export interface FormContextProps { + resetAction: () => Promise; + submitAction: () => Promise; +} + +const key: InjectionKey = Symbol(); + +export function createFormContext(context: FormContextProps) { + return createContext(context, key); +} + +export function useFormContext() { + return useContext(key); +} diff --git a/src/components/Form/src/hooks/useFormEvents.ts b/src/components/Form/src/hooks/useFormEvents.ts new file mode 100644 index 0000000..77086a3 --- /dev/null +++ b/src/components/Form/src/hooks/useFormEvents.ts @@ -0,0 +1,319 @@ +import type { ComputedRef, Ref } from 'vue'; +import type { FormProps, FormSchema, FormActionType } from '../types/form'; +import type { NamePath, ValidateOptions } from 'ant-design-vue/lib/form/interface'; +import { unref, toRaw } from 'vue'; +import { isArray, isFunction, isObject, isString } from '/@/utils/is'; +import { deepMerge, getValueType } from '/@/utils'; +import { dateItemType, handleInputNumberValue, handleInputStringValue } from '../helper'; +import { dateUtil } from '/@/utils/dateUtil'; +import { cloneDeep, uniqBy } from 'lodash-es'; +import { error } from '/@/utils/log'; + +interface UseFormActionContext { + emit: EmitType; + getProps: ComputedRef; + getSchema: ComputedRef; + formModel: Recordable; + defaultValueRef: Ref; + formElRef: Ref; + schemaRef: Ref; + handleFormValues: Fn; +} + +function mergeComponentProps(srcProps: any, targetProps: any) { + if (targetProps == undefined) return srcProps; + if (srcProps == undefined) return targetProps; + if (isObject(srcProps) && isObject(targetProps)) { + return deepMerge(cloneDeep(srcProps), targetProps); + } + if (isFunction(srcProps) && isObject(targetProps)) { + return (ctx: any) => ({ ...(srcProps(ctx) ?? {}), ...targetProps }); + } + if (isObject(srcProps) && isFunction(targetProps)) { + return (ctx: any) => ({ ...srcProps, ...(targetProps(ctx) ?? {}) }); + } + if (isFunction(srcProps) && isFunction(targetProps)) { + return (ctx: any) => ({ ...(srcProps(ctx) ?? {}), ...(targetProps(ctx) ?? {}) }); + } + return targetProps; +} + +export function useFormEvents({ + emit, + getProps, + formModel, + getSchema, + defaultValueRef, + formElRef, + schemaRef, + handleFormValues, +}: UseFormActionContext) { + async function resetFields(): Promise { + const { resetFunc, submitOnReset } = unref(getProps); + resetFunc && isFunction(resetFunc) && (await resetFunc()); + + const formEl = unref(formElRef); + if (!formEl) return; + + Object.keys(formModel).forEach((key) => { + formModel[key] = defaultValueRef.value[key]; + }); + clearValidate(); + emit('reset', toRaw(formModel)); + submitOnReset && handleSubmit(); + } + + /** + * @description: Set form value + */ + async function setFieldsValue(values: Recordable): Promise { + const fields = unref(getSchema) + .map((item) => item.field) + .filter(Boolean); + + const validKeys: string[] = []; + Object.keys(values).forEach((key) => { + const schema = unref(getSchema).find((item) => item.field === key); + let value = values[key]; + + //antd3升级后,online表单时间控件选中值报js错 TypeError: Reflect.has called on non-object + if(!(values instanceof Object)){ + return; + } + + const hasKey = Reflect.has(values, key); + + value = handleInputNumberValue(schema?.component, value); + // 代码逻辑说明: 【QQYUN-7535】popup回填字段inputNumber组件验证错误 + value = handleInputStringValue(schema?.component, value); + // 0| '' is allow + if (hasKey && fields.includes(key)) { + // time type + if (itemIsDateType(key)) { + if (Array.isArray(value)) { + const arr: any[] = []; + for (const ele of value) { + arr.push(ele ? dateUtil(ele) : null); + } + formModel[key] = arr; + } else { + const { componentProps } = schema || {}; + let _props = componentProps as any; + if (typeof componentProps === 'function') { + _props = _props({ formModel }); + } + formModel[key] = value ? (_props?.valueFormat ? value : dateUtil(value)) : null; + } + } else { + formModel[key] = value; + } + validKeys.push(key); + } + }); + validateFields(validKeys).catch((_) => {}); + } + + /** + * 根据字段名获取schema + * @param field + */ + function getSchemaByField(field: string): Nullable { + if (!isString(field)) { + return null + } + const schemaList: FormSchema[] = unref(getSchema); + const index = schemaList.findIndex((schema) => schema.field === field); + if (index !== -1) { + return cloneDeep(schemaList[index]); + } + return null + } + + /** + * @description: Delete based on field name + */ + async function removeSchemaByFiled(fields: string | string[]): Promise { + const schemaList: FormSchema[] = cloneDeep(unref(getSchema)); + if (!fields) { + return; + } + + let fieldList: string[] = isString(fields) ? [fields] : fields; + if (isString(fields)) { + fieldList = [fields]; + } + for (const field of fieldList) { + _removeSchemaByFiled(field, schemaList); + } + schemaRef.value = schemaList; + } + + /** + * @description: Delete based on field name + */ + function _removeSchemaByFiled(field: string, schemaList: FormSchema[]): void { + if (isString(field)) { + const index = schemaList.findIndex((schema) => schema.field === field); + if (index !== -1) { + delete formModel[field]; + schemaList.splice(index, 1); + } + } + } + + /** + * @description: Insert after a certain field, if not insert the last + */ + async function appendSchemaByField(schema: FormSchema, prefixField?: string, first = false) { + const schemaList: FormSchema[] = cloneDeep(unref(getSchema)); + + const index = schemaList.findIndex((schema) => schema.field === prefixField); + const hasInList = schemaList.some((item) => item.field === prefixField || schema.field); + + if (!hasInList) return; + + if (!prefixField || index === -1 || first) { + first ? schemaList.unshift(schema) : schemaList.push(schema); + schemaRef.value = schemaList; + return; + } + if (index !== -1) { + schemaList.splice(index + 1, 0, schema); + } + schemaRef.value = schemaList; + } + + async function resetSchema(data: Partial | Partial[]) { + let updateData: Partial[] = []; + if (isObject(data)) { + updateData.push(data as FormSchema); + } + if (isArray(data)) { + updateData = [...data]; + } + + const hasField = updateData.every((item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field)); + + if (!hasField) { + error('All children of the form Schema array that need to be updated must contain the `field` field'); + return; + } + schemaRef.value = updateData as FormSchema[]; + } + + async function updateSchema(data: Partial | Partial[]) { + let updateData: Partial[] = []; + if (isObject(data)) { + updateData.push(data as FormSchema); + } + if (isArray(data)) { + updateData = [...data]; + } + + const hasField = updateData.every((item) => item.component === 'Divider' || (Reflect.has(item, 'field') && item.field)); + + if (!hasField) { + error('All children of the form Schema array that need to be updated must contain the `field` field'); + return; + } + const schema: FormSchema[] = []; + updateData.forEach((item) => { + unref(getSchema).forEach((val) => { + if (val.field === item.field) { + // update-begin-author:liaozhiyang date:2026-05-12 for:【issue/9612】updateSchema中的函数执行两次 + const { componentProps: itemCp, ...restItem } = item as any; + const newSchema = deepMerge(val, restItem); + if (Reflect.has(item, 'componentProps')) { + //【issues/7940】componentProps写成函数形式时,updateSchema写成对象时,参数没合并 + newSchema.componentProps = mergeComponentProps(val.componentProps, itemCp); + } + // update-end-author:liaozhiyang date:2026-05-12 for:【issue/9612】updateSchema中的函数执行两次 + schema.push(newSchema as FormSchema); + } else { + schema.push(val); + } + }); + }); + schemaRef.value = uniqBy(schema, 'field'); + } + + function getFieldsValue(): Recordable { + const formEl = unref(formElRef); + if (!formEl) return {}; + return handleFormValues(toRaw(unref(formModel))); + } + + /** + * @description: Is it time + */ + function itemIsDateType(key: string) { + return unref(getSchema).some((item) => { + return item.field === key ? dateItemType.includes(item.component) : false; + }); + } + + async function validateFields(nameList?: NamePath[] | undefined, options?: ValidateOptions) { + return unref(formElRef)?.validateFields(nameList, options); + } + + async function validate(nameList?: NamePath[] | undefined) { + return await unref(formElRef)?.validate(nameList); + } + + async function clearValidate(name?: string | string[]) { + await unref(formElRef)?.clearValidate(name); + } + + async function scrollToField(name: NamePath, options?: ScrollOptions | undefined) { + await unref(formElRef)?.scrollToField(name, options); + } + + /** + * @description: Form submission + */ + async function handleSubmit(e?: Event): Promise { + e && e.preventDefault(); + const { submitFunc } = unref(getProps); + if (submitFunc && isFunction(submitFunc)) { + await submitFunc(); + return; + } + const formEl = unref(formElRef); + if (!formEl) return; + try { + const values = await validate(); + //代码逻辑说明: 对查询表单提交的数组处理成字符串------ + for (let key in values) { + if (values[key] instanceof Array) { + let valueType = getValueType(getProps, key); + if (valueType === 'string') { + values[key] = values[key].join(','); + } + } + } + const res = handleFormValues(values); + emit('submit', res); + } catch (error) { + // 代码逻辑说明: 列表查询表单会触发校验错误导致重置失败,原因不明 + emit('submit', {}); + console.error('query form validate error, please ignore!', error) + //throw new Error(error); + } + } + + return { + handleSubmit, + clearValidate, + validate, + validateFields, + getFieldsValue, + updateSchema, + resetSchema, + getSchemaByField, + appendSchemaByField, + removeSchemaByFiled, + resetFields, + setFieldsValue, + scrollToField, + }; +} diff --git a/src/components/Form/src/hooks/useFormValues.ts b/src/components/Form/src/hooks/useFormValues.ts new file mode 100644 index 0000000..db63c8d --- /dev/null +++ b/src/components/Form/src/hooks/useFormValues.ts @@ -0,0 +1,59 @@ +import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is'; +import { unref } from 'vue'; +import type { Ref, ComputedRef } from 'vue'; +import type { FormProps, FormSchema } from '../types/form'; +import dayjs from "dayjs"; +import { set } from 'lodash-es'; +import { handleRangeValue } from '/@/components/Form/src/utils/formUtils'; + +interface UseFormValuesContext { + defaultValueRef: Ref; + getSchema: ComputedRef; + getProps: ComputedRef; + formModel: Recordable; +} +export function useFormValues({ defaultValueRef, getSchema, formModel, getProps }: UseFormValuesContext) { + // Processing form values + function handleFormValues(values: Recordable) { + if (!isObject(values)) { + return {}; + } + const res: Recordable = {}; + for (const item of Object.entries(values)) { + let [, value] = item; + const [key] = item; + if (!key || (isArray(value) && value.length === 0) || isFunction(value)) { + continue; + } + const transformDateFunc = unref(getProps).transformDateFunc; + if (isObject(value)) { + value = transformDateFunc?.(value); + } + // 判断是否是dayjs实例 + if (isArray(value) && dayjs.isDayjs(value[0]) && dayjs.isDayjs(value[1])) { + value = value.map((item) => transformDateFunc?.(item)); + } + // Remove spaces + if (isString(value)) { + value = value.trim(); + } + set(res, key, value); + } + return handleRangeValue(getProps, res); + } + + function initDefault() { + const schemas = unref(getSchema); + const obj: Recordable = {}; + schemas.forEach((item) => { + const { defaultValue } = item; + if (!isNullOrUnDef(defaultValue)) { + obj[item.field] = defaultValue; + formModel[item.field] = defaultValue; + } + }); + defaultValueRef.value = obj; + } + + return { handleFormValues, initDefault }; +} diff --git a/src/components/Form/src/hooks/useLabelWidth.ts b/src/components/Form/src/hooks/useLabelWidth.ts new file mode 100644 index 0000000..b932cf7 --- /dev/null +++ b/src/components/Form/src/hooks/useLabelWidth.ts @@ -0,0 +1,45 @@ +import type { Ref } from 'vue'; +import type { FormProps, FormSchema } from '../types/form'; + +import { computed, unref } from 'vue'; +import { isNumber } from '/@/utils/is'; + +export function useItemLabelWidth(schemaItemRef: Ref, propsRef: Ref) { + return computed(() => { + const schemaItem = unref(schemaItemRef); + const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {}; + const { labelWidth, disabledLabelWidth } = schemaItem; + + const { labelWidth: globalLabelWidth, labelCol: globalLabelCol, wrapperCol: globWrapperCol,layout } = unref(propsRef); + + // 代码逻辑说明: 禁用全局 labelWidth,不自动设置 textAlign -------- + if (disabledLabelWidth) { + return { labelCol, wrapperCol }; + } + + // If labelWidth is set globally, all items setting + if (!globalLabelWidth && !labelWidth && !globalLabelCol) { + labelCol.style = { + textAlign: 'left', + }; + return { labelCol, wrapperCol }; + } + let width = labelWidth || globalLabelWidth; + let col = { ...globalLabelCol, ...labelCol }; + const wrapCol = { ...globWrapperCol, ...wrapperCol }; + + if (width) { + width = isNumber(width) ? `${width}px` : width; + // 代码逻辑说明: 【issues/6865】配置单个的labelWidth不生效 + col = {}; + } + + return { + labelCol: { style: { width: width ? width : '100%' }, ...col }, + wrapperCol: { + style: { width: layout === 'vertical' ? '100%' : `calc(100% - ${width})` }, + ...wrapCol, + }, + }; + }); +} diff --git a/src/components/Form/src/jeecg/components/JAddInput.vue b/src/components/Form/src/jeecg/components/JAddInput.vue new file mode 100644 index 0000000..2b215ff --- /dev/null +++ b/src/components/Form/src/jeecg/components/JAddInput.vue @@ -0,0 +1,121 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JAreaLinkage.vue b/src/components/Form/src/jeecg/components/JAreaLinkage.vue new file mode 100644 index 0000000..734308e --- /dev/null +++ b/src/components/Form/src/jeecg/components/JAreaLinkage.vue @@ -0,0 +1,194 @@ + + diff --git a/src/components/Form/src/jeecg/components/JAreaSelect.vue b/src/components/Form/src/jeecg/components/JAreaSelect.vue new file mode 100644 index 0000000..f57205b --- /dev/null +++ b/src/components/Form/src/jeecg/components/JAreaSelect.vue @@ -0,0 +1,168 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JCascader.vue b/src/components/Form/src/jeecg/components/JCascader.vue new file mode 100644 index 0000000..f6f83d3 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JCascader.vue @@ -0,0 +1,368 @@ + + + + + + + diff --git a/src/components/Form/src/jeecg/components/JCategorySelect.vue b/src/components/Form/src/jeecg/components/JCategorySelect.vue new file mode 100644 index 0000000..fbdfa07 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JCategorySelect.vue @@ -0,0 +1,266 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JCheckbox.vue b/src/components/Form/src/jeecg/components/JCheckbox.vue new file mode 100644 index 0000000..536a20f --- /dev/null +++ b/src/components/Form/src/jeecg/components/JCheckbox.vue @@ -0,0 +1,124 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/JCodeEditor.vue b/src/components/Form/src/jeecg/components/JCodeEditor.vue new file mode 100644 index 0000000..53d6c8e --- /dev/null +++ b/src/components/Form/src/jeecg/components/JCodeEditor.vue @@ -0,0 +1,372 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JDatePickerMultiple.vue b/src/components/Form/src/jeecg/components/JDatePickerMultiple.vue new file mode 100644 index 0000000..b0a3d90 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JDatePickerMultiple.vue @@ -0,0 +1,222 @@ + + + + + + diff --git a/src/components/Form/src/jeecg/components/JDictSelectTag.vue b/src/components/Form/src/jeecg/components/JDictSelectTag.vue new file mode 100644 index 0000000..321b9eb --- /dev/null +++ b/src/components/Form/src/jeecg/components/JDictSelectTag.vue @@ -0,0 +1,257 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JEasyCron/EasyCronInner.vue b/src/components/Form/src/jeecg/components/JEasyCron/EasyCronInner.vue new file mode 100644 index 0000000..fd0aa46 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/EasyCronInner.vue @@ -0,0 +1,321 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/JEasyCron/EasyCronInput.vue b/src/components/Form/src/jeecg/components/JEasyCron/EasyCronInput.vue new file mode 100644 index 0000000..51e8abe --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/EasyCronInput.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JEasyCron/EasyCronModal.vue b/src/components/Form/src/jeecg/components/JEasyCron/EasyCronModal.vue new file mode 100644 index 0000000..5824cd0 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/EasyCronModal.vue @@ -0,0 +1,28 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JEasyCron/LICENSE b/src/components/Form/src/jeecg/components/JEasyCron/LICENSE new file mode 100644 index 0000000..08eddc9 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 知行合一 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/components/Form/src/jeecg/components/JEasyCron/easy.cron.data.ts b/src/components/Form/src/jeecg/components/JEasyCron/easy.cron.data.ts new file mode 100644 index 0000000..335a8c0 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/easy.cron.data.ts @@ -0,0 +1,10 @@ +import { propTypes } from '/@/utils/propTypes'; + +export const cronEmits = ['change', 'update:value']; +export const cronProps = { + value: propTypes.string.def(''), + disabled: propTypes.bool.def(false), + hideSecond: propTypes.bool.def(false), + hideYear: propTypes.bool.def(false), + remote: propTypes.func, +}; diff --git a/src/components/Form/src/jeecg/components/JEasyCron/easy.cron.inner.less b/src/components/Form/src/jeecg/components/JEasyCron/easy.cron.inner.less new file mode 100644 index 0000000..3aab6ba --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/easy.cron.inner.less @@ -0,0 +1,59 @@ +//noinspection LessUnresolvedVariable +@prefix-cls: ~'@{namespace}-easy-cron-inner'; + +.@{prefix-cls} { + .content { + .ant-checkbox-wrapper + .ant-checkbox-wrapper { + margin-left: 0; + } + } + + &-config-list { + text-align: left; + margin: 0 10px 10px 10px; + + .item { + margin-top: 5px; + font-size: 14px; + + span { + padding: 0 2px; + } + } + + .choice { + padding: 5px 8px; + } + + .w60 { + width: 60px; + min-width: 60px; + } + + .w80 { + width: 80px; + min-width: 80px; + } + + .list { + margin: 0 20px; + } + + .list-check-item { + padding: 1px 3px; + width: 4em; + } + + .list-cn .list-check-item { + width: 5em; + } + + .tip-info { + color: #999; + } + } + + .allow-click { + cursor: pointer; + } +} diff --git a/src/components/Form/src/jeecg/components/JEasyCron/easy.cron.input.less b/src/components/Form/src/jeecg/components/JEasyCron/easy.cron.input.less new file mode 100644 index 0000000..d72aa15 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/easy.cron.input.less @@ -0,0 +1,14 @@ +//noinspection LessUnresolvedVariable +@prefix-cls: ~'@{namespace}-easy-cron-input'; + +.@{prefix-cls} { + a.open-btn { + cursor: pointer; + + .app-iconify { + position: relative; + top: 1px; + right: 2px; + } + } +} diff --git a/src/components/Form/src/jeecg/components/JEasyCron/index.ts b/src/components/Form/src/jeecg/components/JEasyCron/index.ts new file mode 100644 index 0000000..1513f0d --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/index.ts @@ -0,0 +1,6 @@ +// 原开源项目地址:https://gitee.com/toktok/easy-cron + +export { default as JEasyCron } from './EasyCronInput.vue'; +export { default as JEasyCronInner } from './EasyCronInner.vue'; +export { default as JEasyCronModal } from './EasyCronModal.vue'; +export { default as JCronValidator } from './validator'; diff --git a/src/components/Form/src/jeecg/components/JEasyCron/tabs/DayUI.vue b/src/components/Form/src/jeecg/components/JEasyCron/tabs/DayUI.vue new file mode 100644 index 0000000..1cd215c --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/tabs/DayUI.vue @@ -0,0 +1,94 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JEasyCron/tabs/HourUI.vue b/src/components/Form/src/jeecg/components/JEasyCron/tabs/HourUI.vue new file mode 100644 index 0000000..c3c5224 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/tabs/HourUI.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JEasyCron/tabs/MinuteUI.vue b/src/components/Form/src/jeecg/components/JEasyCron/tabs/MinuteUI.vue new file mode 100644 index 0000000..34617bf --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/tabs/MinuteUI.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JEasyCron/tabs/MonthUI.vue b/src/components/Form/src/jeecg/components/JEasyCron/tabs/MonthUI.vue new file mode 100644 index 0000000..78f7f4e --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/tabs/MonthUI.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JEasyCron/tabs/SecondUI.vue b/src/components/Form/src/jeecg/components/JEasyCron/tabs/SecondUI.vue new file mode 100644 index 0000000..6b65b85 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/tabs/SecondUI.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JEasyCron/tabs/WeekUI.vue b/src/components/Form/src/jeecg/components/JEasyCron/tabs/WeekUI.vue new file mode 100644 index 0000000..c3b15dc --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/tabs/WeekUI.vue @@ -0,0 +1,125 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JEasyCron/tabs/YearUI.vue b/src/components/Form/src/jeecg/components/JEasyCron/tabs/YearUI.vue new file mode 100644 index 0000000..2be7972 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/tabs/YearUI.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JEasyCron/tabs/useTabMixin.ts b/src/components/Form/src/jeecg/components/JEasyCron/tabs/useTabMixin.ts new file mode 100644 index 0000000..291ca19 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/tabs/useTabMixin.ts @@ -0,0 +1,199 @@ +// 主要用于日和星期的互斥使用 +import { computed, inject, reactive, ref, unref, watch } from 'vue'; +import { propTypes } from '/@/utils/propTypes'; + +export enum TypeEnum { + unset = 'UNSET', + every = 'EVERY', + range = 'RANGE', + loop = 'LOOP', + work = 'WORK', + last = 'LAST', + specify = 'SPECIFY', +} + +// use 公共 props +export function useTabProps(options) { + const defaultValue = options?.defaultValue ?? '?'; + return { + value: propTypes.string.def(defaultValue), + disabled: propTypes.bool.def(false), + ...options?.props, + }; +} + +// use 公共 emits +export function useTabEmits() { + return ['change', 'update:value']; +} + +// use 公共 setup +export function useTabSetup(props, context, options) { + const { emit } = context; + const prefixCls = inject('prefixCls'); + const defaultValue = ref(options?.defaultValue ?? '?'); + // 类型 + const type = ref(options.defaultType ?? TypeEnum.every); + const valueList = ref([]); + // 对于不同的类型,所定义的值也有所不同 + const valueRange = reactive(options.valueRange); + const valueLoop = reactive(options.valueLoop); + const valueWeek = reactive(options.valueWeek); + const valueWork = ref(options.valueWork); + const maxValue = ref(options.maxValue); + const minValue = ref(options.minValue); + + // 根据不同的类型计算出的value + const computeValue = computed(() => { + let valueArray: any[] = []; + switch (type.value) { + case TypeEnum.unset: + valueArray.push('?'); + break; + case TypeEnum.every: + valueArray.push('*'); + break; + case TypeEnum.range: + valueArray.push(`${valueRange.start}-${valueRange.end}`); + break; + case TypeEnum.loop: + valueArray.push(`${valueLoop.start}/${valueLoop.interval}`); + break; + case TypeEnum.work: + valueArray.push(`${valueWork.value}W`); + break; + case TypeEnum.last: + valueArray.push('L'); + break; + case TypeEnum.specify: + if (valueList.value.length === 0) { + valueList.value.push(minValue.value); + } + valueArray.push(valueList.value.join(',')); + break; + default: + valueArray.push(defaultValue.value); + break; + } + return valueArray.length > 0 ? valueArray.join('') : defaultValue.value; + }); + // 指定值范围区间,介于最小值和最大值之间 + const specifyRange = computed(() => { + let range: number[] = []; + if (maxValue.value != null) { + for (let i = minValue.value; i <= maxValue.value; i++) { + range.push(i); + } + } + return range; + }); + + watch( + () => props.value, + (val) => { + if (val !== computeValue.value) { + parseValue(val); + } + }, + { immediate: true } + ); + + watch(computeValue, (v) => updateValue(v)); + + function updateValue(value) { + emit('change', value); + emit('update:value', value); + } + + /** + * parseValue + * @param value + */ + function parseValue(value) { + if (value === computeValue.value) { + return; + } + try { + if (!value || value === defaultValue.value) { + type.value = TypeEnum.every; + } else if (value.indexOf('?') >= 0) { + type.value = TypeEnum.unset; + } else if (value.indexOf('-') >= 0) { + type.value = TypeEnum.range; + const values = value.split('-'); + if (values.length >= 2) { + valueRange.start = parseInt(values[0]); + valueRange.end = parseInt(values[1]); + } + } else if (value.indexOf('/') >= 0) { + type.value = TypeEnum.loop; + const values = value.split('/'); + if (values.length >= 2) { + valueLoop.start = value[0] === '*' ? 0 : parseInt(values[0]); + valueLoop.interval = parseInt(values[1]); + } + } else if (value.indexOf('W') >= 0) { + type.value = TypeEnum.work; + const values = value.split('W'); + if (!values[0] && !isNaN(values[0])) { + valueWork.value = parseInt(values[0]); + } + } else if (value.indexOf('L') >= 0) { + type.value = TypeEnum.last; + } else if (value.indexOf(',') >= 0 || !isNaN(value)) { + type.value = TypeEnum.specify; + valueList.value = value.split(',').map((item) => parseInt(item)); + } else { + type.value = TypeEnum.every; + } + } catch (e) { + type.value = TypeEnum.every; + } + } + + const beforeRadioAttrs = computed(() => ({ + class: ['choice'], + disabled: props.disabled || unref(options.disabled), + })); + const inputNumberAttrs = computed(() => ({ + class: ['w60'], + max: maxValue.value, + min: minValue.value, + precision: 0, + })); + const typeRangeAttrs = computed(() => ({ + disabled: type.value !== TypeEnum.range || props.disabled || unref(options.disabled), + ...inputNumberAttrs.value, + })); + const typeLoopAttrs = computed(() => ({ + disabled: type.value !== TypeEnum.loop || props.disabled || unref(options.disabled), + ...inputNumberAttrs.value, + })); + const typeSpecifyAttrs = computed(() => ({ + disabled: type.value !== TypeEnum.specify || props.disabled || unref(options.disabled), + class: ['list-check-item'], + })); + + return { + type, + TypeEnum, + prefixCls, + defaultValue, + valueRange, + valueLoop, + valueWeek, + valueList, + valueWork, + maxValue, + minValue, + computeValue, + specifyRange, + updateValue, + parseValue, + beforeRadioAttrs, + inputNumberAttrs, + typeRangeAttrs, + typeLoopAttrs, + typeSpecifyAttrs, + }; +} diff --git a/src/components/Form/src/jeecg/components/JEasyCron/validator.ts b/src/components/Form/src/jeecg/components/JEasyCron/validator.ts new file mode 100644 index 0000000..308f1e8 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEasyCron/validator.ts @@ -0,0 +1,48 @@ +import CronParser from 'cron-parser'; +import type { ValidatorRule } from 'ant-design-vue/lib/form/interface'; + +const cronRule: ValidatorRule = { + validator({}, value) { + // 没填写就不校验 + if (!value) { + return Promise.resolve(); + } + const values: string[] = value.split(' ').filter((item) => !!item); + if (values.length > 7) { + return Promise.reject('Cron表达式最多7项!'); + } + // 检查第7项 + let val: string = value; + if (values.length === 7) { + const year = values[6]; + if (year !== '*' && year !== '?') { + let yearValues: string[] = []; + if (year.indexOf('-') >= 0) { + yearValues = year.split('-'); + } else if (year.indexOf('/')) { + yearValues = year.split('/'); + } else { + yearValues = [year]; + } + // 判断是否都是数字 + const checkYear = yearValues.some((item) => isNaN(Number(item))); + if (checkYear) { + return Promise.reject('Cron表达式参数[年]错误:' + year); + } + } + // 取其中的前六项 + val = values.slice(0, 6).join(' '); + } + // 6位 没有年 + // 5位没有秒、年 + try { + const iter = CronParser.parseExpression(val); + iter.next(); + return Promise.resolve(); + } catch (e) { + return Promise.reject('Cron表达式错误:' + e); + } + }, +}; + +export default cronRule.validator; diff --git a/src/components/Form/src/jeecg/components/JEditor.vue b/src/components/Form/src/jeecg/components/JEditor.vue new file mode 100644 index 0000000..d8a8626 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEditor.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JEllipsis.vue b/src/components/Form/src/jeecg/components/JEllipsis.vue new file mode 100644 index 0000000..fed14ac --- /dev/null +++ b/src/components/Form/src/jeecg/components/JEllipsis.vue @@ -0,0 +1,21 @@ + + diff --git a/src/components/Form/src/jeecg/components/JFormContainer.vue b/src/components/Form/src/jeecg/components/JFormContainer.vue new file mode 100644 index 0000000..6fe2398 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JFormContainer.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JImageUpload.vue b/src/components/Form/src/jeecg/components/JImageUpload.vue new file mode 100644 index 0000000..4292170 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JImageUpload.vue @@ -0,0 +1,282 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JImportModal.vue b/src/components/Form/src/jeecg/components/JImportModal.vue new file mode 100644 index 0000000..df2ff16 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JImportModal.vue @@ -0,0 +1,190 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JInput.vue b/src/components/Form/src/jeecg/components/JInput.vue new file mode 100644 index 0000000..dbb1d39 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JInput.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JInputPop.vue b/src/components/Form/src/jeecg/components/JInputPop.vue new file mode 100644 index 0000000..dc2da71 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JInputPop.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JInputSelect.vue b/src/components/Form/src/jeecg/components/JInputSelect.vue new file mode 100644 index 0000000..f537a3c --- /dev/null +++ b/src/components/Form/src/jeecg/components/JInputSelect.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JLinkTableCard/JLinkTableCard.vue b/src/components/Form/src/jeecg/components/JLinkTableCard/JLinkTableCard.vue new file mode 100644 index 0000000..7ea60c4 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JLinkTableCard/JLinkTableCard.vue @@ -0,0 +1,375 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JLinkTableCard/components/LinkTableListModal.vue b/src/components/Form/src/jeecg/components/JLinkTableCard/components/LinkTableListModal.vue new file mode 100644 index 0000000..f61a4e9 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JLinkTableCard/components/LinkTableListModal.vue @@ -0,0 +1,317 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JLinkTableCard/hooks/useLinkTable.ts b/src/components/Form/src/jeecg/components/JLinkTableCard/hooks/useLinkTable.ts new file mode 100644 index 0000000..d0f5798 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JLinkTableCard/hooks/useLinkTable.ts @@ -0,0 +1,356 @@ +import { defHttp } from '/@/utils/http/axios'; +import { ref, watchEffect, computed, reactive } from 'vue'; +import { pick } from 'lodash-es'; +import { filterMultiDictText } from '/@/utils/dict/JDictSelectUtil'; +import { getFileAccessHttpUrl } from '/@/utils/common/compUtils'; + +function queryTableData(tableName, params) { + const url = '/online/cgform/api/getData/' + tableName; + return defHttp.get({ url, params }); +} + +function queryTableColumns(tableName, params) { + const url = '/online/cgform/api/getColumns/' + tableName; + return defHttp.get({ url, params }); +} + +export function useLinkTable(props) { + //TODO 目前只支持查询第一页的数据,可以输入关键字搜索 + const pageNo = ref('1'); + // 查询列 + const baseParam = ref({}); + // 搜素条件 + const searchParam = ref({}); + // 第一个文本列 + const mainContentField = ref(''); + //权限数据 + const auths = reactive({ + add: true, + update: true, + }); + + //显示列 + const textFieldArray = computed(() => { + if (props.textField) { + return props.textField.split(','); + } + return []; + }); + const otherColumns = ref([]); + // 展示的列 配置的很多列,但是只展示三行 + const realShowColumns = computed(() => { + const columns = otherColumns.value; + if (props.multi == true) { + return columns.slice(0, 3); + } else { + return columns.slice(0, 6); + } + }); + + watchEffect(async () => { + const table = props.tableName; + if (table) { + const valueField = props.valueField || ''; + const textField = props.textField || ''; + const arr: any[] = []; + if (valueField) { + arr.push(valueField); + } + if (textField) { + const temp = textField.split(','); + mainContentField.value = temp[0]; + for (const field of temp) { + arr.push(field); + } + } + const imageField = props.imageField || ''; + if (imageField) { + arr.push(imageField); + } + baseParam.value = { + linkTableSelectFields: arr.join(','), + }; + await resetTableColumns(); + await reloadTableLinkOptions(); + } + }); + + const otherFields = computed(() => { + const textField = props.textField || ''; + const others: any[] = []; + let labelField = ''; + if (textField) { + const temp = textField.split(','); + labelField = temp[0]; + for (let i = 0; i < temp.length; i++) { + if (i > 0) { + others.push(temp[i]); + } + } + } + return { + others, + labelField, + }; + }); + + // 选项 + const selectOptions = ref([]); + const tableColumns = ref([]); + const dictOptions = ref({}); + + async function resetTableColumns() { + const params = baseParam.value; + const data = await queryTableColumns(props.tableName, params); + tableColumns.value = data.columns; + if (data.columns) { + const imageField = props.imageField; + const arr = data.columns.filter((c) => c.dataIndex != mainContentField.value && c.dataIndex != imageField); + otherColumns.value = arr; + } + dictOptions.value = data.dictOptions; + // 权限数据 + console.log('隐藏的按钮', data.hideColumns); + if (data.hideColumns) { + const hideCols = data.hideColumns; + if (hideCols.indexOf('add') >= 0) { + auths.add = false; + } else { + auths.add = true; + } + if (hideCols.indexOf('update') >= 0) { + auths.update = false; + } else { + auths.update = true; + } + } + } + + async function reloadTableLinkOptions() { + const params = getLoadDataParams(); + const data = await queryTableData(props.tableName, params); + const records = data.records; + //tableTitle.value = data.head.tableTxt; + const dataList: any[] = []; + const { others, labelField } = otherFields.value; + const imageField = props.imageField; + if (records && records.length > 0) { + for (const rd of records) { + const temp = { ...rd }; + transData(temp); + const result = Object.assign({}, pick(temp, others), { id: temp.id, label: temp[labelField], value: temp[props.valueField] }); + if (imageField) { + result[imageField] = temp[imageField]; + } + dataList.push(result); + } + } + //添加一个空对象 为add操作占位 + // 代码逻辑说明: 【TV360X-1095】高级查询关联记录去掉编辑按钮及去掉记录按钮 + props.editBtnShow && dataList.push({}); + selectOptions.value = dataList; + } + + /** + * 数据简单翻译-字典 + * @param data + */ + function transData(data) { + const columns = tableColumns.value; + const dictInfo = dictOptions.value; + for (const c of columns) { + const { dataIndex, customRender } = c; + if (data[dataIndex] || data[dataIndex] === 0) { + if (customRender && customRender == dataIndex) { + //这样的就是 字典数据了 可以直接翻译 + if (dictInfo[customRender]) { + data[dataIndex] = filterMultiDictText(dictInfo[customRender], data[dataIndex]); + continue; + } + } + } + // 兼容后台翻译字段 + const dictText = data[dataIndex + '_dictText']; + if (dictText) { + data[dataIndex] = dictText; + } + } + } + + //获取加载数据的查询条件 + function getLoadDataParams() { + const params = Object.assign({ pageSize: 100, pageNo: pageNo.value }, baseParam.value, searchParam.value); + return params; + } + + //设置查询条件 + function addQueryParams(text) { + if (!text) { + searchParam.value = {}; + } else { + const arr = textFieldArray.value; + const params: any[] = []; + const fields: any[] = []; + for (let i = 0; i < arr.length; i++) { + if (i <= 1) { + fields.push(arr[i]); + params.push({ field: arr[i], rule: 'like', val: text }); + } + } + // params[arr[i]] = `*${text}*` + // params['selectConditionFields'] = fields.join(',') + // searchParam.value = params; + params['superQueryMatchType'] = 'or'; + params['superQueryParams'] = encodeURI(JSON.stringify(params)); + searchParam.value = params; + } + } + + async function loadOne(value) { + if (!value) { + return []; + } + let valueFieldName = props.valueField; + let params = { + ...baseParam.value, + pageSize: 100, + pageNo: pageNo.value, + }; + params['superQueryMatchType'] = 'and'; + let valueCondition = [{ field: valueFieldName, rule: 'in', val: value }]; + params['superQueryParams'] = encodeURI(JSON.stringify(valueCondition)); + const data = await queryTableData(props.tableName, params); + let records = data.records; + //tableTitle.value = data.head.tableTxt; + let dataList: any[] = []; + if (records && records.length > 0) { + for (let item of records) { + let temp = { ...item }; + transData(temp); + dataList.push(temp); + } + } + return dataList; + } + + /** + * true:数据一致;false:数据不一致 + * @param arr + * @param value + */ + function compareData(arr, value) { + if (!arr || arr.length == 0) { + return false; + } + const valueArray = value.split(','); + if (valueArray.length != arr.length) { + return false; + } + let flag = true; + for (const item of arr) { + const temp = item[props.valueField]; + if (valueArray.indexOf(temp) < 0) { + flag = false; + } + } + return flag; + } + + function formatData(formData) { + Object.keys(formData).map((k) => { + if (formData[k] instanceof Array) { + formData[k] = formData[k].join(','); + } + }); + } + + function initFormData(formData, linkFieldArray, record) { + if (!record) { + record = {}; + } + if (linkFieldArray && linkFieldArray.length > 0) { + for (const str of linkFieldArray) { + const arr = str.split(','); + //["表单字段,表字典字段"] + const field = arr[0]; + const dictField = arr[1]; + if (!formData[field]) { + const value = record[dictField] || ''; + formData[field] = [value]; + } else { + formData[field].push(record[dictField]); + } + } + } + } + + // 获取图片地址 + function getImageSrc(item) { + if (props.imageField) { + let url = item[props.imageField]; + // 代码逻辑说明: 【TV360X-38】关联记录空间,被关联数据优多个图片时,封面图片不展示 + if (typeof url === 'string') { + // 有多张图时默认取第一张 + url = url.split(',')[0]; + } + return getFileAccessHttpUrl(url); + } + return ''; + } + const showImage = computed(() => { + if (props.imageField) { + return true; + } else { + return false; + } + }); + + return { + pageNo, + otherColumns, + realShowColumns, + selectOptions, + reloadTableLinkOptions, + textFieldArray, + addQueryParams, + tableColumns, + transData, + mainContentField, + loadOne, + compareData, + formatData, + initFormData, + getImageSrc, + showImage, + auths, + }; +} + +/** + * 使用固定高度的modal + */ +export function useFixedHeightModal() { + const minWidth = 800; + const popModalFixedWidth = ref(800); + let tempWidth = window.innerWidth - 300; + if (tempWidth < minWidth) { + tempWidth = minWidth; + } + popModalFixedWidth.value = tempWidth; + + // 弹窗高度控制 + const popBodyStyle = ref({}); + function resetBodyStyle() { + const height = window.innerHeight - 210; + popBodyStyle.value = { + height: height + 'px', + overflowY: 'auto', + }; + } + + return { + popModalFixedWidth, + popBodyStyle, + resetBodyStyle, + }; +} diff --git a/src/components/Form/src/jeecg/components/JMarkdownEditor.vue b/src/components/Form/src/jeecg/components/JMarkdownEditor.vue new file mode 100644 index 0000000..38ed12c --- /dev/null +++ b/src/components/Form/src/jeecg/components/JMarkdownEditor.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JPopup.vue b/src/components/Form/src/jeecg/components/JPopup.vue new file mode 100644 index 0000000..708caaf --- /dev/null +++ b/src/components/Form/src/jeecg/components/JPopup.vue @@ -0,0 +1,188 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/JPopupDict.vue b/src/components/Form/src/jeecg/components/JPopupDict.vue new file mode 100644 index 0000000..2b8030b --- /dev/null +++ b/src/components/Form/src/jeecg/components/JPopupDict.vue @@ -0,0 +1,236 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/JRangeDate.vue b/src/components/Form/src/jeecg/components/JRangeDate.vue new file mode 100644 index 0000000..eb252f9 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JRangeDate.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JRangeNumber.vue b/src/components/Form/src/jeecg/components/JRangeNumber.vue new file mode 100644 index 0000000..de95607 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JRangeNumber.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JRangeTime.vue b/src/components/Form/src/jeecg/components/JRangeTime.vue new file mode 100644 index 0000000..645b99d --- /dev/null +++ b/src/components/Form/src/jeecg/components/JRangeTime.vue @@ -0,0 +1,53 @@ + + + \ No newline at end of file diff --git a/src/components/Form/src/jeecg/components/JSearchSelect.vue b/src/components/Form/src/jeecg/components/JSearchSelect.vue new file mode 100644 index 0000000..3d1a504 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JSearchSelect.vue @@ -0,0 +1,554 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JSelectDepartPost.vue b/src/components/Form/src/jeecg/components/JSelectDepartPost.vue new file mode 100644 index 0000000..52856e9 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JSelectDepartPost.vue @@ -0,0 +1,180 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/JSelectDept.vue b/src/components/Form/src/jeecg/components/JSelectDept.vue new file mode 100644 index 0000000..9796e67 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JSelectDept.vue @@ -0,0 +1,198 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/JSelectInput.vue b/src/components/Form/src/jeecg/components/JSelectInput.vue new file mode 100644 index 0000000..9fa1c65 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JSelectInput.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JSelectMultiple.vue b/src/components/Form/src/jeecg/components/JSelectMultiple.vue new file mode 100644 index 0000000..2d2ceac --- /dev/null +++ b/src/components/Form/src/jeecg/components/JSelectMultiple.vue @@ -0,0 +1,244 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/JSelectPosition.vue b/src/components/Form/src/jeecg/components/JSelectPosition.vue new file mode 100644 index 0000000..fc7bc63 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JSelectPosition.vue @@ -0,0 +1,172 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/JSelectRole.vue b/src/components/Form/src/jeecg/components/JSelectRole.vue new file mode 100644 index 0000000..50bec10 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JSelectRole.vue @@ -0,0 +1,164 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/JSelectSingle.vue b/src/components/Form/src/jeecg/components/JSelectSingle.vue new file mode 100644 index 0000000..1b94045 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JSelectSingle.vue @@ -0,0 +1,323 @@ + + + + + + diff --git a/src/components/Form/src/jeecg/components/JSelectUser.vue b/src/components/Form/src/jeecg/components/JSelectUser.vue new file mode 100644 index 0000000..5f48f32 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JSelectUser.vue @@ -0,0 +1,212 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/JSelectUserByDepartment.vue b/src/components/Form/src/jeecg/components/JSelectUserByDepartment.vue new file mode 100644 index 0000000..1fdd5d3 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JSelectUserByDepartment.vue @@ -0,0 +1,176 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/JSelectUserByDept.vue b/src/components/Form/src/jeecg/components/JSelectUserByDept.vue new file mode 100644 index 0000000..9fe290e --- /dev/null +++ b/src/components/Form/src/jeecg/components/JSelectUserByDept.vue @@ -0,0 +1,157 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/JSelectUserByDeptPost.vue b/src/components/Form/src/jeecg/components/JSelectUserByDeptPost.vue new file mode 100644 index 0000000..ec7bf9b --- /dev/null +++ b/src/components/Form/src/jeecg/components/JSelectUserByDeptPost.vue @@ -0,0 +1,157 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/JSwitch.vue b/src/components/Form/src/jeecg/components/JSwitch.vue new file mode 100644 index 0000000..9b8d9ce --- /dev/null +++ b/src/components/Form/src/jeecg/components/JSwitch.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JTreeDict.vue b/src/components/Form/src/jeecg/components/JTreeDict.vue new file mode 100644 index 0000000..783d0ce --- /dev/null +++ b/src/components/Form/src/jeecg/components/JTreeDict.vue @@ -0,0 +1,141 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JTreeSelect.vue b/src/components/Form/src/jeecg/components/JTreeSelect.vue new file mode 100644 index 0000000..6fb1398 --- /dev/null +++ b/src/components/Form/src/jeecg/components/JTreeSelect.vue @@ -0,0 +1,457 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/JUpload/JUpload.vue b/src/components/Form/src/jeecg/components/JUpload/JUpload.vue new file mode 100644 index 0000000..418850f --- /dev/null +++ b/src/components/Form/src/jeecg/components/JUpload/JUpload.vue @@ -0,0 +1,461 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/JUpload/JUploadModal.vue b/src/components/Form/src/jeecg/components/JUpload/JUploadModal.vue new file mode 100644 index 0000000..083e1ec --- /dev/null +++ b/src/components/Form/src/jeecg/components/JUpload/JUploadModal.vue @@ -0,0 +1,45 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JUpload/components/UploadItemActions.vue b/src/components/Form/src/jeecg/components/JUpload/components/UploadItemActions.vue new file mode 100644 index 0000000..61586ab --- /dev/null +++ b/src/components/Form/src/jeecg/components/JUpload/components/UploadItemActions.vue @@ -0,0 +1,90 @@ + + + diff --git a/src/components/Form/src/jeecg/components/JUpload/index.ts b/src/components/Form/src/jeecg/components/JUpload/index.ts new file mode 100644 index 0000000..740bf2d --- /dev/null +++ b/src/components/Form/src/jeecg/components/JUpload/index.ts @@ -0,0 +1,3 @@ +export { UploadTypeEnum } from './upload.data'; +export { default as JUpload } from './JUpload.vue'; +export { default as JUploadModal } from './JUploadModal.vue'; diff --git a/src/components/Form/src/jeecg/components/JUpload/upload.data.ts b/src/components/Form/src/jeecg/components/JUpload/upload.data.ts new file mode 100644 index 0000000..820146d --- /dev/null +++ b/src/components/Form/src/jeecg/components/JUpload/upload.data.ts @@ -0,0 +1,5 @@ +export enum UploadTypeEnum { + all = 'all', + image = 'image', + file = 'file', +} diff --git a/src/components/Form/src/jeecg/components/TreeIcon/TreeIcon.vue b/src/components/Form/src/jeecg/components/TreeIcon/TreeIcon.vue new file mode 100644 index 0000000..7075344 --- /dev/null +++ b/src/components/Form/src/jeecg/components/TreeIcon/TreeIcon.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/base/JSelectBiz.vue b/src/components/Form/src/jeecg/components/base/JSelectBiz.vue new file mode 100644 index 0000000..bafa6db --- /dev/null +++ b/src/components/Form/src/jeecg/components/base/JSelectBiz.vue @@ -0,0 +1,220 @@ + + + diff --git a/src/components/Form/src/jeecg/components/base/JTreeBiz.vue b/src/components/Form/src/jeecg/components/base/JTreeBiz.vue new file mode 100644 index 0000000..cd65c5f --- /dev/null +++ b/src/components/Form/src/jeecg/components/base/JTreeBiz.vue @@ -0,0 +1,91 @@ + + + diff --git a/src/components/Form/src/jeecg/components/modal/DeptSelectModal.vue b/src/components/Form/src/jeecg/components/modal/DeptSelectModal.vue new file mode 100644 index 0000000..fd9ca8f --- /dev/null +++ b/src/components/Form/src/jeecg/components/modal/DeptSelectModal.vue @@ -0,0 +1,223 @@ + + + + diff --git a/src/components/Form/src/jeecg/components/modal/JPopupOnlReportModal.vue b/src/components/Form/src/jeecg/components/modal/JPopupOnlReportModal.vue new file mode 100644 index 0000000..b0f4bff --- /dev/null +++ b/src/components/Form/src/jeecg/components/modal/JPopupOnlReportModal.vue @@ -0,0 +1,348 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/modal/JSelectUserByDepartmentModal.vue b/src/components/Form/src/jeecg/components/modal/JSelectUserByDepartmentModal.vue new file mode 100644 index 0000000..cc946c0 --- /dev/null +++ b/src/components/Form/src/jeecg/components/modal/JSelectUserByDepartmentModal.vue @@ -0,0 +1,935 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/modal/PositionSelectModal.vue b/src/components/Form/src/jeecg/components/modal/PositionSelectModal.vue new file mode 100644 index 0000000..cc7470f --- /dev/null +++ b/src/components/Form/src/jeecg/components/modal/PositionSelectModal.vue @@ -0,0 +1,186 @@ + + + diff --git a/src/components/Form/src/jeecg/components/modal/RoleSelectModal.vue b/src/components/Form/src/jeecg/components/modal/RoleSelectModal.vue new file mode 100644 index 0000000..6dc4e82 --- /dev/null +++ b/src/components/Form/src/jeecg/components/modal/RoleSelectModal.vue @@ -0,0 +1,129 @@ + + + diff --git a/src/components/Form/src/jeecg/components/modal/UserSelectByDepModal.vue b/src/components/Form/src/jeecg/components/modal/UserSelectByDepModal.vue new file mode 100644 index 0000000..fcb7c85 --- /dev/null +++ b/src/components/Form/src/jeecg/components/modal/UserSelectByDepModal.vue @@ -0,0 +1,265 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/modal/UserSelectByDepPostModal.vue b/src/components/Form/src/jeecg/components/modal/UserSelectByDepPostModal.vue new file mode 100644 index 0000000..3debe13 --- /dev/null +++ b/src/components/Form/src/jeecg/components/modal/UserSelectByDepPostModal.vue @@ -0,0 +1,296 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/modal/UserSelectModal.vue b/src/components/Form/src/jeecg/components/modal/UserSelectModal.vue new file mode 100644 index 0000000..77f03fc --- /dev/null +++ b/src/components/Form/src/jeecg/components/modal/UserSelectModal.vue @@ -0,0 +1,330 @@ + + + diff --git a/src/components/Form/src/jeecg/components/positionSelect/PositionSelectModal.vue b/src/components/Form/src/jeecg/components/positionSelect/PositionSelectModal.vue new file mode 100644 index 0000000..88866c1 --- /dev/null +++ b/src/components/Form/src/jeecg/components/positionSelect/PositionSelectModal.vue @@ -0,0 +1,282 @@ + + + + + + diff --git a/src/components/Form/src/jeecg/components/roleSelect/RoleSelectInput.vue b/src/components/Form/src/jeecg/components/roleSelect/RoleSelectInput.vue new file mode 100644 index 0000000..a856565 --- /dev/null +++ b/src/components/Form/src/jeecg/components/roleSelect/RoleSelectInput.vue @@ -0,0 +1,242 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/roleSelect/RoleSelectModal.vue b/src/components/Form/src/jeecg/components/roleSelect/RoleSelectModal.vue new file mode 100644 index 0000000..78f23bd --- /dev/null +++ b/src/components/Form/src/jeecg/components/roleSelect/RoleSelectModal.vue @@ -0,0 +1,318 @@ + + + + + + diff --git a/src/components/Form/src/jeecg/components/userSelect/FilteredUserSelectModal.vue b/src/components/Form/src/jeecg/components/userSelect/FilteredUserSelectModal.vue new file mode 100644 index 0000000..fe4d03e --- /dev/null +++ b/src/components/Form/src/jeecg/components/userSelect/FilteredUserSelectModal.vue @@ -0,0 +1,448 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/userSelect/SelectedUserItem.vue b/src/components/Form/src/jeecg/components/userSelect/SelectedUserItem.vue new file mode 100644 index 0000000..0384c6c --- /dev/null +++ b/src/components/Form/src/jeecg/components/userSelect/SelectedUserItem.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/userSelect/UserList.vue b/src/components/Form/src/jeecg/components/userSelect/UserList.vue new file mode 100644 index 0000000..3f8a6e3 --- /dev/null +++ b/src/components/Form/src/jeecg/components/userSelect/UserList.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/userSelect/UserListAndDepart.vue b/src/components/Form/src/jeecg/components/userSelect/UserListAndDepart.vue new file mode 100644 index 0000000..2699f72 --- /dev/null +++ b/src/components/Form/src/jeecg/components/userSelect/UserListAndDepart.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/userSelect/UserListAndRole.vue b/src/components/Form/src/jeecg/components/userSelect/UserListAndRole.vue new file mode 100644 index 0000000..5139c54 --- /dev/null +++ b/src/components/Form/src/jeecg/components/userSelect/UserListAndRole.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/userSelect/UserSelectModal.vue b/src/components/Form/src/jeecg/components/userSelect/UserSelectModal.vue new file mode 100644 index 0000000..f523f7a --- /dev/null +++ b/src/components/Form/src/jeecg/components/userSelect/UserSelectModal.vue @@ -0,0 +1,376 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/userSelect/index.vue b/src/components/Form/src/jeecg/components/userSelect/index.vue new file mode 100644 index 0000000..7a8f9cd --- /dev/null +++ b/src/components/Form/src/jeecg/components/userSelect/index.vue @@ -0,0 +1,272 @@ + + + + + diff --git a/src/components/Form/src/jeecg/components/userSelect/useUserSelect.ts b/src/components/Form/src/jeecg/components/userSelect/useUserSelect.ts new file mode 100644 index 0000000..2ef7a2e --- /dev/null +++ b/src/components/Form/src/jeecg/components/userSelect/useUserSelect.ts @@ -0,0 +1,11 @@ +/** + * 用户选择组件支持选择 我自己,以表达式的形式传值 + */ +export const mySelfExpress = '#{sys_user_code}'; + +/** + * 用户列表 我自己的数据 + */ +export const mySelfData = { + id: mySelfExpress, username: mySelfExpress, realname: '当前用户', avatarIcon: 'idcard-outlined', avatarColor: 'rgb(75 176 79)' +} diff --git a/src/components/Form/src/jeecg/hooks/useCodeHinting.ts b/src/components/Form/src/jeecg/hooks/useCodeHinting.ts new file mode 100644 index 0000000..76e019e --- /dev/null +++ b/src/components/Form/src/jeecg/hooks/useCodeHinting.ts @@ -0,0 +1,142 @@ +export const useCodeHinting = (CodeMirror, keywords, language) => { + const currentKeywords: any = [...keywords]; + const codeHintingMount = (coder) => { + if (keywords.length) { + coder.setOption('mode', language); + setTimeout(() => { + coder!.on('cursorActivity', function () { + coder?.showHint({ + completeSingle: false, + // container: containerRef.value + }); + }); + }, 1e3); + } + }; + + const codeHintingRegistry = () => { + // 自定义关键词(.的上一级) + const customKeywords: string[] = []; + + currentKeywords.forEach((item) => { + if (item.superiors) { + customKeywords.push(item.superiors); + } + }); + const funcsHint = (cm, callback) => { + // 获取光标位置 + const cur = cm.getCursor(); + // 获取当前单词的信息 + const token = cm.getTokenAt(cur); + const start = token.start; + const end = cur.ch; + const str = token.string; + let recordKeyword = null; + console.log('光标位置:', cur, '单词信息:', token, `start:${start},end:${end},str:${str}`); + + if (str.length) { + if (str === '.') { + // 查找.前面是否有定义的关键词 + const curLineCode = cm.getLine(cur.line); + for (let i = 0, len = customKeywords.length; i < len; i++) { + const k = curLineCode.slice(-(customKeywords[i].length + 1), -1); + if (customKeywords.includes(k)) { + recordKeyword = k; + break; + } + } + } else { + // 查找单词前面是否有.this(.关键词) + const curLineCode = cm.getLine(cur.line); + for (let i = 0, len = customKeywords.length; i < len; i++) { + const k = curLineCode.slice(start - (customKeywords[i].length + 1), start); + if (k.substr(-1) === '.' && customKeywords.includes(k.replace('.', ''))) { + recordKeyword = k.replace('.', ''); + break; + } + } + } + const findIdx = (a, b) => a.toLowerCase().indexOf(b.toLowerCase()); + let list = currentKeywords.filter((item) => { + if (recordKeyword) { + // 查特定对象下的属性or方法 + return item.superiors === recordKeyword; + } else { + // 查全局属性或者方法 + return item.superiors == undefined; + } + }); + if (str === '.') { + if (recordKeyword == null) { + list = []; + } + } else { + list = list + .filter((item) => { + const { text } = item; + const index = findIdx(text, str); + let result = text.startsWith('.') ? index === 1 : index === 0; + return result; + }) + .sort((a, b) => { + if (findIdx(a.text, str) < findIdx(b.text, str)) { + return -1; + } else { + return 1; + } + }); + } + + if (list.length === 1) { + // 只有一个时可能是自己输入,输到最后需要去掉提示。 + const item = list[0]; + if (item.text === str || item.text.substring(1) === str) { + list = []; + } + } + if (list.length) { + // 当str不是点时去掉点 + if (str != '.') { + list = list.map((item) => { + if (item.text.indexOf('.') === 0) { + return { ...item, text: item.text.substring(1) }; + } + return item; + }); + } + callback({ + list: list, + from: CodeMirror.Pos(cur.line, start), + to: CodeMirror.Pos(cur.line, end), + }); + // 代码逻辑说明: 【QQYUN-8865】js增强加上鼠标移入提示 + const item = currentKeywords[0]; + if (item?.desc) { + setTimeout(() => { + const elem: HTMLUListElement = document.querySelector('.CodeMirror-hints')!; + if (elem) { + const childElems = elem.children; + Array.from(childElems).forEach((item) => { + const displayText = item.textContent; + const findItem = currentKeywords.find((item) => item.displayText === displayText); + if (findItem) { + item.setAttribute('title', findItem.desc); + } + }); + } + }, 0); + } + } else { + } + } + }; + funcsHint.async = true; + funcsHint.supportsSelection = true; + // 自动补全 + keywords.length && CodeMirror.registerHelper('hint', language, funcsHint); + }; + return { + codeHintingRegistry, + codeHintingMount, + }; +}; diff --git a/src/components/Form/src/jeecg/hooks/useSelectBiz.ts b/src/components/Form/src/jeecg/hooks/useSelectBiz.ts new file mode 100644 index 0000000..a40cecc --- /dev/null +++ b/src/components/Form/src/jeecg/hooks/useSelectBiz.ts @@ -0,0 +1,172 @@ +import { inject, reactive, ref, watch, unref, Ref } from 'vue'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { isEmpty } from '@/utils/is'; + +export function useSelectBiz(getList, props, emit?) { + //接收下拉框选项 + const selectOptions = inject('selectOptions', ref>([])); + //接收已选择的值 + const selectValues = inject('selectValues', reactive({ value: [], change: false })); + // 是否正在加载回显 + const loadingEcho = inject>('loadingEcho', ref(false)); + //数据集 + const dataSource = ref>([]); + //已选择的值 + const checkedKeys = ref>([]); + //选则的行记录 + const selectRows = ref>([]); + //提示弹窗 + const $message = useMessage(); + // 是否是首次加载回显,只有首次加载,才会显示 loading + let isFirstLoadEcho = true; + + /** + * 监听selectValues变化 + */ + watch( + selectValues, + () => { + //if (selectValues['change'] == false && !isEmpty(selectValues['value'])) { + if (selectValues['change'] == false && !isEmpty(selectValues['value'])) { + // 代码逻辑说明: 【issues/7405】部门选择用户同时全部选择两页用户,回显到父页面。第二页用户显示的不是真是姓名 + let params = { isMultiTranslate: 'true', pageSize: selectValues.value?.length }; + params[props.rowKey] = selectValues['value'].join(','); + loadingEcho.value = isFirstLoadEcho; + isFirstLoadEcho = false; + getDataSource(params, true) + .then() + .finally(() => { + loadingEcho.value = isFirstLoadEcho; + }); + } + //设置列表默认选中 + // 代码逻辑说明: 【QQYUN-12155】弹窗中勾选,再点取消,值被选中了 + checkedKeys['value'] = [...selectValues['value']]; + }, + { immediate: true } + ); + + async function onSelectChange(selectedRowKeys: (string | number)[], selectRow) { + checkedKeys.value = selectedRowKeys; + //判断全选的问题checkedKeys和selectRows必须一致 + if (props.showSelected && unref(checkedKeys).length !== unref(selectRow).length) { + let { records } = await getList({ + code: unref(checkedKeys).join(','), + pageSize: unref(checkedKeys).length, + }); + selectRows.value = records; + } else { + selectRows.value = selectRow; + } + } + + /** + * 选择列配置 + */ + const rowSelection = { + // 代码逻辑说明: 动态设置rowSelection的type值,默认是'checkbox' --- + type: props.isRadioSelection ? 'radio' : 'checkbox', + columnWidth: 20, + selectedRowKeys: checkedKeys, + onChange: onSelectChange, + //table4.4.0新增属性选中之后是否清空上一页下一页的数据,默认false + preserveSelectedRowKeys:true, + }; + + /** + * 序号列配置 + */ + const indexColumnProps = { + dataIndex: 'index', + width: 50, + }; + + /** + * 加载列表数据集 + * @param params + * @param flag 是否是默认回显模式加载 + */ + async function getDataSource(params, flag) { + let { records } = await getList(params); + dataSource.value = records; + if (flag) { + let options = []; + records.forEach((item) => { + options.push({ label: item[props.labelKey], value: item[props.rowKey] }); + }); + selectOptions.value = options; + } + } + async function initSelectRows() { + let { records } = await getList({ + code: selectValues['value'].join(','), + pageSize: selectValues['value'].length, + }); + // 代码逻辑说明: 【QQYUN-12155】弹窗中勾选,再点取消,值被选中了 + checkedKeys['value'] = [...selectValues['value']]; + selectRows['value'] = records; + } + + /** + * 弹出框显示隐藏触发事件 + */ + async function visibleChange(visible) { + if (visible) { + // 代码逻辑说明: 【QQYUN-12179】弹窗勾选了值,点击取消再次打开弹窗遗留了上次的勾选的值 + checkedKeys['value'] = [...selectValues['value']]; + //设置列表默认选中 + props.showSelected && initSelectRows(); + } else { + // 代码逻辑说明: 【QQYUN-9366】用户选择组件取消和关闭会把选择数据带入 + emit?.('close'); + } + } + + /** + * 确定选择 + */ + function getSelectResult(success) { + let options = []; + let values = []; + selectRows.value.forEach((item) => { + options.push({ label: item[props.labelKey], value: item[props.rowKey] }); + }); + checkedKeys.value.forEach((item) => { + values.push(item); + }); + selectOptions.value = options; + if (props.maxSelectCount && values.length > props.maxSelectCount) { + $message.createMessage.warning(`最多只能选择${props.maxSelectCount}条数据`); + return false; + } + success && success(options, values); + } + //删除已选择的信息 + function handleDeleteSelected(record) { + // 代码逻辑说明: 【issues/424】开启右侧列表后,在右侧列表中删除用户时,逻辑有问题------------ + checkedKeys.value = checkedKeys.value.filter((item) => item != record[props.rowKey]); + selectRows.value = selectRows.value.filter((item) => item[props.rowKey] !== record[props.rowKey]); + } + //清空选择项 + function reset() { + checkedKeys.value = []; + selectRows.value = []; + } + return [ + { + onSelectChange, + getDataSource, + visibleChange, + selectOptions, + selectValues, + rowSelection, + indexColumnProps, + checkedKeys, + selectRows, + dataSource, + getSelectResult, + handleDeleteSelected, + reset, + }, + ]; +} diff --git a/src/components/Form/src/jeecg/hooks/useSelectMultipleScrollLoad.ts b/src/components/Form/src/jeecg/hooks/useSelectMultipleScrollLoad.ts new file mode 100644 index 0000000..6dc2ff3 --- /dev/null +++ b/src/components/Form/src/jeecg/hooks/useSelectMultipleScrollLoad.ts @@ -0,0 +1,182 @@ +import { computed, ref, Ref, unref } from 'vue'; +import { useDebounceFn } from '@vueuse/core'; +import { defHttp } from '/@/utils/http/axios'; + +/** 触发「加载更多」的滚动剩余距离阈值(px) */ +const SCROLL_LOAD_THRESHOLD = 10; +/** 搜索输入防抖时间(ms) */ +const SEARCH_DEBOUNCE_MS = 300; + +export interface ScrollLoadDictProps { + dictCode?: string; + pageSize: number; + scrollLoad: boolean; +} + +export function useScrollLoadDict(props: ScrollLoadDictProps, dictOptions: Ref, arrayValue: Ref) { + // --- 状态 --- + const loading = ref(false); + const pageNo = ref(1); + const isHasData = ref(true); + const scrollLoading = ref(false); + const searchKeyword = ref(''); + + // --- 计算:是否表字典、是否启用滚动加载 --- + const isDictTable = computed(() => { + if (!props.dictCode) return false; + return props.dictCode.split(',').length >= 2; + }); + const useLoadDict = computed(() => props.scrollLoad && isDictTable.value); + + /** + * 拉取一页字典数据(loadDict 接口)。 + * @param pageNoToLoad 页码(从 1 开始) + * @param isAppend true=追加到当前列表,false=替换 + * @param keyword 搜索关键字,不传则用内部的 searchKeyword + */ + function fetchLoadDictPage(pageNoToLoad: number, isAppend: boolean, keyword?: string) { + const kw = keyword !== undefined ? keyword : searchKeyword.value; + return defHttp + .get({ + url: `/sys/dict/loadDict/${props.dictCode}`, + params: { pageNo: pageNoToLoad, pageSize: props.pageSize, keyword: kw || '', order: 'asc' }, + }) + .then((res: any) => { + const items = (res || []).map((item: any) => ({ + value: item.value, + label: item.text || item.label, + text: item.text || item.label, + color: item.color, + })); + if (items.length > 0) { + if (isAppend) { + // 追加时按 value 去重,避免与回显补的项重复 + const existValues = new Set(dictOptions.value.map((o) => String(o.value))); + const newItems = items.filter((it: any) => !existValues.has(String(it.value))); + if (newItems.length > 0) { + dictOptions.value = dictOptions.value.concat(newItems); + } + } else { + // 有选中的值且optinos中存在时,需要把选中的值在options中存在且不在新数据中的项保留(防止多次请求) + if (unref(arrayValue).length && unref(dictOptions).length) { + unref(arrayValue).forEach((val: any) => { + const existOption = unref(dictOptions).find((o: any) => String(o.value) === String(val)); + if (existOption && !items.some((item: any) => String(item.value) === String(val))) { + items.push(existOption); + } + }); + } + dictOptions.value = items; + } + pageNo.value = pageNoToLoad + 1; + } else { + if (!isAppend) dictOptions.value = []; + isHasData.value = false; + } + }); + } + + function fetchDictItemByValue(val: any) { + if (val == null || !props.dictCode) return Promise.resolve(null); + return defHttp + .get({ url: `/sys/dict/loadDictItem/${props.dictCode}`, params: { key: val } }) + .then((res: any) => { + if (Array.isArray(res) && res.length > 0) { + const first = res[0]; + if (typeof first === 'string') { + return { value: val, label: first, text: first, color: undefined }; + } + return { + value: first.value ?? val, + label: first.text ?? first.label, + text: first.text ?? first.label ?? '', + color: first.color, + }; + } + return null; + }) + .catch(() => null); + } + + function ensureValueInOptions() { + if (!useLoadDict.value) return; + const vals = arrayValue.value; + if (!vals || vals.length === 0) return; + const existSet = new Set(dictOptions.value.map((o) => String(o.value))); + const missing = vals.filter((v) => !existSet.has(String(v))); + if (missing.length === 0) return; + Promise.all(missing.map((v) => fetchDictItemByValue(v))).then((items) => { + const newItems = items.filter(Boolean); + if (newItems.length > 0) { + dictOptions.value = [...dictOptions.value, ...newItems]; + } + }); + } + + /** 搜索:带防抖,用 keyword 拉取第一页并替换列表 */ + const handleSearch = useDebounceFn((keyword: string) => { + if (!useLoadDict.value) return; + searchKeyword.value = keyword || ''; + pageNo.value = 1; + isHasData.value = true; + loading.value = true; + fetchLoadDictPage(1, false, searchKeyword.value).finally(() => { + loading.value = false; + ensureValueInOptions(); + }); + }, SEARCH_DEBOUNCE_MS); + + function handleDropdownVisibleChange(open: boolean) { + if (!useLoadDict.value || !open) return; + if (!searchKeyword.value) return; + searchKeyword.value = ''; + pageNo.value = 1; + isHasData.value = true; + loading.value = true; + fetchLoadDictPage(1, false, '').finally(() => { + loading.value = false; + ensureValueInOptions(); + }); + } + + /** 初始加载字典:拉取第一页 */ + function loadDictOptions() { + if (!useLoadDict.value) return; + pageNo.value = 1; + isHasData.value = true; + loading.value = true; + fetchLoadDictPage(1, false).finally(() => { + loading.value = false; + ensureValueInOptions(); + }); + } + + /** 下拉内滚动触底时加载下一页,按 value 去重后追加 */ + function handlePopupScroll(e: Event) { + if (!useLoadDict.value) return; + const target = e.target as HTMLElement; + const { scrollTop, scrollHeight, clientHeight } = target; + const nearBottom = scrollTop + clientHeight >= scrollHeight - SCROLL_LOAD_THRESHOLD; + if (!scrollLoading.value && isHasData.value && nearBottom) { + scrollLoading.value = true; + fetchLoadDictPage(pageNo.value, true) + .finally(() => { + scrollLoading.value = false; + }) + .catch(() => { + if (pageNo.value > 1) pageNo.value--; + }); + } + } + + return { + isDictTable, + useLoadDict, + loading, + loadDictOptions, + ensureValueInOptions, + handleSearch, + handleDropdownVisibleChange, + handlePopupScroll, + }; +} diff --git a/src/components/Form/src/jeecg/hooks/useTreeBiz.ts b/src/components/Form/src/jeecg/hooks/useTreeBiz.ts new file mode 100644 index 0000000..6a1ed04 --- /dev/null +++ b/src/components/Form/src/jeecg/hooks/useTreeBiz.ts @@ -0,0 +1,468 @@ +import type { Ref } from 'vue'; +import { inject, reactive, ref, computed, unref, watch, nextTick } from 'vue'; +import { TreeActionType } from '/@/components/Tree'; +import { listToTree } from '/@/utils/common/compUtils'; +import { isEqual } from 'lodash-es'; +import { defHttp } from "@/utils/http/axios"; +import { queryAllParentId } from "/@/api/common/api"; + +export function useTreeBiz(treeRef, getList, props, realProps, emit) { + //接收下拉框选项 + const selectOptions = inject('selectOptions', ref>([])); + //接收已选择的值 + const selectValues = inject('selectValues', reactive({})); + // 是否正在加载回显 + const loadingEcho = inject>('loadingEcho', ref(false)); + //数据集 + const treeData = ref>([]); + //已选择的值 + const checkedKeys = ref>([]); + //选则的行记录 + const selectRows = ref>([]); + //是否是打开弹框模式 + const openModal = ref(false); + //是否层级关联 + const checkStrictly = ref(realProps.multiple ? props.checkStrictly : true); + // 是否开启父子关联,如果不可以多选,就始终取消父子关联 + const getCheckStrictly = computed(() => checkStrictly.value); + // 是否是首次加载回显,只有首次加载,才会显示 loading + let isFirstLoadEcho = true; + let prevSelectValues = []; + // 需要展开的父节点ID列表 + const expandedKeys = ref>([]); + // 是否启用自动展开功能(可以通过props控制) + const enableAutoExpand = props.enableAutoExpand !== false; + /** + * 监听selectValues变化 + */ + watch( + selectValues, + ({ value: values }: Recordable) => { + if(!values){ + return; + } + // 代码逻辑说明: 【issues/8232】代码设置JSelectDept组件值没翻译 + if (values.length > 0) { + // 防止多次请求 + if (isEqual(values, prevSelectValues)) return; + prevSelectValues = values; + loadingEcho.value = isFirstLoadEcho; + isFirstLoadEcho = false; + onLoadData(null, values.join(',')).finally(() => { + loadingEcho.value = false; + }); + } + }, + { immediate: true } + ); + + /** + * 获取树实例 + */ + function getTree() { + const tree = unref(treeRef); + if (!tree) { + //throw new Error('tree is null!'); + return null; + } + return tree; + } + + /** + * 获取需要展开的父节点ID + */ + async function getParentIdsToExpand(selectedIds) { + if (!selectedIds || selectedIds.length === 0) return []; + + try { + const result = await queryAllParentId({ + departId: selectedIds.join(','), + orgCode: props.params?.orgCode + }); + + if (result) { + const allParentIds = []; + // 处理 Map 或 Object 结构 + const valuesToProcess = result instanceof Map + ? Array.from(result.values()) + : Object.values(result); + + // 遍历所有选中节点的父节点 + valuesToProcess.forEach((nodeData: any) => { + if (nodeData && nodeData.parentIds && Array.isArray(nodeData.parentIds)) { + // 添加父节点ID(不包含选中节点本身) + const parentIds = nodeData.parentIds.filter(id => !selectedIds.includes(id)); + allParentIds.push(...parentIds); + } + }); + + return [...new Set(allParentIds)]; // 去重 + } + return []; + } catch (error) { + console.warn('获取父节点ID失败:', error); + return []; + } + } + + /** + * 设置树展开级别 + */ + function expandTree() { + nextTick(() => { + if (props.defaultExpandLevel && props.defaultExpandLevel > 0) { + getTree().filterByLevel(props.defaultExpandLevel); + } + //设置列表默认选中 + checkedKeys.value = selectValues['value']; + + // 如果有需要展开的父节点,则展开它们 + if (expandedKeys.value.length > 0) { + getTree().setExpandedKeys(expandedKeys.value); + } + }).then(() => { + // 再次确保展开,因为树可能还没有完全渲染 + if (expandedKeys.value.length > 0) { + setTimeout(() => { + getTree().setExpandedKeys(expandedKeys.value); + }, 100); + } + }); + } + + /** + * 树节点选择 + */ + function onSelect(keys, info) { + if (props.checkable == false) { + checkedKeys.value = props.checkStrictly ? keys.checked : keys; + const { selectedNodes } = info; + let rows = []; + selectedNodes.forEach((item) => { + rows.push(item); + }); + selectRows.value = rows; + } + } + + /** + * 树节点选择 + */ + function onCheck(keys, info) { + if(!info){ + return; + } + if (props.checkable == true) { + // 如果不能多选,就只保留最后一个选中的 + if (!realProps.multiple) { + if (info.checked) { + // 代码逻辑说明: 单选模式下,设定rowKey,无法选中数据- + checkedKeys.value = [info.node.eventKey]; + let rowKey = props.rowKey; + let temp = info.checkedNodes.find((n) => n[rowKey] === info.node.eventKey); + selectRows.value = [temp]; + } else { + checkedKeys.value = []; + selectRows.value = []; + } + return; + } + // 代码逻辑说明: 【JHHB-250】选择部门加一个层级关联/独立的配置,现在是点击就全勾选了--- + checkedKeys.value = checkStrictly.value ? keys.checked : keys; + const { checkedNodes } = info; + let rows = []; + checkedNodes.forEach((item) => { + rows.push(item); + }); + selectRows.value = rows; + } + } + + /** + * 勾选全部 + */ + async function checkALL(checkAll) { + getTree().checkAll(checkAll); + // 代码逻辑说明: 【issues/394】所属部门树操作全部勾选不生效/【issues/4646】部门全部勾选后,点击确认按钮,部门信息丢失------------ + await nextTick(); + checkedKeys.value = getTree().getCheckedKeys(); + if(checkAll){ + getTreeRow(); + }else{ + selectRows.value = []; + } + } + + /** + * 获取数列表 + * @param res + */ + function getTreeRow() { + let ids = ""; + if(unref(checkedKeys).length>0){ + ids = checkedKeys.value.join(","); + } + getList({ids:ids}).then((res) =>{ + selectRows.value = res; + }) + } + + /** + * 展开全部 + */ + function expandAll(expandAll) { + getTree().expandAll(expandAll); + } + + /** + * 加载树数据 + */ + async function onLoadData(treeNode, ids) { + let params = {}; + let startPid = ''; + if (treeNode) { + startPid = treeNode.eventKey; + // 代码逻辑说明: rowkey不设置成id,sync开启异步的时候,点击上级下级不显示------------ + params['pid'] = treeNode.value; + } + if (ids) { + startPid = ''; + params['ids'] = ids; + } + + if(props.params?.departIds){ + params['departIds'] = props.params.departIds; + } + let record = await getList(params); + let optionData = record; + //只展示公司信息(公司+子公司) + if(props.onlyShowCompany){ + record = getCompanyData(record) + } + //是否只选择部门岗位 + if (props.izOnlySelectDepartPost) { + setCompanyDepartCheckable(record); + } + //是否缩写departNameAbbr + if (props.izShowDepartNameAbbr) { + record = getDepartAbbrData(record); + } + if (!props.serverTreeData) { + //前端处理数据为tree结构 + record = listToTree(record, props, startPid); + if (record.length == 0 && treeNode) { + checkHasChild(startPid, treeData.value); + } + } + + if (openModal.value == true) { + //弹框模式下加载全部数据 + if (!treeNode) { + treeData.value = record; + } else { + return new Promise((resolve: (value?: unknown) => void) => { + if (!treeNode.children) { + resolve(); + return; + } + const asyncTreeAction: TreeActionType | null = unref(treeRef); + if (asyncTreeAction) { + asyncTreeAction.updateNodeByKey(treeNode.eventKey, { children: record }); + asyncTreeAction.setExpandedKeys([treeNode.eventKey, ...asyncTreeAction.getExpandedKeys()]); + } + resolve(); + return; + }); + } + expandTree(); + } else { + const options = []; + optionData.forEach((item) => { + // 代码逻辑说明: issues/I5F3P4 online配置部门选择后编辑,查看数据应该显示部门名称,不是部门代码 + options.push({ label: item[props.labelKey], value: item[props.rowKey] }); + }); + selectOptions.value = options; + } + } + + /** + * 获取到公司/子公司数据 + * @param record + */ + function getCompanyData(record){ + const companyData = record.filter(item=>item.orgCategory && ['1','4'].includes(item.orgCategory)); + return companyData + } + /** + * 获取到公司/子公司数据 + * @param record + */ + function getDepartAbbrData(record){ + const departAbbrData = record; + departAbbrData.forEach(item=>{ + item.title = item.departNameAbbr || item.title; + }) + return departAbbrData + } + /** + * 异步加载时检测是否含有下级节点 + * @param pid 父节点 + * @param treeArray tree数据 + */ + function checkHasChild(pid, treeArray) { + if (treeArray && treeArray.length > 0) { + for (let item of treeArray) { + if (item.key == pid) { + if (!item.child) { + item.isLeaf = true; + } + break; + } else { + checkHasChild(pid, item.children); + } + } + } + } + + /** + * 获取已选择数据 + */ + function getSelectTreeData(success) { + const options = []; + const values = []; + selectRows.value.forEach((item) => { + options.push({ label: item[props.labelKey], value: item[props.rowKey] }); + }); + checkedKeys.value.forEach((item) => { + values.push(item); + }); + selectOptions.value = options; + success && success(options, values); + } + + /** + * 弹出框显示隐藏触发事件 + */ + async function visibleChange(visible) { + if (visible) { + //弹出框打开时加载全部数据 + openModal.value = true; + await onLoadData(null, null); + + // 在数据加载完成后,如果有选中的值且启用了自动展开功能,则展开父节点 + if (enableAutoExpand && selectValues.value && selectValues.value.length > 0) { + try { + const selectedIds = selectValues.value; + const parentIds = await getParentIdsToExpand(selectedIds); + + if (parentIds.length > 0) { + expandedKeys.value = parentIds; + + // 延迟展开,确保树已经渲染完成 + nextTick(() => { + try { + const tree = getTree(); + if (tree) { + tree.setExpandedKeys(parentIds); + + // 再次确保展开 + setTimeout(() => { + try { + const tree = getTree(); + if (tree) { + tree.setExpandedKeys(parentIds); + console.log('父节点已展开:', parentIds); + // 第三次确保展开,使用更长的延迟 + setTimeout(() => { + try { + const tree = getTree(); + if (tree) { + tree.setExpandedKeys(parentIds); + } + } catch (error) { + console.warn('展开父节点失败:', error); + } + }, 500); + } + } catch (error) { + console.warn('展开父节点失败:', error); + } + }, 200); + } + } catch (error) { + console.warn('展开父节点失败:', error); + } + }); + + } + } catch (error) { + console.warn('获取父节点ID失败:', error); + } + } + } else { + openModal.value = false; + emit?.('close'); + } + } + + /** + * 设置公司部门复选框显示 + * @param record + */ + function setCompanyDepartCheckable(record) { + if (record && record.length > 0) { + for (const item of record) { + if (item.orgCategory !== '3') { + item.checkable = false; + item.selectable = false; + } else { + item.checkable = true; + item.selectable = true; + } + if (item.isLeaf) { + setCompanyDepartCheckable(item.children); + } + } + } + } + + /** + * 岗位搜索 + * + * @param value + */ + async function onSearch(value) { + if(value){ + let result = await defHttp.get({ url: "/sys/sysDepart/searchBy", params: { keyWord: value, orgCategory: "3",...props.params } }); + if (Array.isArray(result)) { + treeData.value = result; + } else { + treeData.value = []; + } + } else { + treeData.value = []; + await onLoadData(null, null) + } + } + + return [ + { + visibleChange, + selectOptions, + selectValues, + onLoadData, + onCheck, + onSelect, + checkALL, + expandAll, + checkedKeys, + selectRows, + treeData, + getCheckStrictly, + getSelectTreeData, + onSearch, + expandedKeys, + checkStrictly, + }, + ]; +} diff --git a/src/components/Form/src/jeecg/props/props.ts b/src/components/Form/src/jeecg/props/props.ts new file mode 100644 index 0000000..d30d1b1 --- /dev/null +++ b/src/components/Form/src/jeecg/props/props.ts @@ -0,0 +1,92 @@ +//下拉选择框组件公共props +import { propTypes } from '/@/utils/propTypes'; + +export const selectProps = { + //是否多选 + isRadioSelection: { + type: Boolean, + // 代码逻辑说明: 部门用户组件默认应该单选,否则其他地方有问题------------ + default: false, + }, + //回传value字段名 + rowKey: { + type: String, + default: 'id', + }, + //回传文本字段名 + labelKey: { + type: String, + default: 'name', + }, + //查询参数 + params: { + type: Object, + default: () => {}, + }, + //是否显示选择按钮 + showButton: propTypes.bool.def(true), + //是否显示右侧选中列表 + showSelected: propTypes.bool.def(false), + //最大选择数量 + maxSelectCount: { + type: Number, + default: 0, + }, +}; + +//树形选择组件公共props +export const treeProps = { + //回传value字段名 + rowKey: { + type: String, + default: 'key', + }, + //回传文本字段名 + labelKey: { + type: String, + default: 'title', + }, + //初始展开的层级 + defaultExpandLevel: { + type: [Number], + default: 1, + }, + //根pid值 + startPid: { + type: [Number, String], + default: '', + }, + //主键字段 + primaryKey: { + type: [String], + default: 'id', + }, + //父ID字段 + parentKey: { + type: [String], + default: 'parentId', + }, + //title字段 + titleKey: { + type: [String], + default: 'title', + }, + //是否开启服务端转换tree数据结构 + serverTreeData: propTypes.bool.def(true), + //是否开启异步加载数据 + sync: propTypes.bool.def(true), + //是否显示选择按钮 + showButton: propTypes.bool.def(true), + //是否只显示公司 + onlyShowCompany: propTypes.bool.def(false), + //是否显示复选框 + checkable: propTypes.bool.def(true), + //checkable 状态下节点选择完全受控(父子节点选中状态不再关联) + checkStrictly: propTypes.bool.def(false), + // 是否允许多选,默认 true + multiple: propTypes.bool.def(true), + // 是否只选择岗位 + izOnlySelectDepartPost: propTypes.bool.def(false), + // 是否显示部门简称 + izShowDepartNameAbbr: propTypes.bool.def(false), +}; diff --git a/src/components/Form/src/props.ts b/src/components/Form/src/props.ts new file mode 100644 index 0000000..242d605 --- /dev/null +++ b/src/components/Form/src/props.ts @@ -0,0 +1,121 @@ +import type { FieldMapToTime, FormSchema } from './types/form'; +import type { CSSProperties, PropType } from 'vue'; +import type { ColEx } from './types'; +import type { TableActionType } from '/@/components/Table'; +import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; +import type { RowProps } from 'ant-design-vue/lib/grid/Row'; +import dayjs from "dayjs"; +import { propTypes } from '/@/utils/propTypes'; +import componentSetting from '/@/settings/componentSetting'; + +const { form } = componentSetting; +export const basicProps = { + model: { + type: Object as PropType, + default: {}, + }, + // 标签宽度 固定宽度 + labelWidth: { + type: [Number, String] as PropType, + default: 0, + }, + fieldMapToTime: { + type: Array as PropType, + default: () => [], + }, + fieldMapToNumber: { + type: Array as PropType, + default: () => [], + }, + compact: propTypes.bool, + // 表单配置规则 + schemas: { + type: [Array] as PropType, + default: () => [], + }, + mergeDynamicData: { + type: Object as PropType, + default: null, + }, + baseRowStyle: { + type: Object as PropType, + }, + baseColProps: { + type: Object as PropType>, + }, + autoSetPlaceHolder: propTypes.bool.def(true), + // 在INPUT组件上单击回车时,是否自动提交 + autoSubmitOnEnter: propTypes.bool.def(false), + submitOnReset: propTypes.bool, + size: propTypes.oneOf(['default', 'small', 'large']).def('default'), + // 禁用表单 + disabled: propTypes.bool, + emptySpan: { + type: [Number, Object] as PropType, + default: 0, + }, + // 是否显示收起展开按钮 + showAdvancedButton: propTypes.bool, + // 转化时间 + transformDateFunc: { + type: Function as PropType, + default: (date: any) => { + // 判断是否是dayjs实例 + return dayjs.isDayjs(date) ? date?.format('YYYY-MM-DD HH:mm:ss') : date; + }, + }, + rulesMessageJoinLabel: propTypes.bool.def(true), + // 【jeecg】超过3列自动折叠 + autoAdvancedCol: propTypes.number.def(3), + // 超过3行自动折叠 + // autoAdvancedLine: propTypes.number.def(3), + // 不受折叠影响的行数 + alwaysShowLines: propTypes.number.def(1), + + // 是否显示操作按钮 + showActionButtonGroup: propTypes.bool.def(true), + // 操作列Col配置 + actionColOptions: Object as PropType>, + // 显示重置按钮 + showResetButton: propTypes.bool.def(true), + // 是否聚焦第一个输入框,只在第一个表单项为input的时候作用 + autoFocusFirstItem: propTypes.bool, + // 重置按钮配置 + resetButtonOptions: Object as PropType>, + + // 显示确认按钮 + showSubmitButton: propTypes.bool.def(true), + // 确认按钮配置 + submitButtonOptions: Object as PropType>, + + // 自定义重置函数 + resetFunc: Function as PropType<() => Promise>, + submitFunc: Function as PropType<() => Promise>, + + // 以下为默认props + hideRequiredMark: propTypes.bool, + + labelCol: { + type: Object as PropType>, + default: form.labelCol, + }, + + layout: propTypes.oneOf(['horizontal', 'vertical', 'inline']).def('horizontal'), + tableAction: { + type: Object as PropType, + }, + + wrapperCol: { + type: Object as PropType>, + default: form.wrapperCol, + }, + + colon: propTypes.bool.def(form.colon), + + labelAlign: propTypes.string, + + rowProps: Object as PropType, + + // 当表单是查询条件的时候 当表单改变后自动查询,不需要点击查询按钮 + autoSearch: propTypes.bool.def(false), +}; diff --git a/src/components/Form/src/types/form.ts b/src/components/Form/src/types/form.ts new file mode 100644 index 0000000..75d5303 --- /dev/null +++ b/src/components/Form/src/types/form.ts @@ -0,0 +1,228 @@ +import type { NamePath, RuleObject, ValidateOptions } from 'ant-design-vue/lib/form/interface'; +import type { VNode, ComputedRef } from 'vue'; +import type { ButtonProps as AntdButtonProps } from '/@/components/Button'; +import type { FormItem } from './formItem'; +import type { ColEx, ComponentType } from './index'; +import type { TableActionType } from '/@/components/Table/src/types/table'; +import type { CSSProperties } from 'vue'; +import type { RowProps } from 'ant-design-vue/lib/grid/Row'; + +export type FieldMapToTime = [string, [string, string], string?][]; +export type FieldMapToNumber = [string, [string, string]][]; + +export type Rule = RuleObject & { + trigger?: 'blur' | 'change' | ['change', 'blur']; +}; + +export interface RenderCallbackParams { + schema: FormSchema; + values: Recordable; + model: Recordable; + field: string; +} + +export interface ButtonProps extends AntdButtonProps { + text?: string; +} + +export interface FormActionType { + submit: () => Promise; + setFieldsValue: (values: T) => Promise; + resetFields: () => Promise; + getFieldsValue: () => Recordable; + clearValidate: (name?: string | string[]) => Promise; + updateSchema: (data: Partial | Partial[]) => Promise; + resetSchema: (data: Partial | Partial[]) => Promise; + setProps: (formProps: Partial) => Promise; + getProps: ComputedRef>; + getSchemaByField: (field: string) => Nullable; + removeSchemaByFiled: (field: string | string[]) => Promise; + appendSchemaByField: (schema: FormSchema, prefixField: string | undefined, first?: boolean | undefined) => Promise; + validateFields: (nameList?: NamePath[], options?: ValidateOptions) => Promise; + validate: (nameList?: NamePath[]) => Promise; + scrollToField: (name: NamePath, options?: ScrollOptions) => Promise; + getSchemaComponentProps: (schema: FormSchema) => Recordable +} + +export type RegisterFn = (formInstance: FormActionType) => void; + +export type UseFormReturnType = [RegisterFn, FormActionType]; + +export interface FormProps { + layout?: 'vertical' | 'inline' | 'horizontal'; + // Form value + model?: Recordable; + // The width of all items in the entire form + labelWidth?: number | string; + //alignment + labelAlign?: 'left' | 'right'; + //Row configuration for the entire form + rowProps?: RowProps; + // Submit form on reset + submitOnReset?: boolean; + // Col configuration for the entire form + labelCol?: Partial | null; + // Col configuration for the entire form + wrapperCol?: Partial | null; + + // General row style + baseRowStyle?: CSSProperties; + + // General col configuration + baseColProps?: Partial; + + // Form configuration rules + schemas?: FormSchema[]; + // Function values used to merge into dynamic control form items + mergeDynamicData?: Recordable; + // Compact mode for search forms + compact?: boolean; + // Blank line span + emptySpan?: number | Partial; + // Internal component size of the form + size?: 'default' | 'small' | 'large'; + // Whether to disable + disabled?: boolean; + // Time interval fields are mapped into multiple + fieldMapToTime?: FieldMapToTime; + // number interval fields are mapped into multiple + fieldMapToNumber?: FieldMapToNumber; + // Placeholder is set automatically + autoSetPlaceHolder?: boolean; + // Auto submit on press enter on input + autoSubmitOnEnter?: boolean; + // Check whether the information is added to the label + rulesMessageJoinLabel?: boolean; + // 是否显示展开收起按钮 + showAdvancedButton?: boolean; + // Whether to focus on the first input box, only works when the first form item is input + autoFocusFirstItem?: boolean; + // 【jeecg】如果 showAdvancedButton 为 true,超过指定列数默认折叠,默认为3 + autoAdvancedCol?: number; + // 如果 showAdvancedButton 为 true,超过指定行数行默认折叠 + // 代码逻辑说明: 【issues/7261】表格上方查询项autoAdvancedLine配置没有效果(删除autoAdvancedLine) + // autoAdvancedLine?: number; + // 折叠时始终保持显示的行数 + alwaysShowLines?: number; + // Whether to show the operation button + showActionButtonGroup?: boolean; + + // Reset button configuration + resetButtonOptions?: Partial; + + // Confirm button configuration + submitButtonOptions?: Partial; + + // Operation column configuration + actionColOptions?: Partial; + + // Show reset button + showResetButton?: boolean; + // Show confirmation button + showSubmitButton?: boolean; + + resetFunc?: () => Promise; + submitFunc?: () => Promise; + transformDateFunc?: (date: any) => string; + colon?: boolean; +} +export interface FormSchema { + // Field name + field: string; + // Event name triggered by internal value change, default change + changeEvent?: string; + // Variable name bound to v-model Default value + valueField?: string; + // Label name + // 代码逻辑说明: 【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换 + label: string | VNode | Fn; + // Auxiliary text + subLabel?: string; + // Help text on the right side of the text + helpMessage?: string | string[] | ((renderCallbackParams: RenderCallbackParams) => string | string[]); + // BaseHelp component props + helpComponentProps?: Partial; + // Label width, if it is passed, the labelCol and WrapperCol configured by itemProps will be invalid + labelWidth?: string | number; + // Disable the adjustment of labelWidth with global settings of formModel, and manually set labelCol and wrapperCol by yourself + disabledLabelWidth?: boolean; + // render component + component: ComponentType; + // Component parameters + componentProps?: + | ((opt: { schema: FormSchema; tableAction: TableActionType; formActionType: FormActionType; formModel: Recordable }) => Recordable) + | object; + // Required + required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean); + + suffix?: string | number | VueNode | ((values: RenderCallbackParams) => string | number | VueNode); + // 【QQYUN-12876】是否是紧凑型 suffix(当组件宽度未占满时,可紧挨着组件右侧) + suffixCompact?: boolean; + + // Validation rules + rules?: Rule[]; + // Check whether the information is added to the label + rulesMessageJoinLabel?: boolean; + + // Reference formModelItem + itemProps?: Partial | ((renderCallbackParams: RenderCallbackParams) => Partial); + + // col configuration outside formModelItem + colProps?: Partial; + + // 默认值 + defaultValue?: any; + isAdvanced?: boolean; + + // Matching details components + span?: number; + + ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean); + + show?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean); + + // Render the content in the form-item tag + render?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string; + + // Rendering col content requires outer wrapper form-item + renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string; + + renderComponentContent?: ((renderCallbackParams: RenderCallbackParams) => any) | VNode | VNode[] | string; + + // Custom slot, in from-item + slot?: string; + + // Custom slot, similar to renderColContent + colSlot?: string; + + dynamicDisabled?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean); + + dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[]; + // 设置组件props的key + dynamicPropskey?: string; + dynamicPropsVal?: ((renderCallbackParams: RenderCallbackParams) => any); + + // 这个属性自定义的 用于自定义的业务 比如在表单打开的时候修改表单的禁用状态,但是又不能重写componentProps,因为他的内容太多了,所以使用dynamicDisabled和buss实现 + buss?: any; + + //label字数控制(label宽度) + labelLength?: number; + // update-begin--author:liaozhiyang---date:20240529---for【TV360X-460】basicForm支持v-auth指令(权限控制显隐) + auth?: string; + // update-end--author:liaozhiyang---date:20240529---for【TV360X-460】basicForm支持v-auth指令(权限控制显隐) +} +export interface HelpComponentProps { + maxWidth: string; + // Whether to display the serial number + showIndex: boolean; + // Text list + text: any; + // colour + color: string; + // font size + fontSize: string; + icon: string; + absolute: boolean; + // Positioning + position: any; +} diff --git a/src/components/Form/src/types/formItem.ts b/src/components/Form/src/types/formItem.ts new file mode 100644 index 0000000..77b238a --- /dev/null +++ b/src/components/Form/src/types/formItem.ts @@ -0,0 +1,91 @@ +import type { NamePath } from 'ant-design-vue/lib/form/interface'; +import type { ColProps } from 'ant-design-vue/lib/grid/Col'; +import type { HTMLAttributes, VNodeChild } from 'vue'; + +export interface FormItem { + /** + * Used with label, whether to display : after label text. + * @default true + * @type boolean + */ + colon?: boolean; + + /** + * The extra prompt message. It is similar to help. Usage example: to display error message and prompt message at the same time. + * @type any (string | slot) + */ + extra?: string | VNodeChild | JSX.Element; + + /** + * Used with validateStatus, this option specifies the validation status icon. Recommended to be used only with Input. + * @default false + * @type boolean + */ + hasFeedback?: boolean; + + /** + * The prompt message. If not provided, the prompt message will be generated by the validation rule. + * @type any (string | slot) + */ + help?: string | VNodeChild | JSX.Element; + + /** + * Label test + * @type any (string | slot) + */ + label?: string | VNodeChild | JSX.Element; + + /** + * The layout of label. You can set span offset to something like {span: 3, offset: 12} or sm: {span: 3, offset: 12} same as with + * @type Col + */ + labelCol?: ColProps & HTMLAttributes; + + /** + * Whether provided or not, it will be generated by the validation rule. + * @default false + * @type boolean + */ + required?: boolean; + + /** + * The validation status. If not provided, it will be generated by validation rule. options: 'success' 'warning' 'error' 'validating' + * @type string + */ + validateStatus?: '' | 'success' | 'warning' | 'error' | 'validating'; + + /** + * The layout for input controls, same as labelCol + * @type Col + */ + wrapperCol?: ColProps; + /** + * Set sub label htmlFor. + */ + htmlFor?: string; + /** + * text align of label + */ + labelAlign?: 'left' | 'right'; + /** + * a key of model. In the setting of validate and resetFields method, the attribute is required + */ + name?: NamePath; + /** + * validation rules of form + */ + rules?: object | object[]; + /** + * Whether to automatically associate form fields. In most cases, you can setting automatic association. + * If the conditions for automatic association are not met, you can manually associate them. See the notes below. + */ + autoLink?: boolean; + /** + * Whether stop validate on first rule of error for this field. + */ + validateFirst?: boolean; + /** + * When to validate the value of children node + */ + validateTrigger?: string | string[] | false; +} diff --git a/src/components/Form/src/types/hooks.ts b/src/components/Form/src/types/hooks.ts new file mode 100644 index 0000000..0308e73 --- /dev/null +++ b/src/components/Form/src/types/hooks.ts @@ -0,0 +1,6 @@ +export interface AdvanceState { + isAdvanced: boolean; + hideAdvanceBtn: boolean; + isLoad: boolean; + actionSpan: number; +} diff --git a/src/components/Form/src/types/index.ts b/src/components/Form/src/types/index.ts new file mode 100644 index 0000000..e2c97e0 --- /dev/null +++ b/src/components/Form/src/types/index.ts @@ -0,0 +1,167 @@ +type ColSpanType = number | string; + +export interface ColEx { + style?: any; + /** + * raster number of cells to occupy, 0 corresponds to display: none + * @default none (0) + * @type ColSpanType + */ + span?: ColSpanType; + + /** + * raster order, used in flex layout mode + * @default 0 + * @type ColSpanType + */ + order?: ColSpanType; + + /** + * the layout fill of flex + * @default none + * @type ColSpanType + */ + flex?: ColSpanType; + + /** + * the number of cells to offset Col from the left + * @default 0 + * @type ColSpanType + */ + offset?: ColSpanType; + + /** + * the number of cells that raster is moved to the right + * @default 0 + * @type ColSpanType + */ + push?: ColSpanType; + + /** + * the number of cells that raster is moved to the left + * @default 0 + * @type ColSpanType + */ + pull?: ColSpanType; + + /** + * <576px and also default setting, could be a span value or an object containing above props + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType + */ + xs?: { span: ColSpanType; offset?: ColSpanType } | ColSpanType; + + /** + * ≥576px, could be a span value or an object containing above props + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType + */ + sm?: { span: ColSpanType; offset?: ColSpanType } | ColSpanType; + + /** + * ≥768px, could be a span value or an object containing above props + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType + */ + md?: { span: ColSpanType; offset?: ColSpanType } | ColSpanType; + + /** + * ≥992px, could be a span value or an object containing above props + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType + */ + lg?: { span: ColSpanType; offset?: ColSpanType } | ColSpanType; + + /** + * ≥1200px, could be a span value or an object containing above props + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType + */ + xl?: { span: ColSpanType; offset?: ColSpanType } | ColSpanType; + + /** + * ≥1600px, could be a span value or an object containing above props + * @type { span: ColSpanType, offset: ColSpanType } | ColSpanType + */ + xxl?: { span: ColSpanType; offset?: ColSpanType } | ColSpanType; +} + +export type ComponentType = + | 'Input' + | 'InputGroup' + | 'InputPassword' + | 'InputSearch' + | 'InputTextArea' + | 'InputNumber' + | 'InputCountDown' + | 'Select' + | 'ApiSelect' + | 'TreeSelect' + | 'ApiTreeSelect' + | 'ApiRadioGroup' + | 'RadioButtonGroup' + | 'RadioGroup' + | 'Checkbox' + | 'CheckboxGroup' + | 'AutoComplete' + | 'Cascader' + | 'DatePicker' + | 'MonthPicker' + | 'RangePicker' + | 'WeekPicker' + | 'TimePicker' + | 'DatePickerInFilter' + | 'JDatePickerMultiple' + | 'Switch' + | 'StrengthMeter' + | 'Upload' + | 'IconPicker' + | 'Render' + | 'Slider' + | 'Rate' + | 'Divider' + | 'JAreaLinkage' + | 'JSelectPosition' + | 'JSelectRole' + | 'JSelectUser' + | 'JImageUpload' + | 'JDictSelectTag' + | 'JSelectDept' + | 'JSelectDepartPost' + | 'JAreaSelect' + | 'JEditor' + | 'JMarkdownEditor' + | 'JSelectInput' + | 'JCodeEditor' + | 'JCategorySelect' + | 'JSelectMultiple' + | 'JSelectSingle' + | 'JPopup' + | 'JPopupDict' + | 'JSwitch' + | 'JEasyCron' + | 'JTreeDict' + | 'JInputPop' + | 'JCheckbox' + | 'JInput' + | 'JTreeSelect' + | 'JEllipsis' + | 'JSelectUserByDept' + | 'JSelectUserByDeptPost' + | 'JSelectUserByDepartment' + | 'JTabsSelectUser' + | 'JUpload' + | 'JSearchSelect' + | 'JAddInput' + | 'Time' + | 'OnlineSelectCascade' + | 'LinkTableCard' + | 'LinkTableSelect' + | 'LinkTableForQuery' + | 'CascaderPcaForQuery' + | 'CascaderPcaInFilter' + | 'UserSelect' + | 'RoleSelect' + | 'RangeDate' + | 'RangeNumber' + | 'linkRecordSelect' + | 'RangeTime' + | 'JRangeNumber' + | 'JLinkTableCard' + | 'JInputSelect'; + diff --git a/src/components/Form/src/utils/Area.ts b/src/components/Form/src/utils/Area.ts new file mode 100644 index 0000000..e783501 --- /dev/null +++ b/src/components/Form/src/utils/Area.ts @@ -0,0 +1,163 @@ +import {pcaa as REGION_DATA} from "@/utils/areaData/pcaUtils"; + +/** + * Area 属性all的类型 + */ +interface PlainPca { + id: string; + text: string; + pid: string; + index: Number; +} + +/** + * 省市区工具类 -解决列表省市区组件的翻译问题 + */ +class Area { + all: PlainPca[]; + + /** + * 构造器 + * @param pcaa + */ + constructor(pcaa?) { + if (!pcaa) { + pcaa = REGION_DATA; + } + let arr: PlainPca[] = []; + const province = pcaa['86']; + Object.keys(province).map((key) => { + arr.push({ id: key, text: province[key], pid: '86', index: 1 }); + const city = pcaa[key]; + Object.keys(city).map((key2) => { + arr.push({ id: key2, text: city[key2], pid: key, index: 2 }); + const qu = pcaa[key2]; + if (qu) { + Object.keys(qu).map((key3) => { + arr.push({ id: key3, text: qu[key3], pid: key2, index: 3 }); + }); + } + }); + }); + this.all = arr; + } + + get pca() { + return this.all; + } + + getCode(text) { + if (!text || text.length == 0) { + return ''; + } + for (let item of this.all) { + if (item.text === text) { + return item.id; + } + } + } + +// 代码逻辑说明: [issue/382]省市区组件JAreaLinkage数据不回显--- + getText(code,index=3) { + if (!code || code.length == 0) { + return ''; + } + let arr = []; + this.getAreaBycode(code, arr, index); + return arr.join('/'); + } + + getRealCode(code) { + let arr = []; + this.getPcode(code, arr, 3); + return arr; + } + + getPcode(id, arr, index) { + for (let item of this.all) { + if (item.id === id && item.index == index) { + arr.unshift(id); + if (item.pid != '86') { + this.getPcode(item.pid, arr, --index); + } + } + } + } + + getAreaBycode(code, arr, index) { + for (let item of this.all) { + if (item.id === code && item.index == index) { + arr.unshift(item.text); + if (item.pid != '86') { + this.getAreaBycode(item.pid, arr, --index); + } + } + } + } +} +const jeecgAreaData = new Area(); + +// 根据code找文本 +const getAreaTextByCode = function (code) { + let index = 3; + // 代码逻辑说明: 判断code是否是多code逗号分割的字符串,是的话,获取最后一位的code --- + if (code && code.includes(',')) { + index = code.split(",").length; + code = code.substr(code.lastIndexOf(',') + 1); + } + return jeecgAreaData.getText(code,index); +}; + +/** + * 20260204 + * liaozhiyang + * 【QQYUN-14694】online支持配置独立的省、市、县 (根据 code 找文本(支持仅省、仅省市、省市区)) + * @param includeParent 是否返回父级路径。默认 false 只返回最后一级(如「长治市」),true 返回完整路径(如「山西省/长治市」) + * @param level 层级:1=省,2=市,3=县。传入时作为 index 使用,不传则从 code 推断 + */ +const getAreaTextByCodeAnyLevel = function (code: string | number | undefined, includeParent = false, level?: 1 | 2 | 3): string { + if (!code) return ''; + const codeStr = String(code).trim(); + if (!codeStr.length) return ''; + + let lastCode: string; + let index: number; + let resolvedItem: PlainPca | null = null; + + if (codeStr.includes(',')) { + const parts = codeStr.split(',').map((s) => s.trim()).filter(Boolean); + if (!parts.length) return ''; + index = level ?? parts.length; + lastCode = level != null ? parts[level - 1] : parts[parts.length - 1]; + if (level != null) { + resolvedItem = jeecgAreaData.all.find((it) => String(it.id) === lastCode && it.index == index) ?? null; + } + } else { + const item = jeecgAreaData.all.find((it) => String(it.id) === codeStr); + if (!item) return ''; + const itemIndex = item.index as number; + index = level ?? itemIndex; + lastCode = codeStr; + if (level != null && level !== itemIndex) { + const chain: string[] = []; + jeecgAreaData.getPcode(codeStr, chain, itemIndex); + const targetCode = chain[level - 1]; + if (targetCode) { + lastCode = targetCode; + resolvedItem = jeecgAreaData.all.find((it) => String(it.id) === targetCode && it.index == level) ?? null; + } else { + resolvedItem = item; + } + } else { + resolvedItem = item; + } + } + if (includeParent) { + return jeecgAreaData.getText(lastCode, index); + } else { + const item = resolvedItem ?? jeecgAreaData.all.find((it) => String(it.id) === lastCode && it.index == index); + return item ? item.text : ''; + } +}; + +export { getAreaTextByCode, getAreaTextByCodeAnyLevel }; diff --git a/src/components/Form/src/utils/GroupRequest.ts b/src/components/Form/src/utils/GroupRequest.ts new file mode 100644 index 0000000..ce813df --- /dev/null +++ b/src/components/Form/src/utils/GroupRequest.ts @@ -0,0 +1,27 @@ +import { getAuthCache, setAuthCache } from '/@/utils/auth'; +/** + * 将一个请求分组 + * + * @param getPromise 传入一个可以获取到Promise对象的方法 + * @param groupId 分组ID,如果不传或者为空则不分组 + * @param expire 过期时间,默认 半分钟 + */ +export function httpGroupRequest(getPromise, groupId, expire = 1000 * 30) { + if (groupId == null || groupId === '') { + console.log('--------popup----------getFrom DB-------with---no--groupId '); + return getPromise(); + } + + if (getAuthCache(groupId)) { + console.log('---------popup--------getFrom Cache--------groupId = ' + groupId); + return Promise.resolve(getAuthCache(groupId)); + } else { + console.log('--------popup----------getFrom DB---------groupId = ' + groupId); + } + + // 还没有发出请求,就发出第一次的请求 + return getPromise().then((res) => { + setAuthCache(groupId, res); + return Promise.resolve(res); + }); +} diff --git a/src/components/Form/src/utils/areaDataUtil.js b/src/components/Form/src/utils/areaDataUtil.js new file mode 100644 index 0000000..df96a73 --- /dev/null +++ b/src/components/Form/src/utils/areaDataUtil.js @@ -0,0 +1,193 @@ +import {pcaa as REGION_DATA} from "@/utils/areaData/pcaUtils"; +import { cloneDeep } from 'lodash-es'; + +// code转汉字大对象 +const CodeToText = {}; +// 汉字转code大对象 +const TextToCode = {}; +const provinceObject = REGION_DATA['86']; // 省份对象 +const regionData = []; +let provinceAndCityData = []; + +CodeToText[''] = '全部'; + +// 计算省 +for (const prop in provinceObject) { + regionData.push({ + value: prop, // 省份code值 + label: provinceObject[prop], // 省份汉字 + }); + CodeToText[prop] = provinceObject[prop]; + TextToCode[provinceObject[prop]] = { + code: prop, + }; + TextToCode[provinceObject[prop]]['全部'] = { + code: '', + }; +} +// 计算市 +for (let i = 0, len = regionData.length; i < len; i++) { + const provinceCode = regionData[i].value; + const provinceText = regionData[i].label; + const provinceChildren = []; + for (const prop in REGION_DATA[provinceCode]) { + provinceChildren.push({ + value: prop, + label: REGION_DATA[provinceCode][prop], + }); + CodeToText[prop] = REGION_DATA[provinceCode][prop]; + TextToCode[provinceText][REGION_DATA[provinceCode][prop]] = { + code: prop, + }; + TextToCode[provinceText][REGION_DATA[provinceCode][prop]]['全部'] = { + code: '', + }; + } + if (provinceChildren.length) { + regionData[i].children = provinceChildren; + } +} +provinceAndCityData = cloneDeep(regionData); + +// 计算区 +for (let i = 0, len = regionData.length; i < len; i++) { + const province = regionData[i].children; + const provinceText = regionData[i].label; + if (province) { + for (let j = 0, len = province.length; j < len; j++) { + const cityCode = province[j].value; + const cityText = province[j].label; + const cityChildren = []; + for (const prop in REGION_DATA[cityCode]) { + cityChildren.push({ + value: prop, + label: REGION_DATA[cityCode][prop], + }); + CodeToText[prop] = REGION_DATA[cityCode][prop]; + TextToCode[provinceText][cityText][REGION_DATA[cityCode][prop]] = { + code: prop, + }; + } + if (cityChildren.length) { + province[j].children = cityChildren; + } + } + } +} + +// 添加“全部”选项 +const provinceAndCityDataPlus = cloneDeep(provinceAndCityData); +provinceAndCityDataPlus.unshift({ + value: '', + label: '全部', +}); +for (let i = 0, len = provinceAndCityDataPlus.length; i < len; i++) { + const province = provinceAndCityDataPlus[i].children; + if (province && province.length) { + province.unshift({ + value: '', + label: '全部', + }); + for (let j = 0, len = province.length; j < len; j++) { + const city = province[j].children; + if (city && city.length) { + city.unshift({ + value: '', + label: '全部', + }); + } + } + } +} + +const regionDataPlus = cloneDeep(regionData); +regionDataPlus.unshift({ + value: '', + label: '全部', +}); +for (let i = 0, len = regionDataPlus.length; i < len; i++) { + const province = regionDataPlus[i].children; + if (province && province.length) { + province.unshift({ + value: '', + label: '全部', + }); + + for (let j = 0, len = province.length; j < len; j++) { + const city = province[j].children; + if (city && city.length) { + city.unshift({ + value: '', + label: '全部', + }); + } + } + } +} +//--begin--@updateBy:liusq----date:20210922---for:省市区三级联动需求方法----- +//省份数据 +const provinceOptions = []; +for (const prop in provinceObject) { + provinceOptions.push({ + value: prop, // 省份code值 + label: provinceObject[prop], // 省份汉字 + }); +} +/** + * 根据code获取下拉option的数据 + * @param code + * @returns [] + */ +function getDataByCode(code) { + let data = []; + for (const prop in REGION_DATA[code]) { + data.push({ + value: prop, // 省份code值 + label: REGION_DATA[code][prop], // 省份汉字 + }); + } + return data; +} + +/** + * 获取全部省市区的层级 + * @type {Array} + */ +const pca = []; +Object.keys(provinceObject).map((province) => { + pca.push({ id: province, text: provinceObject[province], pid: '86', index: 1 }); + const cityObject = REGION_DATA[province]; + Object.keys(cityObject).map((city) => { + pca.push({ id: city, text: cityObject[city], pid: province, index: 2 }); + const areaObject = REGION_DATA[city]; + if (areaObject) { + Object.keys(areaObject).map((area) => { + pca.push({ id: area, text: areaObject[area], pid: city, index: 3 }); + }); + } + }); +}); + +/** + * 根据code反推value + * @param code + * @param level + * @returns {Array} + */ +function getRealCode(code, level) { + let arr = []; + getPcode(code, arr, level); + return arr; +} +function getPcode(id, arr, index) { + for (let item of pca) { + if (item.id === id && item.index == index) { + arr.unshift(id); + if (item.pid != '86') { + getPcode(item.pid, arr, --index); + } + } + } +} +//--end--@updateBy:liusq----date:20210922---for:省市区三级联动需求方法----- +export { provinceAndCityData, regionData, provinceAndCityDataPlus, regionDataPlus, getDataByCode, provinceOptions, getRealCode }; diff --git a/src/components/Form/src/utils/formUtils.ts b/src/components/Form/src/utils/formUtils.ts new file mode 100644 index 0000000..eff7655 --- /dev/null +++ b/src/components/Form/src/utils/formUtils.ts @@ -0,0 +1,73 @@ +import { unref } from 'vue'; +import { dateUtil } from '/@/utils/dateUtil'; + +/** + * 表单区间时间数值字段转换 + * @param props + * @param values + */ +export function handleRangeValue(props, values) { + //判断是否配置并处理fieldMapToTime + const fieldMapToTime = unref(props)?.fieldMapToTime; + fieldMapToTime && (values = handleRangeTimeValue(props, values)); + //判断是否配置并处理fieldMapToNumber + const fieldMapToNumber = unref(props)?.fieldMapToNumber; + fieldMapToNumber && (values = handleRangeNumberValue(props, values)); + return values; +} +/** + * 处理时间转换成2个字段 + * @param props + * @param values + */ +export function handleRangeTimeValue(props, values) { + const fieldMapToTime = unref(props).fieldMapToTime; + if (!fieldMapToTime || !Array.isArray(fieldMapToTime)) { + return values; + } + for (const [field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD'] of fieldMapToTime) { + if (!field || !startTimeKey || !endTimeKey || !values[field]) { + continue; + } + + // 【issues/I53G9Y】 日期区间组件有可能是字符串 + let timeValue = values[field]; + if (!Array.isArray(timeValue)) { + timeValue = timeValue.split(','); + } + const [startTime, endTime]: string[] = timeValue; + // 代码逻辑说明: 【issues/7216】当RangePicker组件值允许开始/结束为空时,表单的fieldMapToTime处理异常--- + startTime && (values[startTimeKey] = dateUtil(startTime).format(format)); + endTime && (values[endTimeKey] = dateUtil(endTime).format(format)); + Reflect.deleteProperty(values, field); + } + return values; +} +/** + * 处理数字转换成2个字段 + * @param props + * @param values + * @updateby liusq + * @updateDate:2021-09-16 + */ +export function handleRangeNumberValue(props, values) { + const fieldMapToNumber = unref(props).fieldMapToNumber; + if (!fieldMapToNumber || !Array.isArray(fieldMapToNumber)) { + return values; + } + for (const [field, [startNumberKey, endNumberKey]] of fieldMapToNumber) { + if (!field || !startNumberKey || !endNumberKey || !values[field]) { + continue; + } + // 代码逻辑说明: 用于数值的范围查询 数组格式的中间转换不知道哪里出了问题,这里会变成字符串,需要再强制转成数组 + let temp = values[field]; + if (typeof temp === 'string') { + temp = temp.split(','); + } + const [startNumber, endNumber]: number[] = temp; + values[startNumberKey] = startNumber; + values[endNumberKey] = endNumber; + Reflect.deleteProperty(values, field); + } + return values; +} diff --git a/src/components/Icon/data/icons.data.ts b/src/components/Icon/data/icons.data.ts new file mode 100644 index 0000000..bcac58e --- /dev/null +++ b/src/components/Icon/data/icons.data.ts @@ -0,0 +1,791 @@ +export default [ + 'ant-design:account-book-filled', + 'ant-design:account-book-outlined', + 'ant-design:account-book-twotone', + 'ant-design:aim-outlined', + 'ant-design:alert-filled', + 'ant-design:alert-outlined', + 'ant-design:alert-twotone', + 'ant-design:alibaba-outlined', + 'ant-design:align-center-outlined', + 'ant-design:align-left-outlined', + 'ant-design:align-right-outlined', + 'ant-design:alipay-circle-filled', + 'ant-design:alipay-circle-outlined', + 'ant-design:alipay-outlined', + 'ant-design:alipay-square-filled', + 'ant-design:aliwangwang-filled', + 'ant-design:aliwangwang-outlined', + 'ant-design:aliyun-outlined', + 'ant-design:amazon-circle-filled', + 'ant-design:amazon-outlined', + 'ant-design:amazon-square-filled', + 'ant-design:android-filled', + 'ant-design:android-outlined', + 'ant-design:ant-cloud-outlined', + 'ant-design:ant-design-outlined', + 'ant-design:apartment-outlined', + 'ant-design:api-filled', + 'ant-design:api-outlined', + 'ant-design:api-twotone', + 'ant-design:apple-filled', + 'ant-design:apple-outlined', + 'ant-design:appstore-add-outlined', + 'ant-design:appstore-filled', + 'ant-design:appstore-outlined', + 'ant-design:appstore-twotone', + 'ant-design:area-chart-outlined', + 'ant-design:arrow-down-outlined', + 'ant-design:arrow-left-outlined', + 'ant-design:arrow-right-outlined', + 'ant-design:arrow-up-outlined', + 'ant-design:arrows-alt-outlined', + 'ant-design:audio-filled', + 'ant-design:audio-muted-outlined', + 'ant-design:audio-outlined', + 'ant-design:audio-twotone', + 'ant-design:audit-outlined', + 'ant-design:backward-filled', + 'ant-design:backward-outlined', + 'ant-design:bank-filled', + 'ant-design:bank-outlined', + 'ant-design:bank-twotone', + 'ant-design:bar-chart-outlined', + 'ant-design:barcode-outlined', + 'ant-design:bars-outlined', + 'ant-design:behance-circle-filled', + 'ant-design:behance-outlined', + 'ant-design:behance-square-filled', + 'ant-design:behance-square-outlined', + 'ant-design:bell-filled', + 'ant-design:bell-outlined', + 'ant-design:bell-twotone', + 'ant-design:bg-colors-outlined', + 'ant-design:block-outlined', + 'ant-design:bold-outlined', + 'ant-design:book-filled', + 'ant-design:book-outlined', + 'ant-design:book-twotone', + 'ant-design:border-bottom-outlined', + 'ant-design:border-horizontal-outlined', + 'ant-design:border-inner-outlined', + 'ant-design:border-left-outlined', + 'ant-design:border-outer-outlined', + 'ant-design:border-outlined', + 'ant-design:border-right-outlined', + 'ant-design:border-top-outlined', + 'ant-design:border-verticle-outlined', + 'ant-design:borderless-table-outlined', + 'ant-design:box-plot-filled', + 'ant-design:box-plot-outlined', + 'ant-design:box-plot-twotone', + 'ant-design:branches-outlined', + 'ant-design:bug-filled', + 'ant-design:bug-outlined', + 'ant-design:bug-twotone', + 'ant-design:build-filled', + 'ant-design:build-outlined', + 'ant-design:build-twotone', + 'ant-design:bulb-filled', + 'ant-design:bulb-outlined', + 'ant-design:bulb-twotone', + 'ant-design:calculator-filled', + 'ant-design:calculator-outlined', + 'ant-design:calculator-twotone', + 'ant-design:calendar-filled', + 'ant-design:calendar-outlined', + 'ant-design:calendar-twotone', + 'ant-design:camera-filled', + 'ant-design:camera-outlined', + 'ant-design:camera-twotone', + 'ant-design:car-filled', + 'ant-design:car-outlined', + 'ant-design:car-twotone', + 'ant-design:caret-down-filled', + 'ant-design:caret-down-outlined', + 'ant-design:caret-left-filled', + 'ant-design:caret-left-outlined', + 'ant-design:caret-right-filled', + 'ant-design:caret-right-outlined', + 'ant-design:caret-up-filled', + 'ant-design:caret-up-outlined', + 'ant-design:carry-out-filled', + 'ant-design:carry-out-outlined', + 'ant-design:carry-out-twotone', + 'ant-design:check-circle-filled', + 'ant-design:check-circle-outlined', + 'ant-design:check-circle-twotone', + 'ant-design:check-outlined', + 'ant-design:check-square-filled', + 'ant-design:check-square-outlined', + 'ant-design:check-square-twotone', + 'ant-design:chrome-filled', + 'ant-design:chrome-outlined', + 'ant-design:ci-circle-filled', + 'ant-design:ci-circle-outlined', + 'ant-design:ci-circle-twotone', + 'ant-design:ci-outlined', + 'ant-design:ci-twotone', + 'ant-design:clear-outlined', + 'ant-design:clock-circle-filled', + 'ant-design:clock-circle-outlined', + 'ant-design:clock-circle-twotone', + 'ant-design:close-circle-filled', + 'ant-design:close-circle-outlined', + 'ant-design:close-circle-twotone', + 'ant-design:close-outlined', + 'ant-design:close-square-filled', + 'ant-design:close-square-outlined', + 'ant-design:close-square-twotone', + 'ant-design:cloud-download-outlined', + 'ant-design:cloud-filled', + 'ant-design:cloud-outlined', + 'ant-design:cloud-server-outlined', + 'ant-design:cloud-sync-outlined', + 'ant-design:cloud-twotone', + 'ant-design:cloud-upload-outlined', + 'ant-design:cluster-outlined', + 'ant-design:code-filled', + 'ant-design:code-outlined', + 'ant-design:code-sandbox-circle-filled', + 'ant-design:code-sandbox-outlined', + 'ant-design:code-sandbox-square-filled', + 'ant-design:code-twotone', + 'ant-design:codepen-circle-filled', + 'ant-design:codepen-circle-outlined', + 'ant-design:codepen-outlined', + 'ant-design:codepen-square-filled', + 'ant-design:coffee-outlined', + 'ant-design:column-height-outlined', + 'ant-design:column-width-outlined', + 'ant-design:comment-outlined', + 'ant-design:compass-filled', + 'ant-design:compass-outlined', + 'ant-design:compass-twotone', + 'ant-design:compress-outlined', + 'ant-design:console-sql-outlined', + 'ant-design:contacts-filled', + 'ant-design:contacts-outlined', + 'ant-design:contacts-twotone', + 'ant-design:container-filled', + 'ant-design:container-outlined', + 'ant-design:container-twotone', + 'ant-design:control-filled', + 'ant-design:control-outlined', + 'ant-design:control-twotone', + 'ant-design:copy-filled', + 'ant-design:copy-outlined', + 'ant-design:copy-twotone', + 'ant-design:copyright-circle-filled', + 'ant-design:copyright-circle-outlined', + 'ant-design:copyright-circle-twotone', + 'ant-design:copyright-outlined', + 'ant-design:copyright-twotone', + 'ant-design:credit-card-filled', + 'ant-design:credit-card-outlined', + 'ant-design:credit-card-twotone', + 'ant-design:crown-filled', + 'ant-design:crown-outlined', + 'ant-design:crown-twotone', + 'ant-design:customer-service-filled', + 'ant-design:customer-service-outlined', + 'ant-design:customer-service-twotone', + 'ant-design:dash-outlined', + 'ant-design:dashboard-filled', + 'ant-design:dashboard-outlined', + 'ant-design:dashboard-twotone', + 'ant-design:database-filled', + 'ant-design:database-outlined', + 'ant-design:database-twotone', + 'ant-design:delete-column-outlined', + 'ant-design:delete-filled', + 'ant-design:delete-outlined', + 'ant-design:delete-row-outlined', + 'ant-design:delete-twotone', + 'ant-design:delivered-procedure-outlined', + 'ant-design:deployment-unit-outlined', + 'ant-design:desktop-outlined', + 'ant-design:diff-filled', + 'ant-design:diff-outlined', + 'ant-design:diff-twotone', + 'ant-design:dingding-outlined', + 'ant-design:dingtalk-circle-filled', + 'ant-design:dingtalk-outlined', + 'ant-design:dingtalk-square-filled', + 'ant-design:disconnect-outlined', + 'ant-design:dislike-filled', + 'ant-design:dislike-outlined', + 'ant-design:dislike-twotone', + 'ant-design:dollar-circle-filled', + 'ant-design:dollar-circle-outlined', + 'ant-design:dollar-circle-twotone', + 'ant-design:dollar-outlined', + 'ant-design:dollar-twotone', + 'ant-design:dot-chart-outlined', + 'ant-design:double-left-outlined', + 'ant-design:double-right-outlined', + 'ant-design:down-circle-filled', + 'ant-design:down-circle-outlined', + 'ant-design:down-circle-twotone', + 'ant-design:down-outlined', + 'ant-design:down-square-filled', + 'ant-design:down-square-outlined', + 'ant-design:down-square-twotone', + 'ant-design:download-outlined', + 'ant-design:drag-outlined', + 'ant-design:dribbble-circle-filled', + 'ant-design:dribbble-outlined', + 'ant-design:dribbble-square-filled', + 'ant-design:dribbble-square-outlined', + 'ant-design:dropbox-circle-filled', + 'ant-design:dropbox-outlined', + 'ant-design:dropbox-square-filled', + 'ant-design:edit-filled', + 'ant-design:edit-outlined', + 'ant-design:edit-twotone', + 'ant-design:ellipsis-outlined', + 'ant-design:enter-outlined', + 'ant-design:environment-filled', + 'ant-design:environment-outlined', + 'ant-design:environment-twotone', + 'ant-design:euro-circle-filled', + 'ant-design:euro-circle-outlined', + 'ant-design:euro-circle-twotone', + 'ant-design:euro-outlined', + 'ant-design:euro-twotone', + 'ant-design:exception-outlined', + 'ant-design:exclamation-circle-filled', + 'ant-design:exclamation-circle-outlined', + 'ant-design:exclamation-circle-twotone', + 'ant-design:exclamation-outlined', + 'ant-design:expand-alt-outlined', + 'ant-design:expand-outlined', + 'ant-design:experiment-filled', + 'ant-design:experiment-outlined', + 'ant-design:experiment-twotone', + 'ant-design:export-outlined', + 'ant-design:eye-filled', + 'ant-design:eye-invisible-filled', + 'ant-design:eye-invisible-outlined', + 'ant-design:eye-invisible-twotone', + 'ant-design:eye-outlined', + 'ant-design:eye-twotone', + 'ant-design:facebook-filled', + 'ant-design:facebook-outlined', + 'ant-design:fall-outlined', + 'ant-design:fast-backward-filled', + 'ant-design:fast-backward-outlined', + 'ant-design:fast-forward-filled', + 'ant-design:fast-forward-outlined', + 'ant-design:field-binary-outlined', + 'ant-design:field-number-outlined', + 'ant-design:field-string-outlined', + 'ant-design:field-time-outlined', + 'ant-design:file-add-filled', + 'ant-design:file-add-outlined', + 'ant-design:file-add-twotone', + 'ant-design:file-done-outlined', + 'ant-design:file-excel-filled', + 'ant-design:file-excel-outlined', + 'ant-design:file-excel-twotone', + 'ant-design:file-exclamation-filled', + 'ant-design:file-exclamation-outlined', + 'ant-design:file-exclamation-twotone', + 'ant-design:file-filled', + 'ant-design:file-gif-outlined', + 'ant-design:file-image-filled', + 'ant-design:file-image-outlined', + 'ant-design:file-image-twotone', + 'ant-design:file-jpg-outlined', + 'ant-design:file-markdown-filled', + 'ant-design:file-markdown-outlined', + 'ant-design:file-markdown-twotone', + 'ant-design:file-outlined', + 'ant-design:file-pdf-filled', + 'ant-design:file-pdf-outlined', + 'ant-design:file-pdf-twotone', + 'ant-design:file-ppt-filled', + 'ant-design:file-ppt-outlined', + 'ant-design:file-ppt-twotone', + 'ant-design:file-protect-outlined', + 'ant-design:file-search-outlined', + 'ant-design:file-sync-outlined', + 'ant-design:file-text-filled', + 'ant-design:file-text-outlined', + 'ant-design:file-text-twotone', + 'ant-design:file-twotone', + 'ant-design:file-unknown-filled', + 'ant-design:file-unknown-outlined', + 'ant-design:file-unknown-twotone', + 'ant-design:file-word-filled', + 'ant-design:file-word-outlined', + 'ant-design:file-word-twotone', + 'ant-design:file-zip-filled', + 'ant-design:file-zip-outlined', + 'ant-design:file-zip-twotone', + 'ant-design:filter-filled', + 'ant-design:filter-outlined', + 'ant-design:filter-twotone', + 'ant-design:fire-filled', + 'ant-design:fire-outlined', + 'ant-design:fire-twotone', + 'ant-design:flag-filled', + 'ant-design:flag-outlined', + 'ant-design:flag-twotone', + 'ant-design:folder-add-filled', + 'ant-design:folder-add-outlined', + 'ant-design:folder-add-twotone', + 'ant-design:folder-filled', + 'ant-design:folder-open-filled', + 'ant-design:folder-open-outlined', + 'ant-design:folder-open-twotone', + 'ant-design:folder-outlined', + 'ant-design:folder-twotone', + 'ant-design:folder-view-outlined', + 'ant-design:font-colors-outlined', + 'ant-design:font-size-outlined', + 'ant-design:fork-outlined', + 'ant-design:form-outlined', + 'ant-design:format-painter-filled', + 'ant-design:format-painter-outlined', + 'ant-design:forward-filled', + 'ant-design:forward-outlined', + 'ant-design:frown-filled', + 'ant-design:frown-outlined', + 'ant-design:frown-twotone', + 'ant-design:fullscreen-exit-outlined', + 'ant-design:fullscreen-outlined', + 'ant-design:function-outlined', + 'ant-design:fund-filled', + 'ant-design:fund-outlined', + 'ant-design:fund-projection-screen-outlined', + 'ant-design:fund-twotone', + 'ant-design:fund-view-outlined', + 'ant-design:funnel-plot-filled', + 'ant-design:funnel-plot-outlined', + 'ant-design:funnel-plot-twotone', + 'ant-design:gateway-outlined', + 'ant-design:gif-outlined', + 'ant-design:gift-filled', + 'ant-design:gift-outlined', + 'ant-design:gift-twotone', + 'ant-design:github-filled', + 'ant-design:github-outlined', + 'ant-design:gitlab-filled', + 'ant-design:gitlab-outlined', + 'ant-design:global-outlined', + 'ant-design:gold-filled', + 'ant-design:gold-outlined', + 'ant-design:gold-twotone', + 'ant-design:golden-filled', + 'ant-design:google-circle-filled', + 'ant-design:google-outlined', + 'ant-design:google-plus-circle-filled', + 'ant-design:google-plus-outlined', + 'ant-design:google-plus-square-filled', + 'ant-design:google-square-filled', + 'ant-design:group-outlined', + 'ant-design:hdd-filled', + 'ant-design:hdd-outlined', + 'ant-design:hdd-twotone', + 'ant-design:heart-filled', + 'ant-design:heart-outlined', + 'ant-design:heart-twotone', + 'ant-design:heat-map-outlined', + 'ant-design:highlight-filled', + 'ant-design:highlight-outlined', + 'ant-design:highlight-twotone', + 'ant-design:history-outlined', + 'ant-design:holder-outlined', + 'ant-design:home-filled', + 'ant-design:home-outlined', + 'ant-design:home-twotone', + 'ant-design:hourglass-filled', + 'ant-design:hourglass-outlined', + 'ant-design:hourglass-twotone', + 'ant-design:html5-filled', + 'ant-design:html5-outlined', + 'ant-design:html5-twotone', + 'ant-design:idcard-filled', + 'ant-design:idcard-outlined', + 'ant-design:idcard-twotone', + 'ant-design:ie-circle-filled', + 'ant-design:ie-outlined', + 'ant-design:ie-square-filled', + 'ant-design:import-outlined', + 'ant-design:inbox-outlined', + 'ant-design:info-circle-filled', + 'ant-design:info-circle-outlined', + 'ant-design:info-circle-twotone', + 'ant-design:info-outlined', + 'ant-design:insert-row-above-outlined', + 'ant-design:insert-row-below-outlined', + 'ant-design:insert-row-left-outlined', + 'ant-design:insert-row-right-outlined', + 'ant-design:instagram-filled', + 'ant-design:instagram-outlined', + 'ant-design:insurance-filled', + 'ant-design:insurance-outlined', + 'ant-design:insurance-twotone', + 'ant-design:interaction-filled', + 'ant-design:interaction-outlined', + 'ant-design:interaction-twotone', + 'ant-design:issues-close-outlined', + 'ant-design:italic-outlined', + 'ant-design:key-outlined', + 'ant-design:laptop-outlined', + 'ant-design:layout-filled', + 'ant-design:layout-outlined', + 'ant-design:layout-twotone', + 'ant-design:left-circle-filled', + 'ant-design:left-circle-outlined', + 'ant-design:left-circle-twotone', + 'ant-design:left-outlined', + 'ant-design:left-square-filled', + 'ant-design:left-square-outlined', + 'ant-design:left-square-twotone', + 'ant-design:like-filled', + 'ant-design:like-outlined', + 'ant-design:like-twotone', + 'ant-design:line-chart-outlined', + 'ant-design:line-height-outlined', + 'ant-design:line-outlined', + 'ant-design:link-outlined', + 'ant-design:linkedin-filled', + 'ant-design:linkedin-outlined', + 'ant-design:loading-3-quarters-outlined', + 'ant-design:loading-outlined', + 'ant-design:lock-filled', + 'ant-design:lock-outlined', + 'ant-design:lock-twotone', + 'ant-design:login-outlined', + 'ant-design:logout-outlined', + 'ant-design:mac-command-filled', + 'ant-design:mac-command-outlined', + 'ant-design:mail-filled', + 'ant-design:mail-outlined', + 'ant-design:mail-twotone', + 'ant-design:man-outlined', + 'ant-design:medicine-box-filled', + 'ant-design:medicine-box-outlined', + 'ant-design:medicine-box-twotone', + 'ant-design:medium-circle-filled', + 'ant-design:medium-outlined', + 'ant-design:medium-square-filled', + 'ant-design:medium-workmark-outlined', + 'ant-design:meh-filled', + 'ant-design:meh-outlined', + 'ant-design:meh-twotone', + 'ant-design:menu-fold-outlined', + 'ant-design:menu-outlined', + 'ant-design:menu-unfold-outlined', + 'ant-design:merge-cells-outlined', + 'ant-design:message-filled', + 'ant-design:message-outlined', + 'ant-design:message-twotone', + 'ant-design:minus-circle-filled', + 'ant-design:minus-circle-outlined', + 'ant-design:minus-circle-twotone', + 'ant-design:minus-outlined', + 'ant-design:minus-square-filled', + 'ant-design:minus-square-outlined', + 'ant-design:minus-square-twotone', + 'ant-design:mobile-filled', + 'ant-design:mobile-outlined', + 'ant-design:mobile-twotone', + 'ant-design:money-collect-filled', + 'ant-design:money-collect-outlined', + 'ant-design:money-collect-twotone', + 'ant-design:monitor-outlined', + 'ant-design:more-outlined', + 'ant-design:node-collapse-outlined', + 'ant-design:node-expand-outlined', + 'ant-design:node-index-outlined', + 'ant-design:notification-filled', + 'ant-design:notification-outlined', + 'ant-design:notification-twotone', + 'ant-design:number-outlined', + 'ant-design:one-to-one-outlined', + 'ant-design:ordered-list-outlined', + 'ant-design:paper-clip-outlined', + 'ant-design:partition-outlined', + 'ant-design:pause-circle-filled', + 'ant-design:pause-circle-outlined', + 'ant-design:pause-circle-twotone', + 'ant-design:pause-outlined', + 'ant-design:pay-circle-filled', + 'ant-design:pay-circle-outlined', + 'ant-design:percentage-outlined', + 'ant-design:phone-filled', + 'ant-design:phone-outlined', + 'ant-design:phone-twotone', + 'ant-design:pic-center-outlined', + 'ant-design:pic-left-outlined', + 'ant-design:pic-right-outlined', + 'ant-design:picture-filled', + 'ant-design:picture-outlined', + 'ant-design:picture-twotone', + 'ant-design:pie-chart-filled', + 'ant-design:pie-chart-outlined', + 'ant-design:pie-chart-twotone', + 'ant-design:play-circle-filled', + 'ant-design:play-circle-outlined', + 'ant-design:play-circle-twotone', + 'ant-design:play-square-filled', + 'ant-design:play-square-outlined', + 'ant-design:play-square-twotone', + 'ant-design:plus-circle-filled', + 'ant-design:plus-circle-outlined', + 'ant-design:plus-circle-twotone', + 'ant-design:plus-outlined', + 'ant-design:plus-square-filled', + 'ant-design:plus-square-outlined', + 'ant-design:plus-square-twotone', + 'ant-design:pound-circle-filled', + 'ant-design:pound-circle-outlined', + 'ant-design:pound-circle-twotone', + 'ant-design:pound-outlined', + 'ant-design:poweroff-outlined', + 'ant-design:printer-filled', + 'ant-design:printer-outlined', + 'ant-design:printer-twotone', + 'ant-design:profile-filled', + 'ant-design:profile-outlined', + 'ant-design:profile-twotone', + 'ant-design:project-filled', + 'ant-design:project-outlined', + 'ant-design:project-twotone', + 'ant-design:property-safety-filled', + 'ant-design:property-safety-outlined', + 'ant-design:property-safety-twotone', + 'ant-design:pull-request-outlined', + 'ant-design:pushpin-filled', + 'ant-design:pushpin-outlined', + 'ant-design:pushpin-twotone', + 'ant-design:qq-circle-filled', + 'ant-design:qq-outlined', + 'ant-design:qq-square-filled', + 'ant-design:qrcode-outlined', + 'ant-design:question-circle-filled', + 'ant-design:question-circle-outlined', + 'ant-design:question-circle-twotone', + 'ant-design:question-outlined', + 'ant-design:radar-chart-outlined', + 'ant-design:radius-bottomleft-outlined', + 'ant-design:radius-bottomright-outlined', + 'ant-design:radius-setting-outlined', + 'ant-design:radius-upleft-outlined', + 'ant-design:radius-upright-outlined', + 'ant-design:read-filled', + 'ant-design:read-outlined', + 'ant-design:reconciliation-filled', + 'ant-design:reconciliation-outlined', + 'ant-design:reconciliation-twotone', + 'ant-design:red-envelope-filled', + 'ant-design:red-envelope-outlined', + 'ant-design:red-envelope-twotone', + 'ant-design:reddit-circle-filled', + 'ant-design:reddit-outlined', + 'ant-design:reddit-square-filled', + 'ant-design:redo-outlined', + 'ant-design:reload-outlined', + 'ant-design:rest-filled', + 'ant-design:rest-outlined', + 'ant-design:rest-twotone', + 'ant-design:retweet-outlined', + 'ant-design:right-circle-filled', + 'ant-design:right-circle-outlined', + 'ant-design:right-circle-twotone', + 'ant-design:right-outlined', + 'ant-design:right-square-filled', + 'ant-design:right-square-outlined', + 'ant-design:right-square-twotone', + 'ant-design:rise-outlined', + 'ant-design:robot-filled', + 'ant-design:robot-outlined', + 'ant-design:rocket-filled', + 'ant-design:rocket-outlined', + 'ant-design:rocket-twotone', + 'ant-design:rollback-outlined', + 'ant-design:rotate-left-outlined', + 'ant-design:rotate-right-outlined', + 'ant-design:safety-certificate-filled', + 'ant-design:safety-certificate-outlined', + 'ant-design:safety-certificate-twotone', + 'ant-design:safety-outlined', + 'ant-design:save-filled', + 'ant-design:save-outlined', + 'ant-design:save-twotone', + 'ant-design:scan-outlined', + 'ant-design:schedule-filled', + 'ant-design:schedule-outlined', + 'ant-design:schedule-twotone', + 'ant-design:scissor-outlined', + 'ant-design:search-outlined', + 'ant-design:security-scan-filled', + 'ant-design:security-scan-outlined', + 'ant-design:security-scan-twotone', + 'ant-design:select-outlined', + 'ant-design:send-outlined', + 'ant-design:setting-filled', + 'ant-design:setting-outlined', + 'ant-design:setting-twotone', + 'ant-design:shake-outlined', + 'ant-design:share-alt-outlined', + 'ant-design:shop-filled', + 'ant-design:shop-outlined', + 'ant-design:shop-twotone', + 'ant-design:shopping-cart-outlined', + 'ant-design:shopping-filled', + 'ant-design:shopping-outlined', + 'ant-design:shopping-twotone', + 'ant-design:shrink-outlined', + 'ant-design:signal-filled', + 'ant-design:sisternode-outlined', + 'ant-design:sketch-circle-filled', + 'ant-design:sketch-outlined', + 'ant-design:sketch-square-filled', + 'ant-design:skin-filled', + 'ant-design:skin-outlined', + 'ant-design:skin-twotone', + 'ant-design:skype-filled', + 'ant-design:skype-outlined', + 'ant-design:slack-circle-filled', + 'ant-design:slack-outlined', + 'ant-design:slack-square-filled', + 'ant-design:slack-square-outlined', + 'ant-design:sliders-filled', + 'ant-design:sliders-outlined', + 'ant-design:sliders-twotone', + 'ant-design:small-dash-outlined', + 'ant-design:smile-filled', + 'ant-design:smile-outlined', + 'ant-design:smile-twotone', + 'ant-design:snippets-filled', + 'ant-design:snippets-outlined', + 'ant-design:snippets-twotone', + 'ant-design:solution-outlined', + 'ant-design:sort-ascending-outlined', + 'ant-design:sort-descending-outlined', + 'ant-design:sound-filled', + 'ant-design:sound-outlined', + 'ant-design:sound-twotone', + 'ant-design:split-cells-outlined', + 'ant-design:star-filled', + 'ant-design:star-outlined', + 'ant-design:star-twotone', + 'ant-design:step-backward-filled', + 'ant-design:step-backward-outlined', + 'ant-design:step-forward-filled', + 'ant-design:step-forward-outlined', + 'ant-design:stock-outlined', + 'ant-design:stop-filled', + 'ant-design:stop-outlined', + 'ant-design:stop-twotone', + 'ant-design:strikethrough-outlined', + 'ant-design:subnode-outlined', + 'ant-design:swap-left-outlined', + 'ant-design:swap-outlined', + 'ant-design:swap-right-outlined', + 'ant-design:switcher-filled', + 'ant-design:switcher-outlined', + 'ant-design:switcher-twotone', + 'ant-design:sync-outlined', + 'ant-design:table-outlined', + 'ant-design:tablet-filled', + 'ant-design:tablet-outlined', + 'ant-design:tablet-twotone', + 'ant-design:tag-filled', + 'ant-design:tag-outlined', + 'ant-design:tag-twotone', + 'ant-design:tags-filled', + 'ant-design:tags-outlined', + 'ant-design:tags-twotone', + 'ant-design:taobao-circle-filled', + 'ant-design:taobao-circle-outlined', + 'ant-design:taobao-outlined', + 'ant-design:taobao-square-filled', + 'ant-design:team-outlined', + 'ant-design:thunderbolt-filled', + 'ant-design:thunderbolt-outlined', + 'ant-design:thunderbolt-twotone', + 'ant-design:to-top-outlined', + 'ant-design:tool-filled', + 'ant-design:tool-outlined', + 'ant-design:tool-twotone', + 'ant-design:trademark-circle-filled', + 'ant-design:trademark-circle-outlined', + 'ant-design:trademark-circle-twotone', + 'ant-design:trademark-outlined', + 'ant-design:transaction-outlined', + 'ant-design:translation-outlined', + 'ant-design:trophy-filled', + 'ant-design:trophy-outlined', + 'ant-design:trophy-twotone', + 'ant-design:twitter-circle-filled', + 'ant-design:twitter-outlined', + 'ant-design:twitter-square-filled', + 'ant-design:underline-outlined', + 'ant-design:undo-outlined', + 'ant-design:ungroup-outlined', + 'ant-design:unlock-filled', + 'ant-design:unlock-outlined', + 'ant-design:unlock-twotone', + 'ant-design:unordered-list-outlined', + 'ant-design:up-circle-filled', + 'ant-design:up-circle-outlined', + 'ant-design:up-circle-twotone', + 'ant-design:up-outlined', + 'ant-design:up-square-filled', + 'ant-design:up-square-outlined', + 'ant-design:up-square-twotone', + 'ant-design:upload-outlined', + 'ant-design:usb-filled', + 'ant-design:usb-outlined', + 'ant-design:usb-twotone', + 'ant-design:user-add-outlined', + 'ant-design:user-delete-outlined', + 'ant-design:user-outlined', + 'ant-design:user-switch-outlined', + 'ant-design:usergroup-add-outlined', + 'ant-design:usergroup-delete-outlined', + 'ant-design:verified-outlined', + 'ant-design:vertical-align-bottom-outlined', + 'ant-design:vertical-align-middle-outlined', + 'ant-design:vertical-align-top-outlined', + 'ant-design:vertical-left-outlined', + 'ant-design:vertical-right-outlined', + 'ant-design:video-camera-add-outlined', + 'ant-design:video-camera-filled', + 'ant-design:video-camera-outlined', + 'ant-design:video-camera-twotone', + 'ant-design:wallet-filled', + 'ant-design:wallet-outlined', + 'ant-design:wallet-twotone', + 'ant-design:warning-filled', + 'ant-design:warning-outlined', + 'ant-design:warning-twotone', + 'ant-design:wechat-filled', + 'ant-design:wechat-outlined', + 'ant-design:weibo-circle-filled', + 'ant-design:weibo-circle-outlined', + 'ant-design:weibo-outlined', + 'ant-design:weibo-square-filled', + 'ant-design:weibo-square-outlined', + 'ant-design:whats-app-outlined', + 'ant-design:wifi-outlined', + 'ant-design:windows-filled', + 'ant-design:windows-outlined', + 'ant-design:woman-outlined', + 'ant-design:yahoo-filled', + 'ant-design:yahoo-outlined', + 'ant-design:youtube-filled', + 'ant-design:youtube-outlined', + 'ant-design:yuque-filled', + 'ant-design:yuque-outlined', + 'ant-design:zhihu-circle-filled', + 'ant-design:zhihu-outlined', + 'ant-design:zhihu-square-filled', + 'ant-design:zoom-in-outlined', + 'ant-design:zoom-out-outlined', +]; diff --git a/src/components/Icon/index.ts b/src/components/Icon/index.ts new file mode 100644 index 0000000..01e7d23 --- /dev/null +++ b/src/components/Icon/index.ts @@ -0,0 +1,7 @@ +import Icon from './src/Icon.vue'; +import SvgIcon from './src/SvgIcon.vue'; +import IconPicker from './src/IconPicker.vue'; + +export { Icon, IconPicker, SvgIcon }; + +export default Icon; diff --git a/src/components/Icon/src/Icon.vue b/src/components/Icon/src/Icon.vue new file mode 100644 index 0000000..075c57a --- /dev/null +++ b/src/components/Icon/src/Icon.vue @@ -0,0 +1,116 @@ + + + diff --git a/src/components/Icon/src/IconList.vue b/src/components/Icon/src/IconList.vue new file mode 100644 index 0000000..e4ecb07 --- /dev/null +++ b/src/components/Icon/src/IconList.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/src/components/Icon/src/IconPicker.vue b/src/components/Icon/src/IconPicker.vue new file mode 100644 index 0000000..d91c463 --- /dev/null +++ b/src/components/Icon/src/IconPicker.vue @@ -0,0 +1,245 @@ + + + diff --git a/src/components/Icon/src/SvgIcon.vue b/src/components/Icon/src/SvgIcon.vue new file mode 100644 index 0000000..20bfcca --- /dev/null +++ b/src/components/Icon/src/SvgIcon.vue @@ -0,0 +1,61 @@ + + + diff --git a/src/components/InFilter/CascaderPcaInFilter.vue b/src/components/InFilter/CascaderPcaInFilter.vue new file mode 100644 index 0000000..a3786bd --- /dev/null +++ b/src/components/InFilter/CascaderPcaInFilter.vue @@ -0,0 +1,39 @@ + + + + + + + + diff --git a/src/components/InFilter/DatePickerInFilter.vue b/src/components/InFilter/DatePickerInFilter.vue new file mode 100644 index 0000000..bc26ff7 --- /dev/null +++ b/src/components/InFilter/DatePickerInFilter.vue @@ -0,0 +1,141 @@ + + + + + + + + diff --git a/src/components/InFilter/index.ts b/src/components/InFilter/index.ts new file mode 100644 index 0000000..b37c83c --- /dev/null +++ b/src/components/InFilter/index.ts @@ -0,0 +1,2 @@ +export {default as DatePickerInFilter} from './DatePickerInFilter.vue'; +export {default as CascaderPcaInFilter} from './CascaderPcaInFilter.vue'; diff --git a/src/components/JDragNotice/JDragNotice.vue b/src/components/JDragNotice/JDragNotice.vue new file mode 100644 index 0000000..6b51315 --- /dev/null +++ b/src/components/JDragNotice/JDragNotice.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/src/components/JVxeCustom/index.ts b/src/components/JVxeCustom/index.ts new file mode 100644 index 0000000..9e87a79 --- /dev/null +++ b/src/components/JVxeCustom/index.ts @@ -0,0 +1,36 @@ +import { registerComponent, registerAsyncComponent, registerASyncComponentReal } from '/@/components/jeecg/JVxeTable'; +import { JVxeTypes } from '/@/components/jeecg/JVxeTable/types'; +import { DictSearchSpanCell, DictSearchInputCell } from './src/components/JVxeSelectDictSearchCell'; +import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; +export async function registerJVxeCustom() { + // ----------------- ⚠ 注意事项 ⚠ ----------------- + // 当组件内包含 BasicModal 时,必须使用异步引入! + // 否则将会导致 i18n 失效! + // ----------------- ⚠ 注意事项 ⚠ ----------------- + + // 注册【Popup】(普通封装方式) + await registerAsyncComponent(JVxeTypes.popup, import('./src/components/JVxePopupCell.vue')); + + // 注册【字典搜索下拉】组件(高级封装方式) + registerComponent(JVxeTypes.selectDictSearch, DictSearchInputCell, DictSearchSpanCell); + + // 注册【文件上传】组件 + await registerAsyncComponent(JVxeTypes.file, import('./src/components/JVxeFileCell.vue')); + // 注册【图片上传】组件 + await registerAsyncComponent(JVxeTypes.image, import('./src/components/JVxeImageCell.vue')); + // 注册【用户选择】组件 + await registerAsyncComponent(JVxeTypes.userSelect, import('./src/components/JVxeUserSelectCell.vue')); + // 注册【部门选择】组件 + await registerAsyncComponent(JVxeTypes.departSelect, import('./src/components/JVxeDepartSelectCell.vue')); + // update-begin--author:liaozhiyang---date:20260317---for:【QQYUN-9441】online一对多加上关联记录和他表字段 + // 注册【关联记录】组件 + await registerAsyncComponent(JVxeTypes.linkTable, import('./src/components/JVxeLinkTableCell.vue')); + // update-end--author:liaozhiyang---date:20260317---for:【QQYUN-9441】online一对多加上关联记录和他表字段 + // 注册【省市区选择】组件 + // await registerAsyncComponent(JVxeTypes.pca, import('./src/components/JVxePcaCell.vue')); + // 代码逻辑说明: 【QQYUN-8241】为避免首次加载china-area-data,JVxePcaCell组件需异步加载 + registerASyncComponentReal( + JVxeTypes.pca, + createAsyncComponent(() => import('./src/components/JVxePcaCell.vue')) + ); +} diff --git a/src/components/JVxeCustom/src/components/JVxeDepartSelectCell.vue b/src/components/JVxeCustom/src/components/JVxeDepartSelectCell.vue new file mode 100644 index 0000000..0a23d59 --- /dev/null +++ b/src/components/JVxeCustom/src/components/JVxeDepartSelectCell.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/src/components/JVxeCustom/src/components/JVxeFileCell.vue b/src/components/JVxeCustom/src/components/JVxeFileCell.vue new file mode 100644 index 0000000..6a65155 --- /dev/null +++ b/src/components/JVxeCustom/src/components/JVxeFileCell.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/components/JVxeCustom/src/components/JVxeImageCell.vue b/src/components/JVxeCustom/src/components/JVxeImageCell.vue new file mode 100644 index 0000000..ee8d2cf --- /dev/null +++ b/src/components/JVxeCustom/src/components/JVxeImageCell.vue @@ -0,0 +1,145 @@ + + + + diff --git a/src/components/JVxeCustom/src/components/JVxeLinkTableCell.vue b/src/components/JVxeCustom/src/components/JVxeLinkTableCell.vue new file mode 100644 index 0000000..b1228b0 --- /dev/null +++ b/src/components/JVxeCustom/src/components/JVxeLinkTableCell.vue @@ -0,0 +1,105 @@ + + + diff --git a/src/components/JVxeCustom/src/components/JVxePcaCell.vue b/src/components/JVxeCustom/src/components/JVxePcaCell.vue new file mode 100644 index 0000000..b547892 --- /dev/null +++ b/src/components/JVxeCustom/src/components/JVxePcaCell.vue @@ -0,0 +1,77 @@ + + + + diff --git a/src/components/JVxeCustom/src/components/JVxePopupCell.vue b/src/components/JVxeCustom/src/components/JVxePopupCell.vue new file mode 100644 index 0000000..40b8773 --- /dev/null +++ b/src/components/JVxeCustom/src/components/JVxePopupCell.vue @@ -0,0 +1,75 @@ + + diff --git a/src/components/JVxeCustom/src/components/JVxeSelectDictSearchCell.ts b/src/components/JVxeCustom/src/components/JVxeSelectDictSearchCell.ts new file mode 100644 index 0000000..f1e1298 --- /dev/null +++ b/src/components/JVxeCustom/src/components/JVxeSelectDictSearchCell.ts @@ -0,0 +1,288 @@ +import { computed, ref, watch, defineComponent, h } from 'vue'; +import { cloneDeep, debounce } from 'lodash-es'; +import { defHttp } from '/@/utils/http/axios'; +import { filterDictText } from '/@/utils/dict/JDictSelectUtil'; +import { ajaxGetDictItems, getDictItemsByCode } from '/@/utils/dict'; +import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types'; +import { dispatchEvent } from '/@/components/jeecg/JVxeTable/utils'; +import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { Select, SelectOption, Spin } from 'ant-design-vue' + +/** value - label map,防止重复查询(刷新清空缓存) */ +const LabelMap = new Map(); +// 请求id +let requestId = 0; + +/** 显示组件,自带翻译 */ +export const DictSearchSpanCell = defineComponent({ + name: 'JVxeSelectSearchSpanCell', + props: useJVxeCompProps(), + setup(props: JVxeComponent.Props) { + const { innerOptions, innerSelectValue, innerValue } = useSelectDictSearch(props); + return () => { + return h('span', {}, [filterDictText(innerOptions.value, innerSelectValue.value || innerValue.value)]); + }; + }, +}); + +// 输入选择组件 +export const DictSearchInputCell = defineComponent({ + name: 'JVxeSelectSearchInputCell', + props: useJVxeCompProps(), + setup(props: JVxeComponent.Props) { + const { createMessage } = useMessage(); + const { dict, loading, isAsync, options, innerOptions, originColumn, cellProps, innerSelectValue, handleChangeCommon } = + useSelectDictSearch(props); + const hasRequest = ref(false); + // 提示信息 + const tipsContent = computed(() => { + return originColumn.value.tipsContent || '请输入搜索内容'; + }); + // 筛选函数 + const filterOption = computed(() => { + if (isAsync.value) { + //【jeecgboot-vue3/issues/I5QRT8】JVxeTypes.selectDictSearch sync问题 + return ()=>true; + } + return (input, option) => option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0; + }); + + /** 加载数据 */ + const loadData = debounce((value) => { + const currentRequestId = ++requestId; + loading.value = true; + innerOptions.value = []; + if (value == null || value.trim() === '') { + loading.value = false; + hasRequest.value = false; + return; + } + // 字典code格式:table,text,code + hasRequest.value = true; + loadDictByKeyword(dict.value, value) + .then((res) => { + if (currentRequestId !== requestId) { + return; + } + let { success, result, message } = res; + if (success) { + innerOptions.value = result; + result.forEach((item) => { + LabelMap.set(item.value, [item]); + }); + } else { + createMessage.warning(message || '查询失败'); + } + }) + .finally(() => { + loading.value = false; + }); + }, 300); + + function handleChange(selectedValue) { + innerSelectValue.value = selectedValue; + handleChangeCommon(innerSelectValue.value); + } + + function handleSearch(value) { + if (isAsync.value) { + // 在输入时也应该开启加载,因为loadData加了消抖,所以会有800ms的用户主观上认为的卡顿时间 + loading.value = true; + if (innerOptions.value.length > 0) { + innerOptions.value = []; + } + loadData(value); + } + } + + function renderOptionItem() { + let optionItems: any[] = []; + options.value.forEach(({ value, text, label, title, disabled }) => { + optionItems.push( + h( + SelectOption, + { + key: value, + value: value, + disabled: disabled, + }, + { + default: () => text || label || title, + } + ) + ); + }); + return optionItems; + } + + return () => { + return h( + Select, + { + ...cellProps.value, + value: innerSelectValue.value, + filterOption: filterOption.value, + showSearch: true, + allowClear: true, + autofocus: true, + defaultOpen: true, + style: 'width: 100%', + onSearch: handleSearch, + onChange: handleChange, + }, + { + default: () => renderOptionItem(), + notFoundContent: () => { + if (loading.value) { + return h(Spin, { size: 'small' }); + } else if (hasRequest.value) { + return h('div', '没有查询到任何数据'); + } else { + return h('div', [tipsContent.value]); + } + }, + } + ); + }; + }, + // 【组件增强】注释详见:JVxeComponent.Enhanced + enhanced: { + aopEvents: { + editActived({ $event }) { + dispatchEvent({ + $event, + props: this.props, + className: '.ant-select .ant-select-selection-search-input', + isClick: false, + handler: (el) => el.focus(), + }); + }, + }, + } as JVxeComponent.EnhancedPartial, +}); + +function useSelectDictSearch(props) { + const setup = useJVxeComponent(props); + const { innerValue, originColumn } = setup; + + // 加载状态 + const loading = ref(false); + // 内部选择值 + const innerSelectValue = ref(null); + // 内部 options + const innerOptions = ref([]); + + const dict = computed(() => originColumn.value.dict); + // 是否是异步模式 + const isAsync = computed(() => { + let isAsync = originColumn.value.async; + return isAsync != null && isAsync !== '' ? !!isAsync : true; + }); + const options = computed(() => { + if (isAsync.value) { + return innerOptions.value; + } else { + return originColumn.value.options || []; + } + }); + + /** 公共属性监听 */ + watch( + innerValue, + (value: string) => { + if (value == null || value === '') { + innerSelectValue.value = null; + } else { + loadDataByValue(value); + } + }, + { immediate: true } + ); + watch(dict, () => loadDataByDict()); + + // 根据 value 查询数据,用于回显 + async function loadDataByValue(value) { + if (isAsync.value) { + if (innerSelectValue.value !== value) { + if (LabelMap.has(value)) { + innerOptions.value = cloneDeep(LabelMap.get(value)); + } else { + let result = await loadDictItem(dict.value, value); + if (result && result.length > 0) { + innerOptions.value = [{ value: value, text: result[0] }]; + LabelMap.set(value, cloneDeep(innerOptions.value)); + } + } + } + } + innerSelectValue.value = (value || '').toString(); + } + + // 初始化字典 + async function loadDataByDict() { + if (!isAsync.value) { + // 如果字典项集合有数据 + if (!originColumn.value.options || originColumn.value.options.length === 0) { + // 根据字典Code, 初始化字典数组 + let dictStr = ''; + if (dict.value) { + let arr = dict.value.split(','); + if (arr[0].indexOf('where') > 0) { + let tbInfo = arr[0].split('where'); + dictStr = tbInfo[0].trim() + ',' + arr[1] + ',' + arr[2] + ',' + encodeURIComponent(tbInfo[1]); + } else { + dictStr = dict.value; + } + if (dict.value.indexOf(',') === -1) { + //优先从缓存中读取字典配置 + let cache = getDictItemsByCode(dict.value); + if (cache) { + innerOptions.value = cache; + return; + } + } + let { success, result } = await ajaxGetDictItems(dictStr, null); + if (success) { + innerOptions.value = result; + } + } + } + } + } + + return { + ...setup, + loading, + innerOptions, + innerSelectValue, + dict, + isAsync, + options, + }; +} + +/** 获取字典项 */ +function loadDictItem(dict: string, key: string) { + return defHttp.get({ + url: `/sys/dict/loadDictItem/${dict}`, + params: { + key: key, + }, + }); +} + +/** 根据关键字获取字典项(搜索) */ +function loadDictByKeyword(dict: string, keyword: string) { + return defHttp.get( + { + url: `/sys/dict/loadDict/${dict}`, + params: { + keyword: keyword, + }, + }, + { + isTransformResponse: false, + } + ); +} diff --git a/src/components/JVxeCustom/src/components/JVxeUserSelectCell.vue b/src/components/JVxeCustom/src/components/JVxeUserSelectCell.vue new file mode 100644 index 0000000..da05296 --- /dev/null +++ b/src/components/JVxeCustom/src/components/JVxeUserSelectCell.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/components/JVxeCustom/src/hooks/useFileCell.ts b/src/components/JVxeCustom/src/hooks/useFileCell.ts new file mode 100644 index 0000000..833f175 --- /dev/null +++ b/src/components/JVxeCustom/src/hooks/useFileCell.ts @@ -0,0 +1,101 @@ +import { computed } from 'vue'; +import { fileGetValue, fileSetValue, useJVxeUploadCell } from '/@/components/jeecg/JVxeTable/src/hooks/cells/useJVxeUploadCell'; +import { uploadUrl } from '/@/api/common/api'; +import { JUploadModal, UploadTypeEnum } from '/@/components/Form/src/jeecg/components/JUpload'; +import { useModal } from '/@/components/Modal'; +import { JVxeComponent } from '/@/components/jeecg/JVxeTable/src/types/JVxeComponent'; +import { Icon } from '/@/components/Icon'; +import { Dropdown } from 'ant-design-vue'; +import { LoadingOutlined } from '@ant-design/icons-vue'; + +export function useFileCell(props, fileType: UploadTypeEnum, options?) { + const setup = useJVxeUploadCell(props, { token: true, action: uploadUrl, ...options }); + + const { innerFile, handleChangeCommon, originColumn } = setup; + const [registerModel, { openModal }] = useModal(); + + // 截取文件名 + const ellipsisFileName = computed(() => { + let length = 5; + let file = innerFile.value; + if (!file || !file.name) { + return ''; + } + if (file.name.length > length) { + return file.name.substr(0, length) + '…'; + } + return file.name; + }); + + const modalValue = computed(() => { + if (innerFile.value) { + if (innerFile.value['url']) { + return innerFile.value['url']; + } else if (innerFile.value['path']) { + return innerFile.value['path']; + } + } + return ''; + }); + + const maxCount = computed(() => { + let maxCount = originColumn.value.maxCount; + // online 扩展JSON + if (originColumn.value && originColumn.value.fieldExtendJson) { + let json = JSON.parse(originColumn.value.fieldExtendJson); + maxCount = json.uploadnum ? json.uploadnum : 0; + } + return maxCount ?? 0; + }); + + // 点击更多按钮 + function handleMoreOperation() { + openModal(true, { + removeConfirm: true, + mover: true, + download: true, + ...originColumn.value.props, + maxCount: maxCount.value, + fileType: fileType, + action: originColumn.value.action ?? void 0, + }); + } + + // 更多上传回调 + function onModalChange(path) { + if (path) { + // 代码逻辑说明: 【TV360X-235】富文本禁用状态下图片上传按钮文字看不清 + if (innerFile.value === null) { + innerFile.value = {}; + } + innerFile.value.path = path; + handleChangeCommon(innerFile.value); + } else { + // 代码逻辑说明: [issues/530]JVxeTable 的JVxeTypes.image类型,无法全部删除上传图片 + handleChangeCommon(null); + } + } + + return { + ...setup, + modalValue, + maxCount, + ellipsisFileName, + registerModel, + onModalChange, + handleMoreOperation, + }; +} + +export const components = { + Icon, + Dropdown, + LoadingOutlined, + JUploadModal, +}; + +export const enhanced = { + switches: { visible: true }, + getValue: (value) => fileGetValue(value), + setValue: (value) => fileSetValue(value), +} as JVxeComponent.EnhancedPartial; diff --git a/src/components/Loading/index.ts b/src/components/Loading/index.ts new file mode 100644 index 0000000..3673a44 --- /dev/null +++ b/src/components/Loading/index.ts @@ -0,0 +1,5 @@ +import Loading from './src/Loading.vue'; + +export { Loading }; +export { useLoading } from './src/useLoading'; +export { createLoading } from './src/createLoading'; diff --git a/src/components/Loading/src/Loading.vue b/src/components/Loading/src/Loading.vue new file mode 100644 index 0000000..f626b18 --- /dev/null +++ b/src/components/Loading/src/Loading.vue @@ -0,0 +1,79 @@ + + + diff --git a/src/components/Loading/src/createLoading.ts b/src/components/Loading/src/createLoading.ts new file mode 100644 index 0000000..5efff7f --- /dev/null +++ b/src/components/Loading/src/createLoading.ts @@ -0,0 +1,65 @@ +import { VNode, defineComponent } from 'vue'; +import type { LoadingProps } from './typing'; + +import { createVNode, render, reactive, h } from 'vue'; +import Loading from './Loading.vue'; + +export function createLoading(props?: Partial, target?: HTMLElement, wait = false) { + let vm: Nullable = null; + const data = reactive({ + tip: '', + loading: true, + ...props, + }); + + const LoadingWrap = defineComponent({ + render() { + return h(Loading, { ...data }); + }, + }); + + vm = createVNode(LoadingWrap); + + if (wait) { + // TODO fix https://github.com/anncwb/vue-Jeecg-admin/issues/438 + setTimeout(() => { + render(vm, document.createElement('div')); + }, 0); + } else { + render(vm, document.createElement('div')); + } + + function close() { + if (vm?.el && vm.el.parentNode) { + vm.el.parentNode.removeChild(vm.el); + } + } + + function open(target: HTMLElement = document.body) { + if (!vm || !vm.el) { + return; + } + target.appendChild(vm.el as HTMLElement); + } + + if (target) { + open(target); + } + return { + vm, + close, + open, + setTip: (tip: string) => { + data.tip = tip; + }, + setLoading: (loading: boolean) => { + data.loading = loading; + }, + get loading() { + return data.loading; + }, + get $el() { + return vm?.el as HTMLElement; + }, + }; +} diff --git a/src/components/Loading/src/typing.ts b/src/components/Loading/src/typing.ts new file mode 100644 index 0000000..9af60e6 --- /dev/null +++ b/src/components/Loading/src/typing.ts @@ -0,0 +1,10 @@ +import { SizeEnum } from '/@/enums/sizeEnum'; + +export interface LoadingProps { + tip: string; + size: SizeEnum; + absolute: boolean; + loading: boolean; + background: string; + theme: 'dark' | 'light'; +} diff --git a/src/components/Loading/src/useLoading.ts b/src/components/Loading/src/useLoading.ts new file mode 100644 index 0000000..b5f1215 --- /dev/null +++ b/src/components/Loading/src/useLoading.ts @@ -0,0 +1,47 @@ +import { unref } from 'vue'; +import { createLoading } from './createLoading'; +import type { LoadingProps } from './typing'; +import type { Ref } from 'vue'; + +export interface UseLoadingOptions { + target?: any; + props?: Partial; +} + +interface Fn { + (): void; +} + +export function useLoading(props: Partial): [Fn, Fn, (string) => void]; +export function useLoading(opt: Partial): [Fn, Fn, (string) => void]; + +export function useLoading(opt: Partial | Partial): [Fn, Fn, (string) => void] { + let props: Partial; + let target: HTMLElement | Ref = document.body; + + if (Reflect.has(opt, 'target') || Reflect.has(opt, 'props')) { + const options = opt as Partial; + props = options.props || {}; + target = options.target || document.body; + } else { + props = opt as Partial; + } + + const instance = createLoading(props, undefined, true); + + const open = (): void => { + const t = unref(target as Ref); + if (!t) return; + instance.open(t); + }; + + const close = (): void => { + instance.close(); + }; + + const setTip = (tip: string) => { + instance.setTip(tip); + }; + + return [open, close, setTip]; +} diff --git a/src/components/Markdown/index.ts b/src/components/Markdown/index.ts new file mode 100644 index 0000000..d337681 --- /dev/null +++ b/src/components/Markdown/index.ts @@ -0,0 +1,7 @@ +import { withInstall } from '/@/utils'; +import markDown from './src/Markdown.vue'; +import markDownViewer from './src/MarkdownViewer.vue'; + +export const MarkDown = withInstall(markDown); +export const MarkdownViewer = withInstall(markDownViewer); +export * from './src/typing'; diff --git a/src/components/Markdown/src/Markdown.vue b/src/components/Markdown/src/Markdown.vue new file mode 100644 index 0000000..14eca5b --- /dev/null +++ b/src/components/Markdown/src/Markdown.vue @@ -0,0 +1,255 @@ + + + diff --git a/src/components/Markdown/src/MarkdownViewer.vue b/src/components/Markdown/src/MarkdownViewer.vue new file mode 100644 index 0000000..f25274a --- /dev/null +++ b/src/components/Markdown/src/MarkdownViewer.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/src/components/Markdown/src/typing.ts b/src/components/Markdown/src/typing.ts new file mode 100644 index 0000000..b4bb465 --- /dev/null +++ b/src/components/Markdown/src/typing.ts @@ -0,0 +1,4 @@ +import Vditor from 'vditor'; +export interface MarkDownActionType { + getVditor: () => Vditor; +} diff --git a/src/components/Menu/index.ts b/src/components/Menu/index.ts new file mode 100644 index 0000000..4a59225 --- /dev/null +++ b/src/components/Menu/index.ts @@ -0,0 +1,3 @@ +import BasicMenu from './src/BasicMenu.vue'; + +export { BasicMenu }; diff --git a/src/components/Menu/src/BasicMenu.vue b/src/components/Menu/src/BasicMenu.vue new file mode 100644 index 0000000..6e8d7c4 --- /dev/null +++ b/src/components/Menu/src/BasicMenu.vue @@ -0,0 +1,298 @@ + + + diff --git a/src/components/Menu/src/components/BasicMenuItem.vue b/src/components/Menu/src/components/BasicMenuItem.vue new file mode 100644 index 0000000..fd54497 --- /dev/null +++ b/src/components/Menu/src/components/BasicMenuItem.vue @@ -0,0 +1,20 @@ + + diff --git a/src/components/Menu/src/components/BasicSubMenuItem.vue b/src/components/Menu/src/components/BasicSubMenuItem.vue new file mode 100644 index 0000000..6d516da --- /dev/null +++ b/src/components/Menu/src/components/BasicSubMenuItem.vue @@ -0,0 +1,131 @@ + + + diff --git a/src/components/Menu/src/components/MenuItemContent.vue b/src/components/Menu/src/components/MenuItemContent.vue new file mode 100644 index 0000000..3044fbc --- /dev/null +++ b/src/components/Menu/src/components/MenuItemContent.vue @@ -0,0 +1,34 @@ + + diff --git a/src/components/Menu/src/index.less b/src/components/Menu/src/index.less new file mode 100644 index 0000000..c969109 --- /dev/null +++ b/src/components/Menu/src/index.less @@ -0,0 +1,76 @@ +@basic-menu-prefix-cls: ~'@{namespace}-basic-menu'; + +.app-top-menu-popup { + min-width: 150px; +} + +.@{basic-menu-prefix-cls} { + width: 100%; + + .ant-menu-item { + transition: unset; + } + + &__sidebar-hor { + &.ant-menu-horizontal { + display: flex; + align-items: center; + + &.ant-menu-dark { + background-color: transparent; + // update-begin--author:liaozhiyang---date:20240407---for:【QQYUN-8762】顶部菜单模式文字调整 + color: rgba(255 ,255 ,255, 1); + // update-end--author:liaozhiyang---date:20240407---for:【QQYUN-8762】顶部菜单模式文字调整 + .ant-menu-submenu:hover, + .ant-menu-item-open, + .ant-menu-submenu-open, + .ant-menu-item-selected, + .ant-menu-submenu-selected, + .ant-menu-item:hover, + .ant-menu-item-active, + .ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open, + .ant-menu-submenu-active, + .ant-menu-submenu-title:hover { + color: #fff; + background-color: @top-menu-active-bg-color !important; + } + + .ant-menu-item:hover, + .ant-menu-item-active, + .ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open, + .ant-menu-submenu-active, + .ant-menu-submenu-title:hover { + background-color: @top-menu-active-bg-color; + } + + .@{basic-menu-prefix-cls}-item__level1 { + background-color: transparent; + + &.ant-menu-item-selected, + &.ant-menu-submenu-selected { + background-color: @top-menu-active-bg-color !important; + } + } + + .ant-menu-item, + .ant-menu-submenu { + &.@{basic-menu-prefix-cls}-item__level1, + .ant-menu-submenu-title { + height: @header-height; + line-height: @header-height; + } + } + } + } + } + + .ant-menu-submenu, + .ant-menu-submenu-inline { + transition: unset; + } + + .ant-menu-inline.ant-menu-sub { + box-shadow: unset !important; + transition: unset; + } +} diff --git a/src/components/Menu/src/props.ts b/src/components/Menu/src/props.ts new file mode 100644 index 0000000..ed3f010 --- /dev/null +++ b/src/components/Menu/src/props.ts @@ -0,0 +1,60 @@ +import type { Menu } from '/@/router/types'; +import type { PropType } from 'vue'; + +import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; +import { ThemeEnum } from '/@/enums/appEnum'; +import { propTypes } from '/@/utils/propTypes'; +import type { MenuTheme } from 'ant-design-vue'; +import type { MenuMode } from 'ant-design-vue/lib/menu/src/interface'; +export const basicProps = { + items: { + type: Array as PropType, + default: () => [], + }, + collapsedShowTitle: propTypes.bool, + // 最好是4 倍数 + inlineIndent: propTypes.number.def(20), + // 菜单组件的mode属性 + mode: { + type: String as PropType, + default: MenuModeEnum.INLINE, + }, + + type: { + type: String as PropType, + default: MenuTypeEnum.MIX, + }, + theme: { + type: String as PropType, + default: ThemeEnum.DARK, + }, + inlineCollapsed: propTypes.bool, + mixSider: propTypes.bool, + + isHorizontal: propTypes.bool, + accordion: propTypes.bool.def(true), + beforeClickFn: { + type: Function as PropType<(key: string) => Promise>, + }, +}; + +export const itemProps = { + item: { + type: Object as PropType, + default: {}, + }, + level: propTypes.number, + theme: propTypes.oneOf(['dark', 'light']), + showTitle: propTypes.bool, + isHorizontal: propTypes.bool, +}; + +export const contentProps = { + item: { + type: Object as PropType, + default: null, + }, + showTitle: propTypes.bool.def(true), + level: propTypes.number.def(0), + isHorizontal: propTypes.bool.def(true), +}; diff --git a/src/components/Menu/src/types.ts b/src/components/Menu/src/types.ts new file mode 100644 index 0000000..ad711c2 --- /dev/null +++ b/src/components/Menu/src/types.ts @@ -0,0 +1,25 @@ +// import { ComputedRef } from 'vue'; +// import { ThemeEnum } from '/@/enums/appEnum'; +// import { MenuModeEnum } from '/@/enums/menuEnum'; +export interface MenuState { + // 默认选中的列表 + defaultSelectedKeys: string[]; + + // 模式 + // mode: MenuModeEnum; + + // // 主题 + // theme: ComputedRef | ThemeEnum; + + // 缩进 + inlineIndent?: number; + + // 展开数组 + openKeys: string[]; + + // 当前选中的菜单项 key 数组 + selectedKeys: string[]; + + // 收缩状态下展开的数组 + collapsedOpenKeys: string[]; +} diff --git a/src/components/Menu/src/useBasicMenuContext.ts b/src/components/Menu/src/useBasicMenuContext.ts new file mode 100644 index 0000000..4e687c7 --- /dev/null +++ b/src/components/Menu/src/useBasicMenuContext.ts @@ -0,0 +1,16 @@ +import type { InjectionKey, Ref } from 'vue'; +import { createContext, useContext } from '/@/hooks/core/useContext'; + +export interface BasicRootMenuContextProps { + menuState: any; +} + +const key: InjectionKey = Symbol(); + +export function createBasicRootMenuContext(context: BasicRootMenuContextProps) { + return createContext(context, key, { readonly: false, native: true }); +} + +export function useBasicRootMenuContext() { + return useContext(key); +} diff --git a/src/components/Menu/src/useOpenKeys.ts b/src/components/Menu/src/useOpenKeys.ts new file mode 100644 index 0000000..3e35eac --- /dev/null +++ b/src/components/Menu/src/useOpenKeys.ts @@ -0,0 +1,78 @@ +import { MenuModeEnum } from '/@/enums/menuEnum'; +import type { Menu as MenuType } from '/@/router/types'; +import type { MenuState } from './types'; + +import { computed, Ref, toRaw } from 'vue'; + +import { unref } from 'vue'; +import { uniq } from 'lodash-es'; +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { getAllParentPath } from '/@/router/helper/menuHelper'; +import { useTimeoutFn } from '/@/hooks/core/useTimeout'; + +export function useOpenKeys(menuState: MenuState, menus: Ref, mode: Ref, accordion: Ref) { + const { getCollapsed, getIsMixSidebar } = useMenuSetting(); + + async function setOpenKeys(path: string) { + if (mode.value === MenuModeEnum.HORIZONTAL) { + return; + } + const native = unref(getIsMixSidebar); + useTimeoutFn( + () => { + const menuList = toRaw(menus.value); + if (menuList?.length === 0) { + menuState.openKeys = []; + return; + } + if (!unref(accordion)) { + menuState.openKeys = uniq([...menuState.openKeys, ...getAllParentPath(menuList, path)]); + } else { + menuState.openKeys = getAllParentPath(menuList, path); + } + }, + 16, + !native + ); + } + + const getOpenKeys = computed(() => { + const collapse = unref(getIsMixSidebar) ? false : unref(getCollapsed); + + return collapse ? menuState.collapsedOpenKeys : menuState.openKeys; + }); + + /** + * @description: 重置值 + */ + function resetKeys() { + menuState.selectedKeys = []; + menuState.openKeys = []; + } + + function handleOpenChange(openKeys: string[]) { + if (unref(mode) === MenuModeEnum.HORIZONTAL || !unref(accordion) || unref(getIsMixSidebar)) { + menuState.openKeys = openKeys; + } else { + // const menuList = toRaw(menus.value); + // getAllParentPath(menuList, path); + const rootSubMenuKeys: string[] = []; + for (const { children, path } of unref(menus)) { + if (children && children.length > 0) { + rootSubMenuKeys.push(path); + } + } + if (!unref(getCollapsed)) { + const latestOpenKey = openKeys.find((key) => menuState.openKeys.indexOf(key) === -1); + if (rootSubMenuKeys.indexOf(latestOpenKey as string) === -1) { + menuState.openKeys = openKeys; + } else { + menuState.openKeys = latestOpenKey ? [latestOpenKey] : []; + } + } else { + menuState.collapsedOpenKeys = openKeys; + } + } + } + return { setOpenKeys, resetKeys, getOpenKeys, handleOpenChange }; +} diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts new file mode 100644 index 0000000..6188c5c --- /dev/null +++ b/src/components/Modal/index.ts @@ -0,0 +1,8 @@ +import { withInstall } from '/@/utils'; +import './src/index.less'; +import basicModal from './src/BasicModal.vue'; + +export const BasicModal = withInstall(basicModal); +export { useModalContext } from './src/hooks/useModalContext'; +export { useModal, useModalInner } from './src/hooks/useModal'; +export * from './src/typing'; diff --git a/src/components/Modal/src/BasicModal.vue b/src/components/Modal/src/BasicModal.vue new file mode 100644 index 0000000..4769ea5 --- /dev/null +++ b/src/components/Modal/src/BasicModal.vue @@ -0,0 +1,317 @@ + + + diff --git a/src/components/Modal/src/JModal/JModal.vue b/src/components/Modal/src/JModal/JModal.vue new file mode 100644 index 0000000..04936fc --- /dev/null +++ b/src/components/Modal/src/JModal/JModal.vue @@ -0,0 +1,337 @@ + + + + + + + diff --git a/src/components/Modal/src/components/Modal.tsx b/src/components/Modal/src/components/Modal.tsx new file mode 100644 index 0000000..f2def6d --- /dev/null +++ b/src/components/Modal/src/components/Modal.tsx @@ -0,0 +1,31 @@ +import { Modal } from 'ant-design-vue'; +import { defineComponent, toRefs, unref } from 'vue'; +import { basicProps } from '../props'; +import { useModalDragMove } from '../hooks/useModalDrag'; +import { useAttrs } from '/@/hooks/core/useAttrs'; +import { extendSlots } from '/@/utils/helper/tsxHelper'; +import { omit } from 'lodash-es'; + +export default defineComponent({ + name: 'Modal', + inheritAttrs: false, + props: omit(basicProps, ['visible']), + emits: ['cancel'], + setup(props, { slots, emit }) { + const { open, draggable, destroyOnClose } = toRefs(props); + const attrs = useAttrs(); + useModalDragMove({ + visible: open, + destroyOnClose, + draggable, + }); + const onCancel = (e: Event) => { + emit('cancel', e); + }; + + return () => { + const propsData = { ...unref(attrs), ...props, onCancel } as Recordable; + return {extendSlots(slots)}; + }; + }, +}); diff --git a/src/components/Modal/src/components/ModalClose.vue b/src/components/Modal/src/components/ModalClose.vue new file mode 100644 index 0000000..1d210b7 --- /dev/null +++ b/src/components/Modal/src/components/ModalClose.vue @@ -0,0 +1,160 @@ + + + diff --git a/src/components/Modal/src/components/ModalFooter.vue b/src/components/Modal/src/components/ModalFooter.vue new file mode 100644 index 0000000..7bc5786 --- /dev/null +++ b/src/components/Modal/src/components/ModalFooter.vue @@ -0,0 +1,34 @@ + + diff --git a/src/components/Modal/src/components/ModalHeader.vue b/src/components/Modal/src/components/ModalHeader.vue new file mode 100644 index 0000000..bf6c112 --- /dev/null +++ b/src/components/Modal/src/components/ModalHeader.vue @@ -0,0 +1,22 @@ + + diff --git a/src/components/Modal/src/components/ModalWrapper.vue b/src/components/Modal/src/components/ModalWrapper.vue new file mode 100644 index 0000000..b6ce0d8 --- /dev/null +++ b/src/components/Modal/src/components/ModalWrapper.vue @@ -0,0 +1,214 @@ + + diff --git a/src/components/Modal/src/hooks/useModal.ts b/src/components/Modal/src/hooks/useModal.ts new file mode 100644 index 0000000..8af596f --- /dev/null +++ b/src/components/Modal/src/hooks/useModal.ts @@ -0,0 +1,154 @@ +import type { UseModalReturnType, ModalMethods, ModalProps, ReturnMethods, UseModalInnerReturnType } from '../typing'; +import { ref, onUnmounted, unref, getCurrentInstance, reactive, watchEffect, nextTick, toRaw } from 'vue'; +import { isProdMode } from '/@/utils/env'; +import { isFunction } from '/@/utils/is'; +import { isEqual } from 'lodash-es'; +import { tryOnUnmounted } from '@vueuse/core'; +import { error } from '/@/utils/log'; +import { computed } from 'vue'; + +const dataTransfer = reactive({}); + +const visibleData = reactive<{ [key: number]: boolean }>({}); + +/** + * @description: Applicable to independent modal and call outside + */ +export function useModal(): UseModalReturnType { + const modal = ref>(null); + const loaded = ref>(false); + const uid = ref(''); + + function register(modalMethod: ModalMethods, uuid: string) { + if (!getCurrentInstance()) { + throw new Error('useModal() can only be used inside setup() or functional components!'); + } + uid.value = uuid; + isProdMode() && + onUnmounted(() => { + modal.value = null; + loaded.value = false; + dataTransfer[unref(uid)] = null; + }); + if (unref(loaded) && isProdMode() && modalMethod === unref(modal)) return; + + modal.value = modalMethod; + loaded.value = true; + modalMethod.emitVisible = (visible: boolean, uid: number) => { + visibleData[uid] = visible; + }; + } + + const getInstance = () => { + const instance = unref(modal); + if (!instance) { + error('useModal instance is undefined!'); + } + return instance; + }; + + const methods: ReturnMethods = { + setModalProps: (props: Partial): void => { + getInstance()?.setModalProps(props); + }, + + getVisible: computed((): boolean => { + return visibleData[~~unref(uid)]; + }), + getOpen: computed((): boolean => { + return visibleData[~~unref(uid)]; + }), + redoModalHeight: () => { + getInstance()?.redoModalHeight?.(); + }, + + openModal: (visible = true, data?: T, openOnSet = true): void => { + // 代码逻辑说明: 【QQYUN-6366】升级到antd4.x + getInstance()?.setModalProps({ + open: visible, + }); + + if (!data) return; + const id = unref(uid); + if (openOnSet) { + dataTransfer[id] = null; + dataTransfer[id] = toRaw(data); + return; + } + const equal = isEqual(toRaw(dataTransfer[id]), toRaw(data)); + if (!equal) { + dataTransfer[id] = toRaw(data); + } + }, + + closeModal: () => { + // 代码逻辑说明: 【QQYUN-6366】升级到antd4.x + getInstance()?.setModalProps({ open: false }); + }, + }; + return [register, methods]; +} + +export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => { + const modalInstanceRef = ref>(null); + const currentInstance = getCurrentInstance(); + const uidRef = ref(''); + + const getInstance = () => { + const instance = unref(modalInstanceRef); + if (!instance) { + error('useModalInner instance is undefined!'); + } + return instance; + }; + + const register = (modalInstance: ModalMethods, uuid: string) => { + isProdMode() && + tryOnUnmounted(() => { + modalInstanceRef.value = null; + }); + uidRef.value = uuid; + modalInstanceRef.value = modalInstance; + currentInstance?.emit('register', modalInstance, uuid); + }; + + watchEffect(() => { + const data = dataTransfer[unref(uidRef)]; + if (!data) return; + if (!callbackFn || !isFunction(callbackFn)) return; + nextTick(() => { + callbackFn(data); + }); + }); + + return [ + register, + { + changeLoading: (loading = true) => { + getInstance()?.setModalProps({ loading }); + }, + getVisible: computed((): boolean => { + return visibleData[~~unref(uidRef)]; + }), + getOpen: computed((): boolean => { + return visibleData[~~unref(uidRef)]; + }), + changeOkLoading: (loading = true) => { + getInstance()?.setModalProps({ confirmLoading: loading }); + }, + + closeModal: () => { + getInstance()?.setModalProps({ open: false }); + }, + + setModalProps: (props: Partial) => { + getInstance()?.setModalProps(props); + }, + + redoModalHeight: () => { + const callRedo = getInstance()?.redoModalHeight; + callRedo && callRedo(); + }, + }, + ]; +}; diff --git a/src/components/Modal/src/hooks/useModalContext.ts b/src/components/Modal/src/hooks/useModalContext.ts new file mode 100644 index 0000000..94d4c4e --- /dev/null +++ b/src/components/Modal/src/hooks/useModalContext.ts @@ -0,0 +1,16 @@ +import { InjectionKey } from 'vue'; +import { createContext, useContext } from '/@/hooks/core/useContext'; + +export interface ModalContextProps { + redoModalHeight: () => void; +} + +const key: InjectionKey = Symbol(); + +export function createModalContext(context: ModalContextProps) { + return createContext(context, key); +} + +export function useModalContext() { + return useContext(key); +} diff --git a/src/components/Modal/src/hooks/useModalDrag.ts b/src/components/Modal/src/hooks/useModalDrag.ts new file mode 100644 index 0000000..38e35e1 --- /dev/null +++ b/src/components/Modal/src/hooks/useModalDrag.ts @@ -0,0 +1,111 @@ +import { Ref, unref, watchEffect } from 'vue'; +import { useTimeoutFn } from '/@/hooks/core/useTimeout'; + +export interface UseModalDragMoveContext { + draggable: Ref; + destroyOnClose: Ref | undefined; + visible: Ref; +} + +export function useModalDragMove(context: UseModalDragMoveContext) { + const getStyle = (dom: any, attr: any) => { + return getComputedStyle(dom)[attr]; + }; + const drag = (wrap: any) => { + if (!wrap) return; + wrap.setAttribute('data-drag', unref(context.draggable)); + const dialogHeaderEl = wrap.querySelector('.ant-modal-header'); + const dragDom = wrap.querySelector('.ant-modal'); + + if (!dialogHeaderEl || !dragDom || !unref(context.draggable)) return; + + dialogHeaderEl.style.cursor = 'move'; + + dialogHeaderEl.onmousedown = (e: any) => { + if (!e) return; + // 鼠标按下,计算当前元素距离可视区的距离 + const disX = e.clientX; + const disY = e.clientY; + const screenWidth = document.body.clientWidth; // body当前宽度 + const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取) + + const dragDomWidth = dragDom.offsetWidth; // 对话框宽度 + const dragDomheight = dragDom.offsetHeight; // 对话框高度 + + const minDragDomLeft = dragDom.offsetLeft; + + const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth; + const minDragDomTop = dragDom.offsetTop; + let maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight; + // 代码逻辑说明: [issue/430]弹出页面出现自动吸顶,无法移动和显示头部--- + if(maxDragDomTop<0){ + maxDragDomTop = screenHeight - dragDom.offsetTop + } + // 获取到的值带px 正则匹配替换 + const domLeft = getStyle(dragDom, 'left'); + const domTop = getStyle(dragDom, 'top'); + let styL = +domLeft; + let styT = +domTop; + + // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px + if (domLeft.includes('%')) { + styL = +document.body.clientWidth * (+domLeft.replace(/%/g, '') / 100); + styT = +document.body.clientHeight * (+domTop.replace(/%/g, '') / 100); + } else { + styL = +domLeft.replace(/px/g, ''); + styT = +domTop.replace(/px/g, ''); + } + + document.onmousemove = function (e) { + // 通过事件委托,计算移动的距离 + let left = e.clientX - disX; + let top = e.clientY - disY; + + // 边界处理 + if (-left > minDragDomLeft) { + left = -minDragDomLeft; + } else if (left > maxDragDomLeft) { + left = maxDragDomLeft; + } + + if (-top > minDragDomTop) { + top = -minDragDomTop; + } else if (top > maxDragDomTop) { + top = maxDragDomTop; + } + + // 移动当前元素 + dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`; + }; + + document.onmouseup = () => { + document.onmousemove = null; + document.onmouseup = null; + }; + }; + }; + + const handleDrag = () => { + const dragWraps = document.querySelectorAll('.ant-modal-wrap'); + for (const wrap of Array.from(dragWraps)) { + if (!wrap) continue; + const display = getStyle(wrap, 'display'); + const draggable = wrap.getAttribute('data-drag'); + if (display !== 'none') { + // 拖拽位置 + if (draggable === null || unref(context.destroyOnClose)) { + drag(wrap); + } + } + } + }; + + watchEffect(() => { + if (!unref(context.visible) || !unref(context.draggable)) { + return; + } + useTimeoutFn(() => { + handleDrag(); + }, 30); + }); +} diff --git a/src/components/Modal/src/hooks/useModalFullScreen.ts b/src/components/Modal/src/hooks/useModalFullScreen.ts new file mode 100644 index 0000000..b53563a --- /dev/null +++ b/src/components/Modal/src/hooks/useModalFullScreen.ts @@ -0,0 +1,43 @@ +import { computed, Ref, ref, unref } from 'vue'; + +export interface UseFullScreenContext { + wrapClassName: Ref; + modalWrapperRef: Ref; + extHeightRef: Ref; +} + +export function useFullScreen(context: UseFullScreenContext) { + // const formerHeightRef = ref(0); + const fullScreenRef = ref(false); + + const getWrapClassName = computed(() => { + const clsName = unref(context.wrapClassName) || ''; + return unref(fullScreenRef) ? `fullscreen-modal ${clsName} ` : unref(clsName); + }); + + function handleFullScreen(e: Event) { + e && e.stopPropagation(); + fullScreenRef.value = !unref(fullScreenRef); + + // const modalWrapper = unref(context.modalWrapperRef); + + // if (!modalWrapper) return; + + // const wrapperEl = modalWrapper.$el as HTMLElement; + // if (!wrapperEl) return; + // const modalWrapSpinEl = wrapperEl.querySelector('.ant-spin-nested-loading') as HTMLElement; + + // if (!modalWrapSpinEl) return; + + // if (!unref(formerHeightRef) && unref(fullScreenRef)) { + // formerHeightRef.value = modalWrapSpinEl.offsetHeight; + // } + + // if (unref(fullScreenRef)) { + // modalWrapSpinEl.style.height = `${window.innerHeight - unref(context.extHeightRef)}px`; + // } else { + // modalWrapSpinEl.style.height = `${unref(formerHeightRef)}px`; + // } + } + return { getWrapClassName, handleFullScreen, fullScreenRef }; +} diff --git a/src/components/Modal/src/index.less b/src/components/Modal/src/index.less new file mode 100644 index 0000000..d39a0bc --- /dev/null +++ b/src/components/Modal/src/index.less @@ -0,0 +1,142 @@ +.ant-modal-root .fullscreen-modal { + overflow: hidden; + + .ant-modal { + top: 0 !important; + right: 0 !important; + bottom: 0 !important; + left: 0 !important; + width: 100% !important; + height: 100% !important; + max-width: 100% !important; + max-height: 100% !important; + + &-content { + height: 100%; + } + + .ant-modal-header, + .@{namespace}-basic-title { + cursor: default !important; + } + // update-begin--author:liaozhiyang---date:20241225---for:【issues/7601】ant-design-vue@4.2.6后弹窗全屏底部有空隙 + & > div:has( > .ant-modal-content) { + height: 100%; + } + // update-end--author:liaozhiyang---date:20241225---for:【issues/7601】ant-design-vue@4.2.6后弹窗全屏底部有空隙 + } +} + +.ant-modal { + width: 520px; + padding-bottom: 0; + + .ant-modal-body > .scrollbar { + padding: 14px; + } + + .ant-modal-title { + font-size: 16px; + font-weight: bold; + line-height: 16px; + + .base-title { + cursor: move !important; + } + } + + .ant-modal-body { + padding: 0; + + > .scrollbar > .scrollbar__bar.is-horizontal { + display: none; + } + } + + .ant-modal-large { + top: 60px; + + &--mini { + top: 16px; + } + } + + .ant-modal-header { + padding: 16px; + } + + .ant-modal-content { + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); + } + + .ant-modal-footer { + button + button { + margin-left: 10px; + } + } + + .ant-modal-close { + font-weight: normal; + outline: none; + } + + .ant-modal-close-x { + // update-begin--author:liaozhiyang---date:20241010---for:【issues/7260】原生a-modal关闭按钮位置偏移 + // display: inline-block; + // width: 96px; + // height: 56px; + // line-height: 56px; + // update-end--author:liaozhiyang---date:20241010---for:【issues/7260】原生a-modal关闭按钮位置偏移 + } + + .ant-modal-confirm-body { + .ant-modal-confirm-content { + // color: #fff; + + > * { + color: @text-color-help-dark; + } + } + } + + .ant-modal-confirm-confirm.error .ant-modal-confirm-body > .anticon { + color: @error-color; + } + + .ant-modal-confirm-btns { + .ant-btn:last-child { + margin-right: 0; + } + } + + .ant-modal-confirm-info { + .ant-modal-confirm-body > .anticon { + color: @warning-color; + } + } + + .ant-modal-confirm-confirm.success { + .ant-modal-confirm-body > .anticon { + color: @success-color; + } + } +} + +.ant-modal-confirm .ant-modal-body { + padding: 24px !important; +} +@media screen and (max-height: 600px) { + .ant-modal { + top: 60px; + } +} +@media screen and (max-height: 540px) { + .ant-modal { + top: 30px; + } +} +@media screen and (max-height: 480px) { + .ant-modal { + top: 10px; + } +} diff --git a/src/components/Modal/src/props.ts b/src/components/Modal/src/props.ts new file mode 100644 index 0000000..4112690 --- /dev/null +++ b/src/components/Modal/src/props.ts @@ -0,0 +1,91 @@ +import type { PropType, CSSProperties } from 'vue'; +import type { ModalWrapperProps } from './typing'; +import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; +import { useI18n } from '/@/hooks/web/useI18n'; + +const { t } = useI18n(); + +export const modalProps = { + visible: { type: Boolean }, + scrollTop: { type: Boolean, default: true }, + height: { type: Number }, + minHeight: { type: Number }, + // open drag + draggable: { type: Boolean, default: true }, + centered: { type: Boolean }, + cancelText: { type: String, default: t('common.cancelText') }, + okText: { type: String, default: t('common.okText') }, + + closeFunc: Function as PropType<() => Promise>, + + modalHeaderHeight: Number, + modalFooterHeight: Number, +}; + +export const basicProps = Object.assign({}, modalProps, { + defaultFullscreen: { type: Boolean }, + // Can it be full screen + canFullscreen: { type: Boolean, default: true }, + // After enabling the wrapper, the bottom can be increased in height + wrapperFooterOffset: { type: Number, default: 0 }, + // Warm reminder message + helpMessage: [String, Array] as PropType, + // Whether to setting wrapper + useWrapper: { type: Boolean, default: true }, + loading: { type: Boolean }, + loadingTip: { type: String }, + /** + * @description: Show close button + */ + showCancelBtn: { type: Boolean, default: true }, + /** + * @description: Show confirmation button + */ + showOkBtn: { type: Boolean, default: true }, + + wrapperProps: Object as PropType>, + + afterClose: Function as PropType<() => Promise>, + + bodyStyle: Object as PropType, + + closable: { type: Boolean, default: true }, + + closeIcon: Object as PropType, + + confirmLoading: { type: Boolean }, + + destroyOnClose: { type: Boolean }, + + footer: Object as PropType, + + getContainer: Function as PropType<() => any>, + + mask: { type: Boolean, default: true }, + + maskClosable: { type: Boolean, default: true }, + keyboard: { type: Boolean, default: true }, + + maskStyle: Object as PropType, + + okType: { type: String, default: 'primary' }, + + okButtonProps: Object as PropType, + + cancelButtonProps: Object as PropType, + + title: { type: String }, + + visible: { type: Boolean }, + + open: { type: Boolean }, + + width: [String, Number] as PropType, + + wrapClassName: { type: String }, + + zIndex: { type: Number }, + maxHeight: { type: Number }, + // 是否开启评论区域 + enableComment: { type: Boolean, default: false }, +}); diff --git a/src/components/Modal/src/typing.ts b/src/components/Modal/src/typing.ts new file mode 100644 index 0000000..1d7fb9f --- /dev/null +++ b/src/components/Modal/src/typing.ts @@ -0,0 +1,217 @@ +import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes'; +import type { CSSProperties, VNodeChild, ComputedRef } from 'vue'; +/** + * @description: 弹窗对外暴露的方法 + */ +export interface ModalMethods { + setModalProps: (props: Partial) => void; + emitVisible?: (visible: boolean, uid: number) => void; + redoModalHeight?: () => void; +} + +export type RegisterFn = (modalMethods: ModalMethods, uuid?: string) => void; + +export interface ReturnMethods extends ModalMethods { + openModal: (props?: boolean, data?: T, openOnSet?: boolean) => void; + closeModal: () => void; + getVisible?: ComputedRef; + getOpen?: ComputedRef; +} + +export type UseModalReturnType = [RegisterFn, ReturnMethods]; + +export interface ReturnInnerMethods extends ModalMethods { + closeModal: () => void; + changeLoading: (loading: boolean) => void; + changeOkLoading: (loading: boolean) => void; + getVisible?: ComputedRef; + getOpen?: ComputedRef; + redoModalHeight: () => void; +} + +export type UseModalInnerReturnType = [RegisterFn, ReturnInnerMethods]; + +export interface ModalProps { + minHeight?: number; + height?: number; + // 启用wrapper后 底部可以适当增加高度 + wrapperFooterOffset?: number; + draggable?: boolean; + scrollTop?: boolean; + + // 是否可以进行全屏 + canFullscreen?: boolean; + defaultFullscreen?: boolean; + visible?: boolean; + open?: boolean; + // 温馨提醒信息 + helpMessage: string | string[]; + + // 是否使用modalWrapper + useWrapper: boolean; + + loading: boolean; + loadingTip?: string; + + wrapperProps: Omit; + + showOkBtn: boolean; + showCancelBtn: boolean; + closeFunc: () => Promise; + + /** + * Specify a function that will be called when modal is closed completely. + * @type Function + */ + afterClose?: () => any; + + /** + * Body style for modal body element. Such as height, padding etc. + * @default {} + * @type object + */ + bodyStyle?: CSSProperties; + + /** + * Text of the Cancel button + * @default 'cancel' + * @type string + */ + cancelText?: string; + + /** + * Centered Modal + * @default false + * @type boolean + */ + centered?: boolean; + + /** + * Whether a close (x) button is visible on top right of the modal dialog or not + * @default true + * @type boolean + */ + closable?: boolean; + /** + * Whether a close (x) button is visible on top right of the modal dialog or not + */ + closeIcon?: VNodeChild | JSX.Element; + + /** + * Whether to apply loading visual effect for OK button or not + * @default false + * @type boolean + */ + confirmLoading?: boolean; + + /** + * Whether to unmount child components on onClose + * @default false + * @type boolean + */ + destroyOnClose?: boolean; + + /** + * Footer content, set as :footer="null" when you don't need default buttons + * @default OK and Cancel buttons + * @type any (string | slot) + */ + footer?: VNodeChild | JSX.Element; + + /** + * Return the mount node for Modal + * @default () => document.body + * @type Function + */ + getContainer?: (instance: any) => HTMLElement; + + /** + * Whether show mask or not. + * @default true + * @type boolean + */ + mask?: boolean; + + /** + * Whether to close the modal dialog when the mask (area outside the modal) is clicked + * @default true + * @type boolean + */ + maskClosable?: boolean; + + /** + * Style for modal's mask element. + * @default {} + * @type object + */ + maskStyle?: CSSProperties; + + /** + * Text of the OK button + * @default 'OK' + * @type string + */ + okText?: string; + + /** + * Button type of the OK button + * @default 'primary' + * @type string + */ + okType?: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default'; + + /** + * The ok button props, follow jsx rules + * @type object + */ + okButtonProps?: ButtonProps; + + /** + * The cancel button props, follow jsx rules + * @type object + */ + cancelButtonProps?: ButtonProps; + + /** + * The modal dialog's title + * @type any (string | slot) + */ + title?: VNodeChild | JSX.Element; + + /** + * Width of the modal dialog + * @default 520 + * @type string | number + */ + width?: string | number; + + /** + * The class name of the container of the modal dialog + * @type string + */ + wrapClassName?: string; + + /** + * The z-index of the Modal + * @default 1000 + * @type number + */ + zIndex?: number; + + enableComment?: boolean; + + modalHeaderHeight: number; + modalFooterHeight: number; +} + +export interface ModalWrapperProps { + footerOffset?: number; + loading: boolean; + modalHeaderHeight: number; + modalFooterHeight: number; + minHeight: number; + height: number; + visible: boolean; + fullScreen: boolean; + useWrapper: boolean; +} diff --git a/src/components/Page/index.ts b/src/components/Page/index.ts new file mode 100644 index 0000000..d096264 --- /dev/null +++ b/src/components/Page/index.ts @@ -0,0 +1,7 @@ +import { withInstall } from '/@/utils'; + +import pageFooter from './src/PageFooter.vue'; +import pageWrapper from './src/PageWrapper.vue'; + +export const PageFooter = withInstall(pageFooter); +export const PageWrapper = withInstall(pageWrapper); diff --git a/src/components/Page/injectionKey.ts b/src/components/Page/injectionKey.ts new file mode 100644 index 0000000..9cc4278 --- /dev/null +++ b/src/components/Page/injectionKey.ts @@ -0,0 +1 @@ +export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight'; diff --git a/src/components/Page/src/PageFooter.vue b/src/components/Page/src/PageFooter.vue new file mode 100644 index 0000000..5440d2a --- /dev/null +++ b/src/components/Page/src/PageFooter.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/components/Page/src/PageWrapper.vue b/src/components/Page/src/PageWrapper.vue new file mode 100644 index 0000000..a9a5536 --- /dev/null +++ b/src/components/Page/src/PageWrapper.vue @@ -0,0 +1,186 @@ + + + diff --git a/src/components/Preview/index.ts b/src/components/Preview/index.ts new file mode 100644 index 0000000..c0b4685 --- /dev/null +++ b/src/components/Preview/index.ts @@ -0,0 +1,2 @@ +export { default as ImagePreview } from './src/Preview.vue'; +export { createImgPreview } from './src/functional'; diff --git a/src/components/Preview/src/Functional.vue b/src/components/Preview/src/Functional.vue new file mode 100644 index 0000000..84c51c2 --- /dev/null +++ b/src/components/Preview/src/Functional.vue @@ -0,0 +1,532 @@ + + diff --git a/src/components/Preview/src/Preview.vue b/src/components/Preview/src/Preview.vue new file mode 100644 index 0000000..3bb0b14 --- /dev/null +++ b/src/components/Preview/src/Preview.vue @@ -0,0 +1,94 @@ + + + diff --git a/src/components/Preview/src/functional.ts b/src/components/Preview/src/functional.ts new file mode 100644 index 0000000..e4b27d6 --- /dev/null +++ b/src/components/Preview/src/functional.ts @@ -0,0 +1,18 @@ +import type { Options, Props } from './typing'; +import ImgPreview from './Functional.vue'; +import { isClient } from '/@/utils/is'; +import { createVNode, render } from 'vue'; + +let instance: ReturnType | null = null; + +export function createImgPreview(options: Options) { + if (!isClient) return; + const propsData: Partial = {}; + const container = document.createElement('div'); + Object.assign(propsData, { show: true, index: 0, scaleStep: 100 }, options); + + instance = createVNode(ImgPreview, propsData); + render(instance, container); + document.body.appendChild(container); + return instance.component?.exposed; +} diff --git a/src/components/Preview/src/typing.ts b/src/components/Preview/src/typing.ts new file mode 100644 index 0000000..bbb8a83 --- /dev/null +++ b/src/components/Preview/src/typing.ts @@ -0,0 +1,49 @@ +export interface Options { + show?: boolean; + imageList: string[]; + index?: number; + scaleStep?: number; + defaultWidth?: number; + maskClosable?: boolean; + rememberState?: boolean; + onImgLoad?: ({ index: number, url: string, dom: HTMLImageElement }) => void; + onImgError?: ({ index: number, url: string, dom: HTMLImageElement }) => void; +} + +export interface Props { + show: boolean; + instance: Props; + imageList: string[]; + index: number; + scaleStep: number; + defaultWidth: number; + maskClosable: boolean; + rememberState: boolean; +} + +export interface PreviewActions { + resume: () => void; + close: () => void; + prev: () => void; + next: () => void; + setScale: (scale: number) => void; + setRotate: (rotate: number) => void; +} + +export interface ImageProps { + alt?: string; + fallback?: string; + src: string; + width: string | number; + height?: string | number; + placeholder?: string | boolean; + preview?: + | boolean + | { + visible?: boolean; + onVisibleChange?: (visible: boolean, prevVisible: boolean) => void; + getContainer: string | HTMLElement | (() => HTMLElement); + }; +} + +export type ImageItem = string | ImageProps; diff --git a/src/components/Qrcode/index.ts b/src/components/Qrcode/index.ts new file mode 100644 index 0000000..16a2f40 --- /dev/null +++ b/src/components/Qrcode/index.ts @@ -0,0 +1,5 @@ +import { withInstall } from '/@/utils'; +import qrCode from './src/Qrcode.vue'; + +export const QrCode = withInstall(qrCode); +export * from './src/typing'; diff --git a/src/components/Qrcode/src/Qrcode.vue b/src/components/Qrcode/src/Qrcode.vue new file mode 100644 index 0000000..494053a --- /dev/null +++ b/src/components/Qrcode/src/Qrcode.vue @@ -0,0 +1,117 @@ + + diff --git a/src/components/Qrcode/src/drawCanvas.ts b/src/components/Qrcode/src/drawCanvas.ts new file mode 100644 index 0000000..82aee5f --- /dev/null +++ b/src/components/Qrcode/src/drawCanvas.ts @@ -0,0 +1,32 @@ +import { toCanvas } from 'qrcode'; +import type { QRCodeRenderersOptions } from 'qrcode'; +import { RenderQrCodeParams, ContentType } from './typing'; +import { cloneDeep } from 'lodash-es'; + +export const renderQrCode = ({ canvas, content, width = 0, options: params = {} }: RenderQrCodeParams) => { + const options = cloneDeep(params); + // 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率 + options.errorCorrectionLevel = options.errorCorrectionLevel || getErrorCorrectionLevel(content); + + return getOriginWidth(content, options).then((_width: number) => { + options.scale = width === 0 ? undefined : (width / _width) * 4; + return toCanvas(canvas, content, options); + }); +}; + +// 得到原QrCode的大小,以便缩放得到正确的QrCode大小 +function getOriginWidth(content: ContentType, options: QRCodeRenderersOptions) { + const _canvas = document.createElement('canvas'); + return toCanvas(_canvas, content, options).then(() => _canvas.width); +} + +// 对于内容少的QrCode,增大容错率 +function getErrorCorrectionLevel(content: ContentType) { + if (content.length > 36) { + return 'M'; + } else if (content.length > 16) { + return 'Q'; + } else { + return 'H'; + } +} diff --git a/src/components/Qrcode/src/drawLogo.ts b/src/components/Qrcode/src/drawLogo.ts new file mode 100644 index 0000000..dbfe292 --- /dev/null +++ b/src/components/Qrcode/src/drawLogo.ts @@ -0,0 +1,81 @@ +import { isString } from '/@/utils/is'; +import { RenderQrCodeParams, LogoType } from './typing'; +export const drawLogo = ({ canvas, logo }: RenderQrCodeParams) => { + if (!logo) { + return new Promise((resolve) => { + resolve((canvas as HTMLCanvasElement).toDataURL()); + }); + } + const canvasWidth = (canvas as HTMLCanvasElement).width; + const { logoSize = 0.15, bgColor = '#ffffff', borderSize = 0.05, crossOrigin, borderRadius = 8, logoRadius = 0 } = logo as LogoType; + + const logoSrc: string = isString(logo) ? logo : logo.src; + const logoWidth = canvasWidth * logoSize; + const logoXY = (canvasWidth * (1 - logoSize)) / 2; + const logoBgWidth = canvasWidth * (logoSize + borderSize); + const logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + // logo 底色 + canvasRoundRect(ctx)(logoBgXY, logoBgXY, logoBgWidth, logoBgWidth, borderRadius); + ctx.fillStyle = bgColor; + ctx.fill(); + + // logo + const image = new Image(); + if (crossOrigin || logoRadius) { + image.setAttribute('crossOrigin', crossOrigin || 'anonymous'); + } + image.src = logoSrc; + + // 使用image绘制可以避免某些跨域情况 + const drawLogoWithImage = (image: CanvasImageSource) => { + ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth); + }; + + // 使用canvas绘制以获得更多的功能 + const drawLogoWithCanvas = (image: HTMLImageElement) => { + const canvasImage = document.createElement('canvas'); + canvasImage.width = logoXY + logoWidth; + canvasImage.height = logoXY + logoWidth; + const imageCanvas = canvasImage.getContext('2d'); + if (!imageCanvas || !ctx) return; + imageCanvas.drawImage(image, logoXY, logoXY, logoWidth, logoWidth); + + canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius); + if (!ctx) return; + const fillStyle = ctx.createPattern(canvasImage, 'no-repeat'); + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + }; + + // 将 logo绘制到 canvas上 + return new Promise((resolve) => { + image.onload = () => { + logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image); + resolve((canvas as HTMLCanvasElement).toDataURL()); + }; + }); +}; + +// copy来的方法,用于绘制圆角 +function canvasRoundRect(ctx: CanvasRenderingContext2D) { + return (x: number, y: number, w: number, h: number, r: number) => { + const minSize = Math.min(w, h); + if (r > minSize / 2) { + r = minSize / 2; + } + ctx.beginPath(); + ctx.moveTo(x + r, y); + ctx.arcTo(x + w, y, x + w, y + h, r); + ctx.arcTo(x + w, y + h, x, y + h, r); + ctx.arcTo(x, y + h, x, y, r); + ctx.arcTo(x, y, x + w, y, r); + ctx.closePath(); + return ctx; + }; +} diff --git a/src/components/Qrcode/src/qrcodePlus.ts b/src/components/Qrcode/src/qrcodePlus.ts new file mode 100644 index 0000000..6439861 --- /dev/null +++ b/src/components/Qrcode/src/qrcodePlus.ts @@ -0,0 +1,4 @@ +// 参考 qr-code-with-logo 进行ts版本修改 +import { toCanvas } from './toCanvas'; +export * from './typing'; +export { toCanvas }; diff --git a/src/components/Qrcode/src/toCanvas.ts b/src/components/Qrcode/src/toCanvas.ts new file mode 100644 index 0000000..f74d596 --- /dev/null +++ b/src/components/Qrcode/src/toCanvas.ts @@ -0,0 +1,10 @@ +import { renderQrCode } from './drawCanvas'; +import { drawLogo } from './drawLogo'; +import { RenderQrCodeParams } from './typing'; +export const toCanvas = (options: RenderQrCodeParams) => { + return renderQrCode(options) + .then(() => { + return options; + }) + .then(drawLogo) as Promise; +}; diff --git a/src/components/Qrcode/src/typing.ts b/src/components/Qrcode/src/typing.ts new file mode 100644 index 0000000..3a037e9 --- /dev/null +++ b/src/components/Qrcode/src/typing.ts @@ -0,0 +1,38 @@ +import type { QRCodeSegment, QRCodeRenderersOptions } from 'qrcode'; + +export type ContentType = string | QRCodeSegment[]; + +export type { QRCodeRenderersOptions }; + +export type LogoType = { + src: string; + logoSize: number; + borderColor: string; + bgColor: string; + borderSize: number; + crossOrigin: string; + borderRadius: number; + logoRadius: number; +}; + +export interface RenderQrCodeParams { + canvas: any; + content: ContentType; + width?: number; + options?: QRCodeRenderersOptions; + logo?: LogoType | string; + image?: HTMLImageElement; + downloadName?: string; + download?: boolean | Fn; +} + +export type ToCanvasFn = (options: RenderQrCodeParams) => Promise; + +export interface QrCodeActionType { + download: (fileName?: string) => void; +} + +export interface QrcodeDoneEventParams { + url: string; + ctx?: CanvasRenderingContext2D | null; +} diff --git a/src/components/Scrollbar/index.ts b/src/components/Scrollbar/index.ts new file mode 100644 index 0000000..e5b2cb2 --- /dev/null +++ b/src/components/Scrollbar/index.ts @@ -0,0 +1,8 @@ +/** + * copy from element-ui + */ + +import Scrollbar from './src/Scrollbar.vue'; + +export { Scrollbar }; +export type { ScrollbarType } from './src/types'; diff --git a/src/components/Scrollbar/src/Scrollbar.vue b/src/components/Scrollbar/src/Scrollbar.vue new file mode 100644 index 0000000..3ea4a02 --- /dev/null +++ b/src/components/Scrollbar/src/Scrollbar.vue @@ -0,0 +1,193 @@ + + + diff --git a/src/components/Scrollbar/src/bar.ts b/src/components/Scrollbar/src/bar.ts new file mode 100644 index 0000000..d56b56c --- /dev/null +++ b/src/components/Scrollbar/src/bar.ts @@ -0,0 +1,92 @@ +import { defineComponent, h, computed, ref, getCurrentInstance, onUnmounted, inject, Ref } from 'vue'; +import { on, off } from '/@/utils/domUtils'; + +import { renderThumbStyle, BAR_MAP } from './util'; + +export default defineComponent({ + name: 'Bar', + + props: { + vertical: Boolean, + size: String, + move: Number, + }, + + setup(props) { + const instance = getCurrentInstance(); + const thumb = ref(); + const wrap = inject('scroll-bar-wrap', {} as Ref>) as any; + const bar = computed(() => { + return BAR_MAP[props.vertical ? 'vertical' : 'horizontal']; + }); + const barStore = ref({}); + const cursorDown = ref(); + const clickThumbHandler = (e: any) => { + // prevent click event of right button + if (e.ctrlKey || e.button === 2) { + return; + } + window.getSelection()?.removeAllRanges(); + startDrag(e); + barStore.value[bar.value.axis] = + e.currentTarget[bar.value.offset] - (e[bar.value.client] - e.currentTarget.getBoundingClientRect()[bar.value.direction]); + }; + + const clickTrackHandler = (e: any) => { + const offset = Math.abs(e.target.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]); + const thumbHalf = thumb.value[bar.value.offset] / 2; + const thumbPositionPercentage = ((offset - thumbHalf) * 100) / instance?.vnode.el?.[bar.value.offset]; + + wrap.value[bar.value.scroll] = (thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100; + }; + const startDrag = (e: any) => { + e.stopImmediatePropagation(); + cursorDown.value = true; + on(document, 'mousemove', mouseMoveDocumentHandler); + on(document, 'mouseup', mouseUpDocumentHandler); + document.onselectstart = () => false; + }; + + const mouseMoveDocumentHandler = (e: any) => { + if (cursorDown.value === false) return; + const prevPage = barStore.value[bar.value.axis]; + + if (!prevPage) return; + + const offset = (instance?.vnode.el?.getBoundingClientRect()[bar.value.direction] - e[bar.value.client]) * -1; + const thumbClickPosition = thumb.value[bar.value.offset] - prevPage; + const thumbPositionPercentage = ((offset - thumbClickPosition) * 100) / instance?.vnode.el?.[bar.value.offset]; + wrap.value[bar.value.scroll] = (thumbPositionPercentage * wrap.value[bar.value.scrollSize]) / 100; + }; + + function mouseUpDocumentHandler() { + cursorDown.value = false; + barStore.value[bar.value.axis] = 0; + off(document, 'mousemove', mouseMoveDocumentHandler); + document.onselectstart = null; + } + + onUnmounted(() => { + off(document, 'mouseup', mouseUpDocumentHandler); + }); + + return () => + h( + 'div', + { + class: ['scrollbar__bar', 'is-' + bar.value.key], + onMousedown: clickTrackHandler, + }, + h('div', { + ref: thumb, + class: 'scrollbar__thumb', + onMousedown: clickThumbHandler, + style: renderThumbStyle({ + size: props.size, + move: props.move, + bar: bar.value, + }), + }) + ); + }, +}); diff --git a/src/components/Scrollbar/src/types.d.ts b/src/components/Scrollbar/src/types.d.ts new file mode 100644 index 0000000..4c7eeea --- /dev/null +++ b/src/components/Scrollbar/src/types.d.ts @@ -0,0 +1,18 @@ +export interface BarMapItem { + offset: string; + scroll: string; + scrollSize: string; + size: string; + key: string; + axis: string; + client: string; + direction: string; +} +export interface BarMap { + vertical: BarMapItem; + horizontal: BarMapItem; +} + +export interface ScrollbarType { + wrap: ElRef; +} diff --git a/src/components/Scrollbar/src/util.ts b/src/components/Scrollbar/src/util.ts new file mode 100644 index 0000000..b7c4845 --- /dev/null +++ b/src/components/Scrollbar/src/util.ts @@ -0,0 +1,50 @@ +import type { BarMap } from './types'; +export const BAR_MAP: BarMap = { + vertical: { + offset: 'offsetHeight', + scroll: 'scrollTop', + scrollSize: 'scrollHeight', + size: 'height', + key: 'vertical', + axis: 'Y', + client: 'clientY', + direction: 'top', + }, + horizontal: { + offset: 'offsetWidth', + scroll: 'scrollLeft', + scrollSize: 'scrollWidth', + size: 'width', + key: 'horizontal', + axis: 'X', + client: 'clientX', + direction: 'left', + }, +}; + +// @ts-ignore +export function renderThumbStyle({ move, size, bar }) { + const style = {} as any; + const translate = `translate${bar.axis}(${move}%)`; + + style[bar.size] = size; + style.transform = translate; + style.msTransform = translate; + style.webkitTransform = translate; + + return style; +} + +function extend(to: T, _from: K): T & K { + return Object.assign(to, _from); +} + +export function toObject(arr: Array): Recordable { + const res = {}; + for (let i = 0; i < arr.length; i++) { + if (arr[i]) { + extend(res, arr[i]); + } + } + return res; +} diff --git a/src/components/SimpleMenu/index.ts b/src/components/SimpleMenu/index.ts new file mode 100644 index 0000000..0dfd248 --- /dev/null +++ b/src/components/SimpleMenu/index.ts @@ -0,0 +1,2 @@ +export { default as SimpleMenu } from './src/SimpleMenu.vue'; +export { default as SimpleMenuTag } from './src/SimpleMenuTag.vue'; diff --git a/src/components/SimpleMenu/src/SimpleMenu.vue b/src/components/SimpleMenu/src/SimpleMenu.vue new file mode 100644 index 0000000..0bd3e8c --- /dev/null +++ b/src/components/SimpleMenu/src/SimpleMenu.vue @@ -0,0 +1,193 @@ + + + diff --git a/src/components/SimpleMenu/src/SimpleMenuTag.vue b/src/components/SimpleMenu/src/SimpleMenuTag.vue new file mode 100644 index 0000000..b7d3cb3 --- /dev/null +++ b/src/components/SimpleMenu/src/SimpleMenuTag.vue @@ -0,0 +1,68 @@ + + diff --git a/src/components/SimpleMenu/src/SimpleSubMenu.vue b/src/components/SimpleMenu/src/SimpleSubMenu.vue new file mode 100644 index 0000000..9bd0e31 --- /dev/null +++ b/src/components/SimpleMenu/src/SimpleSubMenu.vue @@ -0,0 +1,116 @@ + + diff --git a/src/components/SimpleMenu/src/components/Menu.vue b/src/components/SimpleMenu/src/components/Menu.vue new file mode 100644 index 0000000..80c0f65 --- /dev/null +++ b/src/components/SimpleMenu/src/components/Menu.vue @@ -0,0 +1,148 @@ + + + + diff --git a/src/components/SimpleMenu/src/components/MenuCollapseTransition.vue b/src/components/SimpleMenu/src/components/MenuCollapseTransition.vue new file mode 100644 index 0000000..5295439 --- /dev/null +++ b/src/components/SimpleMenu/src/components/MenuCollapseTransition.vue @@ -0,0 +1,78 @@ + + diff --git a/src/components/SimpleMenu/src/components/MenuItem.vue b/src/components/SimpleMenu/src/components/MenuItem.vue new file mode 100644 index 0000000..8809b16 --- /dev/null +++ b/src/components/SimpleMenu/src/components/MenuItem.vue @@ -0,0 +1,126 @@ + + + diff --git a/src/components/SimpleMenu/src/components/SubMenuItem.vue b/src/components/SimpleMenu/src/components/SubMenuItem.vue new file mode 100644 index 0000000..ac7d8d3 --- /dev/null +++ b/src/components/SimpleMenu/src/components/SubMenuItem.vue @@ -0,0 +1,317 @@ + + + diff --git a/src/components/SimpleMenu/src/components/menu.less b/src/components/SimpleMenu/src/components/menu.less new file mode 100644 index 0000000..4d25785 --- /dev/null +++ b/src/components/SimpleMenu/src/components/menu.less @@ -0,0 +1,340 @@ +@menu-prefix-cls: ~'@{namespace}-menu'; +@menu-popup-prefix-cls: ~'@{namespace}-menu-popup'; +@submenu-popup-prefix-cls: ~'@{namespace}-menu-submenu-popup'; + +@transition-time: 0.2s; +@menu-dark-subsidiary-color: rgba(255, 255, 255, 0.7); + +.light-border { + &::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + display: block; + width: 2px; + background-color: @primary-color; + content: ''; + } +} + +.@{menu-prefix-cls}-menu-popover { + .ant-popover-arrow { + display: none; + } + + .ant-popover-inner-content { + padding: 0; + } + + .@{menu-prefix-cls} { + &-opened > * > &-submenu-title-icon { + transform: translateY(-50%) rotate(90deg) !important; + } + + &-item, + &-submenu-title { + position: relative; + z-index: 1; + padding: 10px 14px; + color: @menu-dark-subsidiary-color; + cursor: pointer; + transition: all @transition-time @ease-in-out; + + &-icon { + position: absolute; + top: 50%; + right: 18px; + transform: translateY(-50%) rotate(-90deg); + transition: transform @transition-time @ease-in-out; + } + } + + &-dark { + .@{menu-prefix-cls}-item, + .@{menu-prefix-cls}-submenu-title { + color: @menu-dark-subsidiary-color; + // background: @menu-dark-active-bg; + + &:hover { + color: #fff; + } + + &-selected { + color: #fff; + background-color: @primary-color !important; + } + } + // 彩色模式(绿色,橘红等) + &.bright { + .@{menu-prefix-cls}-item, + .@{menu-prefix-cls}-submenu-title { + color: #fff; + &:hover { + color: rgba(255, 255, 255, 0.8); + } + } + } + } + + &-light { + .@{menu-prefix-cls}-item, + .@{menu-prefix-cls}-submenu-title { + color: @text-color-base; + + &:hover { + color: @primary-color; + } + + &-selected { + z-index: 2; + color: @primary-color; + background-color: fade(@primary-color, 10); + + .light-border(); + } + } + } + } +} + +.content(); +.content() { + .@{menu-prefix-cls} { + position: relative; + display: block; + width: 100%; + padding: 0; + margin: 0; + font-size: @font-size-base; + color: @text-color-base; + list-style: none; + outline: none; + + // .collapse-transition { + // transition: @transition-time height ease-in-out, @transition-time padding-top ease-in-out, + // @transition-time padding-bottom ease-in-out; + // } + + &-light { + background-color: #fff; + color: rgba(0, 0, 0, 0.65); + .@{menu-prefix-cls} { + color: rgba(0, 0, 0, 0.65); + } + .@{namespace}-menu-submenu:not(.@{namespace}-menu-item-active) .@{namespace}-menu-submenu-title { + .anticon { + color: rgba(0, 0, 0, 0.9); + } + } + .@{menu-prefix-cls}-submenu-active { + color: @primary-color !important; + + &-border { + .light-border(); + } + } + } + + &-dark { + .@{menu-prefix-cls}-submenu-active { + color: #fff !important; + } + } + + &-item { + position: relative; + z-index: 1; + display: flex; + font-size: @font-size-base; + list-style: none; + cursor: pointer; + outline: none; + align-items: center; + + &:hover, + &:active { + color: inherit; + } + } + + &-item > i { + margin-right: 6px; + } + + &-submenu-title > i, + &-submenu-title span > i { + margin-right: 8px; + } + + // vertical + &-vertical &-item, + &-vertical &-submenu-title { + position: relative; + z-index: 1; + padding: 14px 24px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: pointer; + + &:hover { + color: @primary-color; + } + + .@{menu-prefix-cls}-tooltip { + width: calc(100% - 0px); + padding: 12px 0; + text-align: center; + } + .@{menu-prefix-cls}-submenu-popup { + padding: 12px 0; + } + } + + &-vertical &-submenu-collapse { + .@{submenu-popup-prefix-cls} { + display: flex; + justify-content: center; + align-items: center; + } + .@{menu-prefix-cls}-submenu-collapsed-show-tit { + flex-direction: column; + } + } + + &-vertical&-collapse &-item, + &-vertical&-collapse &-submenu-title { + padding: 0 0; + } + + &-vertical &-submenu-title-icon { + position: absolute; + top: 50%; + right: 18px; + transform: translateY(-50%); + } + + &-submenu-title-icon { + transition: transform @transition-time @ease-in-out; + } + + &-vertical &-opened > * > &-submenu-title-icon { + transform: translateY(-50%) rotate(180deg); + } + + &-vertical &-submenu { + &-nested { + padding-left: 20px; + } + .@{menu-prefix-cls}-item { + padding-left: 43px; + } + } + + &-light&-vertical &-item { + &-active:not(.@{menu-prefix-cls}-submenu) { + z-index: 2; + color: @primary-color; + background-color: fade(@primary-color, 10); + + .light-border(); + } + &-active.@{menu-prefix-cls}-submenu { + color: @primary-color; + } + } + + &-light&-vertical&-collapse { + > li.@{menu-prefix-cls}-item-active, + .@{menu-prefix-cls}-submenu-active { + position: relative; + background-color: fade(@primary-color, 5); + + &::after { + display: none; + } + + &::before { + position: absolute; + top: 0; + left: 0; + width: 3px; + height: 100%; + background-color: @primary-color; + content: ''; + } + } + } + + &-dark&-vertical &-item, + &-dark&-vertical &-submenu-title { + color: @menu-dark-subsidiary-color; + &-active:not(.@{menu-prefix-cls}-submenu) { + color: #fff !important; + background-color: @primary-color !important; + } + + &:hover { + color: #fff; + } + } + // update-begin--author:liaozhiyang---date:20240408---for:【QQYUN-8922】左侧导航栏文字颜色调整区分彩色和暗黑 + &-dark&-vertical&.bright &-item, + &-dark&-vertical.bright &-submenu-title { + color: rgba(255, 255, 255, 1); + &-active:not(.@{menu-prefix-cls}-submenu) { + color: #fff !important; + background-color: @primary-color !important; + } + + &:hover { + color: rgba(255, 255, 255, 0.8); + } + } + // update-end--author:liaozhiyang---date:20240408---for:【QQYUN-8922】左侧导航栏文字颜色调整区分彩色和暗黑 + + &-dark&-vertical&-collapse { + > li.@{menu-prefix-cls}-item-active, + .@{menu-prefix-cls}-submenu-active { + position: relative; + color: #fff !important; + background-color: @primary-color !important; + + &::before { + position: absolute; + top: 0; + left: 0; + width: 3px; + height: 100%; + background-color: @primary-color; + content: ''; + } + + .@{menu-prefix-cls}-submenu-collapse { + background-color: transparent; + } + } + } + + &-dark&-vertical &-submenu &-item { + &-active, + &-active:hover { + color: #fff; + border-right: none; + } + } + + &-dark&-vertical &-child-item-active > &-submenu-title { + color: #fff; + } + + &-dark&-vertical &-opened { + .@{menu-prefix-cls}-submenu-has-parent-submenu { + .@{menu-prefix-cls}-submenu-title { + background-color: transparent; + } + } + } + } +} diff --git a/src/components/SimpleMenu/src/components/types.ts b/src/components/SimpleMenu/src/components/types.ts new file mode 100644 index 0000000..d828e89 --- /dev/null +++ b/src/components/SimpleMenu/src/components/types.ts @@ -0,0 +1,25 @@ +import { Ref } from 'vue'; + +export interface Props { + theme: string; + activeName?: string | number | undefined; + openNames: string[]; + accordion: boolean; + width: string; + collapsedWidth: string; + indentSize: number; + collapse: boolean; + activeSubMenuNames: (string | number)[]; +} + +export interface SubMenuProvider { + addSubMenu: (name: string | number, update?: boolean) => void; + removeSubMenu: (name: string | number, update?: boolean) => void; + removeAll: () => void; + sliceIndex: (index: number) => void; + isRemoveAllPopup: Ref; + getOpenNames: () => (string | number)[]; + handleMouseleave?: Fn; + level: number; + props: Props; +} diff --git a/src/components/SimpleMenu/src/components/useMenu.ts b/src/components/SimpleMenu/src/components/useMenu.ts new file mode 100644 index 0000000..8830559 --- /dev/null +++ b/src/components/SimpleMenu/src/components/useMenu.ts @@ -0,0 +1,84 @@ +import { computed, ComponentInternalInstance, unref } from 'vue'; +import type { CSSProperties } from 'vue'; + +export function useMenuItem(instance: ComponentInternalInstance | null) { + const getParentMenu = computed(() => { + return findParentMenu(['Menu', 'SubMenu']); + }); + + const getParentRootMenu = computed(() => { + return findParentMenu(['Menu']); + }); + + const getParentSubMenu = computed(() => { + return findParentMenu(['SubMenu']); + }); + + const getItemStyle = computed((): CSSProperties => { + let parent = instance?.parent; + if (!parent) return {}; + const indentSize = (unref(getParentRootMenu)?.props.indentSize as number) ?? 20; + let padding = indentSize; + + if (unref(getParentRootMenu)?.props.collapse) { + padding = indentSize; + } else { + while (parent && parent.type.name !== 'Menu') { + if (parent.type.name === 'SubMenu') { + padding += indentSize; + } + parent = parent.parent; + } + } + return { paddingLeft: padding + 'px' }; + }); + + function findParentMenu(name: string[]) { + let parent = instance?.parent; + if (!parent) return null; + while (parent && name.indexOf(parent.type.name!) === -1) { + parent = parent.parent; + } + return parent; + } + + function getParentList() { + let parent = instance; + if (!parent) + return { + uidList: [], + list: [], + }; + const ret: any[] = []; + while (parent && parent.type.name !== 'Menu') { + if (parent.type.name === 'SubMenu') { + ret.push(parent); + } + parent = parent.parent; + } + return { + uidList: ret.map((item) => item.uid), + list: ret, + }; + } + + function getParentInstance(instance: ComponentInternalInstance, name = 'SubMenu') { + let parent = instance.parent; + while (parent) { + if (parent.type.name !== name) { + return parent; + } + parent = parent.parent; + } + return parent; + } + + return { + getParentMenu, + getParentInstance, + getParentRootMenu, + getParentList, + getParentSubMenu, + getItemStyle, + }; +} diff --git a/src/components/SimpleMenu/src/components/useSimpleMenuContext.ts b/src/components/SimpleMenu/src/components/useSimpleMenuContext.ts new file mode 100644 index 0000000..f3d8100 --- /dev/null +++ b/src/components/SimpleMenu/src/components/useSimpleMenuContext.ts @@ -0,0 +1,18 @@ +import type { InjectionKey, Ref } from 'vue'; +import type { Emitter } from '/@/utils/mitt'; +import { createContext, useContext } from '/@/hooks/core/useContext'; + +export interface SimpleRootMenuContextProps { + rootMenuEmitter: Emitter; + activeName: Ref; +} + +const key: InjectionKey = Symbol(); + +export function createSimpleRootMenuContext(context: SimpleRootMenuContextProps) { + return createContext(context, key, { readonly: false, native: true }); +} + +export function useSimpleRootMenuContext() { + return useContext(key); +} diff --git a/src/components/SimpleMenu/src/index.less b/src/components/SimpleMenu/src/index.less new file mode 100644 index 0000000..4f9c9ce --- /dev/null +++ b/src/components/SimpleMenu/src/index.less @@ -0,0 +1,77 @@ +@simple-prefix-cls: ~'@{namespace}-simple-menu'; +@prefix-cls: ~'@{namespace}-menu'; + +.@{prefix-cls} { + &-dark&-vertical .@{simple-prefix-cls}__parent { + background-color: @sider-dark-bg-color; + > .@{prefix-cls}-submenu-title { + background-color: @sider-dark-bg-color; + } + } + + &-dark&-vertical .@{simple-prefix-cls}__children, + &-dark&-popup .@{simple-prefix-cls}__children { + background-color: @sider-dark-lighten-bg-color; + > .@{prefix-cls}-submenu-title { + background-color: @sider-dark-lighten-bg-color; + } + } + + .collapse-title { + overflow: hidden; + font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +.@{simple-prefix-cls} { + &-sub-title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + transition: all 0.3s; + } + + &-tag { + position: absolute; + top: calc(50% - 8px); + right: 30px; + display: inline-block; + padding: 2px 3px; + margin-right: 4px; + font-size: 10px; + line-height: 14px; + color: #fff; + border-radius: 2px; + + &--collapse { + top: 6px !important; + right: 2px; + } + + &--dot { + top: calc(50% - 2px); + width: 6px; + height: 6px; + padding: 0; + border-radius: 50%; + } + + &--primary { + background-color: @primary-color; + } + + &--error { + background-color: @error-color; + } + + &--success { + background-color: @success-color; + } + + &--warn { + background-color: @warning-color; + } + } +} diff --git a/src/components/SimpleMenu/src/types.ts b/src/components/SimpleMenu/src/types.ts new file mode 100644 index 0000000..2e292d4 --- /dev/null +++ b/src/components/SimpleMenu/src/types.ts @@ -0,0 +1,5 @@ +export interface MenuState { + activeName: string; + openNames: string[]; + activeSubMenuNames: string[]; +} diff --git a/src/components/SimpleMenu/src/useOpenKeys.ts b/src/components/SimpleMenu/src/useOpenKeys.ts new file mode 100644 index 0000000..c38b92c --- /dev/null +++ b/src/components/SimpleMenu/src/useOpenKeys.ts @@ -0,0 +1,44 @@ +import type { Menu as MenuType } from '/@/router/types'; +import type { MenuState } from './types'; + +import { computed, Ref, toRaw } from 'vue'; + +import { unref } from 'vue'; +import { uniq } from 'lodash-es'; +import { getAllParentPath } from '/@/router/helper/menuHelper'; + +import { useTimeoutFn } from '/@/hooks/core/useTimeout'; +import { useDebounceFn } from '@vueuse/core'; + +export function useOpenKeys(menuState: MenuState, menus: Ref, accordion: Ref, mixSider: Ref, collapse: Ref) { + const debounceSetOpenKeys = useDebounceFn(setOpenKeys, 50); + async function setOpenKeys(path: string) { + const native = !mixSider.value; + const menuList = toRaw(menus.value); + useTimeoutFn( + () => { + if (menuList?.length === 0) { + menuState.activeSubMenuNames = []; + menuState.openNames = []; + return; + } + const keys = getAllParentPath(menuList, path); + + if (!unref(accordion)) { + menuState.openNames = uniq([...menuState.openNames, ...keys]); + } else { + menuState.openNames = keys; + } + menuState.activeSubMenuNames = menuState.openNames; + }, + 30, + native + ); + } + + const getOpenKeys = computed(() => { + return unref(collapse) ? [] : menuState.openNames; + }); + + return { setOpenKeys: debounceSetOpenKeys, getOpenKeys }; +} diff --git a/src/components/StrengthMeter/index.ts b/src/components/StrengthMeter/index.ts new file mode 100644 index 0000000..9763afa --- /dev/null +++ b/src/components/StrengthMeter/index.ts @@ -0,0 +1,4 @@ +import { withInstall } from '/@/utils'; +import strengthMeter from './src/StrengthMeter.vue'; + +export const StrengthMeter = withInstall(strengthMeter); diff --git a/src/components/StrengthMeter/src/StrengthMeter.vue b/src/components/StrengthMeter/src/StrengthMeter.vue new file mode 100644 index 0000000..149ce4d --- /dev/null +++ b/src/components/StrengthMeter/src/StrengthMeter.vue @@ -0,0 +1,135 @@ + + + + diff --git a/src/components/Table/index.ts b/src/components/Table/index.ts new file mode 100644 index 0000000..7fe08c9 --- /dev/null +++ b/src/components/Table/index.ts @@ -0,0 +1,10 @@ +export { default as BasicTable } from './src/BasicTable.vue'; +export { default as TableAction } from './src/components/TableAction.vue'; +export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue'; +export { default as TableImg } from './src/components/TableImg.vue'; +export * from './src/types/table'; +export * from './src/types/pagination'; +export * from './src/types/tableAction'; +export { useTable } from './src/hooks/useTable'; +export type { FormSchema, FormProps } from '/@/components/Form/src/types/form'; +export type { EditRecordRow } from './src/components/editable'; diff --git a/src/components/Table/src/BasicTable.vue b/src/components/Table/src/BasicTable.vue new file mode 100644 index 0000000..3a109e0 --- /dev/null +++ b/src/components/Table/src/BasicTable.vue @@ -0,0 +1,606 @@ + + + diff --git a/src/components/Table/src/componentMap.ts b/src/components/Table/src/componentMap.ts new file mode 100644 index 0000000..0578a60 --- /dev/null +++ b/src/components/Table/src/componentMap.ts @@ -0,0 +1,26 @@ +import type { Component } from 'vue'; +import { Input, Select, Checkbox, InputNumber, Switch, DatePicker, TimePicker } from 'ant-design-vue'; +import type { ComponentType } from './types/componentType'; +import { ApiSelect, ApiTreeSelect } from '/@/components/Form'; + +const componentMap = new Map(); + +componentMap.set('Input', Input); +componentMap.set('InputNumber', InputNumber); +componentMap.set('Select', Select); +componentMap.set('ApiSelect', ApiSelect); +componentMap.set('ApiTreeSelect', ApiTreeSelect); +componentMap.set('Switch', Switch); +componentMap.set('Checkbox', Checkbox); +componentMap.set('DatePicker', DatePicker); +componentMap.set('TimePicker', TimePicker); + +export function add(compName: ComponentType, component: Component) { + componentMap.set(compName, component); +} + +export function del(compName: ComponentType) { + componentMap.delete(compName); +} + +export { componentMap }; diff --git a/src/components/Table/src/components/CustomSelectHeader.vue b/src/components/Table/src/components/CustomSelectHeader.vue new file mode 100644 index 0000000..6e863f2 --- /dev/null +++ b/src/components/Table/src/components/CustomSelectHeader.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/src/components/Table/src/components/EditTableHeaderIcon.vue b/src/components/Table/src/components/EditTableHeaderIcon.vue new file mode 100644 index 0000000..369820e --- /dev/null +++ b/src/components/Table/src/components/EditTableHeaderIcon.vue @@ -0,0 +1,16 @@ + + diff --git a/src/components/Table/src/components/ExpandIcon.tsx b/src/components/Table/src/components/ExpandIcon.tsx new file mode 100644 index 0000000..3d1d98d --- /dev/null +++ b/src/components/Table/src/components/ExpandIcon.tsx @@ -0,0 +1,23 @@ +import { BasicArrow } from '/@/components/Basic'; + +export default () => { + return (props: Recordable) => { + if (!props.expandable) { + if (props.needIndentSpaced) { + return ; + } else { + return ; + } + } + return ( + { + props.onExpand(props.record, e); + }} + expand={props.expanded} + /> + ); + }; +}; diff --git a/src/components/Table/src/components/HeaderCell.vue b/src/components/Table/src/components/HeaderCell.vue new file mode 100644 index 0000000..4e2f87c --- /dev/null +++ b/src/components/Table/src/components/HeaderCell.vue @@ -0,0 +1,56 @@ + + + diff --git a/src/components/Table/src/components/TableAction.vue b/src/components/Table/src/components/TableAction.vue new file mode 100644 index 0000000..337740c --- /dev/null +++ b/src/components/Table/src/components/TableAction.vue @@ -0,0 +1,311 @@ + + + diff --git a/src/components/Table/src/components/TableFooter.vue b/src/components/Table/src/components/TableFooter.vue new file mode 100644 index 0000000..7fb33cd --- /dev/null +++ b/src/components/Table/src/components/TableFooter.vue @@ -0,0 +1,151 @@ + + + diff --git a/src/components/Table/src/components/TableHeader.vue b/src/components/Table/src/components/TableHeader.vue new file mode 100644 index 0000000..f936b75 --- /dev/null +++ b/src/components/Table/src/components/TableHeader.vue @@ -0,0 +1,165 @@ + + + diff --git a/src/components/Table/src/components/TableImg.vue b/src/components/Table/src/components/TableImg.vue new file mode 100644 index 0000000..29a0907 --- /dev/null +++ b/src/components/Table/src/components/TableImg.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/components/Table/src/components/TableSummary.tsx b/src/components/Table/src/components/TableSummary.tsx new file mode 100644 index 0000000..63709b4 --- /dev/null +++ b/src/components/Table/src/components/TableSummary.tsx @@ -0,0 +1,163 @@ +import type { PropType, VNode } from 'vue'; +import { defineComponent, unref, computed, isVNode } from 'vue'; +import { cloneDeep, pick } from 'lodash-es'; +import { isFunction } from '/@/utils/is'; +import type { BasicColumn } from '../types/table'; +import { INDEX_COLUMN_FLAG } from '../const'; +import { propTypes } from '/@/utils/propTypes'; +import { useTableContext } from '../hooks/useTableContext'; +import { TableSummary, TableSummaryRow, TableSummaryCell } from 'ant-design-vue'; + +const SUMMARY_ROW_KEY = '_row'; +const SUMMARY_INDEX_KEY = '_index'; +export default defineComponent({ + name: 'BasicTableSummary', + components: { TableSummary, TableSummaryRow, TableSummaryCell }, + props: { + summaryFunc: { + type: Function as PropType, + }, + summaryData: { + type: Array as PropType, + }, + rowKey: propTypes.string.def('key'), + // 是否有展开列 + hasExpandedRow: propTypes.bool, + data: { + type: Object as PropType, + default: () => {}, + }, + }, + setup(props) { + const table = useTableContext(); + + const getDataSource = computed((): Recordable[] => { + const { + summaryFunc, + summaryData, + data: { pageData }, + } = props; + if (summaryData?.length) { + summaryData.forEach((item, i) => (item[props.rowKey] = `${i}`)); + return summaryData; + } + if (!isFunction(summaryFunc)) { + return []; + } + let dataSource = cloneDeep(unref(pageData)); + dataSource = summaryFunc(dataSource); + dataSource.forEach((item, i) => { + item[props.rowKey] = `${i}`; + }); + return dataSource; + }); + + const getColumns = computed(() => { + const dataSource = unref(getDataSource); + let columns: BasicColumn[] = cloneDeep(table.getColumns({ sort: true })); + columns = columns.filter((item) => !item.defaultHidden); + const index = columns.findIndex((item) => item.flag === INDEX_COLUMN_FLAG); + const hasRowSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_ROW_KEY)); + const hasIndexSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_INDEX_KEY)); + + // 是否有序号列 + let hasIndexCol = false; + // 是否有选择列 + const hasSelection = table.getRowSelection() && hasRowSummary; + + if (index !== -1) { + if (hasIndexSummary) { + hasIndexCol = true; + columns[index].customSummaryRender = ({ record }) => record[SUMMARY_INDEX_KEY]; + columns[index].ellipsis = false; + } else { + Reflect.deleteProperty(columns[index], 'customSummaryRender'); + } + } + + if (hasSelection) { + const isFixed = columns.some((col) => col.fixed === 'left' || col.fixed === true); + columns.unshift({ + width: 60, + title: 'selection', + key: 'selectionKey', + align: 'center', + ...(isFixed ? { fixed: 'left' } : {}), + customSummaryRender: ({ record }) => (hasIndexCol ? '' : record[SUMMARY_ROW_KEY]), + }); + } + + if (props.hasExpandedRow) { + const isFixed = columns.some((col) => col.fixed === 'left'); + columns.unshift({ + width: 50, + title: 'expandedRow', + key: 'expandedRowKey', + align: 'center', + ...(isFixed ? { fixed: 'left' } : {}), + customSummaryRender: () => '', + }); + } + return columns; + }); + + function isRenderCell(data: any) { + return data && typeof data === 'object' && !Array.isArray(data) && !isVNode(data); + } + + const getValues = (row: Recordable, col: BasicColumn, index: number) => { + const value = row[col.dataIndex as string]; + let childNode: VNode | JSX.Element | string | number | undefined | null; + childNode = value; + if (col.customSummaryRender) { + const renderData = col.customSummaryRender({ + text: value, + value, + record: row, + index, + column: cloneDeep(col), + }); + if (isRenderCell(renderData)) { + childNode = renderData.children; + } else { + childNode = renderData; + } + if (typeof childNode === 'object' && !Array.isArray(childNode) && !isVNode(childNode)) { + childNode = null; + } + if (Array.isArray(childNode) && childNode.length === 1) { + childNode = childNode[0]; + } + return childNode; + } + return childNode; + }; + + const getCellProps = (col: BasicColumn) => { + const cellProps = pick(col, ['colSpan', 'rowSpan', 'align']); + return { + ...cellProps, + }; + }; + + return () => { + return ( + + {(unref(getDataSource) || []).map((row) => { + return ( + + {unref(getColumns).map((col, index) => { + return ( + + {getValues(row, col, index)} + + ); + })} + + ); + })} + + ); + }; + }, +}); diff --git a/src/components/Table/src/components/TableTitle.vue b/src/components/Table/src/components/TableTitle.vue new file mode 100644 index 0000000..0b797e1 --- /dev/null +++ b/src/components/Table/src/components/TableTitle.vue @@ -0,0 +1,53 @@ + + + diff --git a/src/components/Table/src/components/editable/CellComponent.ts b/src/components/Table/src/components/editable/CellComponent.ts new file mode 100644 index 0000000..e07898d --- /dev/null +++ b/src/components/Table/src/components/editable/CellComponent.ts @@ -0,0 +1,38 @@ +import type { FunctionalComponent, defineComponent } from 'vue'; +import type { ComponentType } from '../../types/componentType'; +import { componentMap } from '/@/components/Table/src/componentMap'; + +import { Popover } from 'ant-design-vue'; +import { h } from 'vue'; + +export interface ComponentProps { + component: ComponentType; + rule: boolean; + popoverVisible: boolean; + ruleMessage: string; + getPopupContainer?: Fn; +} + +export const CellComponent: FunctionalComponent = ( + { component = 'Input', rule = true, ruleMessage, popoverVisible, getPopupContainer }: ComponentProps, + { attrs } +) => { + const Comp = componentMap.get(component) as typeof defineComponent; + + const DefaultComp = h(Comp, attrs); + if (!rule) { + return DefaultComp; + } + return h( + Popover, + { + overlayClassName: 'edit-cell-rule-popover', + open: !!popoverVisible, + ...(getPopupContainer ? { getPopupContainer } : {}), + }, + { + default: () => DefaultComp, + content: () => ruleMessage, + } + ); +}; diff --git a/src/components/Table/src/components/editable/EditableCell.vue b/src/components/Table/src/components/editable/EditableCell.vue new file mode 100644 index 0000000..4df75ec --- /dev/null +++ b/src/components/Table/src/components/editable/EditableCell.vue @@ -0,0 +1,544 @@ + + + diff --git a/src/components/Table/src/components/editable/helper.ts b/src/components/Table/src/components/editable/helper.ts new file mode 100644 index 0000000..d901729 --- /dev/null +++ b/src/components/Table/src/components/editable/helper.ts @@ -0,0 +1,28 @@ +import { ComponentType } from '../../types/componentType'; +import { useI18n } from '/@/hooks/web/useI18n'; + +const { t } = useI18n(); + +/** + * @description: 生成placeholder + */ +export function createPlaceholderMessage(component: ComponentType) { + if (component.includes('Input')) { + return t('common.inputText'); + } + if (component.includes('Picker')) { + return t('common.chooseText'); + } + + if ( + component.includes('Select') || + component.includes('Checkbox') || + component.includes('Radio') || + component.includes('Switch') || + component.includes('DatePicker') || + component.includes('TimePicker') + ) { + return t('common.chooseText'); + } + return ''; +} diff --git a/src/components/Table/src/components/editable/index.ts b/src/components/Table/src/components/editable/index.ts new file mode 100644 index 0000000..de9220d --- /dev/null +++ b/src/components/Table/src/components/editable/index.ts @@ -0,0 +1,77 @@ +import type { BasicColumn } from '/@/components/Table/src/types/table'; + +import { h, Ref, toRaw } from 'vue'; + +import EditableCell from './EditableCell.vue'; +import { isArray } from '/@/utils/is'; + +interface Params { + text: string; + record: Recordable; + index: number; +} + +export function renderEditCell(column: BasicColumn) { + return ({ text: value, record, index }: Params) => { + toRaw(record).onValid = async () => { + if (isArray(record?.validCbs)) { + // 代码逻辑说明: 【issues/1165】解决canResize为true时第一行校验不过 + const validFns = (record?.validCbs || []).map((item) => { + const [fn] = Object.values(item); + // @ts-ignore + return fn(); + }); + const res = await Promise.all(validFns); + return res.every((item) => !!item); + } else { + return false; + } + }; + + toRaw(record).onEdit = async (edit: boolean, submit = false) => { + if (!submit) { + record.editable = edit; + } + + if (!edit && submit) { + if (!(await record.onValid())) return false; + const res = await record.onSubmitEdit?.(); + if (res) { + record.editable = false; + return true; + } + return false; + } + // cancel + if (!edit && !submit) { + record.onCancelEdit?.(); + } + return true; + }; + + return h(EditableCell, { + value, + record, + column, + index, + }); + }; +} + +interface Cbs { + [key: string]: Fn; +} + +export type EditRecordRow = Partial< + { + onEdit: (editable: boolean, submit?: boolean) => Promise; + onValid: () => Promise; + editable: boolean; + onCancel: Fn; + onSubmit: Fn; + submitCbs: Cbs[]; + cancelCbs: Cbs[]; + validCbs: Cbs[]; + editValueRefs: Recordable; + } & T +>; diff --git a/src/components/Table/src/components/settings/ColumnSetting.vue b/src/components/Table/src/components/settings/ColumnSetting.vue new file mode 100644 index 0000000..555d4dc --- /dev/null +++ b/src/components/Table/src/components/settings/ColumnSetting.vue @@ -0,0 +1,574 @@ + + + diff --git a/src/components/Table/src/components/settings/FullScreenSetting.vue b/src/components/Table/src/components/settings/FullScreenSetting.vue new file mode 100644 index 0000000..046d647 --- /dev/null +++ b/src/components/Table/src/components/settings/FullScreenSetting.vue @@ -0,0 +1,48 @@ + + diff --git a/src/components/Table/src/components/settings/RedoSetting.vue b/src/components/Table/src/components/settings/RedoSetting.vue new file mode 100644 index 0000000..e584c13 --- /dev/null +++ b/src/components/Table/src/components/settings/RedoSetting.vue @@ -0,0 +1,45 @@ + + diff --git a/src/components/Table/src/components/settings/SizeSetting.vue b/src/components/Table/src/components/settings/SizeSetting.vue new file mode 100644 index 0000000..27ba04c --- /dev/null +++ b/src/components/Table/src/components/settings/SizeSetting.vue @@ -0,0 +1,96 @@ + + diff --git a/src/components/Table/src/components/settings/index.vue b/src/components/Table/src/components/settings/index.vue new file mode 100644 index 0000000..3a615ad --- /dev/null +++ b/src/components/Table/src/components/settings/index.vue @@ -0,0 +1,74 @@ + + + diff --git a/src/components/Table/src/const.ts b/src/components/Table/src/const.ts new file mode 100644 index 0000000..9968ec5 --- /dev/null +++ b/src/components/Table/src/const.ts @@ -0,0 +1,30 @@ +import componentSetting from '/@/settings/componentSetting'; + +const { table } = componentSetting; + +const { pageSizeOptions, defaultPageSize, defaultSize, fetchSetting, defaultSortFn, defaultFilterFn } = table; + +export const ROW_KEY = 'key'; + +// Optional display number per page; +export const PAGE_SIZE_OPTIONS = pageSizeOptions; + +// Number of items displayed per page +export const PAGE_SIZE = defaultPageSize; + +// Common interface field settings +export const FETCH_SETTING = fetchSetting; + +// Configure general sort function +export const DEFAULT_SORT_FN = defaultSortFn; + +export const DEFAULT_FILTER_FN = defaultFilterFn; + +// Default layout of table cells +export const DEFAULT_ALIGN = 'center'; +// Default Size +export const DEFAULT_SIZE = defaultSize; + +export const INDEX_COLUMN_FLAG = 'INDEX'; + +export const ACTION_COLUMN_FLAG = 'ACTION'; diff --git a/src/components/Table/src/hooks/useColumns.ts b/src/components/Table/src/hooks/useColumns.ts new file mode 100644 index 0000000..8efd46a --- /dev/null +++ b/src/components/Table/src/hooks/useColumns.ts @@ -0,0 +1,376 @@ +import type { BasicColumn, BasicTableProps, CellFormat, GetColumnsParams } from '../types/table'; +import type { PaginationProps } from '../types/pagination'; +import type { ComputedRef } from 'vue'; +import { Table } from 'ant-design-vue'; +import { computed, Ref, ref, toRaw, unref, watch, reactive } from 'vue'; +import { renderEditCell } from '../components/editable'; +import { usePermission } from '/@/hooks/web/usePermission'; +import { useI18n } from '/@/hooks/web/useI18n'; +import { isArray, isBoolean, isFunction, isMap, isString } from '/@/utils/is'; +import { cloneDeep, isEqual } from 'lodash-es'; +import { formatToDate } from '/@/utils/dateUtil'; +import { ACTION_COLUMN_FLAG, DEFAULT_ALIGN, INDEX_COLUMN_FLAG, PAGE_SIZE } from '../const'; +import { CUS_SEL_COLUMN_KEY } from './useCustomSelection'; + +function handleItem(item: BasicColumn, ellipsis: boolean) { + const { key, dataIndex, children } = item; + item.align = item.align || DEFAULT_ALIGN; + if (ellipsis) { + if (!key) { + item.key = dataIndex; + } + if (!isBoolean(item.ellipsis)) { + Object.assign(item, { + ellipsis, + }); + } + } + if (children && children.length) { + handleChildren(children, !!ellipsis); + } +} + +function handleChildren(children: BasicColumn[] | undefined, ellipsis: boolean) { + if (!children) return; + children.forEach((item) => { + const { children } = item; + handleItem(item, ellipsis); + handleChildren(children, ellipsis); + }); +} + +function handleIndexColumn(propsRef: ComputedRef, getPaginationRef: ComputedRef, columns: BasicColumn[]) { + const { t } = useI18n(); + + const { showIndexColumn, indexColumnProps, isTreeTable } = unref(propsRef); + + let pushIndexColumns = false; + if (unref(isTreeTable)) { + return; + } + columns.forEach(() => { + const indIndex = columns.findIndex((column) => column.flag === INDEX_COLUMN_FLAG); + if (showIndexColumn) { + pushIndexColumns = indIndex === -1; + } else if (!showIndexColumn && indIndex !== -1) { + columns.splice(indIndex, 1); + } + }); + // 代码逻辑说明: 【TV360X-105】列展示设置问题[列展示复选框不应该判断序号列复选框的状态] + if (columns.length === 0 && showIndexColumn) { + const indIndex = columns.findIndex((column) => column.flag === INDEX_COLUMN_FLAG); + if (indIndex === -1) { + pushIndexColumns = true; + } + } + if (!pushIndexColumns) return; + + const isFixedLeft = columns.some((item) => item.fixed === 'left'); + + columns.unshift({ + flag: INDEX_COLUMN_FLAG, + // 代码逻辑说明: 【TV360X-1634】密度是宽松模式时,序号列表头换行了 + width: propsRef.value.size === 'large' ? 65 : 50, + title: t('component.table.index'), + align: 'center', + customRender: ({ index }) => { + const getPagination = unref(getPaginationRef); + if (isBoolean(getPagination)) { + return `${index + 1}`; + } + const { current = 1, pageSize = PAGE_SIZE } = getPagination; + return ((current < 1 ? 1 : current) - 1) * pageSize + index + 1; + }, + ...(isFixedLeft + ? { + fixed: 'left', + } + : {}), + ...indexColumnProps, + }); +} + +function handleActionColumn(propsRef: ComputedRef, columns: BasicColumn[]) { + const { actionColumn, showActionColumn } = unref(propsRef); + if (!actionColumn || !showActionColumn) return; + + const hasIndex = columns.findIndex((column) => column.flag === ACTION_COLUMN_FLAG); + if (hasIndex === -1) { + columns.push({ + ...columns[hasIndex], + ...actionColumn, + flag: ACTION_COLUMN_FLAG, + }); + } +} + +export function useColumns( + propsRef: ComputedRef, + getPaginationRef: ComputedRef, + handleCustomSelectColumn: Fn +) { + const columnsRef = ref(unref(propsRef).columns) as unknown as Ref; + let cacheColumns = unref(propsRef).columns; + + const getColumnsRef = computed(() => { + const columns = cloneDeep(unref(columnsRef)); + // 代码逻辑说明: 【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换 + if (isArray(columns)) { + columns.forEach((item) => { + item.title = isFunction(item.title) ? item.title() : item.title; + }); + } + handleIndexColumn(propsRef, getPaginationRef, columns); + handleActionColumn(propsRef, columns); + // 代码逻辑说明: 【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题 + handleCustomSelectColumn(columns); + + if (!columns) { + return []; + } + const { ellipsis } = unref(propsRef); + + columns.forEach((item) => { + const { customRender, slots } = item; + + handleItem(item, Reflect.has(item, 'ellipsis') ? !!item.ellipsis : !!ellipsis && !customRender && !slots); + }); + return columns; + }); + + function isIfShow(column: BasicColumn): boolean { + const ifShow = column.ifShow; + + let isIfShow = true; + + if (isBoolean(ifShow)) { + isIfShow = ifShow; + } + if (isFunction(ifShow)) { + isIfShow = ifShow(column); + } + return isIfShow; + } + const { hasPermission } = usePermission(); + + const getViewColumns = computed(() => { + const viewColumns = sortFixedColumn(unref(getColumnsRef)); + + const columns = cloneDeep(viewColumns); + const formatEditColumn = (columns) => { + return columns.map((column) => { + // 代码逻辑说明: 【issues-179】antd3 一些警告以及报错(针对表格) + if(column.slots?.customRender) { + // slots的备份,兼容老的写法,转成新写法避免控制台警告 + column.slotsBak = column.slots; + delete column.slots; + } + + const { slots, customRender, format, edit, editRow, flag, title: metaTitle } = column; + + if (!slots || !slots?.title) { + // column.slots = { title: `header-${dataIndex}`, ...(slots || {}) }; + column.customTitle = column.title as string; + Reflect.deleteProperty(column, 'title'); + } + // 代码逻辑说明: 【online报表】分组标题显示错误,都显示成了联系信息 LOWCOD-2343 + if (column.children) { + column.title = metaTitle; + } + + const isDefaultAction = [INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG].includes(flag!); + if (!customRender && format && !edit && !isDefaultAction) { + column.customRender = ({ text, record, index }) => { + return formatCell(text, format, record, index); + }; + } + + // edit table + if ((edit || editRow) && !isDefaultAction) { + column.customRender = renderEditCell(column); + } + // 代码逻辑说明: 【pull/7333】修复分组表头可编辑表格失效问题 + if (column.children?.length) { + formatEditColumn(column.children.filter((item) => hasPermission(column.auth) && isIfShow(column))); + } + return reactive(column); + }); + }; + // 代码逻辑说明: 【pull/7333】修复分组表头可编辑表格失效问题 + const result = formatEditColumn(columns.filter((item) => hasPermission(item.auth) && isIfShow(item))); + // 代码逻辑说明: 【QQYUN-6387】展开写法(去掉报错) + if (propsRef.value.expandedRowKeys && !propsRef.value.isTreeTable) { + let index = 0; + const findIndex = result.findIndex((item) => item.key === CUS_SEL_COLUMN_KEY); + if (findIndex != -1) { + index = findIndex + 1; + } + const next: any = result[index + 1]; + let expand = Table.EXPAND_COLUMN; + if (next && (next['fixed'] == true || next['fixed'] == 'left')) { + expand = Object.assign(expand, { fixed: 'left' }); + } + result.splice(index, 0, expand); + } + return result; + }); + + watch( + () => unref(propsRef).columns, + (columns) => { + columnsRef.value = columns; + cacheColumns = columns?.filter((item) => !item.flag) ?? []; + } + ); + + function setCacheColumnsByField(dataIndex: string | undefined, value: Partial) { + if (!dataIndex || !value) { + return; + } + cacheColumns.forEach((item) => { + if (item.dataIndex === dataIndex) { + Object.assign(item, value); + return; + } + }); + } + + /** + * set columns + * @param columnList key|column + */ + function setColumns(columnList: Partial[] | (string | string[])[]) { + const columns = cloneDeep(columnList); + if (!isArray(columns)) return; + + if (columns.length <= 0) { + columnsRef.value = []; + return; + } + + const firstColumn = columns[0]; + + const cacheKeys = cacheColumns.map((item) => item.dataIndex); + + if (!isString(firstColumn) && !isArray(firstColumn)) { + columnsRef.value = columns as BasicColumn[]; + } else { + const columnKeys = (columns as (string | string[])[]).map((m) => m.toString()); + const newColumns: BasicColumn[] = []; + cacheColumns.forEach((item) => { + newColumns.push({ + ...item, + defaultHidden: !columnKeys.includes(item.dataIndex?.toString() || (item.key as string)), + }); + }); + // Sort according to another array + if (!isEqual(cacheKeys, columns)) { + newColumns.sort((prev, next) => { + return columnKeys.indexOf(prev.dataIndex?.toString() as string) - columnKeys.indexOf(next.dataIndex?.toString() as string); + }); + } + columnsRef.value = newColumns; + } + } + + function getColumns(opt?: GetColumnsParams) { + const { ignoreIndex, ignoreAction, ignoreAuth, ignoreIfShow, sort } = opt || {}; + let columns = toRaw(unref(getColumnsRef)); + if (ignoreIndex) { + columns = columns.filter((item) => item.flag !== INDEX_COLUMN_FLAG); + } + if (ignoreAction) { + columns = columns.filter((item) => item.flag !== ACTION_COLUMN_FLAG); + } + // 过滤自定义选择列 + columns = columns.filter((item) => item.key !== CUS_SEL_COLUMN_KEY); + // 代码逻辑说明: 【issues/8502】解决权限列在列表中不显示,列配置中还显示 + if (ignoreAuth) { + columns = columns.filter((item) => { + if (item.auth) { + return hasPermission(item.auth); + } + return true; + }); + } + if (ignoreIfShow) { + columns = columns.filter((item) => { + if (isBoolean(item.ifShow)) { + return item.ifShow; + } + if (isFunction(item.ifShow)) { + return item.ifShow(item); + } + return true; + }); + } + if (sort) { + columns = sortFixedColumn(columns); + } + + return columns; + } + function getCacheColumns() { + return cacheColumns; + } + + return { + getColumnsRef, + getCacheColumns, + getColumns, + setColumns, + getViewColumns, + setCacheColumnsByField, + }; +} + +function sortFixedColumn(columns: BasicColumn[]) { + const fixedLeftColumns: BasicColumn[] = []; + const fixedRightColumns: BasicColumn[] = []; + const defColumns: BasicColumn[] = []; + for (const column of columns) { + if (column.fixed === 'left') { + fixedLeftColumns.push(column); + continue; + } + if (column.fixed === 'right') { + fixedRightColumns.push(column); + continue; + } + defColumns.push(column); + } + return [...fixedLeftColumns, ...defColumns, ...fixedRightColumns].filter((item) => !item.defaultHidden); +} + +// format cell +export function formatCell(text: string, format: CellFormat, record: Recordable, index: number) { + if (!format) { + return text; + } + + // custom function + if (isFunction(format)) { + return format(text, record, index); + } + + try { + // date type + const DATE_FORMAT_PREFIX = 'date|'; + if (isString(format) && format.startsWith(DATE_FORMAT_PREFIX)) { + const dateFormat = format.replace(DATE_FORMAT_PREFIX, ''); + + if (!dateFormat) { + return text; + } + return formatToDate(text, dateFormat); + } + + // Map + if (isMap(format)) { + return format.get(text); + } + } catch (error) { + return text; + } +} + diff --git a/src/components/Table/src/hooks/useColumnsCache.ts b/src/components/Table/src/hooks/useColumnsCache.ts new file mode 100644 index 0000000..0572c81 --- /dev/null +++ b/src/components/Table/src/hooks/useColumnsCache.ts @@ -0,0 +1,147 @@ +import { computed, nextTick, unref, watchEffect } from 'vue'; +import { router } from '/@/router'; +import { useRoute } from 'vue-router'; +import { createLocalStorage } from '/@/utils/cache'; +import { useTableContext } from './useTableContext'; +import { useMessage } from '/@/hooks/web/useMessage'; + +/** + * 列表配置缓存 + */ +export function useColumnsCache(opt, setColumns, handleColumnFixed) { + let isInit = false; + const table = useTableContext(); + const $ls = createLocalStorage(); + const { createMessage: $message } = useMessage(); + const route = useRoute(); + // 列表配置缓存key + const cacheKey = computed(() => { + // 代码逻辑说明: 【QQYUN-8367】online报表配置列展示保存,影响到其他页面的table字段的显示隐藏(开发环境热更新会有此问题,生产环境无问题) + const path = route.path; + let key = path.replace(/[\/\\]/g, '_'); + let cacheKey = table.getBindValues.value.tableSetting?.cacheKey; + if (cacheKey) { + key += ':' + cacheKey; + } + return 'columnCache:' + key; + }); + + watchEffect(() => { + const columns = table.getColumns(); + if (columns.length) { + init(); + } + }); + + async function init() { + if (isInit) { + return; + } + isInit = true; + let columnCache = $ls.get(cacheKey.value); + if (columnCache && columnCache.checkedList) { + const { checkedList, sortedList, sortableOrder, checkIndex } = columnCache; + await nextTick(); + // checkbox的排序缓存 + opt.sortableOrder.value = sortableOrder; + // checkbox的选中缓存 + opt.state.checkedList = checkedList; + // tableColumn的排序缓存 + opt.plainSortOptions.value.sort((prev, next) => { + return sortedList.indexOf(prev.value) - sortedList.indexOf(next.value); + }); + // 重新排序tableColumn + checkedList.sort((prev, next) => sortedList.indexOf(prev) - sortedList.indexOf(next)); + // 是否显示行号列 + if (checkIndex) { + table.setProps({ showIndexColumn: true }); + } + setColumns(checkedList); + // 设置固定列 + setColumnFixed(columnCache); + } + } + + /** 设置被固定的列 */ + async function setColumnFixed(columnCache) { + const { fixedColumns } = columnCache; + const columns = opt.plainOptions.value; + for (const column of columns) { + let fixedCol = fixedColumns.find((fc) => fc.key === (column.key || column.dataIndex)); + if (fixedCol) { + await nextTick(); + handleColumnFixed(column, fixedCol.fixed); + } + } + } + + // 判断列固定状态 + const fixedReg = /^(true|left|right)$/; + + /** 获取被固定的列 */ + function getFixedColumns() { + let fixedColumns: any[] = []; + const columns = opt.plainOptions.value; + for (const column of columns) { + if (fixedReg.test((column.fixed ?? '').toString())) { + fixedColumns.push({ + key: column.key || column.dataIndex, + fixed: column.fixed === true ? 'left' : column.fixed, + }); + } + } + return fixedColumns; + } + + /** 保存列配置 */ + function saveSetting() { + const { checkedList } = opt.state; + // 代码逻辑说明: 【TV360X-105】列展示设置问题[重置之后保存的顺序还是上次的] + let sortedList = []; + if (opt.restAfterOptions.value) { + sortedList = opt.restAfterOptions.value.map((item) => item.value); + } else { + sortedList = unref(opt.plainSortOptions).map((item) => item.value); + } + $ls.set(cacheKey.value, { + // 保存的列 + checkedList, + // 排序后的列 + sortedList, + // 是否显示行号列 + checkIndex: unref(opt.checkIndex), + // checkbox原始排序 + sortableOrder: unref(opt.sortableOrder), + // 固定列 + fixedColumns: getFixedColumns(), + }); + $message.success('保存成功'); + // 保存之后直接关闭 + opt.popoverVisible.value = false; + } + + /** 重置(删除)列配置 */ + async function resetSetting() { + // 重置固定列 + await resetFixedColumn(); + $ls.remove(cacheKey.value); + $message.success('重置成功'); + } + + async function resetFixedColumn() { + const columns = opt.plainOptions.value; + for (const column of columns) { + column.fixed; + if (fixedReg.test((column.fixed ?? '').toString())) { + await nextTick(); + handleColumnFixed(column, null); + } + } + } + + return { + saveSetting, + resetSetting, + getCache: () => $ls.get(cacheKey.value), + }; +} diff --git a/src/components/Table/src/hooks/useCustomRow.ts b/src/components/Table/src/hooks/useCustomRow.ts new file mode 100644 index 0000000..3b87cd7 --- /dev/null +++ b/src/components/Table/src/hooks/useCustomRow.ts @@ -0,0 +1,105 @@ +import type { ComputedRef } from 'vue'; +import type { BasicTableProps } from '../types/table'; +import { unref } from 'vue'; +import { ROW_KEY } from '../const'; +import { isString, isFunction } from '/@/utils/is'; + +interface Options { + setSelectedRowKeys: (keys: string[]) => void; + getSelectRowKeys: () => string[]; + clearSelectedRowKeys: () => void; + emit: EmitType; + getAutoCreateKey: ComputedRef; +} + +function getKey(record: Recordable, rowKey: string | ((record: Record) => string) | undefined, autoCreateKey?: boolean) { + if (!rowKey || autoCreateKey) { + return record[ROW_KEY]; + } + if (isString(rowKey)) { + return record[rowKey]; + } + if (isFunction(rowKey)) { + return record[rowKey(record)]; + } + return null; +} + +export function useCustomRow( + propsRef: ComputedRef, + { setSelectedRowKeys, getSelectRowKeys, getAutoCreateKey, clearSelectedRowKeys, emit }: Options +) { + const customRow = (record: Recordable, index: number) => { + return { + onClick: (e: Event) => { + e?.stopPropagation(); + function handleClick() { + const { rowSelection, rowKey, clickToRowSelect } = unref(propsRef); + if (!rowSelection || !clickToRowSelect) return; + const keys = getSelectRowKeys(); + const key = getKey(record, rowKey, unref(getAutoCreateKey)); + if (!key) return; + + const isCheckbox = rowSelection.type === 'checkbox'; + if (isCheckbox) { + // 找到tr + const tr: HTMLElement = (e as MouseEvent).composedPath?.().find((dom: HTMLElement) => dom.tagName === 'TR') as HTMLElement; + if (!tr) return; + // 找到Checkbox,检查是否为disabled + const checkBox = tr.querySelector('input[type=checkbox]'); + if (!checkBox || checkBox.hasAttribute('disabled')) return; + if (!keys.includes(key)) { + setSelectedRowKeys([...keys, key]); + return; + } + const keyIndex = keys.findIndex((item) => item === key); + keys.splice(keyIndex, 1); + setSelectedRowKeys(keys); + return; + } + + const isRadio = rowSelection.type === 'radio'; + if (isRadio) { + // 代码逻辑说明: 【QQYUN-6794】table列表增加radio禁用功能 + const rowSelection = propsRef.value.rowSelection; + if (rowSelection.getCheckboxProps) { + const result = rowSelection.getCheckboxProps(record); + if (result.disabled) { + return; + } + } + if (!keys.includes(key)) { + if (keys.length) { + clearSelectedRowKeys(); + } + setSelectedRowKeys([key]); + return; + } else { + // 点击已经选中的,直接return不在做操作 + return; + } + clearSelectedRowKeys(); + } + } + handleClick(); + emit('row-click', record, index, e); + }, + onDblclick: (event: Event) => { + emit('row-dbClick', record, index, event); + }, + onContextmenu: (event: Event) => { + emit('row-contextmenu', record, index, event); + }, + onMouseenter: (event: Event) => { + emit('row-mouseenter', record, index, event); + }, + onMouseleave: (event: Event) => { + emit('row-mouseleave', record, index, event); + }, + }; + }; + + return { + customRow, + }; +} diff --git a/src/components/Table/src/hooks/useCustomSelection.tsx b/src/components/Table/src/hooks/useCustomSelection.tsx new file mode 100644 index 0000000..1af2ae5 --- /dev/null +++ b/src/components/Table/src/hooks/useCustomSelection.tsx @@ -0,0 +1,730 @@ +import type { BasicColumn } from '/@/components/Table'; +import type { Ref, ComputedRef } from 'vue'; +import type { BasicTableProps, PaginationProps, TableRowSelection } from '/@/components/Table'; +import { computed, nextTick, onUnmounted, ref, toRaw, unref, watch, watchEffect } from 'vue'; +import { omit, isEqual } from 'lodash-es'; +import { throttle } from 'lodash-es'; +import { Checkbox, Radio } from 'ant-design-vue'; +import { isFunction } from '/@/utils/is'; +import { findNodeAll } from '/@/utils/helper/treeHelper'; +import { ROW_KEY } from '/@/components/Table/src/const'; +import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { ModalFunc } from 'ant-design-vue/lib/modal/Modal'; + +// 自定义选择列的key +export const CUS_SEL_COLUMN_KEY = 'j-custom-selected-column'; + +/** + * 自定义选择列 + */ +export function useCustomSelection( + propsRef: ComputedRef, + emit: EmitType, + wrapRef: Ref, + getPaginationRef: ComputedRef, + tableData: Ref, + childrenColumnName: ComputedRef +) { + const { createConfirm } = useMessage(); + // 表格body元素 + const bodyEl = ref(); + // body元素高度 + const bodyHeight = ref(0); + // 表格tr高度 + const rowHeight = ref(0); + // body 滚动高度 + const scrollTop = ref(0); + // 选择的key + const selectedKeys = ref([]); + // 选择的行 + const selectedRows = ref([]); + // 变更的行 + let changeRows: Recordable[] = []; + let allSelected: boolean = false; + + let timer; + + // 扁平化数据,children数据也会放到一起 + const flattedData = computed(() => { + // 代码逻辑说明: 【QQYUN-6774】解决checkbox禁用后全选仍能勾选问题 + const data = flattenData(tableData.value, childrenColumnName.value); + const rowSelection = propsRef.value.rowSelection; + if (rowSelection?.type === 'checkbox' && rowSelection.getCheckboxProps) { + for (let i = 0, len = data.length; i < len; i++) { + const record = data[i]; + const result = rowSelection.getCheckboxProps(record); + if (result.disabled) { + data.splice(i, 1); + i--; + len--; + } + } + } + return data; + }); + + const getRowSelectionRef = computed((): TableRowSelection | null => { + const { rowSelection } = unref(propsRef); + if (!rowSelection) { + return null; + } + + return { + preserveSelectedRowKeys: true, + // selectedRowKeys: unref(selectedKeys), + // onChange: (selectedRowKeys: string[]) => { + // setSelectedRowKeys(selectedRowKeys); + // }, + ...omit(rowSelection, ['onChange', 'selectedRowKeys']), + }; + }); + + // 是否是单选 + const isRadio = computed(() => { + return getRowSelectionRef.value?.type === 'radio'; + }); + + const getAutoCreateKey = computed(() => { + return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey; + }); + + // 列key字段 + const getRowKey = computed(() => { + const { rowKey } = unref(propsRef); + return unref(getAutoCreateKey) ? ROW_KEY : rowKey; + }); + // 获取行的key字段数据 + const getRecordKey = (record) => { + if (!getRowKey.value) { + return record[ROW_KEY]; + } else if (isFunction(getRowKey.value)) { + return getRowKey.value(record); + } else { + return record[getRowKey.value]; + } + }; + + // 分页配置 + const getPagination = computed(() => { + return typeof getPaginationRef.value === 'boolean' ? {} : getPaginationRef.value; + }); + // 当前页条目数量 + const currentPageSize = computed(() => { + const { pageSize = 10, total = flattedData.value.length } = getPagination.value; + return pageSize > total ? total : pageSize; + }); + + // 选择列表头props + const selectHeaderProps = computed(() => { + return { + onSelectAll, + isRadio: isRadio.value, + selectedLength: flattedData.value.filter((data) => selectedKeys.value.includes(getRecordKey(data))).length, + // 【TV360X-53】为空时会报错,加强判断 + pageSize: flattedData.value?.length ?? 0, + // 【QQYUN-6774】解决checkbox禁用后全选仍能勾选问题 + disabled: flattedData.value.length == 0, + hideSelectAll: unref(propsRef)?.rowSelection?.hideSelectAll, + }; + }); + + // 监听传入的selectedRowKeys + // 代码逻辑说明: 【QQYUN-8390】部门人员组件点击重置未清空(selectedRowKeys.value=[],watch没监听到加deep) + watch( + () => unref(propsRef)?.rowSelection?.selectedRowKeys, + (val: string[]) => { + // 解决selectedRowKeys在页面调用处使用ref失效 + const value = unref(val); + if (Array.isArray(value) && !sameArray(value, selectedKeys.value)) { + // 延迟是为了等watch selectedRows + setTimeout(() => { + setSelectedRowKeys(value); + }, 0); + } + }, + { + immediate: true, + deep: true + } + ); + // 编辑时selectedRows可能会回填 + watch( + () => unref(propsRef)?.rowSelection?.selectedRows, + (val: string[]) => { + const value: any = unref(val); + if (Array.isArray(value) && !sameArray(value, selectedRows.value)) { + selectedRows.value = value; + } + }, + { + immediate: true, + deep: true, + } + ); + /** + * 2024-03-06 + * liaozhiyang + * 判断是否同一个数组 (引用地址,长度,元素位置信息相同才是同一个数组。数组元素只有字符串) + */ + function sameArray(a, b) { + if (a === b) { + if (a.length === b.length) { + return a.toString() === b.toString(); + } else { + return false; + } + } else { + // 代码逻辑说明: 【QQYUN-9123】popupdict打开弹窗打开程序运行 + if (isEqual(a, b)) { + return true; + } + return false; + } + } + + // 当任意一个变化时,触发同步检测 + watch([selectedKeys, selectedRows], () => { + nextTick(() => { + syncSelectedRows(); + }); + }); + + // 监听滚动条事件 + const onScrollTopChange = throttle((e) => (scrollTop.value = e?.target?.scrollTop), 150); + + let bodyResizeObserver: Nullable = null; + // 获取首行行高 + watchEffect(() => { + // 这种写法是为了监听到 size 的变化 + propsRef.value.size && void 0; + if (bodyEl.value) { + // 监听div高度变化 + bodyResizeObserver = new ResizeObserver((entries) => { + for (let entry of entries) { + if (entry.target === bodyEl.value && entry.contentRect) { + const { height } = entry.contentRect; + bodyHeight.value = Math.ceil(height); + } + } + updateRowHeight(); + }); + bodyResizeObserver.observe(bodyEl.value); + } + rowHeight.value = 50; + }); + + onMountedOrActivated(async () => { + bodyEl.value = await getTableBody(wrapRef.value!); + bodyEl.value.addEventListener('scroll', onScrollTopChange); + }); + onUnmounted(() => { + if (bodyEl.value) { + bodyEl.value?.removeEventListener('scroll', onScrollTopChange); + } + if (bodyResizeObserver != null) { + bodyResizeObserver.disconnect(); + } + }); + + // 更新首行行高 + function updateRowHeight() { + const el = bodyEl.value?.querySelector('tbody.ant-table-tbody tr.ant-table-row') as HTMLDivElement; + if (el) { + // 代码逻辑说明: 【issues/7442】basicTable从默认切换到宽松紧凑时多选框显示异常 + nextTick(() => rowHeight.value = el.offsetHeight); + } + } + + // 选择全部 + function onSelectAll(checked: boolean, flag = 'currentPage') { + // 代码逻辑说明: 【issues/5577】BasicTable组件全选和取消全选时不触发onSelectAll事件 + if (unref(propsRef)?.rowSelection?.onSelectAll) { + allSelected = checked; + changeRows = getInvertRows(selectedRows.value, checked, flag); + } + // 取消全选 + if (!checked) { + // 代码逻辑说明: 【issues/6958】取消没触发onSelectAll事件,跨页选中后 changeRows 为空 + if (flag === 'allPage') { + selectedKeys.value = []; + selectedRows.value = []; + } else { + flattedData.value.forEach((item) => { + updateSelected(item, false); + }); + } + emitChange('all'); + return; + } + let modal: Nullable> = null; + // 全选 + const checkAll = () => { + if (modal != null) { + modal.update({ + content: '正在分批全选,请稍后……', + cancelButtonProps: { disabled: true }, + }); + } + let showCount = 0; + // 最小选中数量 + let minSelect = 100; + const hidden: Recordable[] = []; + flattedData.value.forEach((item, index, array) => { + if (array.length > 120) { + if (showCount <= minSelect && recordIsShow(index, Math.max((minSelect - 10) / 2, 3))) { + showCount++; + updateSelected(item, checked); + } else { + hidden.push(item); + } + } else { + updateSelected(item, checked); + } + }); + if (hidden.length > 0) { + return batchesSelectAll(hidden, checked, minSelect); + } else { + emitChange('all'); + } + }; + + // 当数据量大于120条时,全选会导致页面卡顿,需进行慢速全选 + if (flattedData.value.length > 120) { + modal = createConfirm({ + title: '全选', + content: '当前数据量较大,全选可能会导致页面卡顿,确定要执行此操作吗?', + iconType: 'warning', + onOk: () => checkAll(), + }); + } else { + checkAll(); + } + } + + // 分批全选 + function batchesSelectAll(hidden: Recordable[], checked: boolean, minSelect: number) { + return new Promise((resolve) => { + (function call() { + // 每隔半秒钟,选择100条数据 + setTimeout(() => { + const list = hidden.splice(0, minSelect); + if (list.length > 0) { + list.forEach((item) => { + updateSelected(item, checked); + }); + call(); + } else { + setTimeout(() => { + emitChange('all'); + // 代码逻辑说明: 【QQYUN-5687】批量选择,提示成功后,又来一个提示 + setTimeout(() =>resolve(), 0); + }, 500); + } + }, 300); + })(); + }); + } + + // 选中单个 + function onSelect(record, checked) { + onSelectChild(record, checked); + updateSelected(record, checked); + onSelectParent(record, checked); + // 代码逻辑说明: 【issues/8690】BasicTable的rowSelection新增onSelect方法 + propsRef.value.rowSelection?.onSelect?.(toRaw(record), checked, toRaw(selectedRows.value)); + emitChange(); + } + + function updateSelected(record, checked) { + const recordKey = getRecordKey(record); + if (isRadio.value) { + selectedKeys.value = [recordKey]; + selectedRows.value = [record]; + return; + } + const index = selectedKeys.value.findIndex((key) => key === recordKey); + if (checked) { + if (index === -1) { + selectedKeys.value.push(recordKey); + selectedRows.value.push(record); + } + } else { + if (index !== -1) { + selectedKeys.value.splice(index, 1); + selectedRows.value.splice(index, 1); + } + } + // 代码逻辑说明: 【issues/7200】basicTable选中后没有选中样式 + clearTimeout(timer); + timer = setTimeout(() => { + selectedKeys.value = [...selectedKeys.value]; + }, 0); + } + + // 调用用户自定义的onChange事件 + function emitChange(mode = 'single') { + const { rowSelection } = unref(propsRef); + if (rowSelection) { + const { onChange } = rowSelection; + if (onChange && isFunction(onChange)) { + setTimeout(() => { + onChange(selectedKeys.value, selectedRows.value); + }, 0); + } + } + emit('selection-change', { + keys: getSelectRowKeys(), + rows: getSelectRows(), + }); + // 代码逻辑说明: 【issues/5577】BasicTable组件全选和取消全选时不触发onSelectAll事件 + if (mode == 'all') { + const rowSelection = unref(propsRef)?.rowSelection; + if (rowSelection?.onSelectAll) { + rowSelection.onSelectAll(allSelected, toRaw(getSelectRows()), toRaw(changeRows)); + } + } + } + /** + * 层级关联时,选中下级数据 + * @param record + * @param checked + */ + function onSelectChild(record, checked) { + if (unref(propsRef)?.isTreeTable && unref(propsRef)?.rowSelection?.checkStrictly === false && !isRadio.value) { + if (record[childrenColumnName.value] && record[childrenColumnName.value].length > 0) { + record[childrenColumnName.value].forEach((children) => { + updateSelected(children, checked); + if (children[childrenColumnName.value] && children[childrenColumnName.value].length > 0) { + onSelectChild(children, checked); + } + }); + } + } + } + /** + * 2024-09-24 + * liaozhiyang + * 层级关联时,选中上级数据 + * 【issues/7217】BasicTable树形表格设置checkStrictly无效 + * */ + function onSelectParent(record, checked) { + if (unref(propsRef)?.isTreeTable && unref(propsRef)?.rowSelection?.checkStrictly === false && !isRadio.value) { + let condition = true, + currentRecord = record; + while (condition) { + const parentRecord: any = findParent(tableData.value, currentRecord, childrenColumnName.value); + if (parentRecord) { + const childrenRecordKeys: any = []; + parentRecord[childrenColumnName.value].forEach((item) => { + childrenRecordKeys.push(getRecordKey(item)); + }); + if (checked === true) { + const isSubSet = childrenRecordKeys.every((item) => selectedKeys.value.includes(item)); + isSubSet && updateSelected(parentRecord, checked); + } else if (checked === false) { + updateSelected(parentRecord, checked); + } + if (tableData.value.find((item) => getRecordKey(item) === getRecordKey(parentRecord))) { + // 循环终止 + condition = false; + } else { + currentRecord = parentRecord; + } + } else { + // 循环终止 + condition = false; + } + } + } + function findParent(tree, record, children = 'children') { + let parent = null; + function search(nodes) { + for (let node of nodes) { + if (node[children]?.some((child) => getRecordKey(child) === getRecordKey(record))) { + parent = node; + return true; + } + if (node[children] && search(node[children])) { + return true; + } + } + return false; + } + search(tree); + return parent; + } + } + // 用于判断是否是自定义选择列 + function isCustomSelection(column: BasicColumn) { + return column.key === CUS_SEL_COLUMN_KEY; + } + + /** + * 判断当前行是否可视,虚拟滚动用 + * @param index 行下标 + * @param threshold 前后阈值,默认可视区域前后显示3条 + */ + function recordIsShow(index: number, threshold = 3) { + // 只有数据量大于50条时,才会进行虚拟滚动 + const isVirtual = flattedData.value.length > 50; + if (isVirtual) { + // 根据 scrollTop、bodyHeight、rowHeight 计算出当前行是否可视(阈值前后3条) + // flag1 = 判断当前行是否在可视区域上方3条 + const flag1 = scrollTop.value - rowHeight.value * threshold < index * rowHeight.value; + // flag2 = 判断当前行是否在可视区域下方3条 + const flag2 = index * rowHeight.value < scrollTop.value + bodyHeight.value + rowHeight.value * threshold; + // 全部条件满足时,才显示当前行 + return flag1 && flag2; + } + return true; + } + + // 自定义渲染Body + function bodyCustomRender(params) { + const { index } = params; + // 代码逻辑说明: 【issues/776】显示100条/页,复选框只能显示3个的问题 + if (propsRef.value.canResize && !recordIsShow(index)) { + return ''; + } + if (isRadio.value) { + return renderRadioComponent(params); + } else { + return renderCheckboxComponent(params); + } + } + + /** + * 渲染checkbox组件 + */ + function renderCheckboxComponent({ record }) { + const recordKey = getRecordKey(record); + // 获取用户自定义checkboxProps + const checkboxProps = ((getCheckboxProps) => { + if (typeof getCheckboxProps === 'function') { + try { + return getCheckboxProps(record) ?? {}; + } catch (error) { + console.error(error); + } + } + return {}; + })(propsRef.value.rowSelection?.getCheckboxProps); + return ( + onSelect(record, checked)} + // 代码逻辑说明: 【QQYUN-8694】BasicTable在使用clickToRowSelect=true下,selection-change 事件在触发多次 + onClick={(e) => e.stopPropagation()} + /> + ); + } + + /** + * 渲染radio组件 + */ + function renderRadioComponent({ record }) { + const recordKey = getRecordKey(record); + // 获取用户自定义radioProps + const checkboxProps = (() => { + const rowSelection = propsRef.value.rowSelection; + if (rowSelection?.getCheckboxProps) { + return rowSelection.getCheckboxProps(record); + } + return {}; + })(); + return ( + onSelect(record, checked)} + // 代码逻辑说明: 【QQYUN-8694】BasicTable在使用clickToRowSelect=true下,selection-change 事件在触发多次 + onClick={(e) => e.stopPropagation()} + /> + ); + } + + // 创建选择列 + function handleCustomSelectColumn(columns: BasicColumn[]) { + // 代码逻辑说明: 【issues/757】JPopup表格的选择列固定配置不生效 + const rowSelection = propsRef.value.rowSelection; + if (!rowSelection) { + return; + } + const isFixedLeft = rowSelection.fixed || columns.some((item) => item.fixed === 'left'); + // 代码逻辑说明: 【issues/757】JPopup表格的选择列固定配置不生效 + columns.unshift({ + title: '选择列', + flag: 'CHECKBOX', + key: CUS_SEL_COLUMN_KEY, + width: 50, + minWidth: 50, + maxWidth: 50, + align: 'center', + ...(isFixedLeft ? { fixed: 'left' } : {}), + customRender: bodyCustomRender, + }); + } + + // 清空所有选择 + function clearSelectedRowKeys() { + onSelectAll(false, 'allPage'); + } + + // 通过 selectedKeys 同步 selectedRows + function syncSelectedRows() { + if (selectedKeys.value.length !== selectedRows.value.length) { + // 延迟是为了等watch selectedRows + setTimeout(() => { + setSelectedRowKeys(selectedKeys.value); + }, 0); + } + } + + // 设置选择的key + function setSelectedRowKeys(rowKeys: string[]) { + const isSomeRowKeys = selectedKeys.value === rowKeys; + selectedKeys.value = rowKeys; + const allSelectedRows = findNodeAll( + toRaw(unref(flattedData)).concat(toRaw(unref(selectedRows))), + (item) => rowKeys.includes(getRecordKey(item)), + { + children: propsRef.value.childrenColumnName ?? 'children', + } + ); + const trueSelectedRows: any[] = []; + rowKeys.forEach((key: string) => { + const found = allSelectedRows.find((item) => getRecordKey(item) === key); + found && trueSelectedRows.push(found); + }); + // 代码逻辑说明: 【issues/828】解决卡死问题 + if (!(isSomeRowKeys && equal(selectedRows.value, trueSelectedRows))) { + selectedRows.value = trueSelectedRows; + emitChange(); + } + } + /** + *2023-11-03 + *廖志阳 + *检测selectedRows.value和trueSelectedRows是否相等,防止死循环 + */ + function equal(oldVal, newVal) { + let oldKeys = [], + newKeys = []; + if (oldVal.length === newVal.length) { + oldKeys = oldVal.map((item) => getRecordKey(item)); + newKeys = newVal.map((item) => getRecordKey(item)); + for (let i = 0, len = oldKeys.length; i < len; i++) { + const findItem = newKeys.find((item) => item === oldKeys[i]); + if (!findItem) { + return false; + } + } + return true; + } + return false; + } + /** + *2024-08-08 + *廖志阳 + *根据全选或者反选(或者使用clearSelectedRowKeys()方法)返回源数据中这次需要变更的数据 + */ + function getInvertRows(selectedRows: any, checked: boolean, flag): any { + if (flag == 'currentPage') { + const curPageRows = findNodeAll(toRaw(unref(flattedData)), () => true, { + children: propsRef.value.childrenColumnName ?? 'children', + }); + const selectedkeys = selectedRows.map((item) => getRecordKey(item)); + const result: any = []; + curPageRows.forEach((item) => { + const curRowkey = getRecordKey(item); + const index = selectedkeys.findIndex((item) => item === curRowkey); + if (index == -1) { + checked && result.push(toRaw(item)); + } else { + !checked && result.push(toRaw(item)); + } + }); + return result; + } else { + return toRaw(selectedRows); + } + } + function getSelectRows() { + return unref(selectedRows) as T[]; + } + + function getSelectRowKeys() { + return unref(selectedKeys); + } + + function getRowSelection() { + return unref(getRowSelectionRef)!; + } + + function deleteSelectRowByKey(key: string) { + const index = selectedKeys.value.findIndex((item) => item === key); + if (index !== -1) { + selectedKeys.value.splice(index, 1); + selectedRows.value.splice(index, 1); + } + } + + // 【QQYUN-5837】动态计算 expandIconColumnIndex + const getExpandIconColumnIndex = computed(() => { + const { expandIconColumnIndex } = unref(propsRef); + // 未设置选择列,则保持不变 + if (getRowSelectionRef.value == null) { + return expandIconColumnIndex; + } + // 设置了选择列,并且未传入 index 参数,则返回 1 + if (expandIconColumnIndex == null) { + return 1; + } + return expandIconColumnIndex; + }); + + return { + getRowSelection, + getRowSelectionRef, + getSelectRows, + getSelectRowKeys, + setSelectedRowKeys, + deleteSelectRowByKey, + selectHeaderProps, + isCustomSelection, + handleCustomSelectColumn, + clearSelectedRowKeys, + getExpandIconColumnIndex, + }; +} + +function getTableBody(wrap: HTMLDivElement) { + return new Promise((resolve) => { + (function fn() { + const bodyEl = wrap.querySelector('.ant-table-wrapper .ant-table-body') as HTMLDivElement; + if (bodyEl) { + resolve(bodyEl); + } else { + setTimeout(fn, 100); + } + })(); + }); +} + +function flattenData(data: RecordType[] | undefined, childrenColumnName: string): RecordType[] { + let list: RecordType[] = []; + (data || []).forEach((record) => { + list.push(record); + + if (record && typeof record === 'object' && childrenColumnName in record) { + list = [...list, ...flattenData((record as any)[childrenColumnName], childrenColumnName)]; + } + }); + + return list; +} + diff --git a/src/components/Table/src/hooks/useDataSource.ts b/src/components/Table/src/hooks/useDataSource.ts new file mode 100644 index 0000000..1520d7b --- /dev/null +++ b/src/components/Table/src/hooks/useDataSource.ts @@ -0,0 +1,354 @@ +import type { BasicTableProps, FetchParams, SorterResult } from '../types/table'; +import type { PaginationProps } from '../types/pagination'; +import { ref, unref, ComputedRef, computed, onMounted, watch, reactive, Ref, watchEffect } from 'vue'; +import { useTimeoutFn } from '/@/hooks/core/useTimeout'; +import { buildUUID } from '/@/utils/uuid'; +import { isFunction, isBoolean } from '/@/utils/is'; +import { get, cloneDeep } from 'lodash-es'; +import { FETCH_SETTING, ROW_KEY, PAGE_SIZE } from '../const'; + +interface ActionType { + getPaginationInfo: ComputedRef; + setPagination: (info: Partial) => void; + setLoading: (loading: boolean) => void; + // 代码逻辑说明: 由于 getFieldsValue 返回的不是逗号分割的数据,所以改用 validate + validate: () => Recordable; + clearSelectedRowKeys: () => void; + tableData: Ref; +} + +interface SearchState { + sortInfo: Recordable; + filterInfo: Record; +} +export function useDataSource( + propsRef: ComputedRef, + { getPaginationInfo, setPagination, setLoading, validate, clearSelectedRowKeys, tableData }: ActionType, + emit: EmitType +) { + const searchState = reactive({ + sortInfo: {}, + filterInfo: {}, + }); + const dataSourceRef = ref([]); + const rawDataSourceRef = ref({}); + + watchEffect(() => { + tableData.value = unref(dataSourceRef); + }); + + watch( + () => unref(propsRef).dataSource, + () => { + const { dataSource, api } = unref(propsRef); + !api && dataSource && (dataSourceRef.value = dataSource); + }, + { + immediate: true, + } + ); + + function handleTableChange(pagination: PaginationProps, filters: Partial>, sorter: SorterResult) { + const { clearSelectOnPageChange, sortFn, filterFn } = unref(propsRef); + if (clearSelectOnPageChange) { + clearSelectedRowKeys(); + } + setPagination(pagination); + + const params: Recordable = {}; + if (sorter && isFunction(sortFn)) { + const sortInfo = sortFn(sorter); + searchState.sortInfo = sortInfo; + params.sortInfo = sortInfo; + } + + if (filters && isFunction(filterFn)) { + const filterInfo = filterFn(filters); + searchState.filterInfo = filterInfo; + params.filterInfo = filterInfo; + } + fetch(params); + } + + function setTableKey(items: any[]) { + if (!items || !Array.isArray(items)) return; + items.forEach((item) => { + if (!item[ROW_KEY]) { + item[ROW_KEY] = buildUUID(); + } + if (item.children && item.children.length) { + setTableKey(item.children); + } + }); + } + + const getAutoCreateKey = computed(() => { + return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey; + }); + + const getRowKey = computed(() => { + const { rowKey } = unref(propsRef); + return unref(getAutoCreateKey) ? ROW_KEY : rowKey; + }); + + const getDataSourceRef = computed(() => { + const dataSource = unref(dataSourceRef); + if (!dataSource || dataSource.length === 0) { + return unref(dataSourceRef); + } + if (unref(getAutoCreateKey)) { + const firstItem = dataSource[0]; + const lastItem = dataSource[dataSource.length - 1]; + + if (firstItem && lastItem) { + if (!firstItem[ROW_KEY] || !lastItem[ROW_KEY]) { + const data = cloneDeep(unref(dataSourceRef)); + data.forEach((item) => { + if (!item[ROW_KEY]) { + item[ROW_KEY] = buildUUID(); + } + if (item.children && item.children.length) { + setTableKey(item.children); + } + }); + dataSourceRef.value = data; + } + } + } + return unref(dataSourceRef); + }); + + async function updateTableData(index: number, key: string, value: any) { + const record = dataSourceRef.value[index]; + if (record) { + dataSourceRef.value[index][key] = value; + } + return dataSourceRef.value[index]; + } + + function updateTableDataRecord(rowKey: string | number, record: Recordable): Recordable | undefined { + const row = findTableDataRecord(rowKey); + + if (row) { + for (const field in row) { + if (Reflect.has(record, field)) row[field] = record[field]; + // 代码逻辑说明: 【TV360X-437】树表 部分组件编辑完后,列表未刷新--- + if (Reflect.has(record, field + '_dictText')) { + row[field + '_dictText'] = record[field + '_dictText']; + } + } + return row; + } + } + function deleteTableDataRecord(rowKey: string | number | string[] | number[]) { + if (!dataSourceRef.value || dataSourceRef.value.length == 0) return; + const rowKeyName = unref(getRowKey); + if (!rowKeyName) return; + const rowKeys = !Array.isArray(rowKey) ? [rowKey] : rowKey; + for (const key of rowKeys) { + let index: number | undefined = dataSourceRef.value.findIndex((row) => { + let targetKeyName: string; + if (typeof rowKeyName === 'function') { + targetKeyName = rowKeyName(row); + } else { + targetKeyName = rowKeyName as string; + } + return row[targetKeyName] === key; + }); + if (index >= 0) { + dataSourceRef.value.splice(index, 1); + } + index = unref(propsRef).dataSource?.findIndex((row) => { + let targetKeyName: string; + if (typeof rowKeyName === 'function') { + targetKeyName = rowKeyName(row); + } else { + targetKeyName = rowKeyName as string; + } + return row[targetKeyName] === key; + }); + if (typeof index !== 'undefined' && index !== -1) unref(propsRef).dataSource?.splice(index, 1); + } + setPagination({ + total: unref(propsRef).dataSource?.length, + }); + } + + function insertTableDataRecord(record: Recordable, index: number): Recordable | undefined { + // if (!dataSourceRef.value || dataSourceRef.value.length == 0) return; + index = index ?? dataSourceRef.value?.length; + unref(dataSourceRef).splice(index, 0, record); + return unref(dataSourceRef); + } + function findTableDataRecord(rowKey: string | number) { + if (!dataSourceRef.value || dataSourceRef.value.length == 0) return; + + const rowKeyName = unref(getRowKey); + if (!rowKeyName) return; + + const { childrenColumnName = 'children' } = unref(propsRef); + + const findRow = (array: any[]) => { + let ret; + array.some(function iter(r) { + if (typeof rowKeyName === 'function') { + if ((rowKeyName(r) as string) === rowKey) { + ret = r; + return true; + } + } else { + if (Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey) { + ret = r; + return true; + } + } + return r[childrenColumnName] && r[childrenColumnName].some(iter); + }); + return ret; + }; + + // const row = dataSourceRef.value.find(r => { + // if (typeof rowKeyName === 'function') { + // return (rowKeyName(r) as string) === rowKey + // } else { + // return Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey + // } + // }) + return findRow(dataSourceRef.value); + } + + async function fetch(opt?: FetchParams) { + const { api, searchInfo, defSort, fetchSetting, beforeFetch, afterFetch, useSearchForm, pagination } = unref(propsRef); + if (!api || !isFunction(api)) return; + try { + setLoading(true); + const { pageField, sizeField, listField, totalField } = Object.assign({}, FETCH_SETTING, fetchSetting); + let pageParams: Recordable = {}; + + const { current = 1, pageSize = PAGE_SIZE } = unref(getPaginationInfo) as PaginationProps; + + if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) { + pageParams = {}; + } else { + pageParams[pageField] = (opt && opt.page) || current; + pageParams[sizeField] = pageSize; + } + + const { sortInfo = {}, filterInfo } = searchState; + + // 扩展默认排序多字段数组写法 + let defSortInfo: Recordable | undefined = {}; + if (defSort && Array.isArray(defSort) && defSort.length > 0) { + defSortInfo['defSortString'] = JSON.stringify(defSort); + } else { + defSortInfo = defSort; + } + + let params: Recordable = { + ...pageParams, + // 由于 getFieldsValue 返回的不是逗号分割的数据,所以改用 validate + ...(useSearchForm ? await validate() : {}), + ...searchInfo, + ...defSortInfo, + ...(opt?.searchInfo ?? {}), + ...sortInfo, + ...filterInfo, + ...(opt?.sortInfo ?? {}), + ...(opt?.filterInfo ?? {}), + }; + if (beforeFetch && isFunction(beforeFetch)) { + params = (await beforeFetch(params)) || params; + } + // 代码逻辑说明: 【QQYUN-8316】table查询条件,请求剔除空字符串字段 + for (let item of Object.entries(params)) { + const [key, val] = item; + if (val === '') { + delete params[key]; + }; + }; + const res = await api(params); + rawDataSourceRef.value = res; + + const isArrayResult = Array.isArray(res); + + let resultItems: Recordable[] = isArrayResult ? res : get(res, listField); + const resultTotal: number = isArrayResult ? 0 : get(res, totalField); + + // 假如数据变少,导致总页数变少并小于当前选中页码,通过getPaginationRef获取到的页码是不正确的,需获取正确的页码再次执行 + if (resultTotal) { + const currentTotalPage = Math.ceil(Number(resultTotal) / pageSize); + if (current > currentTotalPage) { + setPagination({ + current: currentTotalPage, + }); + return await fetch(opt); + } + } + + if (afterFetch && isFunction(afterFetch)) { + resultItems = (await afterFetch(resultItems)) || resultItems; + } + dataSourceRef.value = resultItems; + setPagination({ + total: Number(resultTotal) || 0, + }); + if (opt && opt.page) { + setPagination({ + current: opt.page || 1, + }); + } + emit('fetch-success', { + items: unref(resultItems), + total: Number(resultTotal), + }); + return resultItems; + } catch (error) { + emit('fetch-error', error); + dataSourceRef.value = []; + setPagination({ + total: 0, + }); + } finally { + setLoading(false); + } + } + + function setTableData(values: T[]) { + dataSourceRef.value = values; + } + + function getDataSource() { + return getDataSourceRef.value as T[]; + } + + function getRawDataSource() { + return rawDataSourceRef.value as T; + } + + async function reload(opt?: FetchParams) { + return await fetch(opt); + } + + onMounted(() => { + useTimeoutFn(() => { + unref(propsRef).immediate && fetch(); + }, 16); + }); + + return { + getDataSourceRef, + getDataSource, + getRawDataSource, + getRowKey, + setTableData, + getAutoCreateKey, + fetch, + reload, + updateTableData, + updateTableDataRecord, + deleteTableDataRecord, + insertTableDataRecord, + findTableDataRecord, + handleTableChange, + }; +} diff --git a/src/components/Table/src/hooks/useLoading.ts b/src/components/Table/src/hooks/useLoading.ts new file mode 100644 index 0000000..0a670b0 --- /dev/null +++ b/src/components/Table/src/hooks/useLoading.ts @@ -0,0 +1,21 @@ +import { ref, ComputedRef, unref, computed, watch } from 'vue'; +import type { BasicTableProps } from '../types/table'; + +export function useLoading(props: ComputedRef) { + const loadingRef = ref(unref(props).loading); + + watch( + () => unref(props).loading, + (loading) => { + loadingRef.value = loading; + } + ); + + const getLoading = computed(() => unref(loadingRef)); + + function setLoading(loading: boolean) { + loadingRef.value = loading; + } + + return { getLoading, setLoading }; +} diff --git a/src/components/Table/src/hooks/usePagination.tsx b/src/components/Table/src/hooks/usePagination.tsx new file mode 100644 index 0000000..d90eb29 --- /dev/null +++ b/src/components/Table/src/hooks/usePagination.tsx @@ -0,0 +1,85 @@ +import type { PaginationProps } from '../types/pagination'; +import type { BasicTableProps } from '../types/table'; +import { computed, unref, ref, ComputedRef, watch } from 'vue'; +import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue'; +import { isBoolean } from '/@/utils/is'; +import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '../const'; +import { useI18n } from '/@/hooks/web/useI18n'; + +interface ItemRender { + page: number; + type: 'page' | 'prev' | 'next'; + originalElement: any; +} + +function itemRender({ page, type, originalElement }: ItemRender) { + if (type === 'prev') { + return page === 0 ? null : ; + } else if (type === 'next') { + return page === 1 ? null : ; + } + return originalElement; +} + +export function usePagination(refProps: ComputedRef) { + const { t } = useI18n(); + + const configRef = ref({}); + const show = ref(true); + + watch( + () => unref(refProps).pagination, + (pagination) => { + if (!isBoolean(pagination) && pagination) { + configRef.value = { + ...unref(configRef), + ...(pagination ?? {}), + }; + } + } + ); + + const getPaginationInfo = computed((): PaginationProps | boolean => { + const { pagination } = unref(refProps); + + if (!unref(show) || (isBoolean(pagination) && !pagination)) { + return false; + } + + return { + current: 1, + pageSize: PAGE_SIZE, + size: 'small', + defaultPageSize: PAGE_SIZE, + showTotal: (total) => t('component.table.total', { total }), + showSizeChanger: true, + pageSizeOptions: PAGE_SIZE_OPTIONS, + itemRender: itemRender, + showQuickJumper: true, + ...(isBoolean(pagination) ? {} : pagination), + ...unref(configRef), + }; + }); + + function setPagination(info: Partial) { + const paginationInfo = unref(getPaginationInfo); + configRef.value = { + ...(!isBoolean(paginationInfo) ? paginationInfo : {}), + ...info, + }; + } + + function getPagination() { + return unref(getPaginationInfo); + } + + function getShowPagination() { + return unref(show); + } + + async function setShowPagination(flag: boolean) { + show.value = flag; + } + + return { getPagination, getPaginationInfo, setShowPagination, getShowPagination, setPagination }; +} diff --git a/src/components/Table/src/hooks/useRowSelection.ts b/src/components/Table/src/hooks/useRowSelection.ts new file mode 100644 index 0000000..538c69d --- /dev/null +++ b/src/components/Table/src/hooks/useRowSelection.ts @@ -0,0 +1,125 @@ +import { isFunction } from '/@/utils/is'; +import type { BasicTableProps, TableRowSelection } from '../types/table'; +import { computed, ComputedRef, nextTick, Ref, ref, toRaw, unref, watch } from 'vue'; +import { ROW_KEY } from '../const'; +import { omit } from 'lodash-es'; +import { findNodeAll } from '/@/utils/helper/treeHelper'; + +export function useRowSelection(propsRef: ComputedRef, tableData: Ref, emit: EmitType) { + const selectedRowKeysRef = ref([]); + const selectedRowRef = ref([]); + + const getRowSelectionRef = computed((): TableRowSelection | null => { + const { rowSelection } = unref(propsRef); + if (!rowSelection) { + return null; + } + + return { + // AntDV3.0 之后使用远程加载数据进行分页时, + // 默认会清空上一页选择的行数据(导致无法跨页选择), + // 将此属性设置为 true 即可解决。 + preserveSelectedRowKeys: true, + selectedRowKeys: unref(selectedRowKeysRef), + onChange: (selectedRowKeys: string[]) => { + setSelectedRowKeys(selectedRowKeys); + }, + ...omit(rowSelection, ['onChange']), + }; + }); + + watch( + () => unref(propsRef).rowSelection?.selectedRowKeys, + (v: string[]) => { + setSelectedRowKeys(v); + } + ); + + watch( + () => unref(selectedRowKeysRef), + () => { + nextTick(() => { + const { rowSelection } = unref(propsRef); + if (rowSelection) { + const { onChange } = rowSelection; + if (onChange && isFunction(onChange)) onChange(getSelectRowKeys(), getSelectRows()); + } + //table行选择时卡顿明显 #503 + if (unref(tableData).length > 0) { + emit('selection-change', { + keys: getSelectRowKeys(), + rows: getSelectRows(), + }); + } + }); + }, + { deep: true } + ); + + const getAutoCreateKey = computed(() => { + return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey; + }); + + const getRowKey = computed(() => { + const { rowKey } = unref(propsRef); + return unref(getAutoCreateKey) ? ROW_KEY : rowKey; + }); + + function setSelectedRowKeys(rowKeys: string[]) { + selectedRowKeysRef.value = rowKeys; + const allSelectedRows = findNodeAll( + toRaw(unref(tableData)).concat(toRaw(unref(selectedRowRef))), + (item) => rowKeys.includes(item[unref(getRowKey) as string]), + { + children: propsRef.value.childrenColumnName ?? 'children', + } + ); + const trueSelectedRows: any[] = []; + rowKeys.forEach((key: string) => { + const found = allSelectedRows.find((item) => item[unref(getRowKey) as string] === key); + found && trueSelectedRows.push(found); + }); + selectedRowRef.value = trueSelectedRows; + } + + function setSelectedRows(rows: Recordable[]) { + selectedRowRef.value = rows; + } + + function clearSelectedRowKeys() { + selectedRowRef.value = []; + selectedRowKeysRef.value = []; + } + + function deleteSelectRowByKey(key: string) { + const selectedRowKeys = unref(selectedRowKeysRef); + const index = selectedRowKeys.findIndex((item) => item === key); + if (index !== -1) { + unref(selectedRowKeysRef).splice(index, 1); + } + } + + function getSelectRowKeys() { + return unref(selectedRowKeysRef); + } + + function getSelectRows() { + // const ret = toRaw(unref(selectedRowRef)).map((item) => toRaw(item)); + return unref(selectedRowRef) as T[]; + } + + function getRowSelection() { + return unref(getRowSelectionRef)!; + } + + return { + getRowSelection, + getRowSelectionRef, + getSelectRows, + getSelectRowKeys, + setSelectedRowKeys, + clearSelectedRowKeys, + deleteSelectRowByKey, + setSelectedRows, + }; +} diff --git a/src/components/Table/src/hooks/useTable.ts b/src/components/Table/src/hooks/useTable.ts new file mode 100644 index 0000000..40575a3 --- /dev/null +++ b/src/components/Table/src/hooks/useTable.ts @@ -0,0 +1,168 @@ +import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table'; +import type { PaginationProps } from '../types/pagination'; +import type { DynamicProps } from '/#/utils'; +import type { FormActionType } from '/@/components/Form'; +import type { WatchStopHandle } from 'vue'; +import { getDynamicProps } from '/@/utils'; +import { ref, onUnmounted, unref, watch, toRaw } from 'vue'; +import { isProdMode } from '/@/utils/env'; +import { error } from '/@/utils/log'; + +type Props = Partial>; + +type UseTableMethod = TableActionType & { + getForm: () => FormActionType; +}; + +export function useTable(tableProps?: Props): [ + (instance: TableActionType, formInstance: UseTableMethod) => void, + TableActionType & { + getForm: () => FormActionType; + } +] { + const tableRef = ref>(null); + const loadedRef = ref>(false); + const formRef = ref>(null); + + let stopWatch: WatchStopHandle; + + function register(instance: TableActionType, formInstance: UseTableMethod) { + isProdMode() && + onUnmounted(() => { + tableRef.value = null; + loadedRef.value = null; + }); + + if (unref(loadedRef) && isProdMode() && instance === unref(tableRef)) return; + + tableRef.value = instance; + formRef.value = formInstance; + tableProps && instance.setProps(getDynamicProps(tableProps)); + loadedRef.value = true; + + stopWatch?.(); + + stopWatch = watch( + () => tableProps, + () => { + tableProps && instance.setProps(getDynamicProps(tableProps)); + }, + { + immediate: true, + deep: true, + } + ); + } + + function getTableInstance(): TableActionType { + const table = unref(tableRef); + if (!table) { + error('The table instance has not been obtained yet, please make sure the table is presented when performing the table operation!'); + } + return table as TableActionType; + } + + function getTableRef(){ + return tableRef; + } + + const methods: TableActionType & { + getForm: () => FormActionType; + } & { + getTableRef: () => any; + } = { + reload: async (opt?: FetchParams) => { + return await getTableInstance().reload(opt); + }, + setProps: (props: Partial) => { + getTableInstance().setProps(props); + }, + redoHeight: () => { + getTableInstance().redoHeight(); + }, + setLoading: (loading: boolean) => { + getTableInstance().setLoading(loading); + }, + getDataSource: () => { + return getTableInstance().getDataSource(); + }, + getRawDataSource: () => { + return getTableInstance().getRawDataSource(); + }, + getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => { + const columns = getTableInstance().getColumns({ ignoreIndex }) || []; + return toRaw(columns); + }, + setColumns: (columns: BasicColumn[]) => { + getTableInstance().setColumns(columns); + }, + setTableData: (values: any[]) => { + return getTableInstance().setTableData(values); + }, + setPagination: (info: Partial) => { + return getTableInstance().setPagination(info); + }, + deleteSelectRowByKey: (key: string) => { + getTableInstance().deleteSelectRowByKey(key); + }, + getSelectRowKeys: () => { + return toRaw(getTableInstance().getSelectRowKeys()); + }, + getSelectRows: () => { + return toRaw(getTableInstance().getSelectRows()); + }, + clearSelectedRowKeys: () => { + getTableInstance().clearSelectedRowKeys(); + }, + setSelectedRowKeys: (keys: string[] | number[]) => { + getTableInstance().setSelectedRowKeys(keys); + }, + getPaginationRef: () => { + return getTableInstance().getPaginationRef(); + }, + getSize: () => { + return toRaw(getTableInstance().getSize()); + }, + updateTableData: (index: number, key: string, value: any) => { + return getTableInstance().updateTableData(index, key, value); + }, + deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => { + return getTableInstance().deleteTableDataRecord(rowKey); + }, + insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => { + return getTableInstance().insertTableDataRecord(record, index); + }, + updateTableDataRecord: (rowKey: string | number, record: Recordable) => { + return getTableInstance().updateTableDataRecord(rowKey, record); + }, + findTableDataRecord: (rowKey: string | number) => { + return getTableInstance().findTableDataRecord(rowKey); + }, + getRowSelection: () => { + return toRaw(getTableInstance().getRowSelection()); + }, + getCacheColumns: () => { + return toRaw(getTableInstance().getCacheColumns()); + }, + getForm: () => { + return unref(formRef) as unknown as FormActionType; + }, + setShowPagination: async (show: boolean) => { + getTableInstance().setShowPagination(show); + }, + getShowPagination: () => { + return toRaw(getTableInstance().getShowPagination()); + }, + expandAll: () => { + getTableInstance().expandAll(); + }, + collapseAll: () => { + getTableInstance().collapseAll(); + }, + getTableRef: () => { + return getTableRef(); + } + }; + + return [register, methods]; +} diff --git a/src/components/Table/src/hooks/useTableContext.ts b/src/components/Table/src/hooks/useTableContext.ts new file mode 100644 index 0000000..7ea141c --- /dev/null +++ b/src/components/Table/src/hooks/useTableContext.ts @@ -0,0 +1,33 @@ +import type { Ref } from 'vue'; +import type { BasicTableProps, TableActionType } from '../types/table'; +import { provide, inject, ComputedRef } from 'vue'; + +// 为每个表格实例创建唯一的 Symbol key,避免父子组件或同级组件间的 context 冲突 +// Vue 的 provide/inject 是按组件树传递的,使用唯一 Symbol 可以确保每个 BasicTable 实例独立 +let tableIdCounter = 0; + +type Instance = TableActionType & { + wrapRef: Ref>; + getBindValues: ComputedRef; +}; + +type RetInstance = Omit & { + getBindValues: ComputedRef; +}; + +export function createTableContext(instance: Instance) { + // 每次创建 context 时都生成新的唯一 Symbol + const key = Symbol(`basic-table-${++tableIdCounter}`); + provide(key, instance); + // 同时提供一个内部标记,让子组件能获取到这个 key + provide('__BASIC_TABLE_CONTEXT_KEY__', key); +} + +export function useTableContext(): RetInstance { + // 从最近的父组件获取 context key + const key = inject('__BASIC_TABLE_CONTEXT_KEY__'); + if (!key) { + throw new Error('useTableContext must be used after createTableContext'); + } + return inject(key) as RetInstance; +} diff --git a/src/components/Table/src/hooks/useTableExpand.ts b/src/components/Table/src/hooks/useTableExpand.ts new file mode 100644 index 0000000..96bd3e0 --- /dev/null +++ b/src/components/Table/src/hooks/useTableExpand.ts @@ -0,0 +1,61 @@ +import type { ComputedRef, Ref } from 'vue'; +import type { BasicTableProps } from '../types/table'; +import { computed, unref, ref, toRaw, watch } from 'vue'; +import { ROW_KEY } from '../const'; + +export function useTableExpand(propsRef: ComputedRef, tableData: Ref, emit: EmitType) { + const expandedRowKeys = ref([]); + + const getAutoCreateKey = computed(() => { + return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey; + }); + + const getRowKey = computed(() => { + const { rowKey } = unref(propsRef); + return unref(getAutoCreateKey) ? ROW_KEY : rowKey; + }); + + const getExpandOption = computed(() => { + const { isTreeTable } = unref(propsRef); + if (!isTreeTable) return {}; + + return { + expandedRowKeys: unref(expandedRowKeys), + onExpandedRowsChange: (keys: string[]) => { + expandedRowKeys.value = keys; + emit('expanded-rows-change', keys); + }, + }; + }); + + // 监听并同步props中的expandedRowKeys + watch(() => propsRef.value?.expandedRowKeys, (keys) => { + if (Array.isArray(keys)) { + expandedRowKeys.value = keys; + } + }, {immediate: true}); + + function expandAll() { + const keys = getAllKeys(); + expandedRowKeys.value = keys; + } + + function getAllKeys(data?: Recordable[]) { + const keys: string[] = []; + const { childrenColumnName } = unref(propsRef); + toRaw(data || unref(tableData)).forEach((item) => { + keys.push(item[unref(getRowKey) as string]); + const children = item[childrenColumnName || 'children']; + if (children?.length) { + keys.push(...getAllKeys(children)); + } + }); + return keys; + } + + function collapseAll() { + expandedRowKeys.value = []; + } + + return { getExpandOption, expandAll, collapseAll }; +} diff --git a/src/components/Table/src/hooks/useTableFooter.ts b/src/components/Table/src/hooks/useTableFooter.ts new file mode 100644 index 0000000..bb82847 --- /dev/null +++ b/src/components/Table/src/hooks/useTableFooter.ts @@ -0,0 +1,71 @@ +import type { ComputedRef, Ref, Slots } from 'vue'; +import type { BasicTableProps } from '../types/table'; +import { unref, computed, h, nextTick, watchEffect } from 'vue'; +import TableFooter from '../components/TableFooter.vue'; +import { useEventListener } from '/@/hooks/event/useEventListener'; + +export function useTableFooter( + propsRef: ComputedRef, + slots: Slots, + scrollRef: ComputedRef<{ + x: string | number | true; + y: Nullable; + scrollToFirstRowOnChange: boolean; + }>, + tableElRef: Ref, + getDataSourceRef: ComputedRef +) { + const getIsEmptyData = computed(() => { + return (unref(getDataSourceRef) || []).length === 0; + }); + + // 是否有展开行 + const hasExpandedRow = computed(() => Object.keys(slots).includes('expandedRowRender')) + + const getFooterProps = computed((): Recordable | undefined => { + const { summaryFunc, showSummary, summaryData, bordered } = unref(propsRef); + return showSummary && !unref(getIsEmptyData) ? () => h(TableFooter, { + bordered, + summaryFunc, + summaryData, + scroll: unref(scrollRef), + hasExpandedRow: hasExpandedRow.value + }) : undefined; + }); + + watchEffect(() => { + handleSummary(); + }); + + function handleSummary() { + const { showSummary, canResize } = unref(propsRef); + if (!showSummary || unref(getIsEmptyData)) return; + nextTick(() => { + const tableEl = unref(tableElRef); + if (!tableEl) return; + let bodyDom; + // 代码逻辑说明: 【issues/7422】BasicTable列表canResize属性为true时合计行不能横向滚动 + if (canResize) { + setTimeout(() => { + bodyDom = tableEl.$el.querySelector('.ant-table-body'); + }, 0); + } else { + bodyDom = tableEl.$el.querySelector('.ant-table-content'); + } + setTimeout(() => { + useEventListener({ + el: bodyDom, + name: 'scroll', + listener: () => { + const footerBodyDom = tableEl.$el.querySelector('.ant-table-footer .ant-table-content') as HTMLDivElement; + if (!footerBodyDom || !bodyDom) return; + footerBodyDom.scrollLeft = bodyDom.scrollLeft; + }, + wait: 0, + options: true, + }); + }, 0); + }); + } + return { getFooterProps }; +} diff --git a/src/components/Table/src/hooks/useTableForm.ts b/src/components/Table/src/hooks/useTableForm.ts new file mode 100644 index 0000000..1ad738d --- /dev/null +++ b/src/components/Table/src/hooks/useTableForm.ts @@ -0,0 +1,50 @@ +import type { ComputedRef, Slots } from 'vue'; +import type { BasicTableProps, FetchParams } from '../types/table'; +import { unref, computed } from 'vue'; +import type { FormProps } from '/@/components/Form'; +import { isFunction } from '/@/utils/is'; + +export function useTableForm( + propsRef: ComputedRef, + slots: Slots, + fetch: (opt?: FetchParams | undefined) => Promise, + getLoading: ComputedRef +) { + const getFormProps = computed((): Partial => { + const { formConfig } = unref(propsRef); + const { submitButtonOptions, autoSubmitOnEnter} = formConfig || {}; + return { + showAdvancedButton: true, + ...formConfig, + submitButtonOptions: { loading: unref(getLoading), ...submitButtonOptions }, + compact: true, + // 代码逻辑说明: [issues/568]设置 autoSubmitOnEnter: false 不生效 --- + autoSubmitOnEnter: autoSubmitOnEnter, + }; + }); + + const getFormSlotKeys: ComputedRef = computed(() => { + const keys = Object.keys(slots); + return keys.map((item) => (item.startsWith('form-') ? item : null)).filter((item) => !!item) as string[]; + }); + + function replaceFormSlotKey(key: string) { + if (!key) return ''; + return key?.replace?.(/form\-/, '') ?? ''; + } + + function handleSearchInfoChange(info: Recordable) { + const { handleSearchInfoFn } = unref(propsRef); + if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) { + info = handleSearchInfoFn(info) || info; + } + fetch({ searchInfo: info, page: 1 }); + } + + return { + getFormProps, + replaceFormSlotKey, + getFormSlotKeys, + handleSearchInfoChange, + }; +} diff --git a/src/components/Table/src/hooks/useTableHeader.ts b/src/components/Table/src/hooks/useTableHeader.ts new file mode 100644 index 0000000..597b5ec --- /dev/null +++ b/src/components/Table/src/hooks/useTableHeader.ts @@ -0,0 +1,58 @@ +import type { ComputedRef, Slots } from 'vue'; +import type { BasicTableProps, InnerHandlers } from '../types/table'; +import { unref, computed, h } from 'vue'; +import TableHeader from '../components/TableHeader.vue'; +import { isString } from '/@/utils/is'; +import { getSlot } from '/@/utils/helper/tsxHelper'; + +export function useTableHeader(propsRef: ComputedRef, slots: Slots, handlers: InnerHandlers) { + const getHeaderProps = computed((): Recordable => { + const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(propsRef); + const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting; + if (hideTitle && !isString(title)) { + return {}; + } + + return { + title: hideTitle + ? null + : () => + h( + TableHeader, + { + title, + titleHelpMessage, + showTableSetting, + tableSetting, + onColumnsChange: handlers.onColumnsChange, + } as Recordable, + { + ...(slots.toolbar + ? { + toolbar: () => getSlot(slots, 'toolbar'), + } + : {}), + ...(slots.tableTitle + ? { + tableTitle: () => getSlot(slots, 'tableTitle'), + } + : {}), + ...(slots.headerTop + ? { + headerTop: () => getSlot(slots, 'headerTop'), + } + : {}), + //添加tableTop插槽 + ...(slots.tableTop + ? { + tableTop: () => getSlot(slots, 'tableTop'), + } + : {}), + // 添加alertAfter插槽 + ...(slots.alertAfter ? { alertAfter: () => getSlot(slots, 'alertAfter') } : {}), + } + ), + }; + }); + return { getHeaderProps }; +} diff --git a/src/components/Table/src/hooks/useTableScroll.ts b/src/components/Table/src/hooks/useTableScroll.ts new file mode 100644 index 0000000..8d3869e --- /dev/null +++ b/src/components/Table/src/hooks/useTableScroll.ts @@ -0,0 +1,235 @@ +import type { BasicTableProps, TableRowSelection, BasicColumn } from '../types/table'; +import type { Ref, ComputedRef, Slots } from 'vue'; +import { computed, unref, ref, nextTick, watch } from 'vue'; +import { getViewportOffset } from '/@/utils/domUtils'; +import { isBoolean } from '/@/utils/is'; +import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; +import { useModalContext } from '/@/components/Modal'; +import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated'; +import { useDebounceFn } from '@vueuse/core'; +import componentSetting from '/@/settings/componentSetting'; + +export function useTableScroll( + propsRef: ComputedRef, + tableElRef: Ref, + columnsRef: ComputedRef, + rowSelectionRef: ComputedRef | null>, + getDataSourceRef: ComputedRef, + slots: Slots, + getPaginationInfo: ComputedRef +) { + const tableHeightRef: Ref> = ref(null); + + const modalFn = useModalContext(); + + // Greater than animation time 280 + const debounceRedoHeight = useDebounceFn(redoHeight, 100); + + const getCanResize = computed(() => { + const { canResize, scroll } = unref(propsRef); + return canResize && !(scroll || {}).y; + }); + + watch( + () => [unref(getCanResize), unref(getDataSourceRef)?.length], + () => { + debounceRedoHeight(); + }, + { + flush: 'post', + } + ); + + function redoHeight() { + nextTick(() => { + calcTableHeight(); + }); + } + + function setHeight(heigh: number) { + tableHeightRef.value = heigh; + // Solve the problem of modal adaptive height calculation when the form is placed in the modal + modalFn?.redoModalHeight?.(); + } + + // No need to repeat queries + let paginationEl: HTMLElement | null; + let footerEl: HTMLElement | null; + let bodyEl: HTMLElement | null; + + async function calcTableHeight() { + const { resizeHeightOffset, pagination, maxHeight, minHeight } = unref(propsRef); + const tableData = unref(getDataSourceRef); + + const table = unref(tableElRef); + if (!table) return; + + const tableEl: Element = table.$el; + if (!tableEl) return; + + if (!bodyEl) { + // 代码逻辑说明: issues/355 前端-jeecgboot-vue3 3.4.4版本,BasicTable高度自适应功能失效,设置BasicTable组件maxHeight失效; 原因已找到,请看详情 + bodyEl = tableEl.querySelector('.ant-table-tbody'); + if (!bodyEl) return; + } + + const hasScrollBarY = bodyEl.scrollHeight > bodyEl.clientHeight; + const hasScrollBarX = bodyEl.scrollWidth > bodyEl.clientWidth; + + if (hasScrollBarY) { + tableEl.classList.contains('hide-scrollbar-y') && tableEl.classList.remove('hide-scrollbar-y'); + } else { + !tableEl.classList.contains('hide-scrollbar-y') && tableEl.classList.add('hide-scrollbar-y'); + } + + if (hasScrollBarX) { + tableEl.classList.contains('hide-scrollbar-x') && tableEl.classList.remove('hide-scrollbar-x'); + } else { + !tableEl.classList.contains('hide-scrollbar-x') && tableEl.classList.add('hide-scrollbar-x'); + } + + bodyEl!.style.height = 'unset'; + + if (!unref(getCanResize) || ( !tableData || tableData.length === 0)) return; + + await nextTick(); + //Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight + + const headEl = tableEl.querySelector('.ant-table-thead'); + + if (!headEl) return; + + // Table height from bottom + const { bottomIncludeBody } = getViewportOffset(headEl); + // Table height from bottom height-custom offset + + const paddingHeight = 32; + // Pager height + let paginationHeight = 2; + // 【issues/9217】当配置了pagination: true时,BasicTable组件自适应高度异常 + if (pagination !== false) { + paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement; + if (paginationEl) { + const offsetHeight = paginationEl.offsetHeight; + paginationHeight += offsetHeight || 0; + } else { + // TODO First fix 24 + paginationHeight += 24; + } + } else { + paginationHeight = -8; + } + + let footerHeight = 0; + // 代码逻辑说明: 【issues/1137】BasicTable自适应高度计算没有减去尾部高度 + footerEl = tableEl.querySelector('.ant-table-footer'); + if (footerEl) { + const offsetHeight = footerEl.offsetHeight; + footerHeight = offsetHeight || 0; + } + + let headerHeight = 0; + if (headEl) { + headerHeight = (headEl as HTMLElement).offsetHeight; + } + + let height = bottomIncludeBody - (resizeHeightOffset || 0) - paddingHeight - paginationHeight - footerHeight - headerHeight; + // update-begin--author:liaozhiyang---date:20240603---for【TV360X-861】列表查询区域不可往上滚动 + // 10+6(外层边距padding:10 + 内层padding-bottom:6) + height -= 16; + // 代码逻辑说明: 【issues/8880】BasicTable组件在modal中适应高度 + try { + // 当BasicTable在BasicModal容器中时,扣减容器底部高度 + const modalFooter = tableEl.closest('.ant-modal-root')?.querySelector('.ant-modal-footer'); + if (modalFooter) { + const { bottomIncludeBody: modalFooterHeight } = getViewportOffset(modalFooter); + height = height - modalFooterHeight; + } + } catch (e) {} + height = (height < minHeight! ? (minHeight as number) : height) ?? height; + height = (height > maxHeight! ? (maxHeight as number) : height) ?? height; + setHeight(height); + + bodyEl!.style.height = `${height}px`; + // update-begin--author:liaozhiyang---date:20240609---for【issues/8374】分页始终显示在底部 + nextTick(() => { + if (maxHeight === undefined) { + if (unref(getPaginationInfo) && unref(getDataSourceRef).length) { + const pageSize = unref(getPaginationInfo)?.pageSize; + const current = unref(getPaginationInfo)?.current; + const total = unref(getPaginationInfo)?.total; + const tableBody = tableEl.querySelector('.ant-table-body') as HTMLElement; + const tr = tableEl.querySelector('.ant-table-tbody')?.children ?? []; + const lastrEl = tr[tr.length - 1] as HTMLElement; + const trHeight = lastrEl.offsetHeight; + const dataHeight = trHeight * pageSize; + if (tableBody && lastrEl) { + // table是否隐藏(隐藏的table不能吸底) + const isTableBodyHide = tableBody.offsetHeight == 0 && tableBody.offsetWidth == 0; + if (isTableBodyHide) { + return; + } + if (current === 1 && pageSize > unref(getDataSourceRef).length && total <= pageSize) { + tableBody.style.height = `${height}px`; + } else { + tableBody.style.height = `${dataHeight < height ? dataHeight : height}px`; + } + } + } + } + }); + // update-end--author:liaozhiyang---date:20240609---for【issues/8374】分页始终显示在底部 + } + useWindowSizeFn(calcTableHeight, 280); + onMountedOrActivated(() => { + calcTableHeight(); + nextTick(() => { + debounceRedoHeight(); + }); + }); + + const getScrollX = computed(() => { + let width = 0; + // if (unref(rowSelectionRef)) { + // width += 60; + // } + // 代码逻辑说明: 【issues/5411】BasicTable 配置maxColumnWidth 未生效 + const { maxColumnWidth } = unref(propsRef); + // TODO props ?? 0; + const NORMAL_WIDTH = maxColumnWidth ?? 150; + // date-begin--author:liaozhiyang---date:20250716---for:【QQYUN-13122】有数十个字段时只展示2个字段,其余字段为ifShow:false会有滚动条 + const columns = unref(columnsRef).filter((item) => !(item.defaultHidden == true || item.ifShow == false)) + // date-end--author:liaozhiyang---date:20250716---for:【QQYUN-13122】有数十个字段时只展示2个字段,其余字段为ifShow:false会有滚动条 + columns.forEach((item) => { + width += Number.parseInt(item.width as string) || 0; + }); + const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width')); + + const len = unsetWidthColumns.length; + if (len !== 0) { + width += len * NORMAL_WIDTH; + } + // 代码逻辑说明: 【TV360X-116】内嵌风格字段较多时表格错位 + if (slots.expandedRowRender) { + width += propsRef.value.expandColumnWidth; + } + const table = unref(tableElRef); + const tableWidth = table?.$el?.offsetWidth ?? 0; + return tableWidth > width ? '100%' : width; + }); + + const getScrollRef = computed(() => { + const tableHeight = unref(tableHeightRef); + const { canResize, scroll } = unref(propsRef); + const { table } = componentSetting; + return { + x: unref(getScrollX), + y: canResize ? tableHeight : null, + // 代码逻辑说明: 【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义 + scrollToFirstRowOnChange: table.scrollToFirstRowOnChange, + ...scroll, + }; + }); + + return { getScrollRef, redoHeight }; +} diff --git a/src/components/Table/src/hooks/useTableStyle.ts b/src/components/Table/src/hooks/useTableStyle.ts new file mode 100644 index 0000000..4a0a752 --- /dev/null +++ b/src/components/Table/src/hooks/useTableStyle.ts @@ -0,0 +1,56 @@ +import type { ComputedRef } from 'vue'; +import type { BasicTableProps, TableCustomRecord } from '../types/table'; +import { unref } from 'vue'; +import { isFunction } from '/@/utils/is'; +import { ROW_KEY } from '/@/components/Table/src/const'; + +export function useTableStyle(propsRef: ComputedRef, prefixCls: string) { + /** + * 2024-09-19 + * liaozhiyang + * 【issues/7200】basicTable选中后没有选中样式 + * */ + const isChecked = (propsRef, record) => { + const getAutoCreateKey = () => { + return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey; + }; + const getRowKey = () => { + const { rowKey } = unref(propsRef); + return getAutoCreateKey() ? ROW_KEY : rowKey; + }; + // 获取行的key字段数据 + const getRecordKey = (record) => { + const key = getRowKey(); + if (!key) { + return record[ROW_KEY]; + } else if (isFunction(key)) { + return key(record); + } else { + return record[key]; + } + }; + const { rowSelection } = unref(propsRef); + if (rowSelection?.selectedRowKeys?.length) { + return rowSelection.selectedRowKeys.includes(getRecordKey(record)); + } + return false; + }; + + function getRowClassName(record: TableCustomRecord, index: number) { + const { striped, rowClassName } = unref(propsRef); + const classNames: string[] = []; + if (striped) { + classNames.push((index || 0) % 2 === 1 ? `${prefixCls}-row__striped` : ''); + } + if (rowClassName && isFunction(rowClassName)) { + classNames.push(rowClassName(record, index)); + } + // 代码逻辑说明: 【issues/7200】basicTable选中后没有选中样式 + if (isChecked(propsRef, record)) { + classNames.push('ant-table-row-selected'); + } + return classNames.filter((cls) => !!cls).join(' '); + } + + return { getRowClassName }; +} diff --git a/src/components/Table/src/props.ts b/src/components/Table/src/props.ts new file mode 100644 index 0000000..337859f --- /dev/null +++ b/src/components/Table/src/props.ts @@ -0,0 +1,148 @@ +import type { PropType } from 'vue'; +import type { PaginationProps } from './types/pagination'; +import type { BasicColumn, FetchSetting, TableSetting, SorterResult, TableCustomRecord, TableRowSelection, SizeType } from './types/table'; +import type { FormProps } from '/@/components/Form'; +import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const'; +import { propTypes } from '/@/utils/propTypes'; + +export const basicProps = { + clickToRowSelect: propTypes.bool.def(true), + isTreeTable: propTypes.bool.def(false), + tableSetting: propTypes.shape({}), + inset: propTypes.bool, + sortFn: { + type: Function as PropType<(sortInfo: SorterResult) => any>, + default: DEFAULT_SORT_FN, + }, + filterFn: { + type: Function as PropType<(data: Partial>) => any>, + default: DEFAULT_FILTER_FN, + }, + showTableSetting: propTypes.bool, + autoCreateKey: propTypes.bool.def(true), + striped: propTypes.bool.def(false), + showSummary: propTypes.bool, + summaryFunc: { + type: [Function, Array] as PropType<(...arg: any[]) => any[]>, + default: null, + }, + summaryData: { + type: Array as PropType, + default: null, + }, + indentSize: propTypes.number.def(24), + canColDrag: propTypes.bool.def(true), + api: { + type: Function as PropType<(...arg: any[]) => Promise>, + default: null, + }, + beforeFetch: { + type: Function as PropType, + default: null, + }, + afterFetch: { + type: Function as PropType, + default: null, + }, + handleSearchInfoFn: { + type: Function as PropType, + default: null, + }, + fetchSetting: { + type: Object as PropType, + default: () => { + return FETCH_SETTING; + }, + }, + // 立即请求接口 + immediate: propTypes.bool.def(true), + emptyDataIsShowTable: propTypes.bool.def(true), + // 额外的请求参数 + searchInfo: { + type: Object as PropType, + default: null, + }, + // 默认的排序参数 + defSort: { + type: Object as PropType, + default: null, + }, + // 使用搜索表单 + useSearchForm: propTypes.bool, + // 表单配置 + formConfig: { + type: Object as PropType>, + default: null, + }, + columns: { + type: [Array] as PropType, + default: () => [], + }, + showIndexColumn: propTypes.bool.def(true), + indexColumnProps: { + type: Object as PropType, + default: null, + }, + showActionColumn: { + type: Boolean, + default: true, + }, + actionColumn: { + type: Object as PropType, + default: null, + }, + ellipsis: propTypes.bool.def(true), + canResize: propTypes.bool.def(true), + clearSelectOnPageChange: propTypes.bool, + resizeHeightOffset: propTypes.number.def(0), + rowSelection: { + type: Object as PropType, + default: null, + }, + title: { + type: [String, Function] as PropType string)>, + default: null, + }, + titleHelpMessage: { + type: [String, Array] as PropType, + }, + minHeight: propTypes.number, + maxHeight: propTypes.number, + // 代码逻辑说明: 【TV360X-116】内嵌风格字段较多时表格错位 + expandColumnWidth: propTypes.number.def(48), + // 统一设置列最大宽度 + maxColumnWidth: propTypes.number, + dataSource: { + type: Array as PropType, + default: null, + }, + rowKey: { + type: [String, Function] as PropType string)>, + default: '', + }, + bordered: propTypes.bool, + pagination: { + type: [Object, Boolean] as PropType, + default: null, + }, + loading: propTypes.bool, + rowClassName: { + type: Function as PropType<(record: TableCustomRecord, index: number) => string>, + }, + scroll: { + // 代码逻辑说明: 【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义 + type: Object as PropType<{ x?: number | true; y?: number; scrollToFirstRowOnChange?: boolean }>, + default: null, + }, + beforeEditSubmit: { + type: Function as PropType<(data: { record: Recordable; index: number; key: string | number; value: any }) => Promise>, + }, + size: { + type: String as PropType, + default: DEFAULT_SIZE, + }, + expandedRowKeys: { + type: Array, + default: null, + }, +}; diff --git a/src/components/Table/src/types/column.ts b/src/components/Table/src/types/column.ts new file mode 100644 index 0000000..67a7194 --- /dev/null +++ b/src/components/Table/src/types/column.ts @@ -0,0 +1,197 @@ +import { VNodeChild } from 'vue'; + +export interface ColumnFilterItem { + text?: string; + value?: string; + children?: any; +} + +export declare type SortOrder = 'ascend' | 'descend'; + +export interface RecordProps { + text: any; + record: T; + index: number; +} + +export interface FilterDropdownProps { + prefixCls?: string; + setSelectedKeys?: (selectedKeys: string[]) => void; + selectedKeys?: string[]; + confirm?: () => void; + clearFilters?: () => void; + filters?: ColumnFilterItem[]; + getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement; + visible?: boolean; +} + +export declare type CustomRenderFunction = (record: RecordProps) => VNodeChild | JSX.Element; + +export interface ColumnProps { + /** + * specify how content is aligned + * @default 'left' + * @type string + */ + align?: 'left' | 'right' | 'center'; + + /** + * ellipsize cell content, not working with sorter and filters for now. + * tableLayout would be fixed when ellipsis is true. + * @default false + * @type boolean + */ + ellipsis?: boolean; + + /** + * Span of this column's title + * @type number + */ + colSpan?: number; + + /** + * Display field of the data record, could be set like a.b.c + * @type string + */ + dataIndex?: string; + + /** + * Default filtered values + * @type string[] + */ + defaultFilteredValue?: string[]; + + /** + * Default order of sorted values: 'ascend' 'descend' null + * @type string + */ + defaultSortOrder?: SortOrder; + + /** + * Customized filter overlay + * @type any (slot) + */ + filterDropdown?: VNodeChild | JSX.Element | ((props: FilterDropdownProps) => VNodeChild | JSX.Element); + + /** + * Whether filterDropdown is visible + * @type boolean + */ + filterDropdownOpen?: boolean; + + /** + * Whether the dataSource is filtered + * @default false + * @type boolean + */ + filtered?: boolean; + + /** + * Controlled filtered value, filter icon will highlight + * @type string[] + */ + filteredValue?: string[]; + + /** + * Customized filter icon + * @default false + * @type any + */ + filterIcon?: boolean | VNodeChild | JSX.Element; + + /** + * Whether multiple filters can be selected + * @default true + * @type boolean + */ + filterMultiple?: boolean; + + /** + * Filter menu config + * @type object[] + */ + filters?: ColumnFilterItem[]; + + /** + * Set column to be fixed: true(same as left) 'left' 'right' + * @default false + * @type boolean | string + */ + fixed?: boolean | 'left' | 'right'; + + /** + * Unique key of this column, you can ignore this prop if you've set a unique dataIndex + * @type string + */ + key?: string; + + /** + * Renderer of the table cell. The return value should be a VNode, or an object for colSpan/rowSpan config + * @type Function | ScopedSlot + */ + customRender?: CustomRenderFunction | VNodeChild | JSX.Element; + + /** + * Sort function for local sort, see Array.sort's compareFunction. If you need sort buttons only, set to true + * @type boolean | Function + */ + sorter?: boolean | Function; + + /** + * Order of sorted values: 'ascend' 'descend' false + * @type boolean | string + */ + sortOrder?: boolean | SortOrder; + + /** + * supported sort way, could be 'ascend', 'descend' + * @default ['ascend', 'descend'] + * @type string[] + */ + sortDirections?: SortOrder[]; + + /** + * Title of this column + * @type any (string | slot) + */ + title?: VNodeChild | JSX.Element; + + /** + * Width of this column + * @type string | number + */ + width?: string | number; + + /** + * Set props on per cell + * @type Function + */ + customCell?: (record: T, rowIndex: number) => object; + + /** + * Set props on per header cell + * @type object + */ + customHeaderCell?: (column: ColumnProps) => object; + // 代码逻辑说明: 【pull/1201】添加antd的TableSummary功能兼容老的summary(表尾合计) + customSummaryRender?: CustomRenderFunction | VNodeChild | JSX.Element; + + /** + * Callback executed when the confirm filter button is clicked, Use as a filter event when using template or jsx + * @type Function + */ + onFilter?: (value: any, record: T) => boolean; + + /** + * Callback executed when filterDropdownOpen is changed, Use as a filterDropdownVisible event when using template or jsx + * @type Function + */ + onFilterDropdownVisibleChange?: (visible: boolean) => void; + + /** + * When using columns, you can setting this property to configure the properties that support the slot, + * such as slots: { filterIcon: 'XXX'} + * @type object + */ + slots?: Recordable; +} diff --git a/src/components/Table/src/types/componentType.ts b/src/components/Table/src/types/componentType.ts new file mode 100644 index 0000000..6e66af6 --- /dev/null +++ b/src/components/Table/src/types/componentType.ts @@ -0,0 +1 @@ +export type ComponentType = 'Input' | 'InputNumber' | 'Select' | 'ApiSelect' | 'ApiTreeSelect' | 'Checkbox' | 'Switch' | 'DatePicker' | 'TimePicker'; diff --git a/src/components/Table/src/types/pagination.ts b/src/components/Table/src/types/pagination.ts new file mode 100644 index 0000000..fbaae05 --- /dev/null +++ b/src/components/Table/src/types/pagination.ts @@ -0,0 +1,106 @@ +import Pagination from 'ant-design-vue/lib/pagination'; +import { VNodeChild } from 'vue'; + +interface PaginationRenderProps { + page: number; + type: 'page' | 'prev' | 'next'; + originalElement: any; +} + +type Position = 'topLeft' | 'topCenter' | 'topRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight'; + +export declare class PaginationConfig extends Pagination { + position?: 'top' | 'bottom' | 'both'; +} +export interface PaginationProps { + /** + * total number of data items + * @default 0 + * @type number + */ + total?: number; + + /** + * default initial page number + * @default 1 + * @type number + */ + defaultCurrent?: number; + + /** + * current page number + * @type number + */ + current?: number; + + /** + * default number of data items per page + * @default 10 + * @type number + */ + defaultPageSize?: number; + + /** + * number of data items per page + * @type number + */ + pageSize?: number; + + /** + * Whether to hide pager on single page + * @default false + * @type boolean + */ + hideOnSinglePage?: boolean; + + /** + * determine whether pageSize can be changed + * @default false + * @type boolean + */ + showSizeChanger?: boolean; + + /** + * specify the sizeChanger options + * @default ['10', '20', '30', '40'] + * @type string[] + */ + pageSizeOptions?: string[]; + + /** + * determine whether you can jump to pages directly + * @default false + * @type boolean + */ + showQuickJumper?: boolean | object; + + /** + * to display the total number and range + * @type Function + */ + showTotal?: (total: number, range: [number, number]) => any; + + /** + * specify the size of Pagination, can be set to small + * @default '' + * @type string + */ + size?: string; + + /** + * whether to setting simple mode + * @type boolean + */ + simple?: boolean; + + /** + * to customize item innerHTML + * @type Function + */ + itemRender?: (props: PaginationRenderProps) => VNodeChild | JSX.Element; + /** + * specify the position of Pagination + * @type Position[] + */ + position?: Position[]; +} diff --git a/src/components/Table/src/types/table.ts b/src/components/Table/src/types/table.ts new file mode 100644 index 0000000..10de405 --- /dev/null +++ b/src/components/Table/src/types/table.ts @@ -0,0 +1,490 @@ +import type { VNodeChild, ComputedRef } from 'vue'; +import type { PaginationProps } from './pagination'; +import type { FormProps } from '/@/components/Form'; +import type { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface'; +import type { ColumnProps } from 'ant-design-vue/lib/table'; + +import { ComponentType } from './componentType'; +import { VueNode } from '/@/utils/propTypes'; +import { RoleEnum } from '/@/enums/roleEnum'; + +export declare type SortOrder = 'ascend' | 'descend'; + +export interface TableCurrentDataSource { + currentDataSource: T[]; +} + +export interface TableRowSelection extends ITableRowSelection { + /** + * Callback executed when selected rows change + * @type Function + */ + onChange?: (selectedRowKeys: string[] | number[], selectedRows: T[]) => any; + + /** + * Callback executed when select/deselect one row + * @type Function + */ + onSelect?: (record: T, selected: boolean, selectedRows: Object[]) => any; + + /** + * Callback executed when select/deselect all rows + * @type Function + */ + onSelectAll?: (selected: boolean, selectedRows: T[], changeRows: T[]) => any; + + /** + * Callback executed when row selection is inverted + * @type Function + */ + onSelectInvert?: (selectedRows: string[] | number[]) => any; + //【issues/8163】关联记录新增丢失 + selectedRows?: any[]; +} + +export interface TableCustomRecord { + record?: T; + index?: number; +} + +export interface ExpandedRowRenderRecord extends TableCustomRecord { + indent?: number; + expanded?: boolean; +} + +export interface ColumnFilterItem { + text?: string; + value?: string; + children?: any; +} + +export interface TableCustomRecord { + record?: T; + index?: number; +} + +export interface SorterResult { + column: ColumnProps; + order: SortOrder; + field: string; + columnKey: string; +} + +export interface FetchParams { + searchInfo?: Recordable; + page?: number; + sortInfo?: Recordable; + filterInfo?: Recordable; +} + +export interface GetColumnsParams { + ignoreIndex?: boolean; + ignoreAction?: boolean; + // 代码逻辑说明: 【issues/8502】解决权限列在列表中不显示,列配置中还显示 + ignoreAuth?: boolean; + ignoreIfShow?: boolean | ((column: BasicColumn) => boolean); + sort?: boolean; +} + +export type SizeType = 'middle' | 'small' | 'large'; + +export interface TableActionType { + reload: (opt?: FetchParams) => Promise; + getSelectRows: () => T[]; + clearSelectedRowKeys: () => void; + expandAll: () => void; + collapseAll: () => void; + getSelectRowKeys: () => string[]; + deleteSelectRowByKey: (key: string) => void; + setPagination: (info: Partial) => void; + setTableData: (values: T[]) => void; + updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void; + deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void; + insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void; + findTableDataRecord: (rowKey: string | number) => Recordable | void; + getColumns: (opt?: GetColumnsParams) => BasicColumn[]; + setColumns: (columns: BasicColumn[] | string[]) => void; + getDataSource: () => T[]; + getRawDataSource: () => T; + setLoading: (loading: boolean) => void; + setProps: (props: Partial) => void; + redoHeight: () => void; + setSelectedRowKeys: (rowKeys: string[] | number[]) => void; + getPaginationRef: () => PaginationProps | boolean; + getSize: () => SizeType; + getRowSelection: () => TableRowSelection; + getCacheColumns: () => BasicColumn[]; + emit?: EmitType; + updateTableData: (index: number, key: string, value: any) => Recordable; + setShowPagination: (show: boolean) => Promise; + getShowPagination: () => boolean; + setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void; + getColumnsRef: () => ComputedRef; + getBindValuesRef: () => ComputedRef; +} + +export interface FetchSetting { + // 请求接口当前页数 + pageField: string; + // 每页显示多少条 + sizeField: string; + // 请求结果列表字段 支持 a.b.c + listField: string; + // 请求结果总数字段 支持 a.b.c + totalField: string; +} + +export interface TableSetting { + // 是否显示刷新按钮 + redo?: boolean; + // 是否显示尺寸调整按钮 + size?: boolean; + // 是否显示字段调整按钮 + setting?: boolean; + // 缓存“字段调整”配置的key,用于页面上有多个表格需要区分的情况 + cacheKey?: string; + // 是否显示全屏按钮 + fullScreen?: boolean; +} + +export interface BasicTableProps { + // 点击行选中 + clickToRowSelect?: boolean; + isTreeTable?: boolean; + // 自定义排序方法 + sortFn?: (sortInfo: SorterResult) => any; + // 排序方法 + filterFn?: (data: Partial>) => any; + // 取消表格的默认padding + inset?: boolean; + // 显示表格设置 + showTableSetting?: boolean; + // 表格上方操作按钮设置 + tableSetting?: TableSetting; + // 斑马纹 + striped?: boolean; + // 是否自动生成key + autoCreateKey?: boolean; + // 计算合计行的方法 + summaryFunc?: (...arg: any) => Recordable[]; + // 自定义合计表格内容 + summaryData?: Recordable[]; + // 是否显示合计行 + showSummary?: boolean; + // 是否可拖拽列 + canColDrag?: boolean; + // 接口请求对象 + api?: (...arg: any) => Promise; + // 请求之前处理参数 + beforeFetch?: Fn; + // 自定义处理接口返回参数 + afterFetch?: Fn; + // 查询条件请求之前处理 + handleSearchInfoFn?: Fn; + // 请求接口配置 + fetchSetting?: Partial; + // 立即请求接口 + immediate?: boolean; + // 在开起搜索表单的时候,如果没有数据是否显示表格 + emptyDataIsShowTable?: boolean; + // 额外的请求参数 + searchInfo?: Recordable; + // 默认的排序参数 + defSort?: Recordable | Recordable[]; + // 使用搜索表单 + useSearchForm?: boolean; + // 表单配置 + formConfig?: Partial; + // 列配置 + columns: BasicColumn[]; + // 统一设置列最大宽度 + maxColumnWidth?: number; + // 是否显示序号列 + showIndexColumn?: boolean; + // 序号列配置 + indexColumnProps?: BasicColumn; + // 是否显示操作列 + showActionColumn?: boolean; + // 操作列配置 + actionColumn?: Partial; + // 文本超过宽度是否显示。。。 + ellipsis?: boolean; + // 是否可以自适应高度 + canResize?: boolean; + // 自适应高度偏移, 计算结果-偏移量 + resizeHeightOffset?: number; + // 在分页改变的时候清空选项 + clearSelectOnPageChange?: boolean; + // + rowKey?: string | ((record: Recordable) => string); + // 数据 + dataSource?: Recordable[]; + // 标题右侧提示 + titleHelpMessage?: string | string[]; + // 表格最小高度 + minHeight?: number; + // 表格滚动最大高度 + maxHeight?: number; + // 是否显示边框 + bordered?: boolean; + // 展开列宽度 + expandColumnWidth: number; + // 分页配置 + pagination?: PaginationProps | boolean; + // loading加载 + loading?: boolean; + + /** + * The column contains children to display + * @default 'children' + * @type string | string[] + */ + childrenColumnName?: string; + + /** + * Override default table elements + * @type object + */ + components?: object; + + /** + * Expand all rows initially + * @default false + * @type boolean + */ + defaultExpandAllRows?: boolean; + + /** + * Initial expanded row keys + * @type string[] + */ + defaultExpandedRowKeys?: string[]; + + /** + * Current expanded row keys + * @type string[] + */ + expandedRowKeys?: string[]; + + /** + * Expanded container render for each row + * @type Function + */ + expandedRowRender?: (record?: ExpandedRowRenderRecord) => VNodeChild | JSX.Element; + + /** + * Customize row expand Icon. + * @type Function | VNodeChild + */ + expandIcon?: Function | VNodeChild | JSX.Element; + + /** + * Whether to expand row by clicking anywhere in the whole row + * @default false + * @type boolean + */ + expandRowByClick?: boolean; + + /** + * The index of `expandIcon` which column will be inserted when `expandIconAsCell` is false. default 0 + */ + expandIconColumnIndex?: number; + + /** + * Table footer renderer + * @type Function | VNodeChild + */ + footer?: Function | VNodeChild | JSX.Element; + + /** + * Indent size in pixels of tree data + * @default 15 + * @type number + */ + indentSize?: number; + + /** + * i18n text including filter, sort, empty text, etc + * @default { filterConfirm: 'Ok', filterReset: 'Reset', emptyText: 'No Data' } + * @type object + */ + locale?: object; + + /** + * Row's className + * @type Function + */ + rowClassName?: (record: TableCustomRecord, index: number) => string; + + /** + * Row selection config + * @type object + */ + rowSelection?: TableRowSelection; + + /** + * Set horizontal or vertical scrolling, can also be used to specify the width and height of the scroll area. + * It is recommended to set a number for x, if you want to set it to true, + * you need to add style .ant-table td { white-space: nowrap; }. + * @type object + */ + // 代码逻辑说明: 【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义 + scroll?: { x?: number | true | 'max-content'; y?: number; scrollToFirstRowOnChange?: boolean }; + + /** + * Whether to show table header + * @default true + * @type boolean + */ + showHeader?: boolean; + + /** + * Size of table + * @default 'default' + * @type string + */ + size?: SizeType; + + /** + * Table title renderer + * @type Function | ScopedSlot + */ + title?: VNodeChild | JSX.Element | string | ((data: Recordable) => string); + + /** + * Set props on per header row + * @type Function + */ + customHeaderRow?: (column: ColumnProps, index: number) => object; + + /** + * Set props on per row + * @type Function + */ + customRow?: (record: T, index: number) => object; + + /** + * `table-layout` attribute of table element + * `fixed` when header/columns are fixed, or using `column.ellipsis` + * + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout + * @version 1.5.0 + */ + tableLayout?: 'auto' | 'fixed' | string; + + /** + * the render container of dropdowns in table + * @param triggerNode + * @version 1.5.0 + */ + getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement; + + /** + * Data can be changed again before rendering. + * The default configuration of general user empty data. + * You can configured globally through [ConfigProvider](https://antdv.com/components/config-provider-cn/) + * + * @version 1.5.4 + */ + transformCellText?: Function; + + /** + * Callback executed before editable cell submit value, not for row-editor + * + * The cell will not submit data while callback return false + */ + beforeEditSubmit?: (data: { record: Recordable; index: number; key: string | number; value: any }) => Promise; + + /** + * Callback executed when pagination, filters or sorter is changed + * @param pagination + * @param filters + * @param sorter + * @param currentDataSource + */ + onChange?: (pagination: any, filters: any, sorter: any, extra: any) => void; + + /** + * Callback executed when the row expand icon is clicked + * + * @param expanded + * @param record + */ + onExpand?: (expande: boolean, record: T) => void; + + /** + * Callback executed when the expanded rows change + * @param expandedRows + */ + onExpandedRowsChange?: (expandedRows: string[] | number[]) => void; + + onColumnsChange?: (data: ColumnChangeParam[]) => void; +} + +export type CellFormat = string | ((text: string, record: Recordable, index: number) => string | number) | Map; + +// @ts-ignore +export interface BasicColumn extends ColumnProps { + children?: BasicColumn[]; + filters?: { + text: string; + value: string; + children?: unknown[] | (((props: Record) => unknown[]) & (() => unknown[]) & (() => unknown[])); + }[]; + + // + flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION'; + // 代码逻辑说明: 【issues/6908】多语言无刷新切换时,BasicColumn和FormSchema里面的值不能正常切换 + title: string | Fn; + customTitle?: VueNode; + + slots?: Recordable; + // slots的备份,兼容老的写法,转成新写法避免控制台警告 + slotsBak?: Recordable; + + // Whether to hide the column by default, it can be displayed in the column configuration + defaultHidden?: boolean; + + // Help text for table column header + helpMessage?: string | string[]; + + format?: CellFormat; + + // Editable + edit?: boolean; + editRow?: boolean; + editable?: boolean; + editComponent?: ComponentType; + // 代码逻辑说明: 【issues/8680】editComponentProps可接受一个函数传入record + editComponentProps?: Recordable | ((record: Recordable) => Recordable); + editRule?: boolean | ((text: string, record: Recordable) => Promise); + editValueMap?: (value: any) => string; + onEditRow?: () => void; + // 权限编码控制是否显示 + auth?: RoleEnum | RoleEnum[] | string | string[]; + // 业务控制是否显示 + ifShow?: boolean | ((column: BasicColumn) => boolean); + //compType-用于记录类型 + compType?: string; + // 代码逻辑说明: 【pull/1201】添加antd的TableSummary功能兼容老的summary(表尾合计) + customSummaryRender?: (opt: { + value: any; + text: any; + record: Recordable; + index: number; + renderIndex?: number; + column: BasicColumn; + }) => any | VNodeChild | JSX.Element; + // 额外的属性 + extraProps?: Recordable; +} + +export type ColumnChangeParam = { + dataIndex: string; + fixed: boolean | 'left' | 'right' | undefined; + visible: boolean; +}; + +export interface InnerHandlers { + onColumnsChange: (data: ColumnChangeParam[]) => void; +} diff --git a/src/components/Table/src/types/tableAction.ts b/src/components/Table/src/types/tableAction.ts new file mode 100644 index 0000000..3d2164f --- /dev/null +++ b/src/components/Table/src/types/tableAction.ts @@ -0,0 +1,33 @@ +import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; +import { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip'; +import { RoleEnum } from '/@/enums/roleEnum'; +export interface ActionItem extends ButtonProps { + onClick?: Fn; + label?: string; + color?: 'success' | 'error' | 'warning'; + icon?: string; + popConfirm?: PopConfirm; + disabled?: boolean; + divider?: boolean; + // 权限编码控制是否显示 + auth?: RoleEnum | RoleEnum[] | string | string[]; + // 业务控制是否显示 + ifShow?: boolean | ((action: ActionItem) => boolean); + tooltip?: string | TooltipProps; + // 自定义类名 + class?: string | Record | any[]; + // 自定义图标颜色 + iconColor?: string; +} + +export interface PopConfirm { + title: string; + okText?: string; + cancelText?: string; + confirm: Fn; + cancel?: Fn; + icon?: string; + placement?: string; + overlayClassName?: string; + getPopupContainer?: Fn; +} diff --git a/src/components/Time/index.ts b/src/components/Time/index.ts new file mode 100644 index 0000000..7e2f4c0 --- /dev/null +++ b/src/components/Time/index.ts @@ -0,0 +1,4 @@ +import { withInstall } from '/@/utils/index'; +import time from './src/Time.vue'; + +export const Time = withInstall(time); diff --git a/src/components/Time/src/Time.vue b/src/components/Time/src/Time.vue new file mode 100644 index 0000000..be49ba3 --- /dev/null +++ b/src/components/Time/src/Time.vue @@ -0,0 +1,107 @@ + + diff --git a/src/components/Tinymce/index.ts b/src/components/Tinymce/index.ts new file mode 100644 index 0000000..ce07f95 --- /dev/null +++ b/src/components/Tinymce/index.ts @@ -0,0 +1,4 @@ +import { withInstall } from '/@/utils/index'; +import tinymce from './src/Editor.vue'; + +export const Tinymce = withInstall(tinymce); diff --git a/src/components/Tinymce/src/Editor.vue b/src/components/Tinymce/src/Editor.vue new file mode 100644 index 0000000..538894d --- /dev/null +++ b/src/components/Tinymce/src/Editor.vue @@ -0,0 +1,809 @@ + + + + + + + diff --git a/src/components/Tinymce/src/ImgUpload.vue b/src/components/Tinymce/src/ImgUpload.vue new file mode 100644 index 0000000..3e47ee6 --- /dev/null +++ b/src/components/Tinymce/src/ImgUpload.vue @@ -0,0 +1,134 @@ + + + diff --git a/src/components/Tinymce/src/ProcessMask.vue b/src/components/Tinymce/src/ProcessMask.vue new file mode 100644 index 0000000..b70ae33 --- /dev/null +++ b/src/components/Tinymce/src/ProcessMask.vue @@ -0,0 +1,110 @@ + + + + diff --git a/src/components/Tinymce/src/helper.ts b/src/components/Tinymce/src/helper.ts new file mode 100644 index 0000000..2526ae7 --- /dev/null +++ b/src/components/Tinymce/src/helper.ts @@ -0,0 +1,81 @@ +const validEvents = [ + 'onActivate', + 'onAddUndo', + 'onBeforeAddUndo', + 'onBeforeExecCommand', + 'onBeforeGetContent', + 'onBeforeRenderUI', + 'onBeforeSetContent', + 'onBeforePaste', + 'onBlur', + 'onChange', + 'onClearUndos', + 'onClick', + 'onContextMenu', + 'onCopy', + 'onCut', + 'onDblclick', + 'onDeactivate', + 'onDirty', + 'onDrag', + 'onDragDrop', + 'onDragEnd', + 'onDragGesture', + 'onDragOver', + 'onDrop', + 'onExecCommand', + 'onFocus', + 'onFocusIn', + 'onFocusOut', + 'onGetContent', + 'onHide', + 'onInit', + 'onKeyDown', + 'onKeyPress', + 'onKeyUp', + 'onLoadContent', + 'onMouseDown', + 'onMouseEnter', + 'onMouseLeave', + 'onMouseMove', + 'onMouseOut', + 'onMouseOver', + 'onMouseUp', + 'onNodeChange', + 'onObjectResizeStart', + 'onObjectResized', + 'onObjectSelected', + 'onPaste', + 'onPostProcess', + 'onPostRender', + 'onPreProcess', + 'onProgressState', + 'onRedo', + 'onRemove', + 'onReset', + 'onSaveContent', + 'onSelectionChange', + 'onSetAttrib', + 'onSetContent', + 'onShow', + 'onSubmit', + 'onUndo', + 'onVisualAid', +]; + +const isValidKey = (key: string) => validEvents.indexOf(key) !== -1; + +export const bindHandlers = (initEvent: Event, listeners: any, editor: any): void => { + Object.keys(listeners) + .filter(isValidKey) + .forEach((key: string) => { + const handler = listeners[key]; + if (typeof handler === 'function') { + if (key === 'onInit') { + handler(initEvent, editor); + } else { + editor.on(key.substring(2), (e: any) => handler(e, editor)); + } + } + }); +}; diff --git a/src/components/Tinymce/src/tinymce.ts b/src/components/Tinymce/src/tinymce.ts new file mode 100644 index 0000000..f74c193 --- /dev/null +++ b/src/components/Tinymce/src/tinymce.ts @@ -0,0 +1,19 @@ +// Any plugins you want to setting has to be imported +// Detail plugins list see https://www.tinymce.com/docs/plugins/ +// Custom builds see https://www.tinymce.com/download/custom-builds/ +// colorpicker/contextmenu/textcolor plugin is now built in to the core editor, please remove it from your editor configuration + +export const plugins = [ + 'advlist anchor autolink autosave code codesample directionality fullscreen hr insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace tabfocus template textpattern visualblocks visualchars wordcount image', +]; + +export const toolbar = + 'fullscreen code preview | undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent lineheight|subscript superscript blockquote| numlist bullist checklist | forecolor backcolor casechange permanentpen formatpainter removeformat | pagebreak | charmap emoticons | insertfile image media pageembed link anchor codesample insertdatetime hr| a11ycheck ltr rtl'; + +export const simplePlugins = 'lists image link fullscreen paste'; + +export const simpleToolbar = [ + 'undo redo styles forecolor fontsize bold italic alignleft aligncenter alignright alignjustify bullist numlist outdent indent lists image link fullscreen', +]; + +export const menubar = 'file edit insert view format table'; diff --git a/src/components/Transition/index.ts b/src/components/Transition/index.ts new file mode 100644 index 0000000..55cbe54 --- /dev/null +++ b/src/components/Transition/index.ts @@ -0,0 +1,21 @@ +import { createSimpleTransition, createJavascriptTransition } from './src/CreateTransition'; + +import ExpandTransitionGenerator from './src/ExpandTransition'; + +export { default as CollapseTransition } from './src/CollapseTransition.vue'; + +export const FadeTransition = createSimpleTransition('fade-transition'); +export const ScaleTransition = createSimpleTransition('scale-transition'); +export const SlideYTransition = createSimpleTransition('slide-y-transition'); +export const ScrollYTransition = createSimpleTransition('scroll-y-transition'); +export const SlideYReverseTransition = createSimpleTransition('slide-y-reverse-transition'); +export const ScrollYReverseTransition = createSimpleTransition('scroll-y-reverse-transition'); +export const SlideXTransition = createSimpleTransition('slide-x-transition'); +export const ScrollXTransition = createSimpleTransition('scroll-x-transition'); +export const SlideXReverseTransition = createSimpleTransition('slide-x-reverse-transition'); +export const ScrollXReverseTransition = createSimpleTransition('scroll-x-reverse-transition'); +export const ScaleRotateTransition = createSimpleTransition('scale-rotate-transition'); + +export const ExpandXTransition = createJavascriptTransition('expand-x-transition', ExpandTransitionGenerator('', true)); + +export const ExpandTransition = createJavascriptTransition('expand-transition', ExpandTransitionGenerator('')); diff --git a/src/components/Transition/src/CollapseTransition.vue b/src/components/Transition/src/CollapseTransition.vue new file mode 100644 index 0000000..6b50fa1 --- /dev/null +++ b/src/components/Transition/src/CollapseTransition.vue @@ -0,0 +1,78 @@ + + diff --git a/src/components/Transition/src/CreateTransition.tsx b/src/components/Transition/src/CreateTransition.tsx new file mode 100644 index 0000000..bad23b5 --- /dev/null +++ b/src/components/Transition/src/CreateTransition.tsx @@ -0,0 +1,69 @@ +import type { PropType } from 'vue'; + +import { defineComponent, Transition, TransitionGroup } from 'vue'; +import { getSlot } from '/@/utils/helper/tsxHelper'; + +type Mode = 'in-out' | 'out-in' | 'default' | undefined; + +export function createSimpleTransition(name: string, origin = 'top center 0', mode?: Mode) { + return defineComponent({ + name, + props: { + group: { + type: Boolean as PropType, + default: false, + }, + mode: { + type: String as PropType, + default: mode, + }, + origin: { + type: String as PropType, + default: origin, + }, + }, + setup(props, { slots, attrs }) { + const onBeforeEnter = (el: HTMLElement) => { + el.style.transformOrigin = props.origin; + }; + + return () => { + const Tag = !props.group ? Transition : TransitionGroup; + return ( + + {() => getSlot(slots)} + + ); + }; + }, + }); +} +export function createJavascriptTransition(name: string, functions: Recordable, mode: Mode = 'in-out') { + return defineComponent({ + name, + props: { + mode: { + type: String as PropType, + default: mode, + }, + }, + setup(props, { attrs, slots }) { + return () => { + return ( + + {() => getSlot(slots)} + + ); + }; + }, + }); +} diff --git a/src/components/Transition/src/ExpandTransition.ts b/src/components/Transition/src/ExpandTransition.ts new file mode 100644 index 0000000..2aaef9a --- /dev/null +++ b/src/components/Transition/src/ExpandTransition.ts @@ -0,0 +1,89 @@ +/** + * Makes the first character of a string uppercase + */ +export function upperFirst(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +interface HTMLExpandElement extends HTMLElement { + _parent?: (Node & ParentNode & HTMLElement) | null; + _initialStyle: { + transition: string; + overflow: string | null; + height?: string | null; + width?: string | null; + }; +} + +export default function (expandedParentClass = '', x = false) { + const sizeProperty = x ? 'width' : ('height' as 'width' | 'height'); + const offsetProperty = `offset${upperFirst(sizeProperty)}` as 'offsetHeight' | 'offsetWidth'; + + return { + beforeEnter(el: HTMLExpandElement) { + el._parent = el.parentNode as (Node & ParentNode & HTMLElement) | null; + el._initialStyle = { + transition: el.style.transition, + overflow: el.style.overflow, + [sizeProperty]: el.style[sizeProperty], + }; + }, + + enter(el: HTMLExpandElement) { + const initialStyle = el._initialStyle; + + el.style.setProperty('transition', 'none', 'important'); + el.style.overflow = 'hidden'; + // const offset = `${el[offsetProperty]}px`; + + // el.style[sizeProperty] = '0'; + + void el.offsetHeight; // force reflow + + el.style.transition = initialStyle.transition; + + if (expandedParentClass && el._parent) { + el._parent.classList.add(expandedParentClass); + } + + requestAnimationFrame(() => { + // el.style[sizeProperty] = offset; + }); + }, + + afterEnter: resetStyles, + enterCancelled: resetStyles, + + leave(el: HTMLExpandElement) { + el._initialStyle = { + transition: '', + overflow: el.style.overflow, + [sizeProperty]: el.style[sizeProperty], + }; + + el.style.overflow = 'hidden'; + el.style[sizeProperty] = `${el[offsetProperty]}px`; + /* eslint-disable-next-line */ + void el.offsetHeight; // force reflow + + requestAnimationFrame(() => (el.style[sizeProperty] = '0')); + }, + + afterLeave, + leaveCancelled: afterLeave, + }; + + function afterLeave(el: HTMLExpandElement) { + if (expandedParentClass && el._parent) { + el._parent.classList.remove(expandedParentClass); + } + resetStyles(el); + } + + function resetStyles(el: HTMLExpandElement) { + const size = el._initialStyle[sizeProperty]; + el.style.overflow = el._initialStyle.overflow!; + if (size != null) el.style[sizeProperty] = size; + Reflect.deleteProperty(el, '_initialStyle'); + } +} diff --git a/src/components/Tree/index.ts b/src/components/Tree/index.ts new file mode 100644 index 0000000..169035a --- /dev/null +++ b/src/components/Tree/index.ts @@ -0,0 +1,6 @@ +import BasicTree from './src/BasicTree.vue'; +import './style'; + +export { BasicTree }; +export type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; +export * from './src/types/tree'; diff --git a/src/components/Tree/src/BasicTree.vue b/src/components/Tree/src/BasicTree.vue new file mode 100644 index 0000000..318c2cc --- /dev/null +++ b/src/components/Tree/src/BasicTree.vue @@ -0,0 +1,496 @@ + + diff --git a/src/components/Tree/src/TreeIcon.ts b/src/components/Tree/src/TreeIcon.ts new file mode 100644 index 0000000..900d6bf --- /dev/null +++ b/src/components/Tree/src/TreeIcon.ts @@ -0,0 +1,13 @@ +import type { VNode, FunctionalComponent } from 'vue'; + +import { h } from 'vue'; +import { isString } from '@vue/shared'; +import { Icon } from '/@/components/Icon'; + +export const TreeIcon: FunctionalComponent = ({ icon }: { icon: VNode | string }) => { + if (!icon) return null; + if (isString(icon)) { + return h(Icon, { icon, class: 'mr-1' }); + } + return Icon; +}; diff --git a/src/components/Tree/src/components/TreeHeader.vue b/src/components/Tree/src/components/TreeHeader.vue new file mode 100644 index 0000000..a1792d2 --- /dev/null +++ b/src/components/Tree/src/components/TreeHeader.vue @@ -0,0 +1,171 @@ + + diff --git a/src/components/Tree/src/hooks/useTree.ts b/src/components/Tree/src/hooks/useTree.ts new file mode 100644 index 0000000..17345a2 --- /dev/null +++ b/src/components/Tree/src/hooks/useTree.ts @@ -0,0 +1,207 @@ +import type { InsertNodeParams, KeyType, FieldNames, TreeItem } from '../types/tree'; +import type { Ref, ComputedRef } from 'vue'; +import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree'; + +import { cloneDeep } from 'lodash-es'; +import { unref } from 'vue'; +import { forEach } from '/@/utils/helper/treeHelper'; + +export function useTree(treeDataRef: Ref, getFieldNames: ComputedRef) { + function getAllKeys(list?: TreeDataItem[]) { + const keys: string[] = []; + const treeData = list || unref(treeDataRef); + const { key: keyField, children: childrenField } = unref(getFieldNames); + if (!childrenField || !keyField) return keys; + + for (let index = 0; index < treeData.length; index++) { + const node = treeData[index]; + keys.push(node[keyField]!); + const children = node[childrenField]; + if (children && children.length) { + keys.push(...(getAllKeys(children) as string[])); + } + } + return keys as KeyType[]; + } + + // get keys that can be checked and selected + function getEnabledKeys(list?: TreeDataItem[]) { + const keys: string[] = []; + const treeData = list || unref(treeDataRef); + const { key: keyField, children: childrenField } = unref(getFieldNames); + if (!childrenField || !keyField) return keys; + + for (let index = 0; index < treeData.length; index++) { + const node = treeData[index]; + node.disabled !== true && node.selectable !== false && keys.push(node[keyField]!); + const children = node[childrenField]; + if (children && children.length) { + keys.push(...(getEnabledKeys(children) as string[])); + } + } + return keys as KeyType[]; + } + + function getChildrenKeys(nodeKey: string | number, list?: TreeDataItem[]) { + const keys: KeyType[] = []; + const treeData = list || unref(treeDataRef); + const { key: keyField, children: childrenField } = unref(getFieldNames); + if (!childrenField || !keyField) return keys; + for (let index = 0; index < treeData.length; index++) { + const node = treeData[index]; + const children = node[childrenField]; + if (nodeKey === node[keyField]) { + keys.push(node[keyField]!); + if (children && children.length) { + keys.push(...(getAllKeys(children) as string[])); + } + } else { + if (children && children.length) { + keys.push(...getChildrenKeys(nodeKey, children)); + } + } + } + return keys as KeyType[]; + } + + // Update node + function updateNodeByKey(key: string, node: TreeDataItem, list?: TreeDataItem[]) { + if (!key) return; + const treeData = list || unref(treeDataRef); + const { key: keyField, children: childrenField } = unref(getFieldNames); + + if (!childrenField || !keyField) return; + + for (let index = 0; index < treeData.length; index++) { + const element: any = treeData[index]; + const children = element[childrenField]; + + if (element[keyField] === key) { + treeData[index] = { ...treeData[index], ...node }; + break; + } else if (children && children.length) { + updateNodeByKey(key, node, element[childrenField]); + } + } + } + + // Expand the specified level + function filterByLevel(level = 1, list?: TreeDataItem[], currentLevel = 1) { + if (!level) { + return []; + } + const res: (string | number)[] = []; + const data = list || unref(treeDataRef) || []; + for (let index = 0; index < data.length; index++) { + const item = data[index]; + + const { key: keyField, children: childrenField } = unref(getFieldNames); + const key = keyField ? item[keyField] : ''; + const children = childrenField ? item[childrenField] : []; + res.push(key); + if (children && children.length && currentLevel < level) { + currentLevel += 1; + res.push(...filterByLevel(level, children, currentLevel)); + } + } + return res as string[] | number[]; + } + + /** + * 添加节点 + */ + function insertNodeByKey({ parentKey = null, node, push = 'push' }: InsertNodeParams) { + const treeData: any = cloneDeep(unref(treeDataRef)); + if (!parentKey) { + treeData[push](node); + treeDataRef.value = treeData; + return; + } + const { key: keyField, children: childrenField } = unref(getFieldNames); + if (!childrenField || !keyField) return; + + forEach(treeData, (treeItem) => { + if (treeItem[keyField] === parentKey) { + treeItem[childrenField] = treeItem[childrenField] || []; + treeItem[childrenField][push](node); + return true; + } + }); + treeDataRef.value = treeData; + } + /** + * 批量添加节点 + */ + function insertNodesByKey({ parentKey = null, list, push = 'push' }: InsertNodeParams) { + const treeData: any = cloneDeep(unref(treeDataRef)); + if (!list || list.length < 1) { + return; + } + if (!parentKey) { + for (let i = 0; i < list.length; i++) { + treeData[push](list[i]); + } + } else { + const { key: keyField, children: childrenField } = unref(getFieldNames); + if (!childrenField || !keyField) return; + + forEach(treeData, (treeItem) => { + if (treeItem[keyField] === parentKey) { + treeItem[childrenField] = treeItem[childrenField] || []; + for (let i = 0; i < list.length; i++) { + treeItem[childrenField][push](list[i]); + } + treeDataRef.value = treeData; + return true; + } + }); + } + } + // Delete node + function deleteNodeByKey(key: string, list?: TreeDataItem[]) { + if (!key) return; + const treeData = list || unref(treeDataRef); + const { key: keyField, children: childrenField } = unref(getFieldNames); + if (!childrenField || !keyField) return; + + for (let index = 0; index < treeData.length; index++) { + const element: any = treeData[index]; + const children = element[childrenField]; + + if (element[keyField] === key) { + treeData.splice(index, 1); + break; + } else if (children && children.length) { + deleteNodeByKey(key, element[childrenField]); + } + } + } + + // Get selected node + function getSelectedNode(key: KeyType, list?: TreeItem[], selectedNode?: TreeItem | null) { + if (!key && key !== 0) return null; + const treeData = list || unref(treeDataRef); + treeData.forEach((item) => { + if (selectedNode?.key || selectedNode?.key === 0) return selectedNode; + if (item.key === key) { + selectedNode = item; + return; + } + if (item.children && item.children.length) { + selectedNode = getSelectedNode(key, item.children, selectedNode); + } + }); + return selectedNode || null; + } + return { + deleteNodeByKey, + insertNodeByKey, + insertNodesByKey, + filterByLevel, + updateNodeByKey, + getAllKeys, + getChildrenKeys, + getEnabledKeys, + getSelectedNode, + }; +} diff --git a/src/components/Tree/src/types/tree.ts b/src/components/Tree/src/types/tree.ts new file mode 100644 index 0000000..691daae --- /dev/null +++ b/src/components/Tree/src/types/tree.ts @@ -0,0 +1,195 @@ +import type { ExtractPropTypes } from 'vue'; +import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree'; + +import { buildProps } from '/@/utils/props'; + +export enum ToolbarEnum { + SELECT_ALL, + UN_SELECT_ALL, + EXPAND_ALL, + UN_EXPAND_ALL, + CHECK_STRICTLY, + CHECK_UN_STRICTLY, +} + +export const treeEmits = [ + 'update:expandedKeys', + 'update:selectedKeys', + 'update:value', + 'change', + 'check', + 'search', + 'update:searchValue', +]; + +export interface TreeState { + expandedKeys: KeyType[]; + selectedKeys: KeyType[]; + checkedKeys: CheckKeys; + checkStrictly: boolean; +} + +export interface FieldNames { + children?: string; + title?: string; + key?: string; +} + +export type KeyType = string | number; + +export type CheckKeys = + | KeyType[] + | { checked: string[] | number[]; halfChecked: string[] | number[] }; + +export const treeProps = buildProps({ + value: { + type: [Object, Array] as PropType, + }, + + renderIcon: { + type: Function as PropType<(params: Recordable) => string>, + }, + + helpMessage: { + type: [String, Array] as PropType, + default: '', + }, + + title: { + type: String, + default: '', + }, + toolbar: Boolean, + search: Boolean, + searchValue: { + type: String, + default: '', + }, + checkStrictly: Boolean, + clickRowToExpand: { + type: Boolean, + default: false, + }, + checkable: Boolean, + defaultExpandLevel: { + type: [String, Number] as PropType, + default: '', + }, + defaultExpandAll: Boolean, + + fieldNames: { + type: Object as PropType, + }, + + treeData: { + type: Array as PropType, + }, + + actionList: { + type: Array as PropType, + default: () => [], + }, + + expandedKeys: { + type: Array as PropType, + default: () => [], + }, + + selectedKeys: { + type: Array as PropType, + default: () => [], + }, + + checkedKeys: { + type: Array as PropType, + default: () => [], + }, + + beforeRightClick: { + type: Function as PropType<(...arg: any) => ContextMenuItem[] | ContextMenuOptions>, + default: undefined, + }, + + rightMenuList: { + type: Array as PropType, + }, + // 自定义数据过滤判断方法(注: 不是整个过滤方法,而是内置过滤的判断方法,用于增强原本仅能通过title进行过滤的方式) + filterFn: { + type: Function as PropType< + (searchValue: any, node: TreeItem, fieldNames: FieldNames) => boolean + >, + default: undefined, + }, + // 高亮搜索值,仅高亮具体匹配值(通过title)值为true时使用默认色值,值为#xxx时使用此值替代且高亮开启 + highlight: { + type: [Boolean, String] as PropType, + default: false, + }, + // 搜索完成时自动展开结果 + expandOnSearch: Boolean, + // 搜索完成自动选中所有结果,当且仅当 checkable===true 时生效 + checkOnSearch: Boolean, + // 搜索完成自动select所有结果 + selectedOnSearch: Boolean, + loading: { + type: Boolean, + default: false, + }, +}); + +export type TreeProps = ExtractPropTypes; + +export interface ContextMenuItem { + label: string; + icon?: string; + hidden?: boolean; + disabled?: boolean; + handler?: Fn; + divider?: boolean; + children?: ContextMenuItem[]; +} + +export interface ContextMenuOptions { + icon?: string; + styles?: any; + items?: ContextMenuItem[]; +} + +export interface TreeItem extends TreeDataItem { + icon?: any; +} + +export interface TreeActionItem { + render: (record: Recordable) => any; + show?: boolean | ((record: Recordable) => boolean); +} + +export interface InsertNodeParams { + parentKey: string | null; + node: TreeDataItem; + list?: TreeDataItem[]; + push?: 'push' | 'unshift'; +} + +export interface TreeActionType { + checkAll: (checkAll: boolean) => void; + expandAll: (expandAll: boolean) => void; + setExpandedKeys: (keys: KeyType[]) => void; + getExpandedKeys: () => KeyType[]; + setSelectedKeys: (keys: KeyType[]) => void; + getSelectedKeys: () => KeyType[]; + setCheckedKeys: (keys: CheckKeys) => void; + getCheckedKeys: () => CheckKeys; + filterByLevel: (level: number) => void; + insertNodeByKey: (opt: InsertNodeParams) => void; + insertNodesByKey: (opt: InsertNodeParams) => void; + deleteNodeByKey: (key: string) => void; + updateNodeByKey: (key: string, node: Omit) => void; + setSearchValue: (value: string) => void; + getSearchValue: () => string; + getSelectedNode: ( + key: KeyType, + treeList?: TreeItem[], + selectNode?: TreeItem | null, + ) => TreeItem | null; +} diff --git a/src/components/Tree/style/index.less b/src/components/Tree/style/index.less new file mode 100644 index 0000000..472d4ca --- /dev/null +++ b/src/components/Tree/style/index.less @@ -0,0 +1,52 @@ +@tree-prefix-cls: ~'@{namespace}-tree'; + +.@{tree-prefix-cls} { + background-color: @component-background; + + .ant-tree-node-content-wrapper { + position: relative; + + .ant-tree-title { + position: absolute; + left: 0; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + &__title { + position: relative; + display: flex; + align-items: center; + width: 100%; + padding-right: 10px; + + &:hover { + .@{tree-prefix-cls}__action { + visibility: visible; + } + } + } + + &__content { + overflow: hidden; + } + + &__actions { + position: absolute; + //top: 2px; + right: 3px; + display: flex; + } + + &__action { + margin-left: 4px; + visibility: hidden; + } + + &-header { + border-bottom: 1px solid @border-color-base; + } +} diff --git a/src/components/Tree/style/index.ts b/src/components/Tree/style/index.ts new file mode 100644 index 0000000..d74e52e --- /dev/null +++ b/src/components/Tree/style/index.ts @@ -0,0 +1 @@ +import './index.less'; diff --git a/src/components/Tree_backup/index.ts b/src/components/Tree_backup/index.ts new file mode 100644 index 0000000..f47820d --- /dev/null +++ b/src/components/Tree_backup/index.ts @@ -0,0 +1,5 @@ +import BasicTree from './src/Tree.vue'; + +export { BasicTree }; +export type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; +export * from './src/typing'; diff --git a/src/components/Tree_backup/src/Tree.vue b/src/components/Tree_backup/src/Tree.vue new file mode 100644 index 0000000..e1eb8f3 --- /dev/null +++ b/src/components/Tree_backup/src/Tree.vue @@ -0,0 +1,449 @@ + + diff --git a/src/components/Tree_backup/src/TreeHeader.vue b/src/components/Tree_backup/src/TreeHeader.vue new file mode 100644 index 0000000..fbe36cf --- /dev/null +++ b/src/components/Tree_backup/src/TreeHeader.vue @@ -0,0 +1,181 @@ + + + diff --git a/src/components/Tree_backup/src/TreeIcon.ts b/src/components/Tree_backup/src/TreeIcon.ts new file mode 100644 index 0000000..69e7cd0 --- /dev/null +++ b/src/components/Tree_backup/src/TreeIcon.ts @@ -0,0 +1,17 @@ +import type { VNode, FunctionalComponent } from 'vue'; + +import { h } from 'vue'; +import { isString } from '/@/utils/is'; +import { Icon } from '/@/components/Icon'; + +export interface ComponentProps { + icon: VNode | string; +} + +export const TreeIcon: FunctionalComponent = ({ icon }: ComponentProps) => { + if (!icon) return null; + if (isString(icon)) { + return h(Icon, { icon, class: 'mr-1' }); + } + return Icon; +}; diff --git a/src/components/Tree_backup/src/props.ts b/src/components/Tree_backup/src/props.ts new file mode 100644 index 0000000..e6f6d73 --- /dev/null +++ b/src/components/Tree_backup/src/props.ts @@ -0,0 +1,99 @@ +import type { PropType } from 'vue'; +import type { ReplaceFields, ActionItem, Keys, CheckKeys, ContextMenuOptions, TreeItem } from './typing'; +import type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; +import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree'; +import { propTypes } from '/@/utils/propTypes'; + +export const basicProps = { + value: { + type: [Object, Array] as PropType, + }, + renderIcon: { + type: Function as PropType<(params: Recordable) => string>, + }, + + helpMessage: { + type: [String, Array] as PropType, + default: '', + }, + + title: propTypes.string, + toolbar: propTypes.bool, + search: propTypes.bool, + searchValue: propTypes.string, + checkStrictly: propTypes.bool, + clickRowToExpand: propTypes.bool.def(true), + checkable: propTypes.bool.def(false), + defaultExpandLevel: { + type: [String, Number] as PropType, + default: '', + }, + // 高亮搜索值,仅高亮具体匹配值(通过title)值为true时使用默认色值,值为#xxx时使用此值替代且高亮开启 + highlight: { + type: [Boolean, String] as PropType, + default: false, + }, + defaultExpandAll: propTypes.bool.def(false), + + replaceFields: { + type: Object as PropType, + }, + + treeData: { + type: Array as PropType, + }, + + actionList: { + type: Array as PropType, + default: () => [], + }, + + expandedKeys: { + type: Array as PropType, + default: () => [], + }, + + selectedKeys: { + type: Array as PropType, + default: () => [], + }, + + checkedKeys: { + type: Array as PropType, + default: () => [], + }, + + beforeRightClick: { + type: Function as PropType<(...arg: any) => ContextMenuItem[] | ContextMenuOptions>, + default: null, + }, + + rightMenuList: { + type: Array as PropType, + }, + // 自定义数据过滤判断方法(注: 不是整个过滤方法,而是内置过滤的判断方法,用于增强原本仅能通过title进行过滤的方式) + filterFn: { + type: Function as PropType<(searchValue: any, node: TreeItem, replaceFields: ReplaceFields) => boolean>, + default: null, + }, + // 搜索完成时自动展开结果 + expandOnSearch: propTypes.bool.def(false), + // 搜索完成自动选中所有结果,当且仅当 checkable===true 时生效 + checkOnSearch: propTypes.bool.def(false), + // 搜索完成自动select所有结果 + selectedOnSearch: propTypes.bool.def(false), +}; + +export const treeNodeProps = { + actionList: { + type: Array as PropType, + default: () => [], + }, + replaceFields: { + type: Object as PropType, + }, + treeData: { + type: Array as PropType, + default: () => [], + }, +}; diff --git a/src/components/Tree_backup/src/typing.ts b/src/components/Tree_backup/src/typing.ts new file mode 100644 index 0000000..c606d4d --- /dev/null +++ b/src/components/Tree_backup/src/typing.ts @@ -0,0 +1,53 @@ +import type { TreeDataItem, CheckEvent as CheckEventOrigin } from 'ant-design-vue/es/tree/Tree'; +import { ContextMenuItem } from '/@/hooks/web/useContextMenu'; + +export interface ActionItem { + render: (record: Recordable) => any; + show?: boolean | ((record: Recordable) => boolean); +} + +export interface TreeItem extends TreeDataItem { + icon?: any; +} + +export interface ReplaceFields { + children?: string; + title?: string; + key?: string; +} + +export type Keys = (string | number)[]; +export type CheckKeys = (string | number)[] | { checked: (string | number)[]; halfChecked: (string | number)[] }; + +export interface TreeActionType { + checkAll: (checkAll: boolean) => void; + expandAll: (expandAll: boolean) => void; + setExpandedKeys: (keys: Keys) => void; + getExpandedKeys: () => Keys; + setSelectedKeys: (keys: Keys) => void; + getSelectedKeys: () => Keys; + setCheckedKeys: (keys: CheckKeys) => void; + getCheckedKeys: () => CheckKeys; + filterByLevel: (level: number) => void; + insertNodeByKey: (opt: InsertNodeParams) => void; + insertNodesByKey: (opt: InsertNodeParams) => void; + deleteNodeByKey: (key: string) => void; + updateNodeByKey: (key: string, node: Omit) => void; + setSearchValue: (value: string) => void; + getSearchValue: () => string; +} + +export interface InsertNodeParams { + parentKey: string | null; + node: TreeDataItem; + list?: TreeDataItem[]; + push?: 'push' | 'unshift'; +} + +export interface ContextMenuOptions { + icon?: string; + styles?: any; + items?: ContextMenuItem[]; +} + +export type CheckEvent = CheckEventOrigin; diff --git a/src/components/Tree_backup/src/useTree.ts b/src/components/Tree_backup/src/useTree.ts new file mode 100644 index 0000000..1ba6f69 --- /dev/null +++ b/src/components/Tree_backup/src/useTree.ts @@ -0,0 +1,192 @@ +import type { InsertNodeParams, Keys, ReplaceFields } from './typing'; +import type { Ref, ComputedRef } from 'vue'; +import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree'; + +import { cloneDeep } from 'lodash-es'; +import { unref } from 'vue'; +import { forEach } from '/@/utils/helper/treeHelper'; + +export function useTree(treeDataRef: Ref, getReplaceFields: ComputedRef) { + function getAllKeys(list?: TreeDataItem[]) { + const keys: string[] = []; + const treeData = list || unref(treeDataRef); + const { key: keyField, children: childrenField } = unref(getReplaceFields); + if (!childrenField || !keyField) return keys; + + for (let index = 0; index < treeData.length; index++) { + const node = treeData[index]; + keys.push(node[keyField]!); + const children = node[childrenField]; + if (children && children.length) { + keys.push(...(getAllKeys(children) as string[])); + } + } + return keys as Keys; + } + + // get keys that can be checked and selected + function getEnabledKeys(list?: TreeDataItem[]) { + const keys: string[] = []; + const treeData = list || unref(treeDataRef); + const { key: keyField, children: childrenField } = unref(getReplaceFields); + if (!childrenField || !keyField) return keys; + + for (let index = 0; index < treeData.length; index++) { + const node = treeData[index]; + node.disabled !== true && node.selectable !== false && keys.push(node[keyField]!); + const children = node[childrenField]; + if (children && children.length) { + keys.push(...(getEnabledKeys(children) as string[])); + } + } + return keys as Keys; + } + + function getChildrenKeys(nodeKey: string | number, list?: TreeDataItem[]): Keys { + const keys: Keys = []; + const treeData = list || unref(treeDataRef); + const { key: keyField, children: childrenField } = unref(getReplaceFields); + if (!childrenField || !keyField) return keys; + for (let index = 0; index < treeData.length; index++) { + const node = treeData[index]; + const children = node[childrenField]; + if (nodeKey === node[keyField]) { + keys.push(node[keyField]!); + if (children && children.length) { + keys.push(...(getAllKeys(children) as string[])); + } + } else { + if (children && children.length) { + keys.push(...getChildrenKeys(nodeKey, children)); + } + } + } + return keys as Keys; + } + + // Update node + function updateNodeByKey(key: string, node: TreeDataItem, list?: TreeDataItem[]) { + if (!key) return; + const treeData = list || unref(treeDataRef); + const { key: keyField, children: childrenField } = unref(getReplaceFields); + + if (!childrenField || !keyField) return; + + for (let index = 0; index < treeData.length; index++) { + const element: any = treeData[index]; + const children = element[childrenField]; + + if (element[keyField] === key) { + treeData[index] = { ...treeData[index], ...node }; + break; + } else if (children && children.length) { + updateNodeByKey(key, node, element[childrenField]); + } + } + } + + // Expand the specified level + function filterByLevel(level = 1, list?: TreeDataItem[], currentLevel = 1) { + if (!level) { + return []; + } + const res: (string | number)[] = []; + const data = list || unref(treeDataRef) || []; + for (let index = 0; index < data.length; index++) { + const item = data[index]; + + const { key: keyField, children: childrenField } = unref(getReplaceFields); + const key = keyField ? item[keyField] : ''; + const children = childrenField ? item[childrenField] : []; + res.push(key); + if (children && children.length && currentLevel < level) { + currentLevel += 1; + res.push(...filterByLevel(level, children, currentLevel)); + } + } + return res as string[] | number[]; + } + + /** + * 添加节点 + */ + function insertNodeByKey({ parentKey = null, node, push = 'push' }: InsertNodeParams) { + const treeData: any = cloneDeep(unref(treeDataRef)); + if (!parentKey) { + treeData[push](node); + treeDataRef.value = treeData; + return; + } + const { key: keyField, children: childrenField } = unref(getReplaceFields); + if (!childrenField || !keyField) return; + + forEach(treeData, (treeItem) => { + if (treeItem[keyField] === parentKey) { + treeItem[childrenField] = treeItem[childrenField] || []; + treeItem[childrenField][push](node); + return true; + } + }); + treeDataRef.value = treeData; + } + + /** + * 批量添加节点 + */ + function insertNodesByKey({ parentKey = null, list, push = 'push' }: InsertNodeParams) { + const treeData: any = cloneDeep(unref(treeDataRef)); + if (!list || list.length < 1) { + return; + } + if (!parentKey) { + for (let i = 0; i < list.length; i++) { + treeData[push](list[i]); + } + } else { + const { key: keyField, children: childrenField } = unref(getReplaceFields); + if (!childrenField || !keyField) return; + + forEach(treeData, (treeItem) => { + if (treeItem[keyField] === parentKey) { + treeItem[childrenField] = treeItem[childrenField] || []; + for (let i = 0; i < list.length; i++) { + treeItem[childrenField][push](list[i]); + } + treeDataRef.value = treeData; + return true; + } + }); + } + } + + // Delete node + function deleteNodeByKey(key: string, list?: TreeDataItem[]) { + if (!key) return; + const treeData = list || unref(treeDataRef); + const { key: keyField, children: childrenField } = unref(getReplaceFields); + if (!childrenField || !keyField) return; + + for (let index = 0; index < treeData.length; index++) { + const element: any = treeData[index]; + const children = element[childrenField]; + + if (element[keyField] === key) { + treeData.splice(index, 1); + break; + } else if (children && children.length) { + deleteNodeByKey(key, element[childrenField]); + } + } + } + + return { + deleteNodeByKey, + insertNodeByKey, + insertNodesByKey, + filterByLevel, + updateNodeByKey, + getAllKeys, + getChildrenKeys, + getEnabledKeys, + }; +} diff --git a/src/components/Upload/index.ts b/src/components/Upload/index.ts new file mode 100644 index 0000000..568a7d9 --- /dev/null +++ b/src/components/Upload/index.ts @@ -0,0 +1,4 @@ +import { withInstall } from '/@/utils'; +import basicUpload from './src/BasicUpload.vue'; + +export const BasicUpload = withInstall(basicUpload); diff --git a/src/components/Upload/src/BasicUpload.vue b/src/components/Upload/src/BasicUpload.vue new file mode 100644 index 0000000..7e2f4c5 --- /dev/null +++ b/src/components/Upload/src/BasicUpload.vue @@ -0,0 +1,113 @@ + + diff --git a/src/components/Upload/src/FileList.vue b/src/components/Upload/src/FileList.vue new file mode 100644 index 0000000..19ffb57 --- /dev/null +++ b/src/components/Upload/src/FileList.vue @@ -0,0 +1,102 @@ + + diff --git a/src/components/Upload/src/ThumbUrl.vue b/src/components/Upload/src/ThumbUrl.vue new file mode 100644 index 0000000..80fb203 --- /dev/null +++ b/src/components/Upload/src/ThumbUrl.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/components/Upload/src/UploadModal.vue b/src/components/Upload/src/UploadModal.vue new file mode 100644 index 0000000..19eeeca --- /dev/null +++ b/src/components/Upload/src/UploadModal.vue @@ -0,0 +1,309 @@ + + + diff --git a/src/components/Upload/src/UploadPreviewModal.vue b/src/components/Upload/src/UploadPreviewModal.vue new file mode 100644 index 0000000..0e51cb9 --- /dev/null +++ b/src/components/Upload/src/UploadPreviewModal.vue @@ -0,0 +1,99 @@ + + + diff --git a/src/components/Upload/src/data.tsx b/src/components/Upload/src/data.tsx new file mode 100644 index 0000000..5480788 --- /dev/null +++ b/src/components/Upload/src/data.tsx @@ -0,0 +1,147 @@ +import type { BasicColumn, ActionItem } from '/@/components/Table'; +import { FileItem, PreviewFileItem, UploadResultStatus } from './typing'; +import { + // checkImgType, + isImgTypeByName, +} from './helper'; +import { Progress, Tag } from 'ant-design-vue'; +import TableAction from '/@/components/Table/src/components/TableAction.vue'; +import ThumbUrl from './ThumbUrl.vue'; +import { useI18n } from '/@/hooks/web/useI18n'; + +const { t } = useI18n(); + +// 文件上传列表 +export function createTableColumns(): BasicColumn[] { + return [ + { + dataIndex: 'thumbUrl', + title: t('component.upload.legend'), + width: 100, + customRender: ({ record }) => { + const { thumbUrl } = (record as FileItem) || {}; + return thumbUrl && ; + }, + }, + { + dataIndex: 'name', + title: t('component.upload.fileName'), + align: 'left', + customRender: ({ text, record }) => { + const { percent, status: uploadStatus } = (record as FileItem) || {}; + let status: 'normal' | 'exception' | 'active' | 'success' = 'normal'; + if (uploadStatus === UploadResultStatus.ERROR) { + status = 'exception'; + } else if (uploadStatus === UploadResultStatus.UPLOADING) { + status = 'active'; + } else if (uploadStatus === UploadResultStatus.SUCCESS) { + status = 'success'; + } + return ( + +

+ {text} +

+ +
+ ); + }, + }, + { + dataIndex: 'size', + title: t('component.upload.fileSize'), + width: 100, + customRender: ({ text = 0 }) => { + return text && (text / 1024).toFixed(2) + 'KB'; + }, + }, + // { + // dataIndex: 'type', + // title: '文件类型', + // width: 100, + // }, + { + dataIndex: 'status', + title: t('component.upload.fileStatue'), + width: 100, + customRender: ({ text }) => { + if (text === UploadResultStatus.SUCCESS) { + return {() => t('component.upload.uploadSuccess')}; + } else if (text === UploadResultStatus.ERROR) { + return {() => t('component.upload.uploadError')}; + } else if (text === UploadResultStatus.UPLOADING) { + return {() => t('component.upload.uploading')}; + } + + return text; + }, + }, + ]; +} +export function createActionColumn(handleRemove: Function): BasicColumn { + return { + width: 120, + title: t('component.upload.operating'), + dataIndex: 'action', + fixed: false, + customRender: ({ record }) => { + const actions: ActionItem[] = [ + { + label: t('component.upload.del'), + color: 'error', + onClick: handleRemove.bind(null, record), + }, + ]; + // if (checkImgType(record)) { + // actions.unshift({ + // label: t('component.upload.preview'), + // onClick: handlePreview.bind(null, record), + // }); + // } + return ; + }, + }; +} +// 文件预览列表 +export function createPreviewColumns(): BasicColumn[] { + return [ + { + dataIndex: 'url', + title: t('component.upload.legend'), + width: 100, + customRender: ({ record }) => { + const { url } = (record as PreviewFileItem) || {}; + return isImgTypeByName(url) && ; + }, + }, + { + dataIndex: 'name', + title: t('component.upload.fileName'), + align: 'left', + }, + ]; +} + +export function createPreviewActionColumn({ handleRemove, handleDownload }: { handleRemove: Fn; handleDownload: Fn }): BasicColumn { + return { + width: 160, + title: t('component.upload.operating'), + dataIndex: 'action', + fixed: false, + customRender: ({ record }) => { + const actions: ActionItem[] = [ + { + label: t('component.upload.del'), + color: 'error', + onClick: handleRemove.bind(null, record), + }, + { + label: t('component.upload.download'), + onClick: handleDownload.bind(null, record), + }, + ]; + + return ; + }, + }; +} diff --git a/src/components/Upload/src/helper.ts b/src/components/Upload/src/helper.ts new file mode 100644 index 0000000..7b1f16d --- /dev/null +++ b/src/components/Upload/src/helper.ts @@ -0,0 +1,60 @@ +export function checkFileType(file: File, accepts: string[]) { + // 代码逻辑说明: 【issues/7954】BasicUpload组件上传文件,限制上传格式校验出错 + const mimePatterns: string[] = []; + const suffixList: string[] = []; + // 分类处理 accepts + for (const item of accepts) { + if (item.includes('/')) { + mimePatterns.push(item); + } else { + // 支持.png 或 png(带点后缀或者不带点后缀) + const suffix = item.startsWith('.') ? item.slice(1) : item; + suffixList.push(suffix); + } + } + // 后缀匹配逻辑 + let suffixMatch = false; + if (suffixList.length > 0) { + const suffixRegex = new RegExp(`\\.(${suffixList.join('|')})$`, 'i'); + suffixMatch = suffixRegex.test(file.name); + } + // MIME类型匹配逻辑 + let mimeMatch = false; + if (mimePatterns.length > 0 && file.type) { + mimeMatch = mimePatterns.some((pattern) => { + // 先转义特殊字符,再处理通配符 + const regexPattern = pattern + .replace(/[.+?^${}()|[\]\\]/g, '\\$&') // 先转义特殊字符 + .replace(/\*/g, '.*'); // 再替换通配符 + const regex = new RegExp(`^${regexPattern}$`, 'i'); + return regex.test(file.type); + }); + } + if (mimePatterns.length && suffixList.length) { + return suffixMatch || mimeMatch; + } else if (mimePatterns.length) { + return mimeMatch; + } else if (suffixList.length) { + return suffixMatch; + } +} + +export function checkImgType(file: File) { + return isImgTypeByName(file.name); +} + +export function isImgTypeByName(name: string) { + return /\.(jpg|jpeg|png|gif)$/i.test(name); +} + +export function getBase64WithFile(file: File) { + return new Promise<{ + result: string; + file: File; + }>((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve({ result: reader.result as string, file }); + reader.onerror = (error) => reject(error); + }); +} diff --git a/src/components/Upload/src/props.ts b/src/components/Upload/src/props.ts new file mode 100644 index 0000000..413b95d --- /dev/null +++ b/src/components/Upload/src/props.ts @@ -0,0 +1,83 @@ +import type { PropType } from 'vue'; +import { FileBasicColumn } from './typing'; + +export const basicProps = { + helpText: { + type: String as PropType, + default: '', + }, + // 文件最大多少MB + maxSize: { + type: Number as PropType, + default: 2, + }, + // 最大数量的文件,Infinity不限制 + maxNumber: { + type: Number as PropType, + default: Infinity, + }, + // 根据后缀,或者其他 + accept: { + type: Array as PropType, + default: () => [], + }, + multiple: { + type: Boolean as PropType, + default: true, + }, + uploadParams: { + type: Object as PropType, + default: {}, + }, + api: { + type: Function as PropType, + default: null, + required: true, + }, + name: { + type: String as PropType, + default: 'file', + }, + filename: { + type: String as PropType, + default: null, + }, +}; + +export const uploadContainerProps = { + value: { + type: Array as PropType, + default: () => [], + }, + ...basicProps, + showPreviewNumber: { + type: Boolean as PropType, + default: true, + }, + emptyHidePreview: { + type: Boolean as PropType, + default: false, + }, +}; + +export const previewProps = { + value: { + type: Array as PropType, + default: () => [], + }, +}; + +export const fileListProps = { + columns: { + type: [Array] as PropType, + default: null, + }, + actionColumn: { + type: Object as PropType, + default: null, + }, + dataSource: { + type: Array as PropType, + default: null, + }, +}; diff --git a/src/components/Upload/src/typing.ts b/src/components/Upload/src/typing.ts new file mode 100644 index 0000000..c630110 --- /dev/null +++ b/src/components/Upload/src/typing.ts @@ -0,0 +1,55 @@ +import { UploadApiResult } from '/@/api/sys/model/uploadModel'; + +export enum UploadResultStatus { + SUCCESS = 'success', + ERROR = 'error', + UPLOADING = 'uploading', +} + +export interface FileItem { + thumbUrl?: string; + name: string; + size: string | number; + type?: string; + percent: number; + file: File; + status?: UploadResultStatus; + responseData?: UploadApiResult; + uuid: string; +} + +export interface PreviewFileItem { + url: string; + name: string; + type: string; +} + +export interface FileBasicColumn { + /** + * Renderer of the table cell. The return value should be a VNode, or an object for colSpan/rowSpan config + * @type Function | ScopedSlot + */ + customRender?: Function; + /** + * Title of this column + * @type any (string | slot) + */ + title: string; + + /** + * Width of this column + * @type string | number + */ + width?: number; + /** + * Display field of the data record, could be set like a.b.c + * @type string + */ + dataIndex: string; + /** + * specify how content is aligned + * @default 'left' + * @type string + */ + align?: 'left' | 'right' | 'center'; +} diff --git a/src/components/Upload/src/useUpload.ts b/src/components/Upload/src/useUpload.ts new file mode 100644 index 0000000..694cc27 --- /dev/null +++ b/src/components/Upload/src/useUpload.ts @@ -0,0 +1,60 @@ +import { Ref, unref, computed } from 'vue'; +import { useI18n } from '/@/hooks/web/useI18n'; +const { t } = useI18n(); +export function useUploadType({ + acceptRef, + helpTextRef, + maxNumberRef, + maxSizeRef, +}: { + acceptRef: Ref; + helpTextRef: Ref; + maxNumberRef: Ref; + maxSizeRef: Ref; +}) { + // 文件类型限制 + const getAccept = computed(() => { + const accept = unref(acceptRef); + if (accept && accept.length > 0) { + return accept; + } + return []; + }); + const getStringAccept = computed(() => { + return unref(getAccept) + .map((item) => { + if (item.indexOf('/') > 0 || item.startsWith('.')) { + return item; + } else { + return `.${item}`; + } + }) + .join(','); + }); + + // 支持jpg、jpeg、png格式,不超过2M,最多可选择10张图片,。 + const getHelpText = computed(() => { + const helpText = unref(helpTextRef); + if (helpText) { + return helpText; + } + const helpTexts: string[] = []; + + const accept = unref(acceptRef); + if (accept.length > 0) { + helpTexts.push(t('component.upload.accept', [accept.join(',')])); + } + + const maxSize = unref(maxSizeRef); + if (maxSize) { + helpTexts.push(t('component.upload.maxSize', [maxSize])); + } + + const maxNumber = unref(maxNumberRef); + if (maxNumber && maxNumber !== Infinity) { + helpTexts.push(t('component.upload.maxNumber', [maxNumber])); + } + return helpTexts.join(','); + }); + return { getAccept, getStringAccept, getHelpText }; +} diff --git a/src/components/Verify/index.ts b/src/components/Verify/index.ts new file mode 100644 index 0000000..7c67101 --- /dev/null +++ b/src/components/Verify/index.ts @@ -0,0 +1,7 @@ +import { withInstall } from '/@/utils/index'; +import basicDragVerify from './src/DragVerify.vue'; +import rotateDragVerify from './src/ImgRotate.vue'; + +export const BasicDragVerify = withInstall(basicDragVerify); +export const RotateDragVerify = withInstall(rotateDragVerify); +export * from './src/typing'; diff --git a/src/components/Verify/src/DragVerify.vue b/src/components/Verify/src/DragVerify.vue new file mode 100644 index 0000000..26ce84a --- /dev/null +++ b/src/components/Verify/src/DragVerify.vue @@ -0,0 +1,361 @@ + + diff --git a/src/components/Verify/src/ImgRotate.vue b/src/components/Verify/src/ImgRotate.vue new file mode 100644 index 0000000..e900188 --- /dev/null +++ b/src/components/Verify/src/ImgRotate.vue @@ -0,0 +1,216 @@ + + diff --git a/src/components/Verify/src/props.ts b/src/components/Verify/src/props.ts new file mode 100644 index 0000000..1e14970 --- /dev/null +++ b/src/components/Verify/src/props.ts @@ -0,0 +1,87 @@ +import type { PropType } from 'vue'; +import { useI18n } from '/@/hooks/web/useI18n'; + +const { t } = useI18n(); +export const basicProps = { + value: { + type: Boolean as PropType, + default: false, + }, + + isSlot: { + type: Boolean as PropType, + default: false, + }, + + text: { + type: [String] as PropType, + default: t('component.verify.dragText'), + }, + successText: { + type: [String] as PropType, + default: t('component.verify.successText'), + }, + height: { + type: [Number, String] as PropType, + default: 40, + }, + + width: { + type: [Number, String] as PropType, + default: 220, + }, + + circle: { + type: Boolean as PropType, + default: false, + }, + + wrapStyle: { + type: Object as PropType, + default: {}, + }, + contentStyle: { + type: Object as PropType, + default: {}, + }, + barStyle: { + type: Object as PropType, + default: {}, + }, + actionStyle: { + type: Object as PropType, + default: {}, + }, +}; + +export const rotateProps = { + ...basicProps, + src: { + type: String as PropType, + }, + + imgWidth: { + type: Number as PropType, + default: 260, + }, + + imgWrapStyle: { + type: Object as PropType, + default: {}, + }, + + minDegree: { + type: Number as PropType, + default: 90, + }, + + maxDegree: { + type: Number as PropType, + default: 270, + }, + + diffDegree: { + type: Number as PropType, + default: 20, + }, +}; diff --git a/src/components/Verify/src/typing.ts b/src/components/Verify/src/typing.ts new file mode 100644 index 0000000..48f7d4c --- /dev/null +++ b/src/components/Verify/src/typing.ts @@ -0,0 +1,14 @@ +export interface DragVerifyActionType { + resume: () => void; +} + +export interface PassingData { + isPassing: boolean; + time: number; +} + +export interface MoveData { + event: MouseEvent | TouchEvent; + moveDistance: number; + moveX: number; +} diff --git a/src/components/VirtualScroll/index.ts b/src/components/VirtualScroll/index.ts new file mode 100644 index 0000000..a4c6089 --- /dev/null +++ b/src/components/VirtualScroll/index.ts @@ -0,0 +1,4 @@ +import { withInstall } from '/@/utils/index'; +import vScroll from './src/VirtualScroll.vue'; + +export const VScroll = withInstall(vScroll); diff --git a/src/components/VirtualScroll/src/VirtualScroll.vue b/src/components/VirtualScroll/src/VirtualScroll.vue new file mode 100644 index 0000000..e010423 --- /dev/null +++ b/src/components/VirtualScroll/src/VirtualScroll.vue @@ -0,0 +1,180 @@ + + diff --git a/src/components/chart/Bar.vue b/src/components/chart/Bar.vue new file mode 100644 index 0000000..f3955d0 --- /dev/null +++ b/src/components/chart/Bar.vue @@ -0,0 +1,87 @@ + + diff --git a/src/components/chart/BarAndLine.vue b/src/components/chart/BarAndLine.vue new file mode 100644 index 0000000..7943e26 --- /dev/null +++ b/src/components/chart/BarAndLine.vue @@ -0,0 +1,93 @@ + + diff --git a/src/components/chart/BarMulti.vue b/src/components/chart/BarMulti.vue new file mode 100644 index 0000000..9dd3bfb --- /dev/null +++ b/src/components/chart/BarMulti.vue @@ -0,0 +1,119 @@ + + diff --git a/src/components/chart/ChartCard.vue b/src/components/chart/ChartCard.vue new file mode 100644 index 0000000..da7d58b --- /dev/null +++ b/src/components/chart/ChartCard.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/src/components/chart/Gauge.vue b/src/components/chart/Gauge.vue new file mode 100644 index 0000000..384541f --- /dev/null +++ b/src/components/chart/Gauge.vue @@ -0,0 +1,108 @@ + + diff --git a/src/components/chart/HeadInfo.vue b/src/components/chart/HeadInfo.vue new file mode 100644 index 0000000..2d46ae4 --- /dev/null +++ b/src/components/chart/HeadInfo.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/components/chart/LineMulti.vue b/src/components/chart/LineMulti.vue new file mode 100644 index 0000000..daf1e1d --- /dev/null +++ b/src/components/chart/LineMulti.vue @@ -0,0 +1,114 @@ + + diff --git a/src/components/chart/Pie.vue b/src/components/chart/Pie.vue new file mode 100644 index 0000000..b01370c --- /dev/null +++ b/src/components/chart/Pie.vue @@ -0,0 +1,89 @@ + + diff --git a/src/components/chart/README.md b/src/components/chart/README.md new file mode 100644 index 0000000..ee301ef --- /dev/null +++ b/src/components/chart/README.md @@ -0,0 +1,282 @@ +# 报表组件文档 + +## 柱状图 + +##### 引用方式 + +```js +import Bar from '/@/components/chart/Bar.vue'; +``` + +##### 参数列表 + +| 参数名 | 类型 | 必填 | 说明 | +| --------- | ------ | ---- | ---------- | +| chartData | array | ✔️ | 报表数据源 | +| width | number | | 报表宽度 | +| height | number | | 报表高度 | + +##### chartData 示例 + +```json +[ + { + "name": "1月", + "value": 320 + }, + { + "name": "2月", + "value": 457 + }, + { + "name": "3月", + "value": 182 + } +] +``` + +##### 代码示例 + +```html + + + + + +``` + +## 多列柱状图 + +##### 引用方式 + +```js +import BarMulti from '/@/components/chart/BarMulti.vue'; +``` + +##### 参数列表 + +| 参数名 | 类型 | 必填 | 说明 | +| --------- | ------ | ---- | ---------- | +| chartData | array | ✔️ | 报表数据源 | +| width | number | | 报表宽度 | +| height | number | | 报表高度 | + +##### chartData 示例 + +```json +[ + { + "name": "1月", + "value": 320, + "type": "2021" + }, + { + "name": "2月", + "value": 457, + "type": "2021" + }, + { + "name": "3月", + "value": 182, + "type": "2021" + }, + { + "name": "1月", + "value": 240, + "type": "2022" + }, + { + "name": "2月", + "value": 357, + "type": "2022" + }, + { + "name": "3月", + "value": 456, + "type": "2022" + } +] +``` + +## 迷你柱状图 + +同柱形图,修改配置即可 + +## 面积图 + +##### 引用方式 + +```js +import Line from '/@/components/chart/Line.vue'; +``` + +##### 参数列表 + +| 参数名 | 类型 | 必填 | 说明 | +| --------- | ------ | ---- | ---------- | +| chartData | array | ✔️ | 报表数据源 | +| width | number | | 报表宽度 | +| height | number | | 报表高度 | +| option | object | | 配置项 | + +##### chartData 示例 + +```json +[ + { + "name": "1月", + "value": 320 + }, + { + "name": "2月", + "value": 457 + }, + { + "name": "3月", + "value": 182 + } +] +``` + +## 多行折线图 + +##### 引用方式 + +```js +import LineMulti from '/@/components/chart/LineMulti.vue'; +``` + +##### 参数列表 + +| 参数名 | 类型 | 必填 | 说明 | +| --------- | ------ | ---- | ---------- | +| chartData | array | ✔️ | 报表数据源 | +| width | number | | 报表宽度 | +| height | number | | 报表高度 | +| option | object | | 配置项 | + +##### chartData 示例 + +同柱形图 + +## 饼状图 + +##### 引用方式 + +```js +import Pie from '/@/components/chart/Pie'; +``` + +##### 参数列表 + +| 参数名 | 类型 | 必填 | 说明 | +| --------- | ------ | ---- | ---------- | +| chartData | array | ✔️ | 报表数据源 | +| width | number | | 报表宽度 | +| height | number | | 报表高度 | +| option | object | | 配置项 | + +##### chartData 示例 + +```json +[ + { "name": "一月", "value": 40 }, + { "name": "二月", "value": 21 }, + { "name": "三月", "value": 17 }, + { "name": "四月", "value": 13 }, + { "name": "五月", "value": 9 } +] +``` + +## 雷达图 + +##### 引用方式 + +```js +import Radar from '/@/components/chart/Radar'; +``` + +##### 参数列表 + +| 参数名 | 类型 | 必填 | 说明 | +| --------- | ------ | ---- | ---------- | +| chartData | array | ✔️ | 报表数据源 | +| width | number | | 报表宽度 | +| height | number | | 报表高度 | +| option | object | | 配置项 | + +##### chartData 示例 + +```json +[ + { "item": "一月", "score": 40 }, + { "item": "二月", "score": 20 }, + { "item": "三月", "score": 67 }, + { "item": "四月", "score": 43 }, + { "item": "五月", "score": 90 } +] +``` + +## 仪表盘 + +##### 引用方式 + +```js +import Gauge from '/@/components/chart/Gauge'; +``` + +##### 参数列表 + +| 参数名 | 类型 | 必填 | 说明 | +| --------- | ------ | ---- | ---------- | +| chartData | array | ✔️ | 报表数据源 | +| width | number | | 报表宽度 | +| height | number | | 报表高度 | +| option | object | | 配置项 | + +## 排名列表 + +##### 引用方式 + +```js +import RankList from '@/components/chart/RankList'; +``` + +##### 参数列表 + +| 参数名 | 类型 | 必填 | 说明 | +| ------ | ------ | ---- | ------------------------ | +| title | string | | 报表标题 | +| list | array | | 排名列表数据 | +| height | number | | 报表高度,默认自适应高度 | + +##### list 示例 + +```json +[ + { "name": "北京朝阳 1 号店", "total": 1981 }, + { "name": "北京朝阳 2 号店", "total": 1359 }, + { "name": "北京朝阳 3 号店", "total": 1354 }, + { "name": "北京朝阳 4 号店", "total": 263 }, + { "name": "北京朝阳 5 号店", "total": 446 }, + { "name": "北京朝阳 6 号店", "total": 796 } +] +``` diff --git a/src/components/chart/Radar.vue b/src/components/chart/Radar.vue new file mode 100644 index 0000000..33bc8a8 --- /dev/null +++ b/src/components/chart/Radar.vue @@ -0,0 +1,92 @@ + + diff --git a/src/components/chart/RankList.vue b/src/components/chart/RankList.vue new file mode 100644 index 0000000..25bb2cf --- /dev/null +++ b/src/components/chart/RankList.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/src/components/chart/SingleLine.vue b/src/components/chart/SingleLine.vue new file mode 100644 index 0000000..c3cdfe4 --- /dev/null +++ b/src/components/chart/SingleLine.vue @@ -0,0 +1,89 @@ + + diff --git a/src/components/chart/StackBar.vue b/src/components/chart/StackBar.vue new file mode 100644 index 0000000..d570d4a --- /dev/null +++ b/src/components/chart/StackBar.vue @@ -0,0 +1,107 @@ + + diff --git a/src/components/chart/Trend.vue b/src/components/chart/Trend.vue new file mode 100644 index 0000000..9294219 --- /dev/null +++ b/src/components/chart/Trend.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/src/components/jeecg/AIcon.vue b/src/components/jeecg/AIcon.vue new file mode 100644 index 0000000..699f5ce --- /dev/null +++ b/src/components/jeecg/AIcon.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/src/components/jeecg/ExcelButton.vue b/src/components/jeecg/ExcelButton.vue new file mode 100644 index 0000000..98d5fec --- /dev/null +++ b/src/components/jeecg/ExcelButton.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/src/components/jeecg/JPrompt/JPrompt.vue b/src/components/jeecg/JPrompt/JPrompt.vue new file mode 100644 index 0000000..fabea94 --- /dev/null +++ b/src/components/jeecg/JPrompt/JPrompt.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/src/components/jeecg/JPrompt/hooks/useJPrompt.ts b/src/components/jeecg/JPrompt/hooks/useJPrompt.ts new file mode 100644 index 0000000..44b1bcd --- /dev/null +++ b/src/components/jeecg/JPrompt/hooks/useJPrompt.ts @@ -0,0 +1,59 @@ +import type { JPromptProps } from '../typing'; +import { render, createVNode, nextTick } from 'vue'; +import { error } from '/@/utils/log'; +import { getAppContext } from "@/store"; +import JPrompt from '../JPrompt.vue'; + +export function useJPrompt() { + + function createJPrompt(options: JPromptProps) { + let instance = null; + const box = document.createElement('div'); + const vm = createVNode(JPrompt, { + // 注册 + async onRegister(ins) { + instance = ins; + await nextTick(); + ins.openModal(options); + }, + // 销毁 + afterClose() { + render(null, box); + document.body.removeChild(box); + }, + }); + vm.appContext = getAppContext()!; + // 挂载到 body + render(vm, box); + document.body.appendChild(box); + + function getInstance(): any { + if (instance == null) { + error('useJPrompt instance is undefined!'); + } + return instance; + } + + function updateModal(options: JPromptProps) { + getInstance()?.updateModal(options); + } + + function closeModal() { + getInstance()?.closeModal(); + } + + function setLoading(loading) { + getInstance()?.setLoading(loading); + } + + return { + closeModal, + updateModal, + setLoading, + }; + } + + return { + createJPrompt, + }; +} diff --git a/src/components/jeecg/JPrompt/index.ts b/src/components/jeecg/JPrompt/index.ts new file mode 100644 index 0000000..850fc09 --- /dev/null +++ b/src/components/jeecg/JPrompt/index.ts @@ -0,0 +1,2 @@ +export { useJPrompt } from './hooks/useJPrompt'; +export { default as JPrompt } from './JPrompt.vue'; diff --git a/src/components/jeecg/JPrompt/typing.ts b/src/components/jeecg/JPrompt/typing.ts new file mode 100644 index 0000000..785efe0 --- /dev/null +++ b/src/components/jeecg/JPrompt/typing.ts @@ -0,0 +1,15 @@ +import { ModalOptionsPartial } from '/@/hooks/web/useMessage'; +import { RenderCallbackParams, Rule } from '/@/components/Form'; + +export interface JPromptProps extends ModalOptionsPartial { + // 输入框是否必填 + required?: boolean; + // 校验 + rules?: Rule[]; + // 动态校验 + dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[]; + // 占位字符 + placeholder?: string; + // 输入框默认值 + defaultValue?: string; +} diff --git a/src/components/jeecg/JTabsSelectUser/SelectUserModal.vue b/src/components/jeecg/JTabsSelectUser/SelectUserModal.vue new file mode 100644 index 0000000..d507393 --- /dev/null +++ b/src/components/jeecg/JTabsSelectUser/SelectUserModal.vue @@ -0,0 +1,722 @@ + + + + + diff --git a/src/components/jeecg/JTabsSelectUser/component/PostRankRelation.vue b/src/components/jeecg/JTabsSelectUser/component/PostRankRelation.vue new file mode 100644 index 0000000..9d4df3a --- /dev/null +++ b/src/components/jeecg/JTabsSelectUser/component/PostRankRelation.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/src/components/jeecg/JTabsSelectUser/index.vue b/src/components/jeecg/JTabsSelectUser/index.vue new file mode 100644 index 0000000..b55123a --- /dev/null +++ b/src/components/jeecg/JTabsSelectUser/index.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/src/components/jeecg/JTabsSelectUser/useSelectUser.ts b/src/components/jeecg/JTabsSelectUser/useSelectUser.ts new file mode 100644 index 0000000..05d6d75 --- /dev/null +++ b/src/components/jeecg/JTabsSelectUser/useSelectUser.ts @@ -0,0 +1,101 @@ +import { defHttp } from '/@/utils/http/axios'; +import { BasicColumn, FormSchema } from '/@/components/Table'; + +export enum Api { + departList = '/sys/sysDepart/queryDepartTreeSync', + userList = '/sys/user/list', + departUserList = '/sys/user/queryUserByDepId', + + //获取所有岗位职级信息 + queryALLRankRelation = '/sys/sysDepart/getALLRankRelation', + //根据关键字搜索部门 + searchBy = '/sys/sysDepart/searchBy' +} + +/** + * 获取部门树列表 + */ +export const getDepartTreeData = (params?) => defHttp.get({ url: Api.departList, params }); + +/** + * 获取用户列表 + */ +export const getUserList = (params?) => defHttp.get({ url: Api.userList, params }); + +/** + * 获取指定部门用户列表 + */ +export const getDepartUserList = (params?) => defHttp.get({ url: Api.departUserList, params }); +/** + * 获取职级信息 + */ +export const queryALLRankRelation = (params?) => defHttp.get({ url: Api.queryALLRankRelation, params,timeout: 2 * 60 * 1000 }); + +/** + * 根据关键字搜索部门 + */ +export const searchByKeywords = (params) => defHttp.get({ url: Api.searchBy, params }); +/** + * 用户列表 + */ +export const columns: BasicColumn[] = [ + // { + // title: '用户账号', + // align: 'center', + // dataIndex: 'username', + // ellipsis: true, + // width: 130, + // }, + { + title: '用户姓名', + align: 'center', + width: 150, + dataIndex: 'realname', + ellipsis: true, + }, + { + title: '部门', + align: 'center', + width: 150, + dataIndex: 'orgCodeTxt', + }, +]; + +/** + * 选中用户列表 + */ +export const selectedUserColumns: BasicColumn[] = [ + { + title: '用户姓名', + align: 'center', + width: 150, + dataIndex: 'realname', + ellipsis: true, + }, +]; + +/** + * 查询条件 + */ +export const searchFormSchema: FormSchema[] = [ + { + label: '用户姓名', + field: 'realname', + component: 'Input', + componentProps: { + style: { + width: '150px', + }, + }, + }, + // { + // label: '用户账号', + // field: 'username', + // component: 'JInput', + // componentProps: { + // style: { + // width: '150px', + // }, + // }, + // }, +]; diff --git a/src/components/jeecg/JVxeTable/hooks.ts b/src/components/jeecg/JVxeTable/hooks.ts new file mode 100644 index 0000000..54c46fa --- /dev/null +++ b/src/components/jeecg/JVxeTable/hooks.ts @@ -0,0 +1,2 @@ +export { useJVxeCompProps, useJVxeComponent } from './src/hooks/useJVxeComponent'; +export { useResolveComponent } from './src/hooks/useData'; diff --git a/src/components/jeecg/JVxeTable/index.ts b/src/components/jeecg/JVxeTable/index.ts new file mode 100644 index 0000000..2d8ce0a --- /dev/null +++ b/src/components/jeecg/JVxeTable/index.ts @@ -0,0 +1,21 @@ +import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; +// update-begin--author:liaozhiyang---date:20260318---for:【QQYUN-14948】需要兼容vxetable引入到了页面也不报错 +// 兼容页面直接引入:通过异步组件包装,确保 vxe-table 注册后再渲染,避免页面卡死 +export const JVxeTable = createAsyncComponent( + async () => { + const app = window['JAppRootInstance']; + if (app && !app._context.components['VxeTable']) { + const { registerJVxeTable } = await import('./src/install'); + await registerJVxeTable(app); + const { registerJVxeCustom } = await import('/@/components/JVxeCustom'); + await registerJVxeCustom(); + } + const m = await import('./src/JVxeTable'); + return m.default; + }, + { loading: true } +); +// update-end--author:liaozhiyang---date:20260318---for:【QQYUN-14948】需要兼容vxetable引入到了页面也不报错 +export { registerJVxeTable } from './src/install'; +export { deleteComponent } from './src/componentMap'; +export { registerComponent, registerAsyncComponent, registerASyncComponentReal } from './src/utils/registerUtils'; diff --git a/src/components/jeecg/JVxeTable/src/JVxeTable.ts b/src/components/jeecg/JVxeTable/src/JVxeTable.ts new file mode 100644 index 0000000..6a7bd86 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/JVxeTable.ts @@ -0,0 +1,92 @@ +import { defineComponent, h, ref, useSlots, computed, resolveComponent } from 'vue'; +import { vxeEmits, vxeProps } from './vxe.data'; +import { useData, useRefs } from './hooks/useData'; +import { useColumns } from './hooks/useColumns'; +import { useColumnsCache } from './hooks/useColumnsCache'; +import { useMethods } from './hooks/useMethods'; +import { useDataSource } from './hooks/useDataSource'; +import { useDragSort } from './hooks/useDragSort'; +import { useRenderComponents } from './hooks/useRenderComponents'; +import { useFinallyProps } from './hooks/useFinallyProps'; +import { JVxeTableProps } from './types'; +import { Spin } from 'ant-design-vue'; +import './style/index.less'; + +export default defineComponent({ + name: 'JVxeTable', + inheritAttrs: false, + props: vxeProps(), + emits: [...vxeEmits], + setup(props: JVxeTableProps, context) { + const instanceRef = ref(); + const refs = useRefs(); + const slots = useSlots(); + const data = useData(props); + const { methods, publicMethods, created } = useMethods(props, context, data, refs, instanceRef); + created(); + useColumns(props, data, methods, slots); + useDataSource(props, data, methods, refs); + useDragSort(props, methods); + // 代码逻辑说明: 【QQYUN-8566】JVXETable无法记住列设置 + const { initSetting } = useColumnsCache({ cacheColumnsKey: props.cacheColumnsKey }); + initSetting(props); + // 最终传入到 template 里的 props + const finallyProps = useFinallyProps(props, data, methods); + // 渲染子组件 + const renderComponents = useRenderComponents(props, data, methods, slots); + // update-begin--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + // 在 setup 阶段缓存组件引用,避免每次 render 调用 resolveComponent 查找 + const aSpinComp = Spin; + const vxeGridComp = resolveComponent('vxe-grid'); + // 将 vxeProps 和 data 合并为一个 computed,避免 render 每次 spread 生成新对象 + const vxeGridProps = computed(() => ({ + ...finallyProps.vxeProps.value, + data: data.vxeDataSource.value, + })); + // update-end--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + return { + instanceRef, + ...refs, + ...publicMethods, + ...finallyProps, + ...renderComponents, + vxeDataSource: data.vxeDataSource, + aSpinComp, + vxeGridComp, + vxeGridProps, + }; + }, + render() { + return h( + 'div', + { + class: this.$attrs.class, + style: this.$attrs.style, + }, + h( + this.aSpinComp, + { + spinning: this.loading, + wrapperClassName: this.prefixCls, + }, + { + default: () => [ + this.renderSubPopover(), + this.renderToolbar(), + this.renderToolbarAfterSlot(), + h( + this.vxeGridComp, + this.vxeGridProps, + this.$slots + ), + this.renderPagination(), + this.renderDetailsModal(), + ], + } + ) + ); + }, + created() { + this.instanceRef = this; + }, +}); diff --git a/src/components/jeecg/JVxeTable/src/componentMap.ts b/src/components/jeecg/JVxeTable/src/componentMap.ts new file mode 100644 index 0000000..19c2a91 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/componentMap.ts @@ -0,0 +1,97 @@ +import type { JVxeVueComponent } from './types'; +import { JVxeTypes } from './types/JVxeTypes'; +import { componentMap } from './componentMapStore'; + +import JVxeSlotCell from './components/cells/JVxeSlotCell'; +import JVxeNormalCell from './components/cells/JVxeNormalCell.vue'; +import JVxeDragSortCell from './components/cells/JVxeDragSortCell.vue'; + +import JVxeInputCell from './components/cells/JVxeInputCell.vue'; +import JVxeDateCell from './components/cells/JVxeDateCell.vue'; +import JVxeTimeCell from './components/cells/JVxeTimeCell.vue'; +import JVxeSelectCell from './components/cells/JVxeSelectCell.vue'; +import JVxeRadioCell from './components/cells/JVxeRadioCell.vue'; +import JVxeCheckboxCell from './components/cells/JVxeCheckboxCell.vue'; +import JVxeUploadCell from './components/cells/JVxeUploadCell.vue'; +// import { TagsInputCell, TagsSpanCell } from './components/cells/JVxeTagsCell.vue' +import JVxeProgressCell from './components/cells/JVxeProgressCell.vue'; +import JVxeTextareaCell from './components/cells/JVxeTextareaCell.vue'; +// import JVxeDepartSelectCell from './components/cells/JVxeDepartSelectCell.vue' +// import JVxeUserSelectCell from './components/cells/JVxeUserSelectCell.vue' +import JVxeTreeSelectCell from './components/cells/JVxeTreeSelectCell.vue'; +import JVxeCategorySelectCell from './components/cells/JVxeCategorySelectCell.vue'; + +// 代码逻辑说明: 【issues/860】生成的一对多代码,热更新之后点击新增卡死[暂时先解决] +const JVxeComponents = 'JVxeComponents__'; +/** span 组件结尾 */ +export const spanEnds: string = ':span'; + +/** 定义不能用于注册的关键字 */ +export const excludeKeywords: Array = [ + JVxeTypes.hidden, + JVxeTypes.rowNumber, + JVxeTypes.rowCheckbox, + JVxeTypes.rowRadio, + JVxeTypes.rowExpand, +]; + +/** + * 注册组件 + * + * @param type 组件 type + * @param component Vue组件 + * @param spanComponent 显示组件,可空,默认为 JVxeNormalCell 组件 + */ +export function addComponent(type: JVxeTypes, component: JVxeVueComponent, spanComponent?: JVxeVueComponent) { + if (excludeKeywords.includes(type)) { + throw new Error(`【addComponent】不能使用"${type}"作为组件的name,因为这是关键字。`); + } + if (componentMap.has(type)) { + throw new Error(`【addComponent】组件"${type}"已存在`); + } + componentMap.set(type, component); + if (spanComponent) { + componentMap.set(type + spanEnds, spanComponent); + } + // 代码逻辑说明: 【issues/860】生成的一对多代码,热更新之后点击新增卡死[暂时先解决] + import.meta.env.DEV && (window[JVxeComponents] = componentMap); +} + +export function deleteComponent(type: JVxeTypes) { + componentMap.delete(type); + componentMap.delete(type + spanEnds); + // 代码逻辑说明: 【issues/860】生成的一对多代码,热更新之后点击新增卡死[暂时先解决] + import.meta.env.DEV && (window[JVxeComponents] = componentMap); +} + +/** 定义内置自定义组件 */ +export function definedComponent() { + addComponent(JVxeTypes.slot, JVxeSlotCell); + addComponent(JVxeTypes.normal, JVxeNormalCell); + addComponent(JVxeTypes.rowDragSort, JVxeDragSortCell); + + addComponent(JVxeTypes.input, JVxeInputCell); + addComponent(JVxeTypes.inputNumber, JVxeInputCell); + addComponent(JVxeTypes.radio, JVxeRadioCell); + addComponent(JVxeTypes.checkbox, JVxeCheckboxCell); + addComponent(JVxeTypes.select, JVxeSelectCell); + addComponent(JVxeTypes.selectSearch, JVxeSelectCell); // 下拉搜索 + addComponent(JVxeTypes.selectMultiple, JVxeSelectCell); // 下拉多选 + addComponent(JVxeTypes.date, JVxeDateCell); + addComponent(JVxeTypes.datetime, JVxeDateCell); + addComponent(JVxeTypes.time, JVxeTimeCell); + addComponent(JVxeTypes.upload, JVxeUploadCell); + addComponent(JVxeTypes.textarea, JVxeTextareaCell); + + // addComponent(JVxeTypes.tags, TagsInputCell, TagsSpanCell) + addComponent(JVxeTypes.progress, JVxeProgressCell); + + // addComponent(JVxeTypes.departSelect, JVxeDepartSelectCell) + // addComponent(JVxeTypes.userSelect, JVxeUserSelectCell) + // update-begin--author:liaozhiyang---date:20260413---for:【issues/7633】online子表支持分类字典树,自定义树 + addComponent(JVxeTypes.treeSelect, JVxeTreeSelectCell); + addComponent(JVxeTypes.catTreeSelect, JVxeCategorySelectCell); + // update-end--author:liaozhiyang---date:20260413---for:【issues/7633】online子表支持分类字典树,自定义树 +} + +export { componentMap, clearComponent } from './componentMapStore'; diff --git a/src/components/jeecg/JVxeTable/src/componentMapStore.ts b/src/components/jeecg/JVxeTable/src/componentMapStore.ts new file mode 100644 index 0000000..88f92b0 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/componentMapStore.ts @@ -0,0 +1,20 @@ +import type { JVxeVueComponent } from './types'; +import { JVxeTypes } from './types/JVxeTypes'; + +/** 仅存 componentMap 与 clearComponent,供 qiankun unmount 等场景使用,不引用任何 Cell 组件 */ +let componentMap = new Map(); +const JVxeComponents = 'JVxeComponents__'; +if (import.meta.env.DEV && componentMap.size === 0 && window[JVxeComponents] && window[JVxeComponents].size > 0) { + componentMap = window[JVxeComponents]; +} + +export { componentMap }; + +/** + * 清空注册的组件(乾坤子应用 unmount 时调用,仅引用本文件避免加载所有 Cell) + */ +export function clearComponent() { + componentMap.clear(); + // 代码逻辑说明: 【issues/860】生成的一对多代码,热更新之后点击新增卡死[暂时先解决] + import.meta.env.DEV && (window[JVxeComponents] = componentMap); +} diff --git a/src/components/jeecg/JVxeTable/src/components/JVxeDetailsModal.vue b/src/components/jeecg/JVxeTable/src/components/JVxeDetailsModal.vue new file mode 100644 index 0000000..48d63d0 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/JVxeDetailsModal.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/components/jeecg/JVxeTable/src/components/JVxeReloadEffect.ts b/src/components/jeecg/JVxeTable/src/components/JVxeReloadEffect.ts new file mode 100644 index 0000000..9f792c1 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/JVxeReloadEffect.ts @@ -0,0 +1,92 @@ +import { defineComponent, h, ref, watch } from 'vue'; +import { randomString } from '/@/utils/common/compUtils'; +import '../style/reload-effect.less'; + +// 修改数据特效 +export default defineComponent({ + props: { + vNode: null, + // 是否启用特效 + effect: Boolean, + }, + emits: ['effectBegin', 'effectEnd'], + setup(props, { emit }) { + // vNode: null, + const innerEffect = ref(props.effect); + // 应付同时多个特效 + const effectIdx = ref(0); + const effectList = ref([]); + + watch( + () => props.effect, + () => (innerEffect.value = props.effect) + ); + watch( + () => props.vNode, + (_vNode, old) => { + if (props.effect && old != null) { + let topLayer = renderSpan(old, 'top'); + effectList.value.push(topLayer); + } + }, + { immediate: true } + ); + + // 条件渲染内容 span + function renderVNode() { + if (props.vNode == null) { + return null; + } + let bottom = renderSpan(props.vNode, 'bottom'); + // 启用了特效,并且有旧数据,就渲染特效顶层 + if (innerEffect.value && effectList.value.length > 0) { + emit('effectBegin'); + // 1.4s 以后关闭特效 + window.setTimeout(() => { + let item = effectList.value[effectIdx.value]; + if (item && item.elm) { + // 特效结束后,展示先把 display 设为 none,而不是直接删掉该元素, + // 目的是为了防止页面重新渲染,导致动画重置 + item.elm.style.display = 'none'; + } + // 当所有的层级动画都结束时,再删掉所有元素 + if (++effectIdx.value === effectList.value.length) { + innerEffect.value = false; + effectIdx.value = 0; + effectList.value = []; + emit('effectEnd'); + } + }, 1400); + return [effectList.value, bottom]; + } else { + return bottom; + } + } + + // 渲染内容 span + function renderSpan(vNode, layer) { + let options = { + key: layer + effectIdx.value + randomString(6), + class: ['j-vxe-reload-effect-span', `layer-${layer}`], + style: {}, + // 代码逻辑说明: 【issues/1175】解决vxetable鼠标hover之后title显示不对的问题 + title: vNode, + + }; + if (layer === 'top') { + // 最新渲染的在下面 + options.style['z-index'] = 9999 - effectIdx.value; + } + return h('span', options, [vNode]); + } + + return () => + h( + 'div', + { + class: ['j-vxe-reload-effect-box'], + }, + [renderVNode()] + ); + }, +}); diff --git a/src/components/jeecg/JVxeTable/src/components/JVxeSubPopover.vue b/src/components/jeecg/JVxeTable/src/components/JVxeSubPopover.vue new file mode 100644 index 0000000..497dca2 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/JVxeSubPopover.vue @@ -0,0 +1,207 @@ + + + + diff --git a/src/components/jeecg/JVxeTable/src/components/JVxeToolbar.vue b/src/components/jeecg/JVxeTable/src/components/JVxeToolbar.vue new file mode 100644 index 0000000..9c3241b --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/JVxeToolbar.vue @@ -0,0 +1,144 @@ + + + diff --git a/src/components/jeecg/JVxeTable/src/components/cells/JVxeCategorySelectCell.vue b/src/components/jeecg/JVxeTable/src/components/cells/JVxeCategorySelectCell.vue new file mode 100644 index 0000000..f26f6d9 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/cells/JVxeCategorySelectCell.vue @@ -0,0 +1,190 @@ + + + diff --git a/src/components/jeecg/JVxeTable/src/components/cells/JVxeCheckboxCell.vue b/src/components/jeecg/JVxeTable/src/components/cells/JVxeCheckboxCell.vue new file mode 100644 index 0000000..2d1327d --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/cells/JVxeCheckboxCell.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/src/components/jeecg/JVxeTable/src/components/cells/JVxeDateCell.vue b/src/components/jeecg/JVxeTable/src/components/cells/JVxeDateCell.vue new file mode 100644 index 0000000..61eedfe --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/cells/JVxeDateCell.vue @@ -0,0 +1,91 @@ + + + diff --git a/src/components/jeecg/JVxeTable/src/components/cells/JVxeDragSortCell.vue b/src/components/jeecg/JVxeTable/src/components/cells/JVxeDragSortCell.vue new file mode 100644 index 0000000..971b612 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/cells/JVxeDragSortCell.vue @@ -0,0 +1,122 @@ + + + + + + diff --git a/src/components/jeecg/JVxeTable/src/components/cells/JVxeInputCell.vue b/src/components/jeecg/JVxeTable/src/components/cells/JVxeInputCell.vue new file mode 100644 index 0000000..499150d --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/cells/JVxeInputCell.vue @@ -0,0 +1,152 @@ + + + diff --git a/src/components/jeecg/JVxeTable/src/components/cells/JVxeNormalCell.vue b/src/components/jeecg/JVxeTable/src/components/cells/JVxeNormalCell.vue new file mode 100644 index 0000000..2ed7073 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/cells/JVxeNormalCell.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/components/jeecg/JVxeTable/src/components/cells/JVxeProgressCell.vue b/src/components/jeecg/JVxeTable/src/components/cells/JVxeProgressCell.vue new file mode 100644 index 0000000..39a3a56 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/cells/JVxeProgressCell.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/src/components/jeecg/JVxeTable/src/components/cells/JVxeRadioCell.vue b/src/components/jeecg/JVxeTable/src/components/cells/JVxeRadioCell.vue new file mode 100644 index 0000000..503adf5 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/cells/JVxeRadioCell.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/src/components/jeecg/JVxeTable/src/components/cells/JVxeSelectCell.vue b/src/components/jeecg/JVxeTable/src/components/cells/JVxeSelectCell.vue new file mode 100644 index 0000000..251acd6 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/cells/JVxeSelectCell.vue @@ -0,0 +1,237 @@ + + + diff --git a/src/components/jeecg/JVxeTable/src/components/cells/JVxeSlotCell.ts b/src/components/jeecg/JVxeTable/src/components/cells/JVxeSlotCell.ts new file mode 100644 index 0000000..35fcb9f --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/cells/JVxeSlotCell.ts @@ -0,0 +1,41 @@ +import { computed, defineComponent, h } from 'vue'; +import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/src/hooks/useJVxeComponent'; +import { JVxeComponent } from '/@/components/jeecg/JVxeTable/src/types/JVxeComponent'; + +export default defineComponent({ + name: 'JVxeSlotCell', + props: useJVxeCompProps(), + setup(props: JVxeComponent.Props) { + const data = useJVxeComponent(props); + const slotProps = computed(() => { + return { + value: data.innerValue.value, + row: data.row.value, + column: data.originColumn.value, + params: props.params, + $table: props.params.$table, + rowId: props.params.rowid, + index: props.params.rowIndex, + rowIndex: props.params.rowIndex, + columnIndex: props.params.columnIndex, + scrolling: props.renderOptions.scrolling, + reloadEffect: props.renderOptions.reloadEffect.enabled, + triggerChange: (v) => data.handleChangeCommon(v), + }; + }); + return () => { + let { slot } = props.renderOptions; + if (slot) { + return h('div', {}, slot(slotProps.value)); + } else { + return h('div'); + } + }; + }, + // 【组件增强】注释详见:JVxeComponent.Enhanced + enhanced: { + switches: { + editRender: false, + }, + } as JVxeComponent.EnhancedPartial, +}); diff --git a/src/components/jeecg/JVxeTable/src/components/cells/JVxeTextareaCell.vue b/src/components/jeecg/JVxeTable/src/components/cells/JVxeTextareaCell.vue new file mode 100644 index 0000000..fa892ef --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/cells/JVxeTextareaCell.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/components/jeecg/JVxeTable/src/components/cells/JVxeTimeCell.vue b/src/components/jeecg/JVxeTable/src/components/cells/JVxeTimeCell.vue new file mode 100644 index 0000000..feb0cbb --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/cells/JVxeTimeCell.vue @@ -0,0 +1,64 @@ + + + diff --git a/src/components/jeecg/JVxeTable/src/components/cells/JVxeTreeSelectCell.vue b/src/components/jeecg/JVxeTable/src/components/cells/JVxeTreeSelectCell.vue new file mode 100644 index 0000000..a94fab6 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/cells/JVxeTreeSelectCell.vue @@ -0,0 +1,244 @@ + + + diff --git a/src/components/jeecg/JVxeTable/src/components/cells/JVxeUploadCell.vue b/src/components/jeecg/JVxeTable/src/components/cells/JVxeUploadCell.vue new file mode 100644 index 0000000..f9d73ec --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/components/cells/JVxeUploadCell.vue @@ -0,0 +1,77 @@ + + + diff --git a/src/components/jeecg/JVxeTable/src/hooks/cells/useJVxeUploadCell.ts b/src/components/jeecg/JVxeTable/src/hooks/cells/useJVxeUploadCell.ts new file mode 100644 index 0000000..c3a7979 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/cells/useJVxeUploadCell.ts @@ -0,0 +1,139 @@ +import { ref, computed, watch } from 'vue'; + +import {getTenantId, getToken} from '/@/utils/auth'; +import { getFileAccessHttpUrl } from '/@/utils/common/compUtils'; +import { JVxeComponent } from '../../types/JVxeComponent'; +import { useJVxeComponent } from '../useJVxeComponent'; + +/** + * use 公共上传组件 + * @param props + * @param options 组件选项,token:默认是否传递token,action:默认上传路径,multiple:是否允许多文件 + */ +export function useJVxeUploadCell(props: JVxeComponent.Props, options?) { + const setup = useJVxeComponent(props); + const { innerValue, originColumn, handleChangeCommon } = setup; + + const innerFile = ref(null); + + /** upload headers */ + const uploadHeaders = computed(() => { + let headers = {}; + if ((originColumn.value.token ?? options?.token ?? false) === true) { + headers['X-Access-Token'] = getToken(); + } + let tenantId = getTenantId(); + headers['X-Tenant-Id'] = tenantId ? tenantId : '0'; + return headers; + }); + + /** 上传请求地址 */ + const uploadAction = computed(() => { + if (!originColumn.value.action) { + return options?.action ?? ''; + } else { + return originColumn.value.action; + } + }); + const hasFile = computed(() => innerFile.value != null); + const responseName = computed(() => originColumn.value.responseName ?? 'message'); + + watch( + innerValue, + (val) => { + if (val) { + innerFile.value = val; + } else { + innerFile.value = null; + } + }, + { immediate: true } + ); + + function handleChangeUpload(info) { + let { file } = info; + let value = { + name: file.name, + type: file.type, + size: file.size, + status: file.status, + percent: file.percent, + path: innerFile.value?.path ?? '', + }; + if (file.response) { + value['responseName'] = file.response[responseName.value]; + } + let paths: string[] = []; + if (options?.multiple && innerFile.value && innerFile.value.path) { + paths = innerFile.value.path.split(','); + } + if (file.status === 'done') { + if (typeof file.response.success === 'boolean') { + if (file.response.success) { + paths.push(file.response[responseName.value]); + value['path'] = paths.join(','); + handleChangeCommon(value); + } else { + value['status'] = 'error'; + value['message'] = file.response.message || '未知错误'; + } + } else { + // 考虑到如果设置action上传路径为非jeecg-boot后台,可能不会返回 success 属性的情况,就默认为成功 + paths.push(file.response[responseName.value]); + value['path'] = paths.join(','); + handleChangeCommon(value); + } + } else if (file.status === 'error') { + value['message'] = file.response.message || '未知错误'; + } + innerFile.value = value; + } + + function handleClickDownloadFile() { + let { url, path } = innerFile.value || {}; + if (!url || url.length === 0) { + if (path && path.length > 0) { + url = getFileAccessHttpUrl(path.split(',')[0]); + } + } + if (url) { + window.open(url); + } + } + + function handleClickDeleteFile() { + handleChangeCommon(null); + } + + return { + ...setup, + innerFile, + uploadAction, + uploadHeaders, + hasFile, + responseName, + handleChangeUpload, + handleClickDownloadFile, + handleClickDeleteFile, + }; +} + +export function fileGetValue(value) { + if (value && value.path) { + return value.path; + } + return value; +} + +export function fileSetValue(value) { + if (value) { + let first = value.split(',')[0]; + let name = first.substring(first.lastIndexOf('/') + 1); + return { + name: name, + path: value, + status: 'done', + }; + } + return value; +} diff --git a/src/components/jeecg/JVxeTable/src/hooks/useColumns.ts b/src/components/jeecg/JVxeTable/src/hooks/useColumns.ts new file mode 100644 index 0000000..073cb1a --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/useColumns.ts @@ -0,0 +1,440 @@ +import type { JVxeColumn, JVxeDataProps, JVxeTableProps } from '../types'; +import { computed, nextTick, toRaw } from 'vue'; +import { isArray, isEmpty, isPromise } from '/@/utils/is'; +import { cloneDeep } from 'lodash-es'; +import { JVxeTypePrefix, JVxeTypes } from '../types/JVxeTypes'; +import { initDictOptions } from '/@/utils/dict'; +import { pushIfNotExist } from '/@/utils/common/compUtils'; +import { getEnhanced } from '../utils/enhancedUtils'; +import { isRegistered } from '../utils/registerUtils'; +import { JVxeComponent } from '../types/JVxeComponent'; +import { useValidateRules } from './useValidateRules'; +import { JVxeTableMethods } from '../types'; + +// handle 方法参数 +export interface HandleArgs { + props: JVxeTableProps; + slots: any; + data: JVxeDataProps; + methods: JVxeTableMethods; + col?: JVxeColumn; + columns: JVxeColumn[]; + renderOptions?: any; + enhanced?: JVxeComponent.Enhanced; +} + +export function useColumns(props: JVxeTableProps, data: JVxeDataProps, methods: JVxeTableMethods, slots) { + data.vxeColumns = computed(() => { + // linkageConfig变化时也需要执行 + const linkageConfig = toRaw(props.linkageConfig); + if (linkageConfig) { + // console.log(linkageConfig); + } + let columns: JVxeColumn[] = []; + if (isArray(props.columns)) { + // handle 方法参数 + const args: HandleArgs = { props, slots, data, methods, columns }; + let seqColumn, selectionColumn, expandColumn, dragSortColumn; + + const handleColumn = (column: JVxeColumn, container: JVxeColumn[]) => { + // 排除未授权的列 1 = 显示/隐藏; 2 = 禁用 + let auth = methods.getColAuth(column.key); + if (auth?.type == '1' && !auth.isAuth) { + return; + } else if (auth?.type == '2' && !auth.isAuth) { + column.disabled = true; + } + // type 不填,默认为 normal + if (column.type == null || isEmpty(column.type)) { + column.type = JVxeTypes.normal; + } + let col: JVxeColumn = cloneDeep(column); + // 处理隐藏列 + if (col.type === JVxeTypes.hidden) { + return handleInnerColumn(args, col, handleHiddenColumn); + } + // 处理子级列 + // 判断是否是分组列,如果当前是父级,则无需处理 render + if (Array.isArray(col.children) && col.children.length > 0) { + const children: JVxeColumn[] = []; + col.children.forEach((child: JVxeColumn) => handleColumn(child, children)); + col.children = children; + container.push(col); + return; + } + // 组件未注册,自动设置为 normal + if (!isRegistered(col.type)) { + col.type = JVxeTypes.normal; + } + args.enhanced = getEnhanced(col.type); + args.col = col; + args.renderOptions = { + bordered: props.bordered, + disabled: props.disabled, + scrolling: data.scrolling, + isDisabledRow: methods.isDisabledRow, + listeners: { + trigger: (name, event) => methods.trigger(name, event), + valueChange: (event) => methods.trigger('valueChange', event), + /** 重新排序行 */ + rowResort: (event) => { + methods.doSort(event.oldIndex, event.newIndex); + methods.trigger('dragged', event); + }, + /** 在当前行下面插入一行 */ + rowInsertDown: (rowIndex) => methods.insertRows({}, rowIndex + 1), + }, + }; + if (col.type === JVxeTypes.rowNumber) { + seqColumn = col; + container.push(col); + } else if (col.type === JVxeTypes.rowRadio || col.type === JVxeTypes.rowCheckbox) { + selectionColumn = col; + container.push(col); + } else if (col.type === JVxeTypes.rowExpand) { + expandColumn = col; + container.push(col); + } else if (col.type === JVxeTypes.rowDragSort) { + dragSortColumn = col; + container.push(col); + } else { + col.params = column; + args.columns = container; + handlerCol(args); + } + } + + props.columns.forEach((column: JVxeColumn) => handleColumn(column, columns)); + + handleInnerColumn(args, seqColumn, handleSeqColumn); + handleInnerColumn(args, selectionColumn, handleSelectionColumn); + handleInnerColumn(args, expandColumn, handleExpandColumn); + handleInnerColumn(args, dragSortColumn, handleDragSortColumn, true); + // update-begin--author:liaozhiyang---date:2024-05-30---for【TV360X-371】不可编辑组件必填缺少*号 + customComponentAddStar(columns); + } + return columns; + }); +} + +/** + * 2024-05-30 + * liaozhiyang + * 不可编辑组件必填通过title人为加*号 + */ +function customComponentAddStar(columns) { + columns.forEach((column) => { + const { params } = column; + if (params) { + const { validateRules, type } = params; + if ( + validateRules?.length && + [ + JVxeTypes.checkbox, + JVxeTypes.radio, + JVxeTypes.upload, + JVxeTypes.progress, + JVxeTypes.departSelect, + JVxeTypes.userSelect, + JVxeTypes.image, + JVxeTypes.file, + ].includes(type) + ) { + if (validateRules.find((item) => item.required)) { + column.title = ` * ${column.title}`; + } + } + } + }); +} + +/** 处理内置列 */ +function handleInnerColumn(args: HandleArgs, col: JVxeColumn, handler: (args: HandleArgs) => void, assign?: boolean) { + let renderOptions = col?.editRender || col?.cellRender; + return handler({ + ...args, + col: col, + renderOptions: assign ? Object.assign({}, args.renderOptions, renderOptions) : renderOptions, + }); +} + +/** + * 处理隐藏列 + */ +function handleHiddenColumn({ col, columns }: HandleArgs) { + col!.params = cloneDeep(col); + delete col!.type; + col!.field = col!.key; + col!.visible = false; + columns.push(col!); +} + +/** + * 处理行号列 + */ +function handleSeqColumn({ props, col, columns }: HandleArgs) { + // 判断是否开启了行号列 + if (props.rowNumber) { + let column = { + type: 'seq', + title: '#', + width: 60, + // 【QQYUN-8405】 + fixed: props.rowNumberFixed, + align: 'center', + }; + // 代码逻辑说明: 【QQYUN-8405】vxetable支持序号是否固定(移动端需要) + if (props.rowNumberFixed === 'none') { + delete column.fixed; + } + if (col) { + Object.assign(col, column); + } else { + columns.unshift(column as any); + } + } +} + +/** + * 处理可选择列 + */ +function handleSelectionColumn({ props, data, col, columns }: HandleArgs) { + // 判断是否开启了可选择行 + // -update-begin--author:liaozhiyang---date:20240617---for:【TV360X-1002】详情页面行编辑不显示checkbox + if (props.rowSelection && props.disabled == false) { + // -update-end--author:liaozhiyang---date:20240617---for:【TV360X-1002】详情页面行编辑不显示checkbox + let width = 45; + if (data.statistics.has && !props.rowExpand && !props.dragSort) { + width = 60; + } + let column: any = { + type: props.rowSelectionType, + width: width, + fixed: 'left', + align: 'center', + }; + // 代码逻辑说明: 【issues/1162】JVxeTable列过长(出现横向滚动条)时无法拖拽排序 + if (props.rowSelectionFixed === 'none') { + delete column.fixed; + } + if (col) { + Object.assign(col, column); + } else { + columns.unshift(column as any); + } + } +} + +/** + * 处理可展开行 + */ +function handleExpandColumn({ props, data, col, columns }: HandleArgs) { + // 是否可展开行 + if (props.rowExpand) { + let width = 40; + if (data.statistics.has && !props.dragSort) { + width = 60; + } + let column = { + type: 'expand', + title: '', + width: width, + fixed: 'left', + align: 'center', + slots: { content: 'expandContent' }, + }; + if (col) { + Object.assign(col, column); + } else { + columns.unshift(column as any); + } + } +} + +/** 处理可排序列 */ +function handleDragSortColumn({ props, data, col, columns, renderOptions }: HandleArgs) { + // 是否可拖动排序 + if (props.dragSort) { + let width = 40; + if (data.statistics.has) { + width = 60; + } + let column: any = { + title: '', + width: width, + fixed: 'left', + align: 'center', + // 代码逻辑说明: 【QQYUN-8785】online表单列位置的id未做限制,拖动其他列到id列上面,同步数据库时报错 + params: { + insertRow: props.insertRow, + notAllowDrag: props.notAllowDrag, + ...col?.params, + }, + }; + // 代码逻辑说明: 【issues/1162】JVxeTable列过长(出现横向滚动条)时无法拖拽排序 + if (props.dragSortFixed === 'none') { + delete column.fixed; + } + let cellRender = { + name: JVxeTypePrefix + JVxeTypes.rowDragSort, + sortKey: props.sortKey, + }; + if (renderOptions) { + column.cellRender = Object.assign(renderOptions, cellRender); + } else { + column.cellRender = cellRender; + } + if (col) { + Object.assign(col, column); + } else { + columns.unshift(column); + } + } +} + +/** 处理自定义组件列 */ +function handlerCol(args: HandleArgs) { + const { props, col, columns, enhanced } = args; + if (!col) return; + let { type } = col; + col.field = col.key; + delete col.type; + let renderName = 'cellRender'; + // 渲染选项 + let $renderOptions: any = { name: JVxeTypePrefix + type }; + if (enhanced?.switches.editRender) { + if (!(enhanced.switches.visible || props.alwaysEdit)) { + renderName = 'editRender'; + } + // $renderOptions.type = (enhanced.switches.visible || props.alwaysEdit) ? 'visible' : 'default' + } + col[renderName] = $renderOptions; + // 代码逻辑说明: 【QQYUN-5806】js增强改变下拉搜索options(添加customOptions为true不读字典,走自己的options) + !col.params.customOptions && handleDict(args); + handleRules(args); + handleStatistics(args); + handleSlots(args); + handleLinkage(args); + handleReloadEffect(args); + + if (col.editRender) { + Object.assign(col.editRender, args.renderOptions); + } + if (col.cellRender) { + Object.assign(col.cellRender, args.renderOptions); + } + + // slot 类型列:通过 titlePrefix 在表头显示编辑图标(不能用 editRender,否则非编辑状态下 cellRender 会被绕过) + if (col.params.type === JVxeTypes.slot && col.cellRender && !col.editRender) { + col.titlePrefix = { icon: 'vxe-table-icon-edit' }; + } + + columns.push(col); +} + +/** + * 处理字典 + */ +async function handleDict({ col, methods }: HandleArgs) { + if (col && col.params.dictCode) { + /** 加载数据字典并合并到 options */ + try { + // 查询字典 + if (!isPromise(col.params.optionsPromise)) { + col.params.optionsPromise = new Promise(async (resolve) => { + // 代码逻辑说明: VUEN-1180 【代码生成】子表不支持带条件? + let dictCodeString = col.params.dictCode; + if (dictCodeString) { + dictCodeString = encodeURI(dictCodeString); + } + const dictOptions: any = await initDictOptions(dictCodeString); + let options = col.params.options ?? []; + dictOptions.forEach((dict) => { + // 过滤重复数据 + if (options.findIndex((o) => o.value === dict.value) === -1) { + options.push(dict); + } + }); + resolve(options); + }); + } + col.params.options = await col.params.optionsPromise; + await nextTick(); + await methods.getXTable().updateData(); + } catch (e) { + console.group(`[JVxeTable] 查询字典 "${col.params.dictCode}" 时发生异常!`); + console.warn(e); + console.groupEnd(); + } + } +} + +/** + * 处理校验 + */ +function handleRules(args: HandleArgs) { + if (isArray(args.col?.validateRules)) { + useValidateRules(args); + } +} + +/** + * 处理统计列 + */ +function handleStatistics({ col, data }: HandleArgs) { + // sum = 求和、average = 平均值 + if (col && isArray(col.statistics)) { + data.statistics.has = true; + col.statistics.forEach((item) => { + if (!isEmpty(item)) { + let arr = data.statistics[(item as string).toLowerCase()]; + if (isArray(arr)) { + pushIfNotExist(arr, col.key); + } + } + }); + } +} + +/** + * 处理插槽 + */ +function handleSlots({ slots, col, renderOptions }: HandleArgs) { + // slot 组件特殊处理 + if (col && col.params.type === JVxeTypes.slot) { + if (!isEmpty(col.slotName) && slots.hasOwnProperty(col.slotName)) { + renderOptions.slot = slots[col.slotName]; + } + } +} + +/** 处理联动列 */ +function handleLinkage({ data, col, renderOptions, methods }: HandleArgs) { + // 处理联动列,联动列只能作用于 select 组件 + if (col && col.params.type === JVxeTypes.select && data.innerLinkageConfig != null) { + // 判断当前列是否是联动列 + if (data.innerLinkageConfig.has(col.key)) { + renderOptions.linkage = { + config: data.innerLinkageConfig.get(col.key), + getLinkageOptionsAsync: methods.getLinkageOptionsAsync, + getLinkageOptionsSibling: methods.getLinkageOptionsSibling, + handleLinkageSelectChange: methods.handleLinkageSelectChange, + }; + } + } +} + +function handleReloadEffect({ props, data, renderOptions }: HandleArgs) { + renderOptions.reloadEffect = { + enabled: props.reloadEffect, + getMap() { + return data.reloadEffectRowKeysMap; + }, + isEffect(rowId) { + return data.reloadEffectRowKeysMap[rowId] === true; + }, + removeEffect(rowId) { + return (data.reloadEffectRowKeysMap[rowId] = false); + }, + }; +} diff --git a/src/components/jeecg/JVxeTable/src/hooks/useColumnsCache.ts b/src/components/jeecg/JVxeTable/src/hooks/useColumnsCache.ts new file mode 100644 index 0000000..1f5a761 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/useColumnsCache.ts @@ -0,0 +1,105 @@ +import { computed } from 'vue'; +import { router } from '/@/router'; +import { createLocalStorage } from '/@/utils/cache'; +import { useMessage } from '/@/hooks/web/useMessage'; + +export function useColumnsCache({ cacheColumnsKey, refs }: any) { + const $ls = createLocalStorage(); + const { createMessage: $message } = useMessage(); + const cacheKey = computed(() => { + const path = router.currentRoute.value.fullPath; + let key = path.replace(/[\/\\]/g, '_'); + if (cacheColumnsKey) { + key += ':' + cacheColumnsKey; + } + return 'vxe-columnCache:' + key; + }); + const initSetting = (props) => { + const columnCache = $ls.get(cacheKey.value); + if (columnCache) { + columnCache.forEach((key) => { + const column = props.columns.find((item) => item.key === key); + if (column) { + column.visible = false; + } + }); + } + }; + // const initSetting = (refs) => { + // let columnCache = $ls.get(cacheKey.value); + // if (columnCache) { + // const $grid = refs.gridRef.value!.getRefMaps().refTable.value; + // console.log('refs.gridRef', $grid); + // const { fullColumn } = $grid.getTableColumn(); + // const hideColumns = getHideColumn(fullColumn, columnCache); + // if (hideColumns?.length) { + // hideColumns.forEach((column) => { + // $grid.hideColumn(column); + // }); + // } + // } + // console.log(columnCache); + // }; + function saveSetting($grid: any) { + console.log($grid); + const { fullColumn, visibleColumn } = $grid.getTableColumn(); + const hideColumnKey = getHideColumnKey(fullColumn, visibleColumn); + if (hideColumnKey.length) { + $ls.set(cacheKey.value, hideColumnKey); + $message.success('保存成功'); + } + } + const resetSetting = ($grid) => { + const columnCache = $ls.get(cacheKey.value); + if (columnCache) { + const { fullColumn } = $grid.getTableColumn(); + const hideColumns = getHideColumn(fullColumn, columnCache); + if (hideColumns?.length) { + hideColumns.forEach((column) => { + if (columnCache.includes(column?.params?.key)) { + $grid.showColumn(column); + } + }); + } + } + $ls.remove(cacheKey.value); + $message.success('重置成功'); + }; + const getHideColumn = (fullColumn, columnCache) => { + const result: any = []; + if (columnCache?.length) { + console.log('--fullColumn:',fullColumn); + columnCache.forEach((key) => { + const column = fullColumn.find((item) => item?.params?.key === key); + if (column) { + result.push(column); + } + }); + } + return result; + }; + const getHideColumnKey = (fullColumn, visibleColumn) => { + const reuslt: any = []; + if (fullColumn.length === visibleColumn.length) { + return reuslt; + } else { + fullColumn.forEach((item) => { + const fKey = item?.params?.key; + if (fKey) { + const vItem = visibleColumn.find((item) => { + return item?.params?.key === fKey; + }); + if (!vItem) { + reuslt.push(fKey); + } + } + }); + return reuslt; + } + }; + return { + initSetting, + resetSetting, + saveSetting, + }; +} diff --git a/src/components/jeecg/JVxeTable/src/hooks/useData.ts b/src/components/jeecg/JVxeTable/src/hooks/useData.ts new file mode 100644 index 0000000..64e4523 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/useData.ts @@ -0,0 +1,112 @@ +import { ref, reactive, provide, resolveComponent } from 'vue'; +import { useDesign } from '/@/hooks/web/useDesign'; +import { JVxeDataProps, JVxeRefs, JVxeTableProps } from '../types'; +import { VxeGridInstance } from 'vxe-table'; +import { randomString } from '/@/utils/common/compUtils'; + +export function useData(props: JVxeTableProps): JVxeDataProps { + const { prefixCls } = useDesign('j-vxe-table'); + provide('prefixCls', prefixCls); + return { + prefixCls: prefixCls, + caseId: `j-vxe-${randomString(8)}`, + vxeDataSource: ref([]), + scroll: reactive({ top: 0, left: 0 }), + scrolling: ref(false), + defaultVxeProps: reactive({ + // rowId: props.rowKey, + rowConfig: { + keyField: props.rowKey, + // 高亮hover的行 + isHover: true, + }, + + // --- 【issues/209】自带的tooltip会错位,所以替换成原生的title --- + // 溢出隐藏并显示tooltip + showOverflow: "title", + // 表头溢出隐藏并显示tooltip + showHeaderOverflow: "title", + // --- 【issues/209】自带的tooltip会错位,所以替换成原生的title --- + + showFooterOverflow: true, + // 可编辑配置 + editConfig: { + trigger: 'click', + mode: 'cell', + //activeMethod: () => !props.disabled, + beforeEditMethod: () => !props.disabled, + }, + expandConfig: { + iconClose: 'vxe-icon-arrow-right', + iconOpen: 'vxe-icon-arrow-down', + ...props.expandConfig, + }, + // 虚拟滚动配置,y轴大于xx条数据时启用虚拟滚动 + scrollY: { + gt: 30, + }, + scrollX: { + gt: 20, + // 暂时关闭左右虚拟滚动 + enabled: false, + }, + radioConfig: { + // 保留勾选状态 + reserve: true, + highlight: true, + }, + checkboxConfig: { + // 保留勾选状态 + reserve: true, + highlight: true, + }, + // update-begin--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + // 禁止点击表格外部时清空校验状态,避免每次点击空白区域触发 reactData.validErrorMaps={} 导致不必要的重渲染 + validConfig: { + autoClear: false, + }, + // update-end--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + keyboardConfig: { + // 删除键功能 + isDel: false, + // Esc键关闭编辑功能 + isEsc: true, + // Tab 键功能 + isTab: true, + // 任意键进入编辑(功能键除外) + isEdit: true, + // 方向键功能 + isArrow: true, + // 回车键功能 + isEnter: true, + // 如果功能被支持,用于 column.type=checkbox|radio,开启空格键切换复选框或单选框状态功能 + isChecked: true, + }, + }), + selectedRows: ref([]), + selectedRowIds: ref([]), + disabledRowIds: [], + statistics: reactive({ + has: false, + sum: [], + average: [], + }), + authsMap: ref(null), + innerEditRules: {}, + innerLinkageConfig: new Map(), + reloadEffectRowKeysMap: reactive({}), + }; +} + +export function useRefs(): JVxeRefs { + return { + gridRef: ref(), + subPopoverRef: ref(), + detailsModalRef: ref(), + }; +} + +export function useResolveComponent(...t: any[]): any { + // @ts-ignore + return resolveComponent(...t); +} diff --git a/src/components/jeecg/JVxeTable/src/hooks/useDataSource.ts b/src/components/jeecg/JVxeTable/src/hooks/useDataSource.ts new file mode 100644 index 0000000..db0afd8 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/useDataSource.ts @@ -0,0 +1,37 @@ +import { nextTick, watch } from 'vue'; +import { JVxeDataProps, JVxeRefs, JVxeTableMethods } from '../types'; + +export function useDataSource(props, data: JVxeDataProps, methods: JVxeTableMethods, refs: JVxeRefs) { + watch( + () => props.dataSource, + async () => { + data.disabledRowIds = []; + // update-begin--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + data.vxeDataSource.value = props.dataSource.map(row => ({ ...row })); + // update-end--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + data.vxeDataSource.value.forEach((row, rowIndex) => { + // 判断是否是禁用行 + if (methods.isDisabledRow(row, rowIndex)) { + data.disabledRowIds.push(row.id); + } + // 处理联动回显数据 + methods.handleLinkageBackData(row); + }); + await waitRef(refs.gridRef); + methods.recalcSortNumber(); + }, + { immediate: true } + ); +} + +function waitRef($ref) { + return new Promise((resolve) => { + (function next() { + if ($ref.value) { + resolve($ref); + } else { + nextTick(() => next()); + } + })(); + }); +} diff --git a/src/components/jeecg/JVxeTable/src/hooks/useDragSort.ts b/src/components/jeecg/JVxeTable/src/hooks/useDragSort.ts new file mode 100644 index 0000000..0ff8fe1 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/useDragSort.ts @@ -0,0 +1,146 @@ +import { onMounted, onUnmounted, nextTick, watch } from 'vue'; +import { JVxeTableMethods, JVxeTableProps } from '/@/components/jeecg/JVxeTable/src/types'; +import Sortable from 'sortablejs'; +import { isEnabledVirtualYScroll } from '/@/components/jeecg/JVxeTable/utils'; + +export function useDragSort(props: JVxeTableProps, methods: JVxeTableMethods) { + if (props.dragSort) { + let sortable2: Sortable; + let initTime: any; + + onMounted(() => { + // 加载完成之后再绑定拖动事件 + initTime = setTimeout(createSortable, 300); + }); + + onUnmounted(() => { + clearTimeout(initTime); + if (sortable2) { + sortable2.destroy(); + } + }); + // update-begin--author:liaozhiyang---date:20260415---for:【QQYUN-15134】修复jvxetable使用fixed固定后无法拖拽 + // 代码逻辑说明:监听 maxHeight 变化(弹窗全屏↔缩小切换时触发)。 + // 解决:maxHeight 变化时销毁旧 Sortable 并重新初始化,让其绑定到正确的 tbody。 + watch( + () => props.maxHeight, + () => { + if (sortable2) { + sortable2.destroy(); + sortable2 = null as any; + } + clearTimeout(initTime); + initTime = setTimeout(createSortable, 300); + } + ); + + function createSortable() { + let xTable = methods.getXTable(); + // 代码逻辑说明:拖拽排序列默认 fixed:left,此时 drag-btn 在固定列 wrapper 的 tbody 内。 + // 若 dragSortFixed!='none',优先绑定固定列 wrapper 的 tbody,确保 Sortable 能捕获拖拽事件; + // 若 dragSortFixed='none'(拖拽列不固定),则绑定主 tbody。 + const domFixed = + props.dragSortFixed !== 'none' + ? xTable.$el.querySelector('.vxe-table--fixed-left-wrapper .vxe-table--body tbody') + : null; + const domMain = xTable.$el.querySelector('.vxe-table--body-inner-wrapper > .vxe-table--body tbody'); + const dom = domFixed || domMain; + if (!dom) { + console.warn('[JVxeTable] 拖拽排序初始化失败,可能是vxe-table升级导致的版本不兼容。'); + return; + } + + // 拖拽过程中悬停目标行的 DOM 索引(由 onMove 实时更新) + let hoverIndex = -1; + // 拖拽起始行的 DOM 索引(用于 onMove 中判断是否悬停自身) + let dragStartIndex = -1; + + /** + * 为所有 tbody 中第 idx 行(0-based)添加或移除 CSS class。 + * 用于跨主体 + 固定列 wrapper 同步视觉状态,避免只改一侧 tbody 导致样式不一致。 + */ + function setRowClass(idx: number, cls: string, add: boolean) { + xTable.$el.querySelectorAll(`.vxe-table--body tbody tr:nth-child(${idx + 1})`).forEach((tr) => { + (tr as HTMLElement).classList[add ? 'add' : 'remove'](cls); + }); + } + /** 清除所有带有指定 class 的行 */ + function clearRowClass(cls: string) { + xTable.$el.querySelectorAll(`.${cls}`).forEach((tr) => { + (tr as HTMLElement).classList.remove(cls); + }); + } + + sortable2 = Sortable.create(dom as HTMLElement, { + handle: '.drag-btn', + // 代码逻辑说明: 【QQYUN-8785】online表单列位置的id未做限制,拖动其他列到id列上面,同步数据库时报错 + filter: '.not-allow-drag', + draggable: '.allow-drag', + direction: 'vertical', + animation: 0, + onStart(e) { + // 初始化悬停索引为起始位置,并为被拖起的行添加禁用效果 + hoverIndex = e.oldIndex!; + dragStartIndex = e.oldIndex!; + setRowClass(e.oldIndex!, 'j-vxe-drag-source', true); + }, + onMove(e) { + // 代码逻辑说明:拖拽期间只记录悬停目标的 DOM 索引,阻止 Sortable 实时交换行位置。 + // 好处:所有 tbody(主体 + 固定列 wrapper)在松手之前均保持原顺序,不会产生视觉错位; + // 松开鼠标后统一由 vxe-table 数据驱动重渲染,fixed 列拖拽排序问题彻底解决。 + const idx = Array.from((e.from as HTMLElement).children).indexOf(e.related as HTMLElement); + if (idx !== -1) { + hoverIndex = idx; + } + // 更新悬停行指示线:先清除旧状态,悬停自身时不显示 + clearRowClass('j-vxe-drag-hover-top'); + clearRowClass('j-vxe-drag-hover-bottom'); + if (hoverIndex !== dragStartIndex) { + // 向下拖(插入悬停行下方)→ 底部线;向上拖(插入悬停行上方)→ 顶部线 + const cls = hoverIndex > dragStartIndex ? 'j-vxe-drag-hover-bottom' : 'j-vxe-drag-hover-top'; + setRowClass(hoverIndex, cls, true); + } + return false; // 阻止 Sortable 移动 DOM 行 + }, + onEnd(e: any) { + // 拖拽结束,清除所有视觉状态 + clearRowClass('j-vxe-drag-source'); + clearRowClass('j-vxe-drag-hover-top'); + clearRowClass('j-vxe-drag-hover-bottom'); + // -update-begin--author:liaozhiyang---date:20240619---for:【TV360X-585】拖动字段虚拟滚动不好使 + const isRealEnabledVirtual = isEnabledVirtualYScroll(props, xTable); + let newIndex: number; + let oldIndex: number; + + if (isRealEnabledVirtual) { + // 虚拟滚动:onMove 返回 false 后 DOM 行未动,e.item 就是被拖拽行本身 + const dragNode = e.item as HTMLElement; + const dragRowInfo = xTable.getRowNode(dragNode); + if (!dragRowInfo) return; + oldIndex = dragRowInfo.index; + if (hoverIndex === e.oldIndex) return; + + // 通过 hoverIndex 对应的可视区 DOM 节点获取实际数据索引 + const hoverNode = (e.from as HTMLElement).childNodes[hoverIndex] as HTMLElement; + if (!hoverNode) return; + const hoverRowInfo = xTable.getRowNode(hoverNode); + if (!hoverRowInfo) return; + newIndex = hoverRowInfo.index; + } else { + // 非虚拟滚动:DOM 行从未移动,直接使用 hoverIndex 作为目标索引,无需还原 DOM + oldIndex = e.oldIndex; + newIndex = hoverIndex; + if (oldIndex === newIndex) return; + } + // -update-end--author:liaozhiyang---date:20240619---for:【TV360X-585】拖动字段虚拟滚动不好使 + + nextTick(() => { + methods.doSort(oldIndex, newIndex); + methods.trigger('dragged', { oldIndex: oldIndex, newIndex: newIndex }); + }); + }, + }); + } + // update-begin--author:liaozhiyang---date:20260415---for:【QQYUN-15134】修复jvxetable使用fixed固定后无法拖拽 + } +} diff --git a/src/components/jeecg/JVxeTable/src/hooks/useFinallyProps.ts b/src/components/jeecg/JVxeTable/src/hooks/useFinallyProps.ts new file mode 100644 index 0000000..9314d66 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/useFinallyProps.ts @@ -0,0 +1,132 @@ +import { unref, computed, ref, watch, nextTick } from 'vue'; +import { merge, debounce } from 'lodash-es'; +import { isArray } from '/@/utils/is'; +import { useAttrs } from '/@/hooks/core/useAttrs'; +import { useKeyboardEdit } from '../hooks/useKeyboardEdit'; +import { JVxeDataProps, JVxeTableMethods, JVxeTableProps } from '../types'; + +export function useFinallyProps(props: JVxeTableProps, data: JVxeDataProps, methods: JVxeTableMethods) { + const attrs = useAttrs(); + // vxe 键盘操作配置 + const { keyboardEditConfig } = useKeyboardEdit(props); + // vxe 最终 editRules + const vxeEditRules = computed(() => merge({}, props.editRules, data.innerEditRules)); + // vxe 最终 events + const vxeEvents = computed(() => { + let listeners = { ...unref(attrs) }; + let events = { + onScroll: methods.handleVxeScroll, + onCellClick: methods.handleCellClick, + onEditClosed: methods.handleEditClosed, + onEditActived: methods.handleEditActived, + onRadioChange: methods.handleVxeRadioChange, + onCheckboxAll: methods.handleVxeCheckboxAll, + onCheckboxChange: methods.handleVxeCheckboxChange, + // 代码逻辑说明: 【QQYUN-8566】JVXETable无法记住列设置 + onCustom: methods.handleCustom, + }; + // 用户传递的事件,进行合并操作 + Object.keys(listeners).forEach((key) => { + let listen = listeners[key]; + if (events.hasOwnProperty(key)) { + if (isArray(listen)) { + listen.push(events[key]); + } else { + listen = [events[key], listen]; + } + } + events[key] = listen; + }); + return events; + }); + + // vxe 最终 props + const vxePropsMerge = computed(() => { + // 代码逻辑说明: 【QQYUN-8785】online表单列位置的id未做限制,拖动其他列到id列上面,同步数据库时报错 + let rowClass = {}; + if (props.dragSort) { + rowClass = { + rowClassName: (params) => { + let { row } = params; + const find = props.notAllowDrag?.find((item:any) => { + const {key, value} = item; + return row[key] == value; + }); + // 业务传进的来的rowClassName + const popsRowClassName = props.rowClassName ?? ''; + let outClass = ''; + if(typeof popsRowClassName==='string'){ + popsRowClassName && (outClass = popsRowClassName); + }else if(typeof popsRowClassName==='function'){ + outClass = popsRowClassName(params) + } + return find ? `not-allow-drag ${outClass}` : `allow-drag ${outClass}`; + }, + }; + } + return merge( + {}, + data.defaultVxeProps, + { + showFooter: data.statistics.has, + }, + unref(attrs), + { + ref: 'gridRef', + size: props.size, + loading: false, + disabled: props.disabled, + // columns: unref(data.vxeColumns), + editRules: unref(vxeEditRules), + height: props.height === 'auto' ? null : props.height, + maxHeight: props.maxHeight, + // 代码逻辑说明: 【QQYUN-5133】JVxeTable 行编辑升级 + scrollY: props.scrollY, + scrollX: props.scrollX, + border: props.bordered, + footerMethod: methods.handleFooterMethod, + // 展开行配置 + expandConfig: { + toggleMethod: methods.handleExpandToggleMethod, + }, + // 可编辑配置 + editConfig: { + //activeMethod: methods.handleActiveMethod, + beforeEditMethod: methods.handleActiveMethod, + }, + radioConfig: { + checkMethod: methods.handleCheckMethod, + }, + checkboxConfig: { + checkMethod: methods.handleCheckMethod, + }, + ...rowClass + // rowClassName:(params)=>{ + // const { row } = params; + // return row.dbFieldName=='id'?"not-allow-drag":"allow-drag" + // } + }, + unref(vxeEvents), + unref(keyboardEditConfig) + ); + }); + + const vxeColumnsRef = ref(data.vxeColumns!.value || []) + const watchColumnsDebounce = debounce(async () => { + vxeColumnsRef.value = data.vxeColumns!.value + }, 50) + watch(data.vxeColumns!, watchColumnsDebounce) + + const vxeProps = computed(() => { + return { + ...unref(vxePropsMerge), + // 【issue/8695】单独抽出 columns,防止性能问题 + columns: unref(vxeColumnsRef), + } + }); + + return { + vxeProps, + prefixCls: data.prefixCls, + }; +} diff --git a/src/components/jeecg/JVxeTable/src/hooks/useJVxeComponent.ts b/src/components/jeecg/JVxeTable/src/hooks/useJVxeComponent.ts new file mode 100644 index 0000000..e870b83 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/useJVxeComponent.ts @@ -0,0 +1,318 @@ +import { computed, nextTick, ref, unref, watch } from 'vue'; +import { propTypes } from '/@/utils/propTypes'; +import { useDesign } from '/@/hooks/web/useDesign'; +import { getEnhanced, replaceProps } from '../utils/enhancedUtils'; +import { vModel } from '/@/components/jeecg/JVxeTable/utils'; +import { JVxeRenderType } from '../types/JVxeTypes'; +import { isBoolean, isFunction, isObject, isPromise } from '/@/utils/is'; +import { JVxeComponent } from '../types/JVxeComponent'; +import { filterDictText } from '/@/utils/dict/JDictSelectUtil'; +import { getAreaTextByCode } from "@/components/Form/src/utils/Area"; + +export function useJVxeCompProps() { + return { + // 组件类型 + type: propTypes.string, + // 渲染类型 + renderType: propTypes.string.def('default'), + // 渲染参数 + params: propTypes.object, + // 渲染自定义选项 + renderOptions: propTypes.object, + }; +} + +export function useJVxeComponent(props: JVxeComponent.Props) { + const value = computed(() => { + // 代码逻辑说明: 【QQYUN-9125】oracle数据库日期类型字段会默认带上时分秒 + const val = props.params.row[props.params.column.property]; + if (props.type === 'date' && typeof val === 'string') { + return val.split(' ').shift(); + } else { + return val; + } + }); + const innerValue = ref(value.value); + const row = computed(() => props.params.row); + const rows = computed(() => props.params.data); + const column = computed(() => props.params.column); + // 用户配置的原始 column + const originColumn = computed(() => column.value.params); + const rowIndex = computed(() => props.params._rowIndex); + const columnIndex = computed(() => props.params._columnIndex); + // 表格数据长度 + const fullDataLength = computed(() => props.params.$table.internalData.tableFullData.length); + // 是否正在滚动中 + const scrolling = computed(() => !!props.renderOptions.scrolling); + // 当有formatter时,优先使用formatter + const innerLabel = computed(() => { + if(typeof column.value?.formatter === 'function'){ + return column.value.formatter({ + cellValue: innerValue.value, + row: row.value, + column: column.value, + }); + } + return innerValue.value + }); + const cellProps = computed(() => { + let renderOptions = props.renderOptions; + let col = originColumn.value; + + let cellProps = {}; + + // 输入占位符 + cellProps['placeholder'] = replaceProps(col, col.placeholder); + + // 解析props + if (isObject(col.props)) { + Object.keys(col.props).forEach((key) => { + cellProps[key] = replaceProps(col, col.props[key]); + }); + } + + // 判断是否是禁用的列 + cellProps['disabled'] = isBoolean(col['disabled']) ? col['disabled'] : cellProps['disabled']; + // 判断是否禁用行 + if (renderOptions.isDisabledRow(row.value, rowIndex.value)) { + cellProps['disabled'] = true; + } + // 代码逻辑说明: 【TV360X-291】没勾选同步数据库禁用排序功能 + if (col.props && col.props.isDisabledCell) { + if (col.props.isDisabledCell({ row: row.value, rowIndex: rowIndex.value, column: col, columnIndex: columnIndex.value })) { + cellProps['disabled'] = true; + } + } + // 判断是否禁用所有组件 + if (renderOptions.disabled === true) { + cellProps['disabled'] = true; + // 代码逻辑说明: 【TV360X-1068】行编辑整体禁用时上传按钮不显示 + cellProps['disabledTable'] = true; + } + // 代码逻辑说明: VUEN-1111 一对多子表 部门选择 不应该级联 + if (col.checkStrictly === true) { + cellProps['checkStrictly'] = true; + } + + // 代码逻辑说明: 用户组件 控制单选多选新的参数配置 + if (col.isRadioSelection === true) { + cellProps['isRadioSelection'] = true; + } else if (col.isRadioSelection === false) { + cellProps['isRadioSelection'] = false; + } + + return cellProps; + }); + + const listeners = computed(() => { + let listeners = Object.assign({}, props.renderOptions.listeners || {}); + // 默认change事件 + if (!listeners.change) { + listeners.change = async (event) => { + vModel(event.value, row, column); + await nextTick(); + // 处理 change 事件相关逻辑(例如校验) + props.params.$table.updateStatus(props.params); + }; + } + return listeners; + }); + const context = { + innerLabel, + innerValue, + row, + rows, + rowIndex, + column, + columnIndex, + originColumn, + fullDataLength, + cellProps, + scrolling, + handleChangeCommon, + handleBlurCommon, + }; + const ctx = { props, context }; + + // 获取组件增强 + let enhanced = getEnhanced(props.type); + + watch( + value, + (newValue) => { + // -update-begin--author:liaozhiyang---date:20241210---for:【issues/7497】隐藏某一列后,字典没翻译,恢复后正常 + // TODO 先这样修复解决问题,根因后期再看看 + // enhanced = getEnhanced(props.type); + // -update-end--author:liaozhiyang---date:20241210---for:【issues/7497】隐藏某一列后,字典没翻译,恢复后 + // 解决online中对同一条数据点击编辑多次他表字段变成空格的问题 + if (props.type === 'input' && originColumn.value.flag === 'link-table-field' && (newValue === undefined || newValue === null)) { + return; + } + // 验证值格式 + let getValue = enhanced.getValue(newValue, ctx); + if (newValue !== getValue) { + // 值格式不正确,重新赋值 + newValue = getValue; + vModel(newValue, row, column); + } + innerValue.value = enhanced.setValue(newValue, ctx); + // 代码逻辑说明: 【QQYUN-9205】一对多(jVxetable组件date)支持年,年月,年度度,年周 + if (props.type === 'date' && props.renderType === JVxeRenderType.spaner && enhanced.translate.enabled === true) { + if (isFunction(enhanced.translate.handler)) { + innerValue.value = enhanced.translate.handler(newValue, ctx); + } + return; + } + + // 代码逻辑说明: 【issues/7203】自动生成一对多表单代码中,省市区回显问题--- + if (props.type === 'pca' && props.renderType === JVxeRenderType.spaner) { + innerValue.value = getAreaTextByCode(newValue); + return; + } + + // 判断是否启用翻译 + if (props.renderType === JVxeRenderType.spaner && enhanced.translate.enabled === true) { + if (isFunction(enhanced.translate.handler)) { + let res = enhanced.translate.handler(newValue, ctx); + // 异步翻译,可解决字典查询慢的问题 + if (isPromise(res)) { + res.then((v) => (innerValue.value = v)); + } else { + innerValue.value = res; + } + } + } + }, + { immediate: true } + ); + + /** 通用处理 change 事件 */ + function handleChangeCommon($value, force = false) { + const newValue = enhanced.getValue($value, ctx); + const oldValue = value.value; + // 代码逻辑说明: 【issues-5025】JVueTable的事件 @valueChange重复触发问题 + const execute = force ? true : newValue !== oldValue; + if (execute) { + trigger('change', { value: newValue }); + // 触发valueChange事件 + parentTrigger('valueChange', { + type: props.type, + value: newValue, + oldValue: oldValue, + col: originColumn.value, + rowIndex: rowIndex.value, + columnIndex: columnIndex.value, + }); + } + } + + /** 通用处理 blur 事件 */ + function handleBlurCommon($value) { + // 代码逻辑说明: 【issues/636】JVxeTable加上blur事件 + const newValue = enhanced.getValue($value, ctx); + const oldValue = value.value; + //trigger('blur', { value }); + // 触发blur事件 + parentTrigger('blur', { + type: props.type, + value: newValue, + oldValue: oldValue, + col: originColumn.value, + rowIndex: rowIndex.value, + columnIndex: columnIndex.value, + }); + } + + /** + * 如果事件存在的话,就触发 + * @param name 事件名 + * @param event 事件参数 + * @param args 其他附带参数 + */ + function trigger(name, event?, args: any[] = []) { + let listener = listeners.value[name]; + if (isFunction(listener)) { + if (isObject(event)) { + event = packageEvent(name, event); + } + listener(event, ...args); + } + } + + function parentTrigger(name, event, args: any[] = []) { + args.unshift(packageEvent(name, event)); + trigger('trigger', name, args); + } + + function packageEvent(name, event: any = {}) { + event.row = row.value; + event.column = column.value; + // online增强参数兼容 + event.column['key'] = column.value['property']; + // event.cellTarget = this + if (!event.type) { + event.type = name; + } + if (!event.cellType) { + event.cellType = props.type; + } + // 是否校验表单,默认为true + if (isBoolean(event.validate)) { + event.validate = true; + } + return event; + } + + /** + * 防样式冲突类名生成器 + * @param scope + */ + function useCellDesign(scope: string) { + return useDesign(`vxe-cell-${scope}`); + } + + return { + ...context, + enhanced, + trigger, + useCellDesign, + }; +} + +/** + * 获取组件默认增强 + */ +export function useDefaultEnhanced(): JVxeComponent.EnhancedPartial { + return { + installOptions: { + autofocus: '', + }, + interceptor: { + 'event.clearActived': () => true, + 'event.clearActived.className': () => true, + }, + switches: { + editRender: true, + visible: false, + }, + aopEvents: { + editActived() {}, + editClosed() {}, + activeMethod: () => true, + }, + translate: { + enabled: false, + handler(value, ctx) { + // 默认翻译方法 + if (ctx) { + return filterDictText(unref(ctx.context.column).params.options, value); + } else { + return value; + } + }, + }, + getValue: (value) => value, + setValue: (value) => value, + createValue: (defaultValue) => defaultValue, + } as JVxeComponent.Enhanced; +} diff --git a/src/components/jeecg/JVxeTable/src/hooks/useKeyboardEdit.ts b/src/components/jeecg/JVxeTable/src/hooks/useKeyboardEdit.ts new file mode 100644 index 0000000..a6bb9b9 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/useKeyboardEdit.ts @@ -0,0 +1,37 @@ +/* + * JVxeTable 键盘操作 + */ +import type { VxeTablePropTypes } from 'vxe-table'; +import type { JVxeTableProps } from '../types'; +import { computed } from 'vue'; + +/** + * JVxeTable 键盘操作 + * + * @param props + */ +export function useKeyboardEdit(props: JVxeTableProps) { + // 是否开启了键盘操作 + const enabledKeyboard = computed(() => props.keyboardEdit ?? false); + // 重写 keyboardConfig + const keyboardConfig: VxeTablePropTypes.KeyboardConfig = { + editMethod({ row, column, $table }) { + // 重写默认的覆盖式,改为追加式 + $table.setActiveCell(row, column); + return true; + }, + }; + // 键盘操作配置 + const keyboardEditConfig = computed(() => { + return { + mouseConfig: { + selected: enabledKeyboard.value, + }, + keyboardConfig, + }; + }); + + return { + keyboardEditConfig, + }; +} diff --git a/src/components/jeecg/JVxeTable/src/hooks/useLinkage.ts b/src/components/jeecg/JVxeTable/src/hooks/useLinkage.ts new file mode 100644 index 0000000..f5916d1 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/useLinkage.ts @@ -0,0 +1,145 @@ +import { watch } from 'vue'; +import { isFunction, isPromise, isArray } from '/@/utils/is'; +import { JVxeColumn, JVxeDataProps, JVxeTableProps, JVxeLinkageConfig } from '../types'; + +/** + * 多级联动 + */ +export function useLinkage(props: JVxeTableProps, data: JVxeDataProps, methods) { + // 整理多级联动配置 + watch( + () => props.linkageConfig, + (linkageConfig: JVxeLinkageConfig[]) => { + data.innerLinkageConfig.clear(); + if (isArray(linkageConfig) && linkageConfig.length > 0) { + linkageConfig.forEach((config) => { + let keys = getLinkageKeys(config.key, []); + // 多个key共享一个,引用地址 + let configItem = { + ...config, + keys, + optionsMap: new Map(), + }; + keys.forEach((k) => data.innerLinkageConfig.set(k, configItem)); + }); + } + }, + { immediate: true } + ); + + // 获取联动的key顺序 + function getLinkageKeys(key: string, keys: string[]): string[] { + let col = props.columns?.find((col: JVxeColumn) => col.key === key) as JVxeColumn; + if (col) { + keys.push(col.key); + // 寻找下级 + if (col.linkageKey) { + return getLinkageKeys(col.linkageKey, keys); + } + } + return keys; + } + + // 处理联动回显数据 + function handleLinkageBackData(row) { + if (data.innerLinkageConfig.size > 0) { + for (let configItem of data.innerLinkageConfig.values()) { + autoSetLinkageOptionsByData(row, '', configItem, 0); + } + } + } + + /** 【多级联动】获取同级联动下拉选项 */ + function getLinkageOptionsSibling(row, col, config, request) { + // 如果当前列不是顶级列 + let key = ''; + if (col.key !== config.key) { + // 就找出联动上级列 + let idx = config.keys.findIndex((k) => col.key === k); + let parentKey = config.keys[idx - 1]; + key = row[parentKey]; + // 如果联动上级列没有选择数据,就直接返回空数组 + if (key === '' || key == null) { + return []; + } + } else { + key = 'root'; + } + let options = config.optionsMap.get(key); + if (!Array.isArray(options)) { + if (request) { + let parent = key === 'root' ? '' : key; + return getLinkageOptionsAsync(config, parent); + } else { + options = []; + } + } + return options; + } + + /** 【多级联动】获取联动下拉选项(异步) */ + function getLinkageOptionsAsync(config, parent) { + return new Promise((resolve) => { + let key = parent ? parent : 'root'; + let options; + if (config.optionsMap.has(key)) { + options = config.optionsMap.get(key); + if (isPromise(options)) { + options.then((opt) => { + config.optionsMap.set(key, opt); + resolve(opt); + }); + } else { + resolve(options); + } + } else if (isFunction(config.requestData)) { + // 调用requestData方法,通过传入parent来获取子级 + // noinspection JSVoidFunctionReturnValueUsed,TypeScriptValidateJSTypes + let promise = config.requestData(parent); + config.optionsMap.set(key, promise); + promise.then((opt) => { + config.optionsMap.set(key, opt); + resolve(opt); + }); + } else { + resolve([]); + } + }); + } + + // 【多级联动】 用于回显数据,自动填充 optionsMap + function autoSetLinkageOptionsByData(data, parent, config, level) { + if (level === 0) { + getLinkageOptionsAsync(config, ''); + } else { + getLinkageOptionsAsync(config, parent); + } + if (config.keys.length - 1 > level) { + let value = data[config.keys[level]]; + if (value) { + autoSetLinkageOptionsByData(data, value, config, level + 1); + } + } + } + + // 【多级联动】联动组件change时,清空下级组件 + function handleLinkageSelectChange(row, col, config, value) { + if (col.linkageKey) { + getLinkageOptionsAsync(config, value); + let idx = config.keys.findIndex((k) => k === col.key); + let values = {}; + for (let i = idx; i < config.keys.length; i++) { + values[config.keys[i]] = ''; + } + // 清空后几列的数据 + methods.setValues([{ rowKey: row.id, values }]); + } + } + + return { + getLinkageOptionsAsync, + getLinkageOptionsSibling, + handleLinkageSelectChange, + handleLinkageBackData, + }; +} diff --git a/src/components/jeecg/JVxeTable/src/hooks/useMethods.ts b/src/components/jeecg/JVxeTable/src/hooks/useMethods.ts new file mode 100644 index 0000000..3385eae --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/useMethods.ts @@ -0,0 +1,920 @@ +import { Ref, watch } from 'vue'; +import XEUtils from 'xe-utils'; +import { simpleDebounce } from '/@/utils/common/compUtils'; +import { JVxeDataProps, JVxeRefs, JVxeTableProps, JVxeTypes } from '../types'; +import { getEnhanced } from '../utils/enhancedUtils'; +import { VxeTableInstance, VxeTablePrivateMethods } from 'vxe-table'; +import { cloneDeep } from 'lodash-es'; +import { isArray, isEmpty, isNull, isString } from '/@/utils/is'; +import { useLinkage } from './useLinkage'; +import { useWebSocket } from './useWebSocket'; +import { getPrefix, getJVxeAuths } from '../utils/authUtils'; +import { excludeKeywords } from '../componentMap'; +import { useColumnsCache } from './useColumnsCache'; +import { isEnabledVirtualYScroll } from '/@/components/jeecg/JVxeTable/utils'; + +export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps, refs: JVxeRefs, instanceRef: Ref) { + let xTableTemp: VxeTableInstance & VxeTablePrivateMethods; + + function getXTable() { + if (!xTableTemp) { + // !. 为 typescript 的非空断言 + xTableTemp = refs.gridRef.value!.getRefMaps().refTable.value; + } + return xTableTemp; + } + + // noinspection JSUnusedGlobalSymbols + const hookMethods = { + getXTable, + addRows, + pushRows, + insertRows, + addOrInsert, + setValues, + getValues, + getTableData, + getNewData, + getNewDataWithId, + getIfRowById, + getNewRowById, + getDeleteData, + getSelectionData, + getSelectedData, + removeRows, + removeRowsById, + removeSelection, + resetScrollTop, + validateTable, + fullValidateTable, + clearSelection, + filterNewRows, + isDisabledRow, + recalcDisableRows, + rowResort, + }; + + // 多级联动 + const linkageMethods = useLinkage(props, data, hookMethods); + // WebSocket 无痕刷新 + const socketMethods = useWebSocket(props, data, hookMethods); + + // 可显式供外部调用的方法 + const publicMethods = { + ...hookMethods, + ...linkageMethods, + ...socketMethods, + }; + + /** 监听vxe滚动条位置 */ + function handleVxeScroll(event) { + let { scroll } = data; + + // 记录滚动条的位置 + scroll.top = event.scrollTop; + scroll.left = event.scrollLeft; + + refs.subPopoverRef.value?.close(); + data.scrolling.value = true; + closeScrolling(); + } + + // 当手动勾选单选时触发的事件 + function handleVxeRadioChange(event) { + let row = event.$table.getRadioRecord(); + data.selectedRows.value = row ? [row] : []; + handleSelectChange('radio', data.selectedRows.value, event); + } + + // 当手动勾选全选时触发的事件 + function handleVxeCheckboxAll(event) { + data.selectedRows.value = event.$table.getCheckboxRecords(); + handleSelectChange('checkbox-all', data.selectedRows.value, event); + } + + // 当手动勾选并且值发生改变时触发的事件 + function handleVxeCheckboxChange(event) { + data.selectedRows.value = event.$table.getCheckboxRecords(); + handleSelectChange('checkbox', data.selectedRows.value, event); + } + + // 行选择change事件 + function handleSelectChange(type, selectedRows, $event) { + let action; + if (type === 'radio') { + action = 'selected'; + } else if (type === 'checkbox') { + action = selectedRows.includes($event.row) ? 'selected' : 'unselected'; + } else { + action = 'selected-all'; + } + + data.selectedRowIds.value = selectedRows.map((row) => row.id); + trigger('selectRowChange', { + type: type, + action: action, + $event: $event, + row: $event.row, + selectedRows: data.selectedRows.value, + selectedRowIds: data.selectedRowIds.value, + }); + } + + // 点击单元格时触发的事件 + function handleCellClick(event) { + let { row, column, $event, $table } = event; + + // 点击了可编辑的 + if (column.editRender) { + refs.subPopoverRef.value?.close(); + return; + } + + // 显示详细信息 + if (column.params?.showDetails) { + refs.detailsModalRef.value?.open(event); + } else if (refs.subPopoverRef.value) { + refs.subPopoverRef.value.toggle(event); + } else if (props.clickSelectRow) { + let className = $event.target.className || ''; + className = isString(className) ? className : className.toString(); + // 点击的是expand,不做处理 + if (className.includes('vxe-table--expand-btn')) { + return; + } + // 点击的是checkbox,不做处理 + if (className.includes('vxe-checkbox--icon') || className.includes('vxe-cell--checkbox')) { + return; + } + // 点击的是radio,不做处理 + if (className.includes('vxe-radio--icon') || className.includes('vxe-cell--radio')) { + return; + } + + // 代码逻辑说明: 【issues/9038】JVxeTable表格点击行选择BUG--- + if(!data.disabledRowIds.includes(row.id)){ + if (props.rowSelectionType === 'radio') { + $table.setRadioRow(row); + handleVxeRadioChange(event); + } else { + $table.toggleCheckboxRow(row); + handleVxeCheckboxChange(event); + } + } + + } + } + + // 单元格被激活编辑时会触发该事件 + function handleEditActived({ column }) { + // 执行增强 + getEnhanced(column.params.type).aopEvents.editActived!.apply(instanceRef.value, arguments as any); + } + + // 单元格编辑状态下被关闭时会触发该事件 + function handleEditClosed({ column }) { + // 执行增强 + getEnhanced(column.params.type).aopEvents.editClosed!.apply(instanceRef.value, arguments as any); + } + + // 返回值决定行是否可选中 + function handleCheckMethod({ row }) { + if (props.disabled) { + return false; + } + return !data.disabledRowIds.includes(row.id); + } + + // 返回值决定单元格是否可以编辑 + function handleActiveMethod({ row, column }) { + let flag = (() => { + if (props.disabled) { + return false; + } + if (data.disabledRowIds.includes(row.id)) { + return false; + } + if (column.params?.disabled) { + return false; + } + // 执行增强 + return getEnhanced(column.params.type).aopEvents.activeMethod!.apply(instanceRef.value, arguments as any) ?? true; + })(); + if (!flag) { + // -update-begin--author:liaozhiyang---date:20240619---for:【TV360X-1404】vxetable警告 + getXTable().clearEdit(); + // -update-end--author:liaozhiyang---date:20240619---for:【TV360X-1404】vxetable警告 + } + return flag; + } + + /** + * 判断是否是禁用行 + * @param row 行数据 + * @param rowIndex 行号 + * @param force 是否强制判断 + */ + function isDisabledRow(row, rowIndex: number | boolean = -1, force = true) { + if(typeof rowIndex === 'boolean'){ + force = rowIndex; + rowIndex = -1; + } + if (!force) { + return !data.disabledRowIds.includes(row.id); + } + if (props.disabledRows == null || isEmpty(props.disabledRows)) { + return false; + } + let disabled: boolean = false; + let keys: string[] = Object.keys(props.disabledRows); + for (const key of keys) { + // 判断是否有该属性 + if (row.hasOwnProperty(key)) { + let value = row[key]; + let temp: any = props.disabledRows![key]; + // 禁用规则可以是一个函数 + if (typeof temp === 'function') { + disabled = temp(value, row, rowIndex); + } else if (isArray(temp)) { + // 禁用规则可以是一个数组 + disabled = temp.includes(value); + } else { + // 禁用规则可以是一个具体值 + disabled = temp === value; + } + if (disabled) { + break; + } + } + } + return disabled; + } + + // 重新计算禁用行 + function recalcDisableRows() { + let xTable = getXTable(); + data.disabledRowIds = []; + const { tableFullData } = xTable.internalData; + tableFullData.forEach((row, rowIndex) => { + // 判断是否是禁用行 + if (isDisabledRow(row, rowIndex)) { + data.disabledRowIds.push(row.id); + } + }); + xTable.updateData(); + } + + // 监听 disabledRows,更改时重新计算禁用行 + watch( + () => props.disabledRows, + () => recalcDisableRows() + ); + + // 返回值决定是否允许展开、收起行 + function handleExpandToggleMethod({ expanded }) { + return !(expanded && props.disabled); + } + + // 设置 data.scrolling 防抖模式 + const closeScrolling = simpleDebounce(function () { + data.scrolling.value = false; + }, 100); + + /** 表尾数据处理方法,用于显示统计信息 */ + function handleFooterMethod({ columns, data: $data }) { + const { statistics } = data; + let footers: any[] = []; + if (statistics.has) { + if (statistics.sum.length > 0) { + footers.push( + getFooterStatisticsMap({ + columns: columns, + title: '合计', + checks: statistics.sum, + method: (column) => XEUtils.sum($data, column.property), + }) + ); + } + if (statistics.average.length > 0) { + footers.push( + getFooterStatisticsMap({ + columns: columns, + title: '平均', + checks: statistics.average, + method: (column) => XEUtils.mean($data, column.property), + }) + ); + } + } + return footers; + } + + /** 获取底部统计Map */ + function getFooterStatisticsMap({ columns, title, checks, method }) { + return columns.map((column, columnIndex) => { + if (columnIndex === 0) { + return title; + } + if (checks.includes(column.property)) { + return method(column, columnIndex); + } + return null; + }); + } + + // 创建新行,自动添加默认值 + function createRow(record: Recordable = {}) { + let xTable = getXTable(); + // 添加默认值 + xTable.internalData.tableFullColumn.forEach((column) => { + let col = column.params; + // 不能被注册的列不获取增强 + if (col && !excludeKeywords.includes(col.type)) { + if (col.key && (record[col.key] == null || record[col.key] === '')) { + // 设置默认值 + let createValue = getEnhanced(col.type).createValue; + let defaultValue = col.defaultValue ?? ''; + let ctx = { context: { row: record, column, $table: xTable } }; + record[col.key] = createValue(defaultValue, ctx); + } + // 处理联动列 + if (col.type === JVxeTypes.select && data.innerLinkageConfig.size > 0) { + // 判断当前列是否是联动列 + if (data.innerLinkageConfig.has(col.key)) { + let configItem = data.innerLinkageConfig.get(col.key); + linkageMethods.getLinkageOptionsAsync(configItem, ''); + } + } + } else if (col?.type === JVxeTypes.hidden) { + record[col.key] = col.defaultValue ?? ''; + } + }); + return record; + } + + async function addOrInsert(rows: Recordable | Recordable[] = {}, index, triggerName, options?: IAddRowsOptions) { + let xTable = getXTable(); + let records; + if (isArray(rows)) { + records = rows; + } else { + records = [rows]; + } + // 遍历添加默认值 + records.forEach((record) => createRow(record)); + let setActive = options?.setActive ?? props.addSetActive ?? true; + let result = await pushRows(records, { index: index, setActive }); + // 遍历插入的行 + // online js增强时以传过来值为准,不再赋默认值 + if (!(options?.isOnlineJS ?? false)) { + if (triggerName != null) { + for (let i = 0; i < result.rows.length; i++) { + let row = result.rows[i]; + trigger(triggerName, { + row: row, + rows: result.rows, + insertIndex: index, + $table: xTable, + target: instanceRef.value, + isModalData: options?.isModalData + }); + } + } + } + return result; + } + + // 新增、插入一行时的可选参数 + interface IAddRowsOptions { + // 是否是 onlineJS增强 触发的 + isOnlineJS?: boolean; + // 是否激活编辑状态 + setActive?: boolean; + //是否需要触发change事件 + emitChange?:boolean + // 是否是modal弹窗添加的数据 + isModalData?:boolean + } + + /** + * 添加一行或多行 + * + * @param rows + * @param options 参数 + * @return + */ + async function addRows(rows: Recordable | Recordable[] = {}, options?: IAddRowsOptions) { + // 代码逻辑说明: VUEN-1892【online子表弹框】有主从关联js时,子表弹框修改了数据,主表字段未修改 + let result = await addOrInsert(rows, -1, 'added', options); + if(options && options!.emitChange==true){ + trigger('valueChange', {column: 'all', row: result.row}) + } + // 代码逻辑说明: 【TV360X-279】行编辑添加新字段滚动对应位置 + let xTable = getXTable(); + setTimeout(() => { + xTable.scrollToRow(result.row); + }, 0); + return result; + } + + /** + * 添加一行或多行临时数据,不会填充默认值,传什么就添加进去什么 + * @param rows + * @param options 选项 + * @param options.setActive 是否激活最后一行的编辑模式 + */ + async function pushRows(rows: Recordable | Recordable[] = {}, options = { setActive: false, index: -1 }) { + let xTable = getXTable(); + let { setActive, index } = options; + index = index === -1 ? index : xTable.internalData.tableFullData[index]; + index = index == null ? -1 : index; + // 插入行 + let result = await xTable.insertAt(rows, index); + if (setActive) { + // -update-begin--author:liaozhiyang---date:20240619---for:【TV360X-1404】vxetable警告 + // 激活最后一行的编辑模式 + xTable.setEditRow(result.rows[result.rows.length - 1], true); + // -update-end--author:liaozhiyang---date:20240619---for:【TV360X-1404】vxetable警告 + } + await recalcSortNumber(); + return result; + } + + /** + * 插入一行或多行临时数据 + * + * @param rows + * @param index 添加下标,数字,必填 + * @param options 参数 + * @return + */ + function insertRows(rows: Recordable | Recordable[] = {}, index: number, options?: IAddRowsOptions) { + if (index < 0) { + console.warn(`【JVxeTable】insertRows:index必须传递数字,且大于-1`); + return; + } + return addOrInsert(rows, index, 'inserted', options); + } + + /** 获取表格表单里的值 */ + function getValues(callback, rowIds) { + let tableData = getTableData({ rowIds: rowIds }); + // 代码逻辑说明: 【issues/7631】JVxeTable组件的getValues回调函数参数修正 + callback(tableData, tableData); + } + + type getTableDataOptions = { + rowIds?: string[]; + // 是否保留新行的id + keepNewId?: boolean; + } + + /** 获取表格数据 */ + function getTableData(options: getTableDataOptions = {}) { + let { rowIds } = options; + let tableData; + // 仅查询指定id的行 + if (isArray(rowIds) && rowIds.length > 0) { + tableData = []; + rowIds.forEach((rowId) => { + let { row } = getIfRowById(rowId); + if (row) { + tableData.push(row); + } + }); + } else { + // 查询所有行 + tableData = getXTable().getTableData().fullData; + } + return filterNewRows(tableData, { + keepNewId: options.keepNewId ?? false, + removeNewLine: false, + }); + } + + /** 仅获取新增的数据 */ + function getNewData() { + let newData = getNewDataWithId(); + newData.forEach((row) => delete row.id); + return newData; + } + + /** 仅获取新增的数据,带有id */ + function getNewDataWithId() { + let xTable = getXTable(); + return cloneDeep(xTable.getInsertRecords()); + } + + /** 根据ID获取行,新增的行也能查出来 */ + function getIfRowById(id) { + let xTable = getXTable(); + let row = xTable.getRowById(id), + isNew = false; + if (!row) { + row = getNewRowById(id); + if (!row) { + console.warn(`JVxeTable.getIfRowById:没有找到id为"${id}"的行`); + return { row: null }; + } + isNew = true; + } + return { row, isNew }; + } + + /** 通过临时ID获取新增的行 */ + function getNewRowById(id) { + let records = getXTable().getInsertRecords(); + for (let record of records) { + if (record.id === id) { + return record; + } + } + return null; + } + + type filterNewRowsOptions = { + keepNewId?: boolean; + removeNewLine?: boolean; + } | boolean + + /** + * 过滤添加的行 + * @param rows 要筛选的行数据 + * @param optOrRm 如果传 boolean 则是 removeNewLine 参数(true = 删除新增,false=只删除id),如果传对象则是配置参数 + * @param handler function + */ + function filterNewRows(rows, optOrRm:filterNewRowsOptions = true, handler?: Fn) { + let insertRecords = getXTable().getInsertRecords(); + let records: Recordable[] = []; + optOrRm = typeof optOrRm === 'boolean' ? { removeNewLine: optOrRm } : optOrRm; + // true = 删除新增,false=只删除id + let removeNewLine = optOrRm?.removeNewLine ?? true; + for (let row of rows) { + // update-begin--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + let item = { ...row }; + // update-end--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + if (insertRecords.includes(row)) { + handler ? handler({ item, row, insertRecords }) : null; + if (removeNewLine) { + continue; + } + if (!optOrRm?.keepNewId) { + delete item.id; + } + } + records.push(item); + } + return records; + } + + /** + * 重置滚动条Top位置 + * @param top 新top位置,留空则滚动到上次记录的位置,用于解决切换tab选项卡时导致白屏以及自动将滚动条滚动到顶部的问题 + */ + function resetScrollTop(top?) { + let xTable = getXTable(); + xTable.scrollTo(null, top == null || top === '' ? data.scroll.top : top); + } + + /** 校验table,失败返回errMap,成功返回null */ + async function validateTable(rows?) { + let xTable = getXTable(); + const errMap = await xTable.validate(rows ?? true).catch((errMap) => errMap); + return errMap ? errMap : null; + } + + /** 完整校验 */ + async function fullValidateTable(rows?) { + let xTable = getXTable(); + const errMap = await xTable.fullValidate(rows ?? true).catch((errMap) => errMap); + return errMap ? errMap : null; + } + + type setValuesParam = { rowKey: string; values: Recordable }; + + /** + * 设置某行某列的值 + * + * @param values + * @return 返回受影响的单元格数量 + */ + function setValues(values: setValuesParam[]): number { + if (!isArray(values)) { + console.warn(`[JVxeTable] setValues 必须传递数组`); + return 0; + } + let xTable = getXTable(); + let count = 0; + values.forEach((item) => { + let { rowKey, values: record } = item; + let { row } = getIfRowById(rowKey); + if (!row) { + return; + } + Object.keys(record).forEach((colKey) => { + let column = xTable.getColumnByField(colKey); + if (column) { + let oldValue = row[colKey]; + let newValue = record[colKey]; + if (newValue !== oldValue) { + row[colKey] = newValue; + // 触发 valueChange 事件 + trigger('valueChange', { + type: column.params.type, + value: newValue, + oldValue: oldValue, + col: column.params, + column: column, + isSetValues: true, + row: {...row} + }); + count++; + } + } else { + console.warn(`[JVxeTable] setValues 没有找到key为"${colKey}"的列`); + } + }); + }); + if (count > 0) { + xTable.updateData(); + } + return count; + } + + /** 清空选择行 */ + async function clearSelection() { + const xTable = getXTable(); + let event = { $table: xTable, target: instanceRef.value }; + if (['radio', JVxeTypes.rowRadio].includes(props.rowSelectionType ?? '')) { + await xTable.clearRadioRow(); + handleVxeRadioChange(event); + } else { + await xTable.clearCheckboxRow(); + handleVxeCheckboxChange(event); + } + } + + /** + * 获取选中数据 + * @param isFull 如果 isFull=true 则获取全表已选中的数据 + */ + function getSelectionData(isFull?: boolean) { + const xTable = getXTable(); + if (['radio', JVxeTypes.rowRadio].includes(props.rowSelectionType ?? '')) { + let row = xTable.getRadioRecord(isFull); + if (isNull(row)) { + return []; + } + return filterNewRows([row], false); + } else { + return filterNewRows(xTable.getCheckboxRecords(isFull), false); + } + } + + /** 仅获取被删除的数据(新增又被删除的数据不会被获取到) */ + function getDeleteData() { + return filterNewRows(getXTable().getRemoveRecords(), false); + } + + /** 删除一行或多行数据 */ + async function removeRows(rows, asyncRemove = false) { + // 代码逻辑说明: vxe-table removeRows方法加上异步删除 + const xTable = getXTable(); + const removeEvent: any = { deleteRows: rows, $table: xTable }; + if (asyncRemove) { + const selectedRows = Array.isArray(rows) ? rows : [rows]; + const deleteOldRows = filterNewRows(selectedRows); + if (deleteOldRows.length) { + return new Promise((resolve) => { + // 确认删除,只有调用这个方法才会真删除 + removeEvent.confirmRemove = async () => { + const insertRecords = xTable.getInsertRecords(); + selectedRows.forEach((item) => { + // 删除新添加的数据id + if (insertRecords.includes(item)) { + delete item.id; + } + }); + const res = await xTable.remove(rows); + await recalcSortNumber(); + resolve(res); + }; + trigger('removed', removeEvent); + }); + } else { + // 全新的行立马删除,不等待。 + const res = await xTable.remove(rows); + removeEvent.confirmRemove = () => {}; + trigger('removed', removeEvent); + await recalcSortNumber(); + return res; + } + } else { + const res = await xTable.remove(rows); + trigger('removed', removeEvent); + await recalcSortNumber(); + return res; + } + } + + /** 根据id删除一行或多行 */ + function removeRowsById(rowId) { + let rowIds; + if (isArray(rowId)) { + rowIds = rowId; + } else { + rowIds = [rowId]; + } + let rows = rowIds + .map((id) => { + let { row } = getIfRowById(id); + if (!row) { + return; + } + if (row) { + return row; + } else { + console.warn(`【JVxeTable】removeRowsById:${id}不存在`); + return null; + } + }) + .filter((row) => row != null); + return removeRows(rows); + } + + // 删除选中的数据 + async function removeSelection() { + let xTable = getXTable(); + let res; + if (['radio', JVxeTypes.rowRadio].includes(props.rowSelectionType ?? '')) { + res = await xTable.removeRadioRow(); + } else { + res = await xTable.removeCheckboxRow(); + } + await clearSelection(); + await recalcSortNumber(); + return res; + } + + /** 重新计算排序字段的数值 */ + async function recalcSortNumber(force = false) { + if (props.dragSort || force) { + let xTable = getXTable(); + let sortKey = props.sortKey ?? 'orderNum'; + let sortBegin = props.sortBegin ?? 0; + xTable.internalData.tableFullData.forEach((data) => (data[sortKey] = sortBegin++)); + // 4.1.0 + //await xTable.updateCache(); + // 4.1.1 + await xTable.cacheRowMap(true) + return await xTable.updateData(); + } + } + + /** + * 排序表格 + * @param oldIndex + * @param newIndex + * @param force 强制排序 + */ + async function doSort(oldIndex: number, newIndex: number, force = false) { + if (props.dragSort || force) { + let xTable = getXTable(); + let sort = (array) => { + // 存储old数据,并删除该项 + let row = array.splice(oldIndex, 1)[0]; + // 向newIndex处添加old数据 + array.splice(newIndex, 0, row); + }; + sort(xTable.internalData.tableFullData); + if (xTable.keepSource) { + sort(xTable.internalData.tableSourceData); + } + // -update-begin--author:liaozhiyang---date:20240620---for:【TV360X-585】拖动字段虚拟滚动不好使 + if (isEnabledVirtualYScroll(props, xTable)) { + await xTable.loadData(xTable.internalData.tableFullData); + } + // -update-end--author:liaozhiyang---date:20240620---for:【TV360X-585】拖动字段虚拟滚动不好使 + return await recalcSortNumber(force); + } + } + + /** 行重新排序 */ + function rowResort(oldIndex: number, newIndex: number) { + return doSort(oldIndex, newIndex, true); + } + + // ---------------- begin 权限控制 ---------------- + // 加载权限 + function loadAuthsMap() { + if (!props.authPre || props.authPre.length == 0) { + data.authsMap.value = null; + } else { + data.authsMap.value = getJVxeAuths(props.authPre); + } + } + + /** + * 根据 权限code 获取权限 + * @param authCode + */ + function getAuth(authCode) { + if (data.authsMap.value != null && props.authPre) { + let prefix = getPrefix(props.authPre); + return data.authsMap.value.get(prefix + authCode); + } + return null; + } + + // 获取列权限 + function getColAuth(key: string) { + return getAuth(key); + } + + // 判断按钮权限 + function hasBtnAuth(key: string) { + return getAuth('btn:' + key)?.isAuth ?? true; + } + + // ---------------- end 权限控制 ---------------- + + /* --- 辅助方法 ---*/ + + function created() { + loadAuthsMap(); + } + + // 触发事件 + function trigger(name, event: any = {}) { + event.$target = instanceRef.value; + event.$table = getXTable(); + //online增强参数兼容 + event.target = instanceRef.value; + emit(name, event); + } + + /** + * 获取选中的行-和 getSelectionData 区别在于对于新增的行也会返回ID + * 用于onlinePopForm + * @param isFull + */ + function getSelectedData(isFull?: boolean) { + const xTable = getXTable(); + let rows:any[] = [] + if (['radio', JVxeTypes.rowRadio].includes(props.rowSelectionType ?? '')) { + let row = xTable.getRadioRecord(isFull); + if (isNull(row)) { + return []; + } + rows = [row] + } else { + rows = xTable.getCheckboxRecords(isFull) + } + let records: Recordable[] = []; + for (let row of rows) { + // update-begin--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + records.push({ ...row }); + // update-end--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + } + return records; + } + /** + * 2024-03-21 + * liaozhiyang + * VXETable列设置保存缓存字段名 + * */ + function handleCustom({ type, $grid }) { + const { saveSetting, resetSetting } = useColumnsCache({ cacheColumnsKey: props.cacheColumnsKey }); + if (type === 'confirm') { + saveSetting($grid); + } else if (type == 'reset') { + resetSetting($grid); + } + } + + return { + methods: { + trigger, + ...publicMethods, + closeScrolling, + doSort, + recalcSortNumber, + handleVxeScroll, + handleVxeRadioChange, + handleVxeCheckboxAll, + handleVxeCheckboxChange, + handleFooterMethod, + handleCellClick, + handleEditActived, + handleEditClosed, + handleCheckMethod, + handleActiveMethod, + handleExpandToggleMethod, + getColAuth, + hasBtnAuth, + handleCustom, + }, + publicMethods, + created, + }; +} diff --git a/src/components/jeecg/JVxeTable/src/hooks/usePagination.ts b/src/components/jeecg/JVxeTable/src/hooks/usePagination.ts new file mode 100644 index 0000000..79c5505 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/usePagination.ts @@ -0,0 +1,70 @@ +import { computed, reactive, h } from 'vue'; +import { JVxeTableMethods, JVxeTableProps } from '/@/components/jeecg/JVxeTable/src/types'; +import { isEmpty } from '/@/utils/is'; +import { Pagination } from 'ant-design-vue'; + +export function usePagination(props: JVxeTableProps, methods: JVxeTableMethods) { + const innerPagination = reactive({ + current: 1, + pageSize: 10, + pageSizeOptions: ['10', '20', '30'], + showTotal: (total, range) => { + return range[0] + '-' + range[1] + ' 共 ' + total + ' 条'; + }, + showQuickJumper: true, + showSizeChanger: true, + total: 100, + }); + + const bindProps = computed(() => { + return { + ...innerPagination, + ...props.pagination, + size: props.size === 'tiny' ? 'small' : '', + }; + }); + + const boxClass = computed(() => { + return { + 'j-vxe-pagination': true, + 'show-quick-jumper': !!bindProps.value.showQuickJumper, + }; + }); + + function handleChange(current, pageSize) { + innerPagination.current = current; + methods.trigger('pageChange', { current, pageSize }); + } + + function handleShowSizeChange(current, pageSize) { + innerPagination.pageSize = pageSize; + // -update-begin--author:liaozhiyang---date:20251209---for:【issues/9169】切换页码时,pageChange事件加载了两次 + // 因为 handleShowSizeChange先触发,紧接着会触发 handleChange,所以可以注释掉。 + // methods.trigger('pageChange', { current, pageSize }); + // -update-end--author:liaozhiyang---date:20251209---for:【issues/9169】切换页码时,pageChange事件加载了两次 + } + + /** 渲染分页器 */ + function renderPagination() { + if (props.pagination && !isEmpty(props.pagination)) { + return h( + 'div', + { + class: boxClass.value, + }, + [ + h(Pagination, { + ...bindProps.value, + // 代码逻辑说明: 【issues/8137】vxetable表格禁用后分页隐藏了 + disabled: false, + onChange: handleChange, + onShowSizeChange: handleShowSizeChange, + }), + ] + ); + } + return null; + } + + return { renderPagination }; +} diff --git a/src/components/jeecg/JVxeTable/src/hooks/useRenderComponents.ts b/src/components/jeecg/JVxeTable/src/hooks/useRenderComponents.ts new file mode 100644 index 0000000..e8ad036 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/useRenderComponents.ts @@ -0,0 +1,61 @@ +import { h } from 'vue'; +import { JVxeDataProps, JVxeTableMethods, JVxeTableProps } from '../types'; +import JVxeSubPopover from '../components/JVxeSubPopover.vue'; +import JVxeDetailsModal from '../components/JVxeDetailsModal.vue'; +import { useToolbar } from '/@/components/jeecg/JVxeTable/src/hooks/useToolbar'; +import { usePagination } from '/@/components/jeecg/JVxeTable/src/hooks/usePagination'; + +export function useRenderComponents(props: JVxeTableProps, data: JVxeDataProps, methods: JVxeTableMethods, slots) { + // 渲染 toolbar + const { renderToolbar } = useToolbar(props, data, methods, slots); + // 渲染分页器 + const { renderPagination } = usePagination(props, methods); + + // 渲染 toolbarAfter 插槽 + function renderToolbarAfterSlot() { + if (slots['toolbarAfter']) { + return slots['toolbarAfter'](); + } + return null; + } + + // 渲染点击时弹出的子表 + function renderSubPopover() { + if (props.clickRowShowSubForm && slots.subForm) { + return h( + JVxeSubPopover, + { + ref: 'subPopoverRef', + }, + { + subForm: slots.subForm, + } + ); + } + return null; + } + + // 渲染点击时弹出的详细信息 + function renderDetailsModal() { + if (props.clickRowShowMainForm && slots.mainForm) { + return h( + JVxeDetailsModal, + { + ref: 'detailsModalRef', + trigger: methods.trigger, + }, + { + mainForm: slots.mainForm, + } + ); + } + } + + return { + renderToolbar, + renderPagination, + renderSubPopover, + renderDetailsModal, + renderToolbarAfterSlot, + }; +} diff --git a/src/components/jeecg/JVxeTable/src/hooks/useToolbar.ts b/src/components/jeecg/JVxeTable/src/hooks/useToolbar.ts new file mode 100644 index 0000000..8195db9 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/useToolbar.ts @@ -0,0 +1,72 @@ +import { h } from 'vue'; +import JVxeToolbar from '../components/JVxeToolbar.vue'; +import { JVxeDataProps, JVxeTableMethods, JVxeTableProps } from '../types'; + +export function useToolbar(props: JVxeTableProps, data: JVxeDataProps, methods: JVxeTableMethods, $slots) { + /** 渲染工具栏 */ + function renderToolbar() { + if (props.toolbar) { + return h( + JVxeToolbar, + { + size: props.size, + disabled: props.disabled, + toolbarConfig: props.toolbarConfig, + disabledRows: props.disabledRows, + hasBtnAuth: methods.hasBtnAuth, + selectedRowIds: data.selectedRowIds.value, + custom: props.custom, + addBtnCfg: props.addBtnCfg, + removeBtnCfg: props.removeBtnCfg, + // 新增事件 + onAdd: () => { + // 代码逻辑说明: 【TV360X-212】online新增字段就出校验提示 + setTimeout(() => { + methods.addRows(); + }, 0); + }, + // 保存事件 + onSave: () => methods.trigger('save'), + onRemove() { + const $table = methods.getXTable(); + // 触发删除事件 + if (data.selectedRows.value.length > 0) { + const deleteOldRows = methods.filterNewRows(data.selectedRows.value); + const removeEvent: any = { deleteRows: data.selectedRows.value, $table }; + const insertRecords = $table.getInsertRecords(); + if (props.asyncRemove && deleteOldRows.length) { + data.selectedRows.value.forEach((item) => { + // 删除新添加的数据id + if (insertRecords.includes(item)) { + delete item.id; + } + }); + // 确认删除,只有调用这个方法才会真删除 + removeEvent.confirmRemove = () => methods.removeSelection(); + } else { + if (props.asyncRemove) { + // asyncRemove删除的只有新增的数据时,防止调用confirmRemove报错 + removeEvent.confirmRemove = () => {}; + } + methods.removeSelection(); + } + methods.trigger('removed', removeEvent); + } else { + methods.removeSelection(); + } + }, + // 清除选择事件 + onClearSelection: () => methods.clearSelection(), + onRegister: ({ xToolbarRef }) => methods.getXTable().connect(xToolbarRef.value), + }, + { + toolbarPrefix: $slots.toolbarPrefix, + toolbarSuffix: $slots.toolbarSuffix, + } + ); + } + return null; + } + + return { renderToolbar }; +} diff --git a/src/components/jeecg/JVxeTable/src/hooks/useValidateRules.ts b/src/components/jeecg/JVxeTable/src/hooks/useValidateRules.ts new file mode 100644 index 0000000..296cf9d --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/useValidateRules.ts @@ -0,0 +1,105 @@ +import { VxeTablePropTypes } from 'vxe-table'; +import { isArray } from '/@/utils/is'; +import { HandleArgs } from './useColumns'; +import { replaceProps } from '../utils/enhancedUtils'; + +export function useValidateRules(args: HandleArgs) { + const { data } = args; + const col = args.col!; + let rules: VxeTablePropTypes.EditRules[] = []; + if (isArray(col.validateRules)) { + for (let rule of col.validateRules) { + let replace = { + message: replaceProps(col, rule.message), + }; + if (rule.unique || rule.pattern === 'only') { + // 唯一校验器 + rule.validator = uniqueValidator(args); + } else if (rule.pattern) { + // 非空 + if (rule.pattern === fooPatterns[0].value) { + rule.required = true; + delete rule.pattern; + } else { + // 兼容Online表单的特殊规则 + for (let foo of fooPatterns) { + if (foo.value === rule.pattern) { + rule.pattern = foo.pattern; + break; + } + } + } + } else if (typeof rule.handler === 'function') { + // 自定义函数校验 + rule.validator = handlerConvertToValidator; + } + rules.push(Object.assign({}, rule, replace)); + } + } + data.innerEditRules[col.key] = rules; +} + +/** 唯一校验器 */ +function uniqueValidator({ methods }: HandleArgs) { + return function (event) { + const { cellValue, column, rule } = event; + // 代码逻辑说明: 【TV360X-299】JVxetable组件中唯一校验过滤掉空字符串 + if (cellValue == '') return Promise.resolve(); + let tableData = methods.getTableData(); + let findCount = 0; + for (let rowData of tableData) { + if (rowData[column.params.key] === cellValue) { + if (++findCount >= 2) { + return Promise.reject(new Error(rule.message)); + } + } + } + return Promise.resolve(); + }; +} + +/** 旧版handler转为新版Validator */ +function handlerConvertToValidator(event) { + const { column, rule } = event; + return new Promise((resolve, reject) => { + rule.handler(event, (flag, msg) => { + let message = rule.message; + if (typeof msg === 'string') { + message = replaceProps(column.params, msg); + } + if (flag == null) { + resolve(message); + } else if (!!flag) { + resolve(message); + } else { + reject(new Error(message)); + } + }); + }); +} + +// 兼容 online 的规则 +const fooPatterns = [ + { title: '非空', value: '*', pattern: /^.+$/ }, + { title: '6到16位数字', value: 'n6-16', pattern: /^\d{6,16}$/ }, + { title: '6到16位任意字符', value: '*6-16', pattern: /^.{6,16}$/ }, + { title: '6到18位字母', value: 's6-18', pattern: /^[a-z|A-Z]{6,18}$/ }, + // 代码逻辑说明: VUEN-1160 对多子表,网址校验不正确 + { + title: '网址', + value: 'url', + pattern: /^((ht|f)tps?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/, + }, + // 代码逻辑说明: 【TV360X-466】邮箱跟一对第一校验规则一致 + { title: '电子邮件', value: 'e', pattern: /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/ }, + { title: '手机号码', value: 'm', pattern: /^1[3456789]\d{9}$/ }, + { title: '邮政编码', value: 'p', pattern: /^\d{6}$/ }, + { title: '字母', value: 's', pattern: /^[A-Z|a-z]+$/ }, + { title: '数字', value: 'n', pattern: /^-?\d+(\.?\d+|\d?)$/ }, + { title: '整数', value: 'z', pattern: /^-?\d+$/ }, + { + title: '金额', + value: 'money', + pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,5}))$/, + }, +]; diff --git a/src/components/jeecg/JVxeTable/src/hooks/useWebSocket.ts b/src/components/jeecg/JVxeTable/src/hooks/useWebSocket.ts new file mode 100644 index 0000000..50c6d51 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/hooks/useWebSocket.ts @@ -0,0 +1,235 @@ +import { watch, onUnmounted } from 'vue'; +import { buildUUID } from '/@/utils/uuid'; +import { useGlobSetting } from '/@/hooks/setting'; +import { useUserStore } from '/@/store/modules/user'; +import { JVxeDataProps, JVxeTableMethods, JVxeTableProps } from '../types'; +import { isArray } from '/@/utils/is'; +import { getToken } from '/@/utils/auth'; + +// vxe socket +const vs = { + // 页面唯一 id,用于标识同一用户,不同页面的websocket + pageId: buildUUID(), + // webSocket 对象 + ws: null, + // 一些常量 + constants: { + // 消息类型 + TYPE: 'type', + // 消息数据 + DATA: 'data', + // 消息类型:心跳检测 + TYPE_HB: 'heart_beat', + // 消息类型:更新vxe table数据 + TYPE_UVT: 'update_vxe_table', + }, + // 心跳检测 + heartCheck: { + // 间隔时间,间隔多久发送一次心跳消息 + interval: 10000, + // 心跳消息超时时间,心跳消息多久没有回复后重连 + timeout: 6000, + timeoutTimer: -1, + clear() { + clearTimeout(this.timeoutTimer); + return this; + }, + start() { + vs.sendMessage(vs.constants.TYPE_HB, ''); + // 如果超过一定时间还没重置,说明后端主动断开了 + this.timeoutTimer = window.setTimeout(() => { + vs.reconnect(); + }, this.timeout); + return this; + }, + // 心跳消息返回 + back() { + this.clear(); + window.setTimeout(() => this.start(), this.interval); + }, + }, + + /** 初始化 WebSocket */ + initialWebSocket() { + if (this.ws === null) { + const userId = useUserStore().getUserInfo?.id; + const domainURL = useGlobSetting().uploadUrl!; + const domain = domainURL.replace('https://', 'wss://').replace('http://', 'ws://'); + const url = `${domain}/vxeSocket/${userId}/${this.pageId}`; + // 代码逻辑说明: v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278 + let token = (getToken() || '') as string; + this.ws = new WebSocket(url, [token]); + this.ws.onopen = this.on.open.bind(this); + this.ws.onerror = this.on.error.bind(this); + this.ws.onmessage = this.on.message.bind(this); + this.ws.onclose = this.on.close.bind(this); + } + }, + + // 发送消息 + sendMessage(type, message) { + try { + let ws = this.ws; + if (ws != null && ws.readyState === ws.OPEN) { + ws.send( + JSON.stringify({ + type: type, + data: message, + }) + ); + } + } catch (err: any) { + console.warn('【JVxeWebSocket】发送消息失败:(' + err.code + ')'); + } + }, + + /** 绑定全局VXE表格 */ + tableMap: new Map(), + /** 添加绑定 */ + addBind(map, key, value: VmArgs) { + let binds = map.get(key); + if (isArray(binds)) { + binds.push(value); + } else { + map.set(key, [value]); + } + }, + /** 移除绑定 */ + removeBind(map, key, value: VmArgs) { + let binds = map.get(key); + if (isArray(binds)) { + for (let i = 0; i < binds.length; i++) { + let bind = binds[i]; + if (bind === value) { + binds.splice(i, 1); + break; + } + } + if (binds.length === 0) { + map.delete(key); + } + } else { + map.delete(key); + } + }, + // 呼叫绑定的表单 + callBind(map, key, callback) { + let binds = map.get(key); + if (isArray(binds)) { + binds.forEach(callback); + } + }, + + lockReconnect: false, + /** 尝试重连 */ + reconnect() { + if (this.lockReconnect) return; + this.lockReconnect = true; + setTimeout(() => { + if (this.ws && this.ws.close) { + this.ws.close(); + } + this.ws = null; + console.info('【JVxeWebSocket】尝试重连...'); + this.initialWebSocket(); + this.lockReconnect = false; + }, 5000); + }, + + on: { + open() { + console.info('【JVxeWebSocket】连接成功'); + this.heartCheck.start(); + }, + error(e) { + console.warn('【JVxeWebSocket】连接发生错误:', e); + this.reconnect(); + }, + message(e) { + // 解析消息 + let json; + try { + json = JSON.parse(e.data); + } catch (e: any) { + console.warn('【JVxeWebSocket】收到无法解析的消息:', e.data); + return; + } + let type = json[this.constants.TYPE]; + let data = json[this.constants.DATA]; + switch (type) { + // 心跳检测 + case this.constants.TYPE_HB: + this.heartCheck.back(); + break; + // 更新form数据 + case this.constants.TYPE_UVT: + this.callBind(this.tableMap, data.socketKey, (args) => this.onVM.onUpdateTable(args, ...data.args)); + break; + default: + console.warn('【JVxeWebSocket】收到不识别的消息类型:' + type); + break; + } + }, + close(e) { + console.info('【JVxeWebSocket】连接被关闭:', e); + this.reconnect(); + }, + }, + + onVM: { + /** 收到更新表格的消息 */ + onUpdateTable({ props, data, methods }: VmArgs, row, caseId) { + if (data.caseId !== caseId) { + const tableRow = methods.getIfRowById(row.id).row; + // 局部保更新数据 + if (tableRow) { + if (props.reloadEffect) { + data.reloadEffectRowKeysMap[row.id] = true; + } + Object.assign(tableRow, row, { id: tableRow.id }); + methods.getXTable().reloadRow(tableRow); + } + } + }, + }, +} as { + ws: Nullable; +} & Recordable; + +type VmArgs = { + props: JVxeTableProps; + data: JVxeDataProps; + methods: JVxeTableMethods; +}; + +export function useWebSocket(props: JVxeTableProps, data: JVxeDataProps, methods) { + const args: VmArgs = { props, data, methods }; + watch( + () => props.socketReload, + (socketReload: boolean) => { + if (socketReload) { + vs.initialWebSocket(); + vs.addBind(vs.tableMap, props.socketKey, args); + } else { + vs.removeBind(vs.tableMap, props.socketKey, args); + } + }, + { immediate: true } + ); + + /** 发送socket消息更新行 */ + function socketSendUpdateRow(row) { + vs.sendMessage(vs.constants.TYPE_UVT, { + socketKey: props.socketKey, + args: [row, data.caseId], + }); + } + + onUnmounted(() => { + vs.removeBind(vs.tableMap, props.socketKey, args); + }); + + return { + socketSendUpdateRow, + }; +} diff --git a/src/components/jeecg/JVxeTable/src/install.ts b/src/components/jeecg/JVxeTable/src/install.ts new file mode 100644 index 0000000..bbebb32 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/install.ts @@ -0,0 +1,86 @@ +import type { App } from 'vue'; +// 引入 vxe-table +import 'xe-utils'; +import VxeUIAll from 'vxe-pc-ui'; +import VXETable /*Grid*/ from 'vxe-table'; +import VXETablePluginAntd from 'vxe-table-plugin-antd'; +import 'vxe-pc-ui/lib/style.css'; +import 'vxe-table/lib/style.css'; + +import { getEventPath } from '/@/utils/common/compUtils'; +import { registerAllComponent } from './utils/registerUtils'; +import { getEnhanced } from './utils/enhancedUtils'; +import type { JVxeTypes } from './types/JVxeTypes'; +export interface RegisterJVxeTableOptions { + /** 仅注册指定的内置类型;不传则注册全部内置组件 */ + builtinComponents?: JVxeTypes[]; +} + +export async function registerJVxeTable(app: App) { + // VXETable 全局配置 + const VXETableSettings = { + // z-index 起始值 + zIndex: 1000, + table: {}, + }; + + // 添加事件拦截器 event.clearActived + // 比如点击了某个组件的弹出层面板之后,此时被激活单元格不应该被自动关闭,通过返回 false 可以阻止默认的行为。 + VXETable.interceptor.add('event.clearActived', preventClosingPopUp); + VXETable.interceptor.add('event.clearEdit', preventClosingPopUp); + // 注册插件 + VXETable.use(VXETablePluginAntd); + // 注册自定义组件 + registerAllComponent(); + // 执行注册方法 + app.use(VxeUIAll); + app.use(VXETable, VXETableSettings); +} + + +/** + * 阻止行编辑中关闭弹窗 + * @param params + */ +function preventClosingPopUp(this: any, params) { + // 获取组件增强 + let col = params.column.params; + // 代码逻辑说明: 【issues/8178】使用原生vxe-table组件编辑模式下失去焦点报错 + if (col === undefined) { + // 说明使用的是纯原生的vxe-table + return; + } + let { $event } = params; + const interceptor = getEnhanced(col.type).interceptor; + // 执行增强 + let flag = interceptor['event.clearActived']?.call(this, ...arguments); + if (flag === false) { + return false; + } + + let path = getEventPath($event); + for (let p of path) { + let className: any = p.className || ''; + className = typeof className === 'string' ? className : className.toString(); + + /* --- 特殊处理以下组件,点击以下标签时不清空编辑状态 --- */ + + // 点击的标签是JInputPop + if (className.includes('j-input-pop')) { + return false; + } + // 点击的标签是JPopup的弹出层、部门选择、用户选择 + if (className.includes('j-popup-modal') || className.includes('j-depart-select-modal') || className.includes('j-user-select-modal')) { + return false; + } + // 点击的是日期选择器 + if (className.includes('j-vxe-date-picker')) { + return false; + } + // 执行增强 + let flag = interceptor['event.clearActived.className']?.call(this, className, ...arguments); + if (flag === false) { + return false; + } + } +} diff --git a/src/components/jeecg/JVxeTable/src/style/index.less b/src/components/jeecg/JVxeTable/src/style/index.less new file mode 100644 index 0000000..ff20d0d --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/style/index.less @@ -0,0 +1,115 @@ +@import 'vxe.const'; +@import 'vxe.dark'; + +.@{prefix-cls} { + // 编辑按钮样式 + .vxe-cell--edit-icon { + border-color: #606266; + } + + .sort--active { + border-color: @primary-color; + } + + // toolbar 样式 + &-toolbar { + &-collapsed { + [data-collapse] { + display: none; + } + } + + &-button.div .ant-btn { + margin-right: 8px; + } + } + + // 分页器 + .j-vxe-pagination { + margin-top: 8px; + text-align: right; + + .ant-pagination-options-size-changer.ant-select { + margin-right: 0; + } + + &.show-quick-jumper { + .ant-pagination-options-size-changer.ant-select { + margin-right: 8px; + } + } + } + + // 更改 header 底色 + .vxe-table.border--default .vxe-table--header-wrapper, + .vxe-table.border--full .vxe-table--header-wrapper, + .vxe-table.border--outer .vxe-table--header-wrapper { + //background-color: #FFFFFF; + } + + // 更改 tooltip 校验失败的颜色 + .vxe-table--tooltip-wrapper.vxe-table--valid-error { + background-color: #f5222d !important; + } + + // 更改 输入框 校验失败的颜色 + .col--valid-error > .vxe-cell > .ant-input, + .col--valid-error > .vxe-cell > .ant-select .ant-input, + .col--valid-error > .vxe-cell > .ant-select .ant-select-selection, + .col--valid-error > .vxe-cell > .ant-input-number, + .col--valid-error > .vxe-cell > .ant-cascader-picker .ant-cascader-input, + .col--valid-error > .vxe-cell > .ant-calendar-picker .ant-calendar-picker-input, + .col--valid-error > .vxe-tree-cell > .ant-input, + .col--valid-error > .vxe-tree-cell > .ant-select .ant-input, + .col--valid-error > .vxe-tree-cell > .ant-select .ant-select-selection, + .col--valid-error > .vxe-tree-cell > .ant-input-number, + .col--valid-error > .vxe-tree-cell > .ant-cascader-picker .ant-cascader-input, + .col--valid-error > .vxe-tree-cell > .ant-calendar-picker .ant-calendar-picker-input { + border-color: #f5222d !important; + } + // update-begin--author:liaozhiyang---date:20260415---for:【QQYUN-15134】修复jvxetable使用fixed固定后无法拖拽 + // 被拖起的源行:禁用/半透明效果,跨主体和固定列 wrapper 同步生效 + .vxe-body--row.j-vxe-drag-source { + opacity: 0.4; + } + // 拖拽方向指示线(用 box-shadow 实现,不影响行高/布局) + .vxe-body--row.j-vxe-drag-hover-top { + box-shadow: inset 0 2px 0 0 #1890ff; + } + .vxe-body--row.j-vxe-drag-hover-bottom { + box-shadow: inset 0 -3px 0 0 #1890ff; + } + + // update-end--author:liaozhiyang---date:20260415---for:【QQYUN-15134】修复jvxetable使用fixed固定后无法拖拽 + .vxe-body--row.sortable-ghost, + .vxe-body--row.sortable-chosen { + background-color: transparent; + } + + // ----------- 【VUEN-1691】默认隐藏滚动条,鼠标放上去才显示 ------------------------------------------- + .vxe-table { + //.vxe-table--footer-wrapper.body--wrapper, + .vxe-table--body-wrapper.body--wrapper { + // overflow-x: hidden; + } + + &:hover { + //.vxe-table--footer-wrapper.body--wrapper, + .vxe-table--body-wrapper.body--wrapper { + // overflow-x: auto; + } + } + } + // ----------- 【VUEN-1691】默认隐藏滚动条,鼠标放上去才显示 ------------------------------------------- + + // 调整展开/收起图标样式 + .vxe-table--render-default .vxe-table--expanded .vxe-table--expand-btn { + width: 17px; + height: 17px; + } + /*【美化表单】行编辑table的title字体改小一号*/ + .vxe-header--column.col--ellipsis>.vxe-cell .vxe-cell--title{ + font-size: 13px; + } + +} diff --git a/src/components/jeecg/JVxeTable/src/style/reload-effect.less b/src/components/jeecg/JVxeTable/src/style/reload-effect.less new file mode 100644 index 0000000..0333c81 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/style/reload-effect.less @@ -0,0 +1,44 @@ +.j-vxe-reload-effect-box { + &, + .j-vxe-reload-effect-span { + display: inline; + height: 100%; + position: relative; + } + + .j-vxe-reload-effect-span { + &.layer-top { + display: inline-block; + width: 100%; + + position: absolute; + z-index: 2; + background-color: white; + + transform-origin: 0 0; + animation: reload-effect 1.5s forwards; + } + + &.layer-bottom { + z-index: 1; + } + } + + // 定义动画 + @keyframes reload-effect { + 0% { + opacity: 1; + transform: rotateX(0); + } + 10% { + opacity: 1; + } + 90% { + opacity: 0; + } + 100% { + opacity: 0; + transform: rotateX(180deg); + } + } +} diff --git a/src/components/jeecg/JVxeTable/src/style/vxe.const.less b/src/components/jeecg/JVxeTable/src/style/vxe.const.less new file mode 100644 index 0000000..49db4e3 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/style/vxe.const.less @@ -0,0 +1,2 @@ +//noinspection LessUnresolvedVariable +@prefix-cls: ~'@{namespace}-j-vxe-table'; diff --git a/src/components/jeecg/JVxeTable/src/style/vxe.dark.less b/src/components/jeecg/JVxeTable/src/style/vxe.dark.less new file mode 100644 index 0000000..b050917 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/style/vxe.dark.less @@ -0,0 +1,124 @@ +@import 'vxe.const'; +// update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8493】修正暗黑模式online表单Erp和编辑页面显示不正确 +html[data-theme='dark'] { + --vxe-table-body-background-color: #151515; + --vxe-table-footer-background-color: #151515; + --vxe-table-border-color: #606060; + --vxe-table-popup-border-color:#606060; + --vxe-table-row-hover-background-color:#1e1e1e; + --vxe-input-border-color: #606266; +} +// update-end--author:liaozhiyang---date:20240313---for:【QQYUN-8493】修正暗黑模式online表单Erp和编辑页面显示不正确 +[data-theme='dark'] .@{prefix-cls} { + @fontColor: #c9d1d9; + @bgColor: #151515; + @borderColor: #606060; + + .vxe-cell--item, + .vxe-cell--title, + .vxe-cell, + .vxe-body--expanded-cell { + color: @fontColor; + } + + .vxe-toolbar { + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8493】修正暗黑模式online表单Erp和编辑页面显示不正确 + background-color: #1f1f1f; + // update-end--author:liaozhiyang---date:20240313---for:【QQYUN-8493】修正暗黑模式online表单Erp和编辑页面显示不正确 + } + + .vxe-table--render-default .vxe-table--body-wrapper, + .vxe-table--render-default .vxe-table--footer-wrapper { + background-color: @bgColor; + } + + // 外边框 + .vxe-table--render-default .vxe-table--border-line { + border-color: @borderColor; + } + + // header 下边框 + .vxe-table .vxe-table--header-wrapper .vxe-table--header-border-line { + border-bottom-color: @borderColor; + } + + // footer 上边框 + .vxe-table--render-default .vxe-table--footer-wrapper { + border-top-color: @borderColor; + } + + // 展开行 边框 + .vxe-table--render-default .vxe-body--expanded-column { + border-bottom-color: @borderColor; + } + + // 行斑马纹 + .vxe-table--render-default .vxe-body--row.row--stripe { + background-color: #1e1e1e; + } + + // 行hover + .vxe-table--render-default .vxe-body--row.row--hover { + background-color: #262626; + } + + // 选中行 + .vxe-table--render-default .vxe-body--row.row--checked { + background-color: #44403a; + + &.row--hover { + background-color: #59524b; + } + } + + .vxe-table--render-default.border--default .vxe-table--header-wrapper, + .vxe-table--render-default.border--full .vxe-table--header-wrapper, + .vxe-table--render-default.border--outer .vxe-table--header-wrapper { + background-color: #1d1d1d; + } + + .vxe-table--render-default.border--default .vxe-body--column, + .vxe-table--render-default.border--default .vxe-footer--column, + .vxe-table--render-default.border--default .vxe-header--column, + .vxe-table--render-default.border--inner .vxe-body--column, + .vxe-table--render-default.border--inner .vxe-footer--column, + .vxe-table--render-default.border--inner .vxe-header--column { + background-image: linear-gradient(#1d1d1d, #1d1d1d); + } + + // 列宽拖动 + .vxe-header--column .vxe-resizable.is--line:before { + background-color: #505050; + } + + // checkbox + .vxe-custom--option .vxe-checkbox--icon:before, + .vxe-export--panel-column-option .vxe-checkbox--icon:before, + .vxe-table--filter-option .vxe-checkbox--icon:before, + .vxe-table--render-default .vxe-cell--checkbox .vxe-checkbox--icon:before { + background-color: @bgColor; + border-color: @borderColor; + } + + .vxe-toolbar .vxe-custom--option-wrapper { + background-color: @bgColor; + } + + .vxe-button { + background-color: @bgColor; + border-color: @borderColor; + } + + .vxe-button.type--button:not(.is--disabled):active { + background-color: @bgColor; + } + + .vxe-toolbar .vxe-custom--wrapper.is--active > .vxe-button { + background-color: @bgColor; + } + + .vxe-toolbar .vxe-custom--option-wrapper .vxe-custom--footer button { + color: @fontColor; + } +} + diff --git a/src/components/jeecg/JVxeTable/src/types/JVxeComponent.ts b/src/components/jeecg/JVxeTable/src/types/JVxeComponent.ts new file mode 100644 index 0000000..1e7009a --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/types/JVxeComponent.ts @@ -0,0 +1,87 @@ +import { ComponentInternalInstance, ExtractPropTypes } from 'vue'; +import { useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks'; + +export namespace JVxeComponent { + export type Props = ExtractPropTypes>; + + interface EnhancedCtx { + props?: JVxeComponent.Props; + context?: any; + } + + /** 组件增强类型 */ + export interface Enhanced { + // 注册参数(详见:https://xuliangzhan_admin.gitee.io/vxe-table/v4/table/renderer/edit) + installOptions: { + // 自动聚焦的 class 类名 + autofocus?: string; + } & Recordable; + // 事件拦截器(用于兼容) + interceptor: { + // 已实现:event.clearActived + // 说明:比如点击了某个组件的弹出层面板之后,此时被激活单元格不应该被自动关闭,通过返回 false 可以阻止默认的行为。 + 'event.clearActived'?: (params, event, target, ctx?: EnhancedCtx) => boolean; + // 自定义:event.clearActived.className + // 说明:比原生的多了一个参数:className,用于判断点击的元素的样式名(递归到顶层) + 'event.clearActived.className'?: (params, event, target, ctx?: EnhancedCtx) => boolean; + }; + // 【功能开关】 + switches: { + // 是否使用 editRender 模式(仅当前组件,并非全局) + // 如果设为true,则表头上方会出现一个可编辑的图标 + editRender?: boolean; + // false = 组件触发后可视);true = 组件一直可视 + visible?: boolean; + }; + // 【切面增强】切面事件处理,一般在某些方法执行后同步执行 + aopEvents: { + // 单元格被激活编辑时会触发该事件 + editActived?: (this: ComponentInternalInstance, ...args) => any; + // 单元格编辑状态下被关闭时会触发该事件 + editClosed?: (this: ComponentInternalInstance, ...args) => any; + // 返回值决定单元格是否可以编辑 + activeMethod?: (this: ComponentInternalInstance, ...args) => boolean; + }; + // 【翻译增强】可以实现例如select组件保存的value,但是span模式下需要显示成text + translate: { + // 是否启用翻译 + enabled?: boolean; + /** + * 【翻译处理方法】如果handler留空,则使用默认的翻译方法 + * + * @param value 需要翻译的值 + * @returns{*} 返回翻译后的数据 + */ + handler?: (value, ctx?: EnhancedCtx) => any; + }; + /** + * 【获取值增强】组件抛出的值 + * + * @param value 保存到数据库里的值 + * @returns{*} 返回处理后的值 + */ + getValue: (value, ctx?: EnhancedCtx) => any; + /** + * 【设置值增强】设置给组件的值 + * + * @param value 组件触发的值 + * @returns{*} 返回处理后的值 + */ + setValue: (value, ctx?: EnhancedCtx) => any; + /** + * 【新增行增强】在用户点击新增时触发的事件,返回新行的默认值 + * + * @param defaultValue 默认值 + * @param row 行数据 + * @param column 列配置,.params 是用户配置的参数 + * @param $table vxe 实例 + * @param renderOptions 渲染选项 + * @param params 可以在这里获取 $table + * + * @returns 返回新值 + */ + createValue: (defaultValue: any, ctx?: EnhancedCtx) => any; + } + + export type EnhancedPartial = Partial; +} diff --git a/src/components/jeecg/JVxeTable/src/types/JVxeTypes.ts b/src/components/jeecg/JVxeTable/src/types/JVxeTypes.ts new file mode 100644 index 0000000..6b7601a --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/types/JVxeTypes.ts @@ -0,0 +1,68 @@ +/** 组件类型 */ +export enum JVxeTypes { + // 行号列 + rowNumber = 'row-number', + // 选择列 + rowCheckbox = 'row-checkbox', + // 单选列 + rowRadio = 'row-radio', + // 展开列 + rowExpand = 'row-expand', + // 上下排序 + rowDragSort = 'row-drag-sort', + + input = 'input', + inputNumber = 'input-number', + textarea = 'textarea', + select = 'select', + date = 'date', + datetime = 'datetime', + time = 'time', + checkbox = 'checkbox', + upload = 'upload', + // 下拉搜索 + selectSearch = 'select-search', + // 下拉多选 + selectMultiple = 'select-multiple', + // 进度条 + progress = 'progress', + //部门选择 + departSelect = 'depart-select', + //用户选择 + userSelect = 'user-select', + + // 拖轮Tags(暂无用) + tags = 'tags', // TODO 待实现 + + slot = 'slot', + normal = 'normal', + hidden = 'hidden', + + // 以下为自定义组件 + popup = 'popup', + selectDictSearch = 'selectDictSearch', + radio = 'radio', + image = 'image', + file = 'file', + // 省市区 + pca = 'pca', + // 关联记录 + linkTable = 'link-table', + // update-begin--author:liaozhiyang---date:20260413---for:【issues/7633】online子表支持分类字典树,自定义树 + // 自定义树控件 + treeSelect = 'sel-tree', + // 分类字典树 + catTreeSelect = 'cat-tree', + // update-end--author:liaozhiyang---date:20260413---for:【issues/7633】online子表支持分类字典树,自定义树 +} + +// 为了防止和 vxe 内置的类型冲突,所以加上一个前缀 +// 前缀是自动加的,代码中直接用就行(JVxeTypes.input) +export const JVxeTypePrefix = 'j-'; + +/** VxeTable 渲染类型 */ +export enum JVxeRenderType { + editer = 'editer', + spaner = 'spaner', + default = 'default', +} diff --git a/src/components/jeecg/JVxeTable/src/types/index.ts b/src/components/jeecg/JVxeTable/src/types/index.ts new file mode 100644 index 0000000..a7dca8e --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/types/index.ts @@ -0,0 +1,120 @@ +import type { Component, Ref, ComputedRef, ExtractPropTypes } from 'vue'; +import type { VxeColumnProps } from 'vxe-table/types/column'; +import type { JVxeComponent } from './JVxeComponent'; +import type { VxeGridInstance, VxeTablePropTypes } from 'vxe-table'; +import { JVxeTypes } from './JVxeTypes'; +import { vxeProps } from '../vxe.data'; +import { useMethods } from '../hooks/useMethods'; +import { getJVxeAuths } from '../utils/authUtils'; + +export type JVxeTableProps = Partial>>; +export type JVxeTableMethods = ReturnType['methods']; + +export type JVxeVueComponent = { + enhanced?: JVxeComponent.EnhancedPartial; +} & Component; + +type statisticsTypes = 'sum' | 'average'; + +export type JVxeColumn = IJVxeColumn & Recordable; + +/** + * JVxe 列配置项 + */ +export interface IJVxeColumn extends VxeColumnProps { + type?: any; + // 行唯一标识 + key: string; + // 表单预期值的提示信息,可以使用${...}变量替换文本 + placeholder?: string; + // 默认值 + defaultValue?: any; + // 是否禁用当前列,默认false + disabled?: boolean; + // 校验规则 TODO 类型待定义 + validateRules?: any; + // 联动下一级的字段key + linkageKey?: string; + // 自定义传入组件的其他属性 + props?: Recordable; + allowClear?: boolean; // 允许清除 + // 【inputNumber】是否是统计列,只有 inputNumber 才能设置统计列。统计列:sum 求和;average 平均值 + statistics?: boolean | [statisticsTypes, statisticsTypes?]; + // 【select】 + dictCode?: string; // 字典 code + options?: { title?: string; label?: string; text?: string; value: any; disabled?: boolean }[]; // 下拉选项列表 + allowInput?: boolean; // 允许输入 + allowSearch?: boolean; // 允许搜索 + // 【slot】 + slotName?: string; // 插槽名 + // 【checkbox】 + customValue?: [any, any]; // 自定义值 + defaultChecked?: boolean; // 默认选中 + // 【upload】 upload + btnText?: string; // 上传按钮文字 + token?: boolean; // 是否传递 token + responseName?: string; // 返回取值名称 + action?: string; // 上传地址 + allowRemove?: boolean; // 是否允许删除 + allowDownload?: boolean; // 是否允许下载 + // 【下拉字典搜索】 + dict?: string; // 字典表配置信息:数据库表名,显示字段名,存储字段名 + async?: boolean; // 是否同步模式 + tipsContent?: string; + // 【popup】 + popupCode?: string; + field?: string; + orgFields?: string; + destFields?: string; +} + +export interface JVxeRefs { + gridRef: Ref; + subPopoverRef: Ref; + detailsModalRef: Ref; +} + +export interface JVxeDataProps { + prefixCls: string; + // vxe 实例ID + caseId: string; + // vxe 最终 columns + vxeColumns?: ComputedRef; + // vxe 最终 dataSource + vxeDataSource: Ref; + // 记录滚动条位置 + scroll: { top: number; left: number }; + // 当前是否正在滚动 + scrolling: Ref; + // vxe 默认配置 + defaultVxeProps: object; + // 绑定左侧选择框 + selectedRows: Ref; + // 绑定左侧选择框已选择的id + selectedRowIds: Ref; + disabledRowIds: string[]; + // 统计列配置 + statistics: { + has: boolean; + sum: string[]; + average: string[]; + }; + // 所有和当前表格相关的授权信息 + authsMap: Ref>>; + // 内置 EditRules + innerEditRules: Recordable; + // 联动下拉选项(用于隔离不同的下拉选项) + // 内部联动配置,map + innerLinkageConfig: Map; + // 开启了数据刷新效果的行 + reloadEffectRowKeysMap: Recordable; +} + +export interface JVxeLinkageConfig { + // 联动第一级的 key + key: string; + // 获取数据的方法 + requestData: (parent: string) => Promise; +} + +export { JVxeTypes }; diff --git a/src/components/jeecg/JVxeTable/src/utils/authUtils.ts b/src/components/jeecg/JVxeTable/src/utils/authUtils.ts new file mode 100644 index 0000000..689e381 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/utils/authUtils.ts @@ -0,0 +1,48 @@ +/* JVxeTable 行编辑 权限 */ +import { usePermissionStoreWithOut } from '/@/store/modules/permission'; + +/** + * JVxe 专用,获取权限 + * @param prefix + */ +export function getJVxeAuths(prefix) { + const permissionStore = usePermissionStoreWithOut(); + prefix = getPrefix(prefix); + let { authList, allAuthList } = permissionStore; + let authsMap = new Map(); + if (!prefix || prefix.length == 0) { + return authsMap; + } + // 将所有vxe用到的权限取出来 + for (let auth of allAuthList) { + if (auth.status == '1' && (auth.action || '').startsWith(prefix)) { + authsMap.set(auth.action, { ...auth, isAuth: false }); + } + } + // 设置是否已授权 + for (let auth of authList) { + let getAuth = authsMap.get(auth.action); + if (getAuth != null) { + getAuth.isAuth = true; + } + } + // 代码逻辑说明: VUEN-1162 子表按钮没控制 + let onlineButtonAuths = permissionStore.getOnlineSubTableAuth(prefix); + if (onlineButtonAuths && onlineButtonAuths.length > 0) { + for (let auth of onlineButtonAuths) { + authsMap.set(prefix + 'btn:' + auth, { action: auth, type: 1, status: 1, isAuth: false }); + } + } + return authsMap; +} + +/** + * 获取前缀 + * @param prefix + */ +export function getPrefix(prefix: string) { + if (prefix && !prefix.endsWith(':')) { + return prefix + ':'; + } + return prefix; +} diff --git a/src/components/jeecg/JVxeTable/src/utils/enhancedUtils.ts b/src/components/jeecg/JVxeTable/src/utils/enhancedUtils.ts new file mode 100644 index 0000000..32c3466 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/utils/enhancedUtils.ts @@ -0,0 +1,55 @@ +import { useDefaultEnhanced } from '../hooks/useJVxeComponent'; +import { isFunction, isObject, isString } from '/@/utils/is'; +import { JVxeTypes } from '../types'; +import { JVxeComponent } from '../types/JVxeComponent'; +import { componentMap } from '../componentMap'; + +// 已注册的组件增强 +const enhancedMap = new Map(); + +/** + * 获取某个组件的增强 + * @param type JVxeTypes + */ +export function getEnhanced(type: JVxeTypes | string): JVxeComponent.Enhanced { + let $type: JVxeTypes = type; + if (!enhancedMap.has($type)) { + let defaultEnhanced = useDefaultEnhanced(); + if (componentMap.has($type)) { + let enhanced = componentMap.get($type)?.enhanced ?? {}; + if (isObject(enhanced)) { + Object.keys(defaultEnhanced).forEach((key) => { + let def = defaultEnhanced[key]; + if (enhanced.hasOwnProperty(key)) { + // 方法如果存在就不覆盖 + if (!isFunction(def) && !isString(def)) { + enhanced[key] = Object.assign({}, def, enhanced[key]); + } + } else { + enhanced[key] = def; + } + }); + enhancedMap.set($type, enhanced); + return enhanced; + } + } else { + throw new Error(`[JVxeTable] ${$type} 组件尚未注册,获取增强失败`); + } + enhancedMap.set($type, defaultEnhanced); + } + return enhancedMap.get($type); +} + +/** 辅助方法:替换${...}变量 */ +export function replaceProps(col, value) { + if (value && typeof value === 'string') { + let text = value; + text = text.replace(/\${title}/g, col.title); + text = text.replace(/\${key}/g, col.key); + text = text.replace(/\${defaultValue}/g, col.defaultValue); + return text; + } + return value; +} + + diff --git a/src/components/jeecg/JVxeTable/src/utils/registerUtils.ts b/src/components/jeecg/JVxeTable/src/utils/registerUtils.ts new file mode 100644 index 0000000..74cae91 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/utils/registerUtils.ts @@ -0,0 +1,150 @@ +import type { Component } from 'vue'; +import { h } from 'vue'; +import VXETable from 'vxe-table'; +import { definedComponent, addComponent, componentMap, spanEnds, excludeKeywords } from '../componentMap'; +import { JVxeRenderType, JVxeTypePrefix, JVxeTypes } from '../types/JVxeTypes'; +import { getEnhanced } from './enhancedUtils'; +import { isFunction } from '/@/utils/is'; + +/** + * 判断某个组件是否已注册 + * @param type + */ +export function isRegistered(type: JVxeTypes | string) { + if (excludeKeywords.includes(type)) { + return true; + } + return componentMap.has(type); +} + +/** + * 注册vxe自定义组件 + * + * @param type + * @param component 编辑状态显示的组件 + * @param spanComponent 非编辑状态显示的组件,可以为空 + */ +export function registerComponent(type: JVxeTypes, component: Component, spanComponent?: Component) { + addComponent(type, component, spanComponent); + registerOneComponent(type); +} + +/** + * 异步注册vxe自定义组件 + * + * @param type + * @param promise + */ +export async function registerAsyncComponent(type: JVxeTypes, promise: Promise) { + const result = await promise; + if (isFunction(result.installJVxe)) { + result.install((component: Component, spanComponent?: Component) => { + addComponent(type, component, spanComponent); + registerOneComponent(type); + }); + } else { + addComponent(type, result.default); + registerOneComponent(type); + } +} + +/** + * 2024-03-08 + * liaozhiyang + * 异步注册vxe自定义组件 + * 【QQYUN-8241】 + * @param type + * @param promise + */ +export function registerASyncComponentReal(type: JVxeTypes, component) { + addComponent(type, component); + registerOneComponent(type); +} + +/** + * 安装所有vxe组件 + */ +export function registerAllComponent() { + definedComponent(); + // 遍历所有组件批量注册 + const components = [...componentMap.keys()]; + components.forEach((type) => { + if (!type.endsWith(spanEnds)) { + registerOneComponent(type); + } + }); +} + +/** + * 注册单个vxe组件 + * + * @param type 组件 type + */ +export function registerOneComponent(type: JVxeTypes) { + const component = componentMap.get(type); + if (component) { + const switches = getEnhanced(type).switches; + if (switches.editRender && !switches.visible) { + createEditRender(type, component); + } else { + createCellRender(type, component); + } + } else { + throw new Error(`【registerOneComponent】"${type}"不存在于componentMap中`); + } +} + +/** 注册可编辑组件 */ +function createEditRender(type: JVxeTypes, component: Component, spanComponent?: Component) { + // 获取当前组件的增强 + const enhanced = getEnhanced(type); + if (!spanComponent) { + if (componentMap.has(type + spanEnds)) { + spanComponent = componentMap.get(type + spanEnds); + } else { + // 默认的 span 组件为 normal + spanComponent = componentMap.get(JVxeTypes.normal); + } + } + // 添加渲染 + VXETable.renderer.add(JVxeTypePrefix + type, { + // 可编辑模板 + renderEdit: createRender(type, component, JVxeRenderType.editer), + // 显示模板 + renderCell: createRender(type, spanComponent, JVxeRenderType.spaner), + // 增强注册 + ...enhanced.installOptions, + }); +} + +/** 注册普通组件 */ +function createCellRender(type: JVxeTypes, component: Component = componentMap.get(JVxeTypes.normal)) { + // 获取当前组件的增强 + const enhanced = getEnhanced(type); + VXETable.renderer.add(JVxeTypePrefix + type, { + // 默认显示模板 + renderDefault: createRender(type, component, JVxeRenderType.default), + // 增强注册 + ...enhanced.installOptions, + }); +} + +function createRender(type, component, renderType) { + return function (renderOptions, params) { + // update-begin--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + // 为每个 cell 生成唯一 key,防止相邻列(值相同但 options 不同)被错误复用 + const rowId = params.row?.id ?? params.rowIndex ?? ''; + const colId = params.column?.property ?? params.column?.id ?? ''; + const cellKey = `cell-${rowId}-${colId}`; + // update-end--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + return [ + h(component, { + key: cellKey, + type: type, + params: params, + renderOptions: renderOptions, + renderType: renderType, + }), + ]; + }; +} diff --git a/src/components/jeecg/JVxeTable/src/utils/vxeUtils.ts b/src/components/jeecg/JVxeTable/src/utils/vxeUtils.ts new file mode 100644 index 0000000..108fd67 --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/utils/vxeUtils.ts @@ -0,0 +1,21 @@ +/** + * + * 根据 tagName 获取父级节点 + * + * @param dom 一级dom节点 + * @param tagName 标签名,不区分大小写 + */ +export function getParentNodeByTagName(dom: HTMLElement, tagName: string = 'body'): HTMLElement | null { + if (tagName === 'body') { + return document.body; + } + if (dom.parentElement) { + if (dom.parentElement.tagName.toLowerCase() === tagName.trim().toLowerCase()) { + return dom.parentElement; + } else { + return getParentNodeByTagName(dom.parentElement, tagName); + } + } else { + return null; + } +} diff --git a/src/components/jeecg/JVxeTable/src/vxe.data.ts b/src/components/jeecg/JVxeTable/src/vxe.data.ts new file mode 100644 index 0000000..e603dbd --- /dev/null +++ b/src/components/jeecg/JVxeTable/src/vxe.data.ts @@ -0,0 +1,121 @@ +import { propTypes } from '/@/utils/propTypes'; + +export const vxeProps = () => ({ + rowKey: propTypes.string.def('id'), + // 列信息 + columns: { + type: Array, + required: true, + }, + // 数据源 + dataSource: { + type: Array, + required: true, + }, + authPre: { + type: String, + required: false, + default: '', + }, + // 是否显示工具栏 + toolbar: propTypes.bool.def(false), + // 工具栏配置 + toolbarConfig: propTypes.object.def(() => ({ + // prefix 前缀;suffix 后缀; + slots: ['prefix', 'suffix'], + // add 新增按钮;remove 删除按钮;clearSelection 清空选择按钮;collapse 展开收起 + btns: ['add', 'remove', 'clearSelection'], + })), + // 是否显示行号 + rowNumber: propTypes.bool.def(false), + // 固定行号位置或者不固定 【QQYUN-8405】 + rowNumberFixed: propTypes.oneOf(['left', 'none']).def('left'), + // 代码逻辑说明: 【issues/1162】JVxeTable列过长(出现横向滚动条)时无法拖拽排序 + dragSortFixed: propTypes.oneOf(['left', 'none']).def('left'), + rowSelectionFixed: propTypes.oneOf(['left', 'none']).def('left'), + // 是否可选择行 + rowSelection: propTypes.bool.def(false), + // 选择行类型 + rowSelectionType: propTypes.oneOf(['checkbox', 'radio']).def('checkbox'), + // 是否可展开行 + rowExpand: propTypes.bool.def(false), + // 展开行配置 + expandConfig: propTypes.object.def(() => ({})), + // 是否可插入行 + insertRow: propTypes.bool.def(true), + // 页面是否在加载中 + loading: propTypes.bool.def(false), + // 表格高度 + height: propTypes.oneOfType([propTypes.number, propTypes.string]).def('auto'), + // 最大高度 + maxHeight: { + type: Number, + default: () => null, + }, + // 要禁用的行 + disabledRows: propTypes.object.def(() => ({})), + // 是否禁用全部组件 + disabled: propTypes.bool.def(false), + // 是否可拖拽排序(有固定列的情况下无法拖拽排序,仅可上下排序) + dragSort: propTypes.bool.def(false), + // 排序字段保存的Key + sortKey: propTypes.string.def('orderNum'), + // 排序序号开始值,默认为 0 + sortBegin: propTypes.number.def(0), + // 大小,可选值有:medium(中)、small(小)、mini(微) + size: propTypes.oneOf(['medium', 'small', 'mini']).def('medium'), + // 是否显示边框线 + bordered: propTypes.bool.def(false), + // 自定义列配置 默认继承 setup.toolbar.custom + custom: propTypes.bool.def(false), + // 分页器参数,设置了即可显示分页器 + pagination: propTypes.object.def(() => ({})), + // 点击行时是否显示子表单 + clickRowShowSubForm: propTypes.bool.def(false), + // 点击行时是否显示主表单 + clickRowShowMainForm: propTypes.bool.def(false), + // 是否点击选中行,优先级最低 + clickSelectRow: propTypes.bool.def(false), + // 是否开启 reload 数据效果 + reloadEffect: propTypes.bool.def(false), + // 校验规则 + editRules: propTypes.object.def(() => ({})), + // 是否异步删除行,如果你要实现异步删除,那么需要把这个选项开启, + // 在remove事件里调用confirmRemove方法才会真正删除(除非删除的全是新增的行) + asyncRemove: propTypes.bool.def(false), + // 是否一直显示组件,如果为false则只有点击的时候才出现组件 + // 注:该参数不能动态修改;如果行、列字段多的情况下,会根据机器性能造成不同程度的卡顿。 + // TODO 新版vxe-table取消了 visible 参数,导致无法实现该功能 + alwaysEdit: propTypes.bool.def(false), + // 联动配置,数组,详情配置见文档 + linkageConfig: propTypes.array.def(() => []), + // 是否开启使用 webSocket 无痕刷新 + socketReload: propTypes.bool.def(false), + // 相同的socketKey更改时会互相刷新 + socketKey: propTypes.string.def('vxe-default'), + // 新增行时切换行的激活状态 + addSetActive: propTypes.bool.def(true), + // 是否开启键盘编辑 + keyboardEdit: propTypes.bool.def(false), + // 横向虚拟滚动配置(不支持展开行) + // 【QQYUN-7676】x滚动条滚动时字典变成了id + scrollX: propTypes.object.def(() => ({ enabled: false })), + // 纵向虚拟滚动配置(不支持展开行) + scrollY: propTypes.object.def(() => ({ enabled: true })), + //【QQYUN-8566】缓存列设置的key(路由页面内唯一) + cacheColumnsKey: propTypes.string.def(''), + // 代码逻辑说明: 【QQYUN-8785】online表单列位置的id未做限制,拖动其他列到id列上面,同步数据库时报错 + rowClassName: { + type: [String, Function], + default: null, + }, + // 不允许拖拽的行 [{'key':field,'value':value}] + notAllowDrag: propTypes.array.def(() => []), + + // 新增按钮配置 + addBtnCfg: propTypes.object, + // 删除按钮配置 + removeBtnCfg: propTypes.object, +}); + +export const vxeEmits = ['save', 'added', 'removed', 'inserted', 'dragged', 'selectRowChange', 'pageChange', 'valueChange', 'blur']; diff --git a/src/components/jeecg/JVxeTable/types.ts b/src/components/jeecg/JVxeTable/types.ts new file mode 100644 index 0000000..7e668f7 --- /dev/null +++ b/src/components/jeecg/JVxeTable/types.ts @@ -0,0 +1,6 @@ +import JVxeTable from './src/JVxeTable'; + +export type { JVxeComponent } from './src/types/JVxeComponent'; +export type { JVxeColumn, JVxeLinkageConfig } from './src/types'; +export { JVxeTypes } from './src/types/JVxeTypes'; +export type JVxeTableInstance = InstanceType; diff --git a/src/components/jeecg/JVxeTable/useVxeTableRegister.ts b/src/components/jeecg/JVxeTable/useVxeTableRegister.ts new file mode 100644 index 0000000..9d2fcb5 --- /dev/null +++ b/src/components/jeecg/JVxeTable/useVxeTableRegister.ts @@ -0,0 +1,12 @@ +// 给使用原生vxe-table的页面注册vxe-table组件 +export const useVxeTableRegister = async () => { + const app = window['JAppRootInstance']; + if (app._context.components.VxeTable) { + // 已全局注册 + } else { + const { registerJVxeTable } = await import('/@/components/jeecg/JVxeTable'); + await registerJVxeTable(app); + const { registerJVxeCustom } = await import('/@/components/JVxeCustom'); + await registerJVxeCustom(); + } +} diff --git a/src/components/jeecg/JVxeTable/utils.ts b/src/components/jeecg/JVxeTable/utils.ts new file mode 100644 index 0000000..287c45f --- /dev/null +++ b/src/components/jeecg/JVxeTable/utils.ts @@ -0,0 +1,132 @@ +import type { Ref, ComponentInternalInstance } from 'vue'; +import { unref, isRef } from 'vue'; +import { isFunction } from '/@/utils/is'; + +type dispatchEventOptions = { + // JVxeTable 的 props + props; + // 触发的 event 事件对象 + $event; + // 行、列 + row?; + column?; + // JVxeTable的vue3实例 + instance?: ComponentInternalInstance | any; + // 要寻找的className + className: string; + // 重写找到dom后的处理方法 + handler?: Fn; + // 是否直接执行click方法而不是模拟click事件 + isClick?: boolean; +}; + +/** 模拟触发事件 */ +export function dispatchEvent(options: dispatchEventOptions) { + const { props, $event, row, column, instance, className, handler, isClick } = options; + if ((!$event || !$event.path) && !instance) { + return; + } + // alwaysEdit 下不模拟触发事件,否者会导致触发两次 + if (props && props.alwaysEdit) { + return; + } + let getCell = () => { + let paths: HTMLElement[] = [...($event?.path ?? [])]; + // 通过 instance 获取 cell dom对象 + if (row && column) { + let selector = `table.vxe-table--body tbody tr[rowid='${row.id}'] td[colid='${column.id}']`; + let cellDom = instance!.vnode?.el?.querySelector(selector); + // -update-begin--author:liaozhiyang---date:20230830---for:【QQYUN-6390】解决online新增字段警告(兼容下) + if (!cellDom) { + cellDom = instance!.$el?.querySelector(selector); + } + // -update-begin--author:liaozhiyang---date:20230830---for:【QQYUN-6390】解决online新增字段警告(兼容下) + if (cellDom) { + paths.unshift(cellDom); + } + } + for (const el of paths) { + if (el.classList?.contains('vxe-body--column')) { + return el; + } + } + return null; + }; + let cell = getCell(); + if (cell) { + window.setTimeout(() => { + let getElement = () => { + let classList = className.split(' '); + if (classList.length > 0) { + const getClassName = (cls: string) => { + if (cls.startsWith('.')) { + return cls.substring(1, cls.length); + } + return cls; + }; + let get = (target, className, idx = 0) => { + let elements = target.getElementsByClassName(getClassName(className)); + if (elements && elements.length > 0) { + return elements[idx]; + } + return null; + }; + let element: HTMLElement = get(cell, classList[0]); + for (let i = 1; i < classList.length; i++) { + if (!element) { + break; + } + element = get(element, classList[i]); + } + return element; + } + return null; + }; + let element = getElement(); + if (element) { + if (isFunction(handler)) { + handler(element); + } else { + // 模拟触发点击事件 + if (isClick) { + element.click(); + } else { + element.dispatchEvent($event); + } + } + } + }, 10); + } else { + console.warn('【JVxeTable】dispatchEvent 获取 cell 失败'); + } +} + +/** 绑定 VxeTable 数据 */ +export function vModel(value, row, column: Ref | string) { + // @ts-ignore + let property = isRef(column) ? column.value.property : column; + unref(row)[property] = value; +} + +/** + * liaozhiyang + * 2024-06-20 + * 判断当前行编辑是否使用了虚拟滚动(并不是开启了就是,还得满足数据数量大于gt值) + */ +export function isEnabledVirtualYScroll(props, xTable): boolean { + let isRealEnabledVirtual = false; + const isEnabledVScroll = props?.scrollY?.enabled; + // 100是底层的默认值 + const gtYNum = props?.scrollY?.gt || 100; + if (isEnabledVScroll) { + const tableFullData = xTable.internalData.tableFullData; + if (gtYNum === 0) { + isRealEnabledVirtual = true; + } else { + if (tableFullData.length > gtYNum) { + isRealEnabledVirtual = true; + } + } + } + return isRealEnabledVirtual; +} diff --git a/src/components/jeecg/OnLine/JPopupOnlReport.vue b/src/components/jeecg/OnLine/JPopupOnlReport.vue new file mode 100644 index 0000000..5c02b16 --- /dev/null +++ b/src/components/jeecg/OnLine/JPopupOnlReport.vue @@ -0,0 +1,306 @@ + + + + + diff --git a/src/components/jeecg/OnLine/SearchFormItem.vue b/src/components/jeecg/OnLine/SearchFormItem.vue new file mode 100644 index 0000000..cb766d0 --- /dev/null +++ b/src/components/jeecg/OnLine/SearchFormItem.vue @@ -0,0 +1,327 @@ + + + + + diff --git a/src/components/jeecg/OnLine/hooks/usePopBiz.ts b/src/components/jeecg/OnLine/hooks/usePopBiz.ts new file mode 100644 index 0000000..a02592c --- /dev/null +++ b/src/components/jeecg/OnLine/hooks/usePopBiz.ts @@ -0,0 +1,973 @@ +import { reactive, ref, unref, defineAsyncComponent, toRaw, markRaw, isRef, watch, onUnmounted } from 'vue'; +import { httpGroupRequest } from '/@/components/Form/src/utils/GroupRequest'; +import { defHttp } from '/@/utils/http/axios'; +import { filterMultiDictText } from '/@/utils/dict/JDictSelectUtil.js'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { OnlineColumn } from '/@/components/jeecg/OnLine/types/onlineConfig'; +import { h } from 'vue'; +import { useRouter, useRoute } from 'vue-router'; +import { useMethods } from '/@/hooks/system/useMethods'; +import { importViewsFile, _eval } from '/@/utils'; +import {getToken} from "@/utils/auth"; +import {replaceUserInfoByExpression} from "@/utils/common/compUtils"; +import { isString } from '/@/utils/is'; + +export function usePopBiz(ob, tableRef?) { + // 代码逻辑说明: 【issues/675】子表字段Popup弹框数据不更新 + let props: any; + if (isRef(ob)) { + props = ob.value; + const stopWatch = watch(ob, (newVal) => { + props = newVal; + }); + onUnmounted(() => stopWatch()); + } else { + props = ob; + } + const { createMessage } = useMessage(); + //弹窗可视状态 + const visible = ref(false); + //表格加载 + const loading = ref(false); + //cgRpConfigId + const cgRpConfigId = ref(''); + //标题 + const title = ref('列表'); + // 排序字段,默认无排序 + const iSorter = ref(''); + // 查询对象 + const queryInfo = ref([]); + // 查询参数 + const queryParam = ref({}); + // 动态参数 + const dynamicParam = ref({}); + //字典配置项 + const dictOptions = ref({}); + //数据集 + const dataSource = ref>([]); + //定义表格信息 + const columns = ref>([]); + // 当前路由 + const route = useRoute(); + //定义请求url信息 + const configUrl = reactive({ + //列表页加载column和data + getColumnsAndData: '/online/cgreport/api/getColumnsAndData/', + getColumns: '/online/cgreport/api/getRpColumns/', + getData: '/online/cgreport/api/getData/', + getQueryInfo: '/online/cgreport/api/getQueryInfo/', + export: '/online/cgreport/api/exportManySheetXls/', + }); + //已选择的值 + const checkedKeys = ref>([]); + //选择的行记录 + const selectRows = ref>([]); + // 点击单元格选中行 popup需要 但是报表预览不需要 + let clickThenCheckFlag = true; + if (props.clickToRowSelect === false) { + clickThenCheckFlag = false; + } + + /** + * 选择列配置 + */ + const rowSelection = { + fixed: true, + type: props.multi ? 'checkbox' : 'radio', + selectedRowKeys: checkedKeys, + selectionRows: selectRows, + onChange: onSelectChange, + }; + + /** + * 序号列配置 + */ + const indexColumnProps = { + dataIndex: 'index', + width: '15px', + }; + /** + * 分页配置 + */ + const pagination = reactive({ + current: 1, + pageSize: 10, + pageSizeOptions: ['10', '20', '30'], + // showTotal: (total, range) => { + // return range[0] + '-' + range[1] + ' 共' + total + '条' + // }, + showQuickJumper: true, + showSizeChanger: true, + total: 0, + // 合计逻辑 [待优化 3.0] + showTotal: (total) => onShowTotal(total), + realPageSize: 10, + realTotal: 0, + // 是否有合计列,默认为"",在第一次获取到数据之后会设计为ture或者false + isTotal: '', + onShowSizeChange: (current, pageSize) => onSizeChange(current, pageSize), + }); + + /** + * 表格选择事件 + * @param selectedRowKeys + * @param selectRow + */ + function onSelectChange(selectedRowKeys: (string | number)[]) { + // 代码逻辑说明: 【QQYUN-7514】popup单选显示radio + if (!props.multi) { + selectRows.value = []; + checkedKeys.value = []; + // selectedRowKeys = [selectedRowKeys[selectedRowKeys.length - 1]]; + } + // 代码逻辑说明: 【QQYUN-4263】跨页选择导出问题 + if (!selectedRowKeys || selectedRowKeys.length == 0) { + selectRows.value = []; + checkedKeys.value = []; + } else { + if (selectRows.value.length > selectedRowKeys.length) { + // 取消 + selectRows.value.forEach((item, index) => { + const rowKey = combineRowKey(item); + if (!selectedRowKeys.find((key) => key === rowKey)) { + selectRows.value.splice(index, 1); + } + }); + } else { + // 新增 + const append: any = []; + const beforeRowKeys = selectRows.value.map((item) => combineRowKey(item)); + selectedRowKeys.forEach((key) => { + if (!beforeRowKeys.find((item) => item === key)) { + // 那就是新增选中的行 + const row = getRowByKey(key); + row && append.push(row); + } + }); + selectRows.value = [...selectRows.value, ...append]; + } + checkedKeys.value = [...selectedRowKeys]; + } + } + /** + * 过滤没用选项 + * @param selectedRowKeys + */ + function filterUnuseSelect() { + selectRows.value = unref(selectRows).filter((item) => { + let combineKey = combineRowKey(item); + return unref(checkedKeys).indexOf(combineKey) >= 0; + }); + } + + /** + * 根据key获取row信息 + * @param key + */ + function getRowByKey(key) { + let row = unref(dataSource).filter((record) => combineRowKey(record) === key); + return row && row.length > 0 ? row[0] : ''; + } + + /** + * 加载rowKey + */ + function combineRowKey(record) { + let res = record?.id || ''; + if (props?.rowkey) { + // 代码逻辑说明: 【issues/3656】popupdict回显 + res = record[props.rowkey]; + } else { + Object.keys(record).forEach((key) => { + res = key == 'rowIndex' ? record[key] + res : res + record[key]; + }); + res = res.length > 50 ? res.substring(0, 50) : res; + } + return res; + } + + /** + * 加载列信息 + */ + function loadColumnsInfo() { + const {code} = handleCodeParams(true) + let url = `${configUrl.getColumns}${code}`; + //缓存key + let groupIdKey = props.groupId ? `${props.groupId}${url}` : ''; + httpGroupRequest(() => defHttp.get({ url }, { isTransformResponse: false, successMessageMode: 'none' }), groupIdKey).then((res) => { + if (res.success) { + initDictOptionData(res.result.dictOptions); + cgRpConfigId.value = res.result.cgRpConfigId; + title.value = res.result.cgRpConfigName; + let currColumns = res.result.columns; + for (let a = 0; a < currColumns.length; a++) { + if (currColumns[a].customRender) { + let dictCode = currColumns[a].customRender; + currColumns[a].customRender = ({ text }) => { + return filterMultiDictText(unref(dictOptions)[dictCode], text + ''); + }; + } + // 排序字段受控 + if (unref(iSorter) && currColumns[a].dataIndex === unref(iSorter).column) { + currColumns[a].sortOrder = unref(iSorter).order === 'asc' ? 'ascend' : 'descend'; + } + } + // 代码逻辑说明: 【issues/946】popup列宽和在线报表列宽读取配置 + currColumns.forEach((item) => { + if (item.fieldWidth != null) { + if (isString(item.fieldWidth) && item.fieldWidth.trim().length == 0) return; + item.width = item.fieldWidth; + delete item.fieldWidth; + } + }); + if (currColumns[0].key !== 'rowIndex') { + currColumns.unshift({ + title: '序号', + dataIndex: 'rowIndex', + key: 'rowIndex', + width: 60, + align: 'center', + customRender: function ({ text }) { + // 代码逻辑说明: 【QQYUN-7584】popup有合计时序号列会出现NaN + if (text == undefined) { + return ''; + } else { + return parseInt(text) + 1; + } + }, + }); + } + columns.value = [...currColumns]; + initQueryInfo(null); + } + }); + } + + /** + * 加载列和数据[列表专用] + */ + function loadColumnsAndData() { + // 第一次加载 置空isTotal 在这里调用确保 该方法只是进入页面后 加载一次 其余查询不走该方法 + pagination.isTotal = ''; + let url = `${configUrl.getColumnsAndData}${props.id}`; + + const {query} = handleCodeParams() + if (query) { + url = url + query + } + //缓存key + let groupIdKey = props.groupId ? `${props.groupId}${url}` : ''; + httpGroupRequest(() => defHttp.get({ url }, { isTransformResponse: false, successMessageMode: 'none' }), groupIdKey).then((res) => { + if (res.success) { + initDictOptionData(res.result.dictOptions); + cgRpConfigId.value = props.id; + let { columns: metaColumnList, cgreportHeadName, fieldHrefSlots, isGroupTitle } = res.result; + title.value = cgreportHeadName; + // href 跳转 + const fieldHrefSlotKeysMap = {}; + fieldHrefSlots.forEach((item) => (fieldHrefSlotKeysMap[item.slotName] = item)); + let currColumns: any = handleColumnHrefAndDict(metaColumnList, fieldHrefSlotKeysMap); + // 代码逻辑说明: 【issues/946】popup列宽和在线报表列宽读取配置 + currColumns.forEach((item) => { + if (isString(item.fieldWidth) && item.fieldWidth.trim().length == 0) return; + if (item.fieldWidth != null) { + item.width = item.fieldWidth; + delete item.fieldWidth; + } + }); + + // popup需要序号, 普通列表不需要 + if (clickThenCheckFlag === true) { + currColumns.unshift({ + title: '序号', + dataIndex: 'rowIndex', + key: 'rowIndex', + width: 60, + align: 'center', + customRender: function ({ text }) { + return parseInt(text) + 1; + }, + }); + } + + // 合并表头 + if (isGroupTitle === true) { + currColumns = handleGroupTitle(currColumns); + } + columns.value = [...currColumns]; + initQueryInfo(res.result.data); + } else { + // 代码逻辑说明: VUEN-583【vue3】JeecgBootException: sql黑名单校验不通过,请联系管理员!,前台无提示 + createMessage.warning(res.message); + } + }); + } + + // 处理动态参数和系统变量 + function handleCodeParams(onlyCode: boolean = false) { + if (!props.code) { + return {code: '', query: ''} + } + const firstIndex = props.code.indexOf('?') + if (firstIndex === -1) { + return {code: props.code, query: ''} + } + const code = props.code.substring(0, firstIndex) + if (onlyCode) { + return {code: code, query: ''} + } + const queryOrigin = props.code.substring(firstIndex, props.code.length); + let query: string + // 替换系统变量 + query = replaceUserInfoByExpression(queryOrigin) + // 获取表单值 + if (typeof props.getFormValues === 'function') { + const values = props.getFormValues() + // 替换动态参数,如果有 ${xxx} 则替换为实际值 + query = query.replace(/\${([^}]+)}/g, (_$0, $1) => { + if (values[$1] == null) { + return '' + } + return values[$1] + }); + + } + + return {code, query, queryOrigin} + } + + /** + * 处理求和的列 合计逻辑 [待优化 3.0] + */ + function handleSumColumn(metaColumnList: OnlineColumn[], dataTotal: number): void { + // 获取需要合计列的dataIndex + let sumColumnList = getNeedSumColumns(metaColumnList); + // 判断是否为第一次获取数据,如果是的话,则需要重新设置pageSize + if (pagination.isTotal == '') { + if (sumColumnList.length > 0) { + pagination.isTotal = true; + // 有合计字段时,每次最多查询原pageSize-1条记录,另外需要第一次时将查询的10条中删除最后一条 + // 删除最后一条数据 如果第一次得到的数据长度等于pageSize的话,则删除最后一条 + if (dataSource.value.length == pagination.pageSize) { + let remove_data = dataSource.value.pop(); + } + pagination.realPageSize = pagination.pageSize - 1; + } else { + pagination.isTotal = false; + } + } + // 需要添加合计字段 + if (pagination.isTotal) { + let totalRow = {}; + sumColumnList.forEach((dataIndex) => { + let count = 0; + dataSource.value.forEach((row) => { + // 统计去除null及空数据 + if (row[dataIndex] != null && row[dataIndex] != '') { + count += parseFloat(row[dataIndex]); + } + }); + totalRow[dataIndex] = isNaN(count) ? '包含非数字内容' : count.toFixed(2); + + // 长整形时合计不显示.00后缀 + let v = metaColumnList.find((v) => v.dataIndex == dataIndex); + if (v && v.fieldType == 'Long') { + totalRow[dataIndex] = parseInt(totalRow[dataIndex]); + } + }); + dataSource.value.push(totalRow); + pagination.realTotal = dataTotal; + pagination.total = Number(dataTotal) + Number(Math.floor(dataTotal / pagination.realPageSize)); + } + } + + /** + * 获取需要求和的列 dataIndex + * @param columns + */ + function getNeedSumColumns(columns: OnlineColumn[]): string[] { + let arr: string[] = []; + for (let column of columns) { + if (column.isTotal === '1') { + arr.push(column.dataIndex!); + } + // 【VUEN-1569】【online报表】合计无效 + if (column.children && column.children.length > 0) { + let subArray = getNeedSumColumns(column.children); + if (subArray.length > 0) { + arr.push(...subArray); + } + } + } + return arr; + } + + /** + * 处理列的href和字典翻译 + */ + function handleColumnHrefAndDict(columns: OnlineColumn[], fieldHrefSlotKeysMap: {}): OnlineColumn[] { + for (let column of columns) { + let { customRender, hrefSlotName, fieldType } = column; + // online 报表中类型配置为日期(yyyy-MM-dd ),但是实际展示为日期时间格式(yyyy-MM-dd HH:mm:ss) issues/3042 + if (fieldType == 'Date') { + column.customRender = ({ text }) => { + if (!text) { + return ''; + } + if (text.length > 10) { + return text.substring(0, 10); + } + return text; + }; + } else { + if (!hrefSlotName && column.scopedSlots && column.scopedSlots.customRender) { + //【Online报表】字典和href互斥 这里通过fieldHrefSlotKeysMap 先找到是href的列 + if (fieldHrefSlotKeysMap.hasOwnProperty(column.scopedSlots.customRender)) { + hrefSlotName = column.scopedSlots.customRender; + } + } + // 如果 customRender 有值则代表使用了字典 + // 如果 hrefSlotName 有值则代表使用了href跳转 + // 两者可以兼容。兼容的具体思路为:先获取到字典替换的值,再添加href链接跳转 + if (customRender || hrefSlotName) { + let dictCode = customRender as string; + let replaceFlag = '_replace_text_'; + column.customRender = ({ text, record }) => { + let value = text; + // 如果 dictCode 有值,就进行字典转换 + if (dictCode) { + if (dictCode.startsWith(replaceFlag)) { + let textFieldName = dictCode.replace(replaceFlag, ''); + value = record[textFieldName]; + } else { + value = filterMultiDictText(unref(dictOptions)[dictCode], text + ''); + } + } + // 扩展参数设置列的内容长度 + if (column.showLength) { + if (value && value.length > column.showLength) { + value = value.substr(0, column.showLength) + '...'; + } + } + // 如果 hrefSlotName 有值,就生成一个 a 标签,包裹住字典替换后(或原生)的值 + if (hrefSlotName) { + let field = fieldHrefSlotKeysMap[hrefSlotName]; + if (field) { + return h( + 'a', + { + onClick: () => handleClickFieldHref(field, record), + }, + value + ); + } + } + return value; + }; + } + } + } + return columns; + } + + /** + * 处理合并表头 + * @param columns + */ + function handleGroupTitle(columns: OnlineColumn[]): OnlineColumn[] { + let newColumns: OnlineColumn[] = []; + for (let column of columns) { + //排序字段受控 ---- 此逻辑为新增逻辑 待 + if (unref(iSorter) && column.dataIndex === unref(iSorter).column) { + column.sortOrder = unref(iSorter).order === 'asc' ? 'ascend' : 'descend'; + } + //判断字段是否需要合并表头 + if (column.groupTitle) { + let clIndex = newColumns.findIndex((im) => im.title === column.groupTitle); + if (clIndex !== -1) { + //表头已存在直接push children + newColumns[clIndex].children!.push(column); + } else { + //表头不存在组装表头信息 + let clGroup: OnlineColumn = {}, + child: OnlineColumn[] = []; + child.push(column); + clGroup.title = column.groupTitle; + clGroup.align = 'center'; + clGroup.children = child; + newColumns.push(clGroup); + } + } else { + newColumns.push(column); + } + } + return newColumns; + } + + // 获取路由器对象 href跳转用到 + let router = useRouter(); + /** + * href 点击事件 + * @param field + * @param record + */ + function handleClickFieldHref(field, record) { + let href = field.href; + let urlPattern = /(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_]*)?/; + let compPattern = /\.vue(\?.*)?$/; + let jsPattern = /{{([^}]+)}}/g; // {{ xxx }} + if (typeof href === 'string') { + href = href.trim().replace(/\${([^}]+)?}/g, (s1, s2) => record[s2]); + // 执行 {{...}} JS增强语句 + if (jsPattern.test(href)) { + href = href.replace(jsPattern, function (text, s0) { + try { + // 支持 {{ ACCESS_TOKEN }} 占位符 + if (s0.trim() === 'ACCESS_TOKEN') { + return getToken() + } + + // 代码逻辑说明: 【QQYUN-6390】eval替换成new Function,解决build警告 + return _eval(s0); + } catch (e) { + console.error(e); + return text; + } + }); + } + if (urlPattern.test(href)) { + window.open(href, '_blank'); + } else if (compPattern.test(href)) { + // 处理弹框 + openHrefCompModal(href); + } else { + router.push(href); + } + } + } + + /** + * 导出 + */ + function handleExport() { + const { handleExportXls } = useMethods(); + let url = `${configUrl.export}${cgRpConfigId.value}`; + let params = getQueryParams(); //查询条件 + // 【VUEN-1568】如果选中了某些行,就只导出选中的行 + let keys = unref(checkedKeys); + if (keys.length > 0) { + keys = keys + .map((i) => selectRows.value.find((item) => combineRowKey(item) === i)?.id) + .filter((i) => i != null && i !== ''); + // 判断是否有ID字段 + if (keys.length === 0) { + createMessage.warning('由于数据中缺少ID字段,故无法使用选中导出功能'); + return; + } + params['force_id'] = keys.join(','); + } + handleExportXls(title.value, url, params); + } + + /** + * 合计逻辑 [待优化 3.0] + * 分页 大小改变事件 + * @param _current + * @param size + */ + function onSizeChange(_current, size) { + pagination.isTotal = ''; + pagination.pageSize = size; + if (pagination.isTotal) { + pagination.realPageSize = size - 1; + } else { + pagination.realPageSize = size; + } + pagination.current = 1; + } + + /** + * 合计逻辑 [待优化 3.0] + * 显示总条数 + * @param total + */ + function onShowTotal(total) { + // 重新根据是否有合计计算每页显示的数据 + let start = (pagination.current - 1) * pagination.realPageSize + 1; + let end = start + (pagination.isTotal ? dataSource.value.length - 1 : dataSource.value.length) - 1; + let realTotal = pagination.isTotal ? pagination.realTotal : total; + return start + '-' + end + ' 共' + realTotal + '条'; + } + + /** + * 弹出框显示隐藏触发事件 + */ + async function visibleChange($event) { + visible.value = $event; + $event && loadColumnsInfo(); + } + + /** + * 初始化查询条件 + * @param data 数据结果集 + */ + function initQueryInfo(data) { + let url = `${configUrl.getQueryInfo}${unref(cgRpConfigId)}`; + //缓存key + let groupIdKey = props.groupId ? `${props.groupId}${url}` : ''; + httpGroupRequest(() => defHttp.get({ url }, { isTransformResponse: false, successMessageMode: 'none' }), groupIdKey).then((res) => { + // console.log("获取查询条件", res); + if (res.success) { + dynamicParamHandler(res.result); + queryInfo.value = res.result; + console.log('queryInfo==>', queryInfo.value); + //查询条件加载后再请求数据 + if (data) { + setDataSource(data); + //传递路由参数和动态参数,不生效, + loadData(1); + } else { + //没有传递data时查询数据 + loadData(1); + } + } else { + createMessage.warning(res.message); + } + }); + } + + /** + * 加载表格数据 + * @param arg + */ + function loadData(arg?) { + if (arg == 1) { + pagination.current = 1; + } + let params = getQueryParams(); //查询条件 + params['onlRepUrlParamStr'] = getUrlParamString(); + console.log('params', params); + loading.value = true; + // 代码逻辑说明: 【TV360X-578】online报表SQL翻译,第二页不翻页数据 + let url = `${configUrl.getColumnsAndData}${unref(cgRpConfigId)}`; + const {query} = handleCodeParams() + if (query) { + url = url + query + } + //缓存key + let groupIdKey = props.groupId ? `${props.groupId}${url}${JSON.stringify(params)}` : ''; + httpGroupRequest(() => defHttp.get({ url, params }, { isTransformResponse: false, successMessageMode: 'none' }), groupIdKey).then((res) => { + // 代码逻辑说明: 【TV360X-578】online报表SQL翻译,第二页不翻页数据 + res.result.dictOptions && initDictOptionData(res.result.dictOptions); + loading.value = false; + // 代码逻辑说明: 【TV360X-578】online报表SQL翻译,第二页不翻页数据 + let data = res.result.data; + console.log('表格信息:', data); + setDataSource(data); + }); + } + + /** + * 获取地址栏的参数 + */ + function getUrlParamString() { + let query = route.query; + let arr:any[] = [] + if(query && Object.keys(query).length>0){ + Object.keys(query).map(k=>{ + arr.push(`${k}=${query[k]}`) + }) + } + return arr.join('&') + } + + /** + * 设置dataSource + */ + function setDataSource(data) { + if (data) { + pagination.total = Number(data.total); + let currentPage = pagination?.current ?? 1; + for (let a = 0; a < data.records.length; a++) { + if (!data.records[a].rowIndex) { + data.records[a].rowIndex = a + (currentPage - 1) * 10; + } + } + dataSource.value = data.records; + // 代码逻辑说明: issues/426 修复356时候引入的回归错误 JPopupOnlReportModal.vue 中未修改 + tableRef?.value && tableRef?.value?.setPagination({ + total: Number(data.total) + }) + } else { + pagination.total = 0; + dataSource.value = []; + } + // 合计逻辑 [待优化 3.0] + handleSumColumn(columns.value, pagination.total); + } + + /** + * 获取查询参数 + */ + function getQueryParams() { + let paramTarget = {}; + if (unref(dynamicParam)) { + //处理自定义参数 + Object.keys(unref(dynamicParam)).map((key) => { + paramTarget['self_' + key] = unref(dynamicParam)[key]; + }); + } + let param = Object.assign(paramTarget, unref(queryParam), unref(iSorter)); + param.pageNo = pagination.current; + // 合计逻辑 [待优化 3.0] + // 实际查询时不使用table组件的pageSize,而使用自定义的realPageSize,realPageSize会在第一次获取到数据后变化 + param.pageSize = pagination.realPageSize; + return filterObj(param); + } + + /** + * 处理动态参数 + */ + function dynamicParamHandler(arr?) { + if (arr && arr.length > 0) { + //第一次加载查询条件前 初始化queryParam为空对象 + let queryTemp = {}; + for (let item of arr) { + if (item.mode === 'single') { + queryTemp[item.field] = ''; + } + } + queryParam.value = { ...queryTemp }; + } + // 合并路由参数 + if (props.routeQuery) { + queryParam.value = Object.assign(queryParam.value, props.routeQuery); + } + + let dynamicTemp = {}; + if (props.param) { + Object.keys(props.param).map((key) => { + let str = props.param[key]; + //【issues/8426】解决JPopup组件传参不能接收 + if (key in queryParam.value) { + if (str && str.startsWith("'") && str.endsWith("'")) { + str = str.substring(1, str.length - 1); + } + //如果查询条件包含参数 设置值 + unref(queryParam)[key] = str; + } + dynamicTemp[key] = props.param[key]; + }); + } + dynamicParam.value = { ...dynamicTemp }; + } + + /** + * 分页 + * @param page + * @param filters + * @param sorter + */ + function handleChangeInTable(page, filters, sorter) { + console.log(page, filters, sorter); + //分页、排序、筛选变化时触发 + if (Object.keys(sorter).length > 0) { + iSorter.value = { + column: sorter.field, + order: 'ascend' === sorter.order ? 'asc' : 'desc', + }; + // 排序字段受控 + unref(columns).forEach((col) => { + if (col['dataIndex'] === sorter.field) { + col['sortOrder'] = sorter.order; + } + }); + } + pagination.current = page.current; + pagination.pageSize = page.pageSize; + loadData(); + } + + /** + * 行点击事件 + * @param record + */ + function clickThenCheck(record) { + if (clickThenCheckFlag === true) { + // 代码逻辑说明: 【QQYUN-7514】popup单选显示radio + if (!props.multi) { + selectRows.value = []; + checkedKeys.value = []; + } + let rowKey = combineRowKey(record); + if (!unref(checkedKeys) || unref(checkedKeys).length == 0) { + let arr1: any[] = [], + arr2: any[] = []; + arr1.push(record); + arr2.push(rowKey); + checkedKeys.value = arr2; + //selectRows.value = arr1; + } else { + if (unref(checkedKeys).indexOf(rowKey) < 0) { + //不存在就选中 + checkedKeys.value.push(rowKey); + //selectRows.value.push(record); + } else { + //已选中就取消 + let rowKey_index = unref(checkedKeys).indexOf(rowKey); + checkedKeys.value.splice(rowKey_index, 1); + //selectRows.value.splice(rowKey_index, 1); + } + } + // 代码逻辑说明: 【issues/5357】点击行选中 + tableRef.value.setSelectedRowKeys([...checkedKeys.value]); + } + } + + //防止字典中有垃圾数据 + function initDictOptionData(arr) { + let obj = {}; + Object.keys(arr).map((k) => { + obj[k] = arr[k].filter((item) => { + return item != null; + }); + }); + dictOptions.value = obj; + } + + /** + * 过滤对象中为空的属性 + * @param obj + * @returns {*} + */ + function filterObj(obj) { + if (!(typeof obj == 'object')) { + return; + } + + for (let key in obj) { + if (obj.hasOwnProperty(key) && (obj[key] == null || obj[key] == undefined || obj[key] === '')) { + delete obj[key]; + } + } + return obj; + } + + // 样式 + const dialogStyle = { + top: 0, + left: 0, + height: '100%', + margin: 0, + padding: 0, + }; + + // 弹窗属性配置 + const hrefComponent = ref({ + model: { + title: '', + okText: '关闭', + width: '100%', + open: false, + destroyOnClose: true, + style: dialogStyle, + // dialogStyle: dialogStyle, + bodyStyle: { + padding: '8px', + height: 'calc(100vh - 108px)', + overflow: 'auto', + overflowX: 'hidden', + }, + // 隐藏掉取消按钮 + cancelButtonProps: { style: { display: 'none' } }, + }, + on: { + ok: () => (hrefComponent.value.model.open = false), + cancel: () => (hrefComponent.value.model.open = false), + }, + is: null, + params: {}, + }); + + // 超链点击事件--> 打开一个modal窗口 + function openHrefCompModal(href) { + // 解析 href 参数 + let index = href.indexOf('?'); + let path = href; + if (index !== -1) { + path = href.substring(0, index); + let paramString = href.substring(index + 1, href.length); + let paramArray = paramString.split('&'); + let params = {}; + paramArray.forEach((paramObject) => { + let paramItem = paramObject.split('='); + params[paramItem[0]] = paramItem[1]; + }); + hrefComponent.value.params = params; + } else { + hrefComponent.value.params = {}; + } + hrefComponent.value.model.open = true; + hrefComponent.value.model.title = '操作'; + hrefComponent.value.is = markRaw(defineAsyncComponent(() => importViewsFile(path))); + } + + /** + * emit事件 获取选中的行数据 + */ + function getOkSelectRows(): any[] { + let arr = unref(selectRows); + let selectedRowKeys = checkedKeys.value; + console.log('arr', arr); + if (!selectedRowKeys || selectedRowKeys.length <= 0) { + return []; + } + if (!arr || arr.length <= 0) { + return []; + } + let rows: any = []; + for (let key of selectedRowKeys) { + for (let i = 0; i < arr.length; i++) { + let combineKey = combineRowKey(arr[i]); + if (key === combineKey) { + rows.push(toRaw(arr[i])); + break; + } + } + } + return rows; + } + + return [ + { + visibleChange, + loadColumnsInfo, + loadColumnsAndData, + dynamicParamHandler, + loadData, + handleChangeInTable, + combineRowKey, + clickThenCheck, + filterUnuseSelect, + handleExport, + getOkSelectRows, + }, + { + hrefComponent, + visible, + rowSelection, + checkedKeys, + selectRows, + pagination, + dataSource, + columns, + indexColumnProps, + loading, + title, + iSorter, + queryInfo, + queryParam, + dictOptions, + }, + ]; +} diff --git a/src/components/jeecg/OnLine/types/onlineConfig.ts b/src/components/jeecg/OnLine/types/onlineConfig.ts new file mode 100644 index 0000000..67d4a80 --- /dev/null +++ b/src/components/jeecg/OnLine/types/onlineConfig.ts @@ -0,0 +1,45 @@ +interface ScopedSlots { + customRender: string; +} + +interface HrefSlots { + // 链接地址 + href: string; + // fieldHref_字段名 + slotName: string; +} + +interface OnlineColumn { + dataIndex?: string; + title?: string; + key?: string; + fieldType?: string; + width?: number | string; + align?: string; + sorter?: string | boolean; + isTotal?: string | number | boolean; + groupTitle?: string; + // 超链的时候 和HrefSlots中的slotName匹配 + scopedSlots?: ScopedSlots; + // 一般用于字典 字典传过来的是字典编码字符串 后转函数 + customRender?: string | Function; + // 这个类型不知道有什么用 + hrefSlotName?: string; + showLength?: number | string; + children?: OnlineColumn[]; + sortOrder?: string; + // 插槽对应控件类型(列表) + slots?: ScopedSlots; + //超过宽度将自动省略,暂不支持和排序筛选一起使用。 + ellipsis?: boolean; + // 是否固定列 + fixed?: boolean | 'left' | 'right'; + //字段类型 int/string + dbType?:string; + //他表字段用 + linkField?:string; + fieldExtendJson?:string + resizable?: boolean; +} + +export { OnlineColumn, HrefSlots }; diff --git a/src/components/jeecg/UserAvatar.vue b/src/components/jeecg/UserAvatar.vue new file mode 100644 index 0000000..e459fe8 --- /dev/null +++ b/src/components/jeecg/UserAvatar.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/src/components/jeecg/captcha/CaptchaModal.vue b/src/components/jeecg/captcha/CaptchaModal.vue new file mode 100644 index 0000000..07d4ae8 --- /dev/null +++ b/src/components/jeecg/captcha/CaptchaModal.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/src/components/jeecg/comment/CommentFiles.vue b/src/components/jeecg/comment/CommentFiles.vue new file mode 100644 index 0000000..a34fbbe --- /dev/null +++ b/src/components/jeecg/comment/CommentFiles.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/src/components/jeecg/comment/CommentList.vue b/src/components/jeecg/comment/CommentList.vue new file mode 100644 index 0000000..fa0c053 --- /dev/null +++ b/src/components/jeecg/comment/CommentList.vue @@ -0,0 +1,367 @@ + + + + + diff --git a/src/components/jeecg/comment/CommentPanel.vue b/src/components/jeecg/comment/CommentPanel.vue new file mode 100644 index 0000000..c9dc21f --- /dev/null +++ b/src/components/jeecg/comment/CommentPanel.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/src/components/jeecg/comment/DataLogList.vue b/src/components/jeecg/comment/DataLogList.vue new file mode 100644 index 0000000..5a443b4 --- /dev/null +++ b/src/components/jeecg/comment/DataLogList.vue @@ -0,0 +1,177 @@ + + + + + diff --git a/src/components/jeecg/comment/HistoryFileList.vue b/src/components/jeecg/comment/HistoryFileList.vue new file mode 100644 index 0000000..ba039ee --- /dev/null +++ b/src/components/jeecg/comment/HistoryFileList.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/components/jeecg/comment/MyComment.vue b/src/components/jeecg/comment/MyComment.vue new file mode 100644 index 0000000..7e3d912 --- /dev/null +++ b/src/components/jeecg/comment/MyComment.vue @@ -0,0 +1,445 @@ + + + + + diff --git a/src/components/jeecg/comment/UploadChunk.vue b/src/components/jeecg/comment/UploadChunk.vue new file mode 100644 index 0000000..2ec08ab --- /dev/null +++ b/src/components/jeecg/comment/UploadChunk.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/src/components/jeecg/comment/comment.less b/src/components/jeecg/comment/comment.less new file mode 100644 index 0000000..5b9b64a --- /dev/null +++ b/src/components/jeecg/comment/comment.less @@ -0,0 +1,252 @@ +/*文件上传列表-begin*/ +.selected-file-warp, +.comment-file-his-list { + margin: 10px 20px; + &.in-comment{ + margin: 10px 6px; + } +} +.selected-file-list { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + margin-right: -6px; + .item { + box-sizing: border-box; + display: inline-block; + flex: 1 1 0%; + height: 118px; + margin: 0 6px 6px 0; + min-width: 140px; + max-width: 200px; + width: 150px; + &.empty { + height: 0; + margin-bottom: 0; + margin-top: 0; + } + .complex { + border: 1px solid #e0e0e0; + box-sizing: border-box; + height: 100%; + position: relative; + .content { + display: flex; + flex-direction: column; + height: 100%; + box-sizing: border-box; + .content-top { + align-items: center; + background-color: #f5f5f5; + display: flex; + flex: 1 1 0%; + justify-content: center; + .content-icon { + background-position: 50%; + background-size: contain !important; + height: 55px; + width: 40px; + display: inline-block; + overflow: hidden; + text-align: left; + text-indent: -9999px; + } + .content-image{ + background-position: 50%; + background-repeat: no-repeat; + background-size: cover; + height: 100%; + width: 100%; + } + } + .content-bottom { + align-items: center; + background-color: #fff; + display: flex; + flex-basis: 30px; + font-size: 13px; + justify-content: flex-start; + padding: 0 10px; + span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } + .layer { + opacity: 0; + background-color: #f5f5f5; + cursor: pointer; + display: flex; + flex-direction: column; + height: 100%; + left: 0; + position: absolute; + top: 0; + transition: opacity 0.2s; + width: 100%; + &:hover { + opacity: 1; + } + .next { + height: 75px; + padding: 5px; + .text { + color: rgba(51, 51, 51, 0.6) !important; + align-items: center; + display: flex; + flex-basis: 30px; + font-size: 12px; + justify-content: flex-start; + padding: 3px 7px 4px; + word-break: break-all; + display: -webkit-box; + line-height: 14px; + overflow: hidden; + text-overflow: ellipsis; + } + } + .buttons { + flex-basis: 32px; + text-align: right; + display: flex; + align-items: flex-end; + padding-right: 5px; + justify-content: flex-end; + .opt-icon { + background-color: #fff; + border-radius: 2px; + cursor: pointer; + height: 24px; + width: 32px; + margin: 5px; + text-align: center; + .anticon-delete:hover { + color: red; + } + .anticon-download:hover{ + color: #1e88e5 !important + } + } + } + } + .layer-image{ + background: #000; + &:hover { + opacity: 0.6; + } + .next{ + .text{ + color: #fff !important; + } + } + .opt-icon{ + color: #000 !important; + .anticon-delete:hover { + color: red; + } + } + } + + } + } +} + +.jeecg-comment-files { + margin: 0 20px; + padding-top: 3px; + padding-bottom: 3px; + &.ant-alert-info{ + background-color: #f5f5f5; + border: 1px solid #f5f5f5; + } + .j-icon { + cursor: pointer; + display: inline-block; + border: 1px solid #e6f7ff; + padding: 2px 7px; + margin: 0 10px; + &:hover, + &:focus, + &:active { + border-color: #fff; + color: #096dd9; + } + .inner-button { + display: inline-block; + color:#9e9e9e; + &:hover, + &:focus, + &:active { + /*border-color: #fff;*/ + /* color: #096dd9;*/ + color: #000; + } + span{ + margin-right: 3px; + } + } + } +} + +.comment-file-list { + .detail-item { + display: flex; + flex-direction: row; + align-items: stretch; + line-height: 24px; + border-bottom: 1px solid #f0f0f0; + height: 100%; + + .item-title { + display: flex; + align-items: center; + justify-content: flex-end; + flex-shrink: 0; + flex-grow: 0; + min-width: 100px; + width: 20%; + max-width: 220px; + background-color: #fafafa; + border-right: 1px solid #f0f0f0; + /* border-left: 1px solid #f0f0f0;*/ + padding: 10px 0; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } + + .item-content { + border-right: 1px solid #f0f0f0; + flex-grow: 1; + padding-left: 10px; + display: flex; + align-items: center; + justify-content: flex-start; + .anticon { + &:hover { + color: #40a9ff; + } + } + } + } +} + +// update-begin--author:liaozhiyang---date:20240327---for:【QQYUN-8639】暗黑主题适配 +html[data-theme='dark'] { + .jeecg-comment-files { + &.ant-alert-info { + background-color: #141414; + border: 1px solid #3a3a3a; + } + .j-icon, + .j-icon:hover { + border-color: #3a3a3a; + .inner-button:hover { + color: #bebebe; + } + } + } +} +// update-end--author:liaozhiyang---date:20240327---for:【QQYUN-8639】暗黑主题适配 diff --git a/src/components/jeecg/comment/image/emoji.png b/src/components/jeecg/comment/image/emoji.png new file mode 100644 index 0000000..eaef1f3 Binary files /dev/null and b/src/components/jeecg/comment/image/emoji.png differ diff --git a/src/components/jeecg/comment/image/emoji_native.png b/src/components/jeecg/comment/image/emoji_native.png new file mode 100644 index 0000000..9efea64 Binary files /dev/null and b/src/components/jeecg/comment/image/emoji_native.png differ diff --git a/src/components/jeecg/comment/useComment.ts b/src/components/jeecg/comment/useComment.ts new file mode 100644 index 0000000..8db8f66 --- /dev/null +++ b/src/components/jeecg/comment/useComment.ts @@ -0,0 +1,459 @@ +import { useMessage } from '/@/hooks/web/useMessage'; +import { defHttp } from '/@/utils/http/axios'; +import { useGlobSetting } from '/@/hooks/setting'; +const globSetting = useGlobSetting(); +const baseUploadUrl = globSetting.uploadUrl; +import { ref, toRaw, unref, reactive } from 'vue'; +import { uploadMyFile } from '/@/api/common/api'; + +import excel from '/@/assets/svg/fileType/excel.svg'; +import other from '/@/assets/svg/fileType/other.svg'; +import pdf from '/@/assets/svg/fileType/pdf.svg'; +import txt from '/@/assets/svg/fileType/txt.svg'; +import word from '/@/assets/svg/fileType/word.svg'; +import image from '/@/assets/svg/fileType/image.png'; +import { getFileAccessHttpUrl } from '/@/utils/common/compUtils'; +import { createImgPreview } from '/@/components/Preview'; +import data from "emoji-mart-vue-fast/data/apple.json"; +import { EmojiIndex } from "emoji-mart-vue-fast/src"; +import { encryptByBase64 } from '/@/utils/cipher'; + +enum Api { + list = '/sys/comment/listByForm', + addText = '/sys/comment/addText', + deleteOne = '/sys/comment/deleteOne', + fileList = '/sys/comment/fileList', + logList = '/sys/dataLog/queryDataVerList', + queryById = '/sys/comment/queryById', + getFileViewDomain = '/sys/comment/getFileViewDomain', +} + +// 文件预览地址的domain 在后台配置的 +let onlinePreviewDomain = ''; + +/** + * 获取文件预览的domain + */ +const getViewFileDomain = () => defHttp.get({ url: Api.getFileViewDomain }); + +/** + * 列表接口 + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); + +export function getGloablEmojiIndex(){ + if(window['myEmojiIndex']){ + console.log("----走window['myEmojiIndex']缓存,不new新对象!") + return window['myEmojiIndex']; + } + + window['myEmojiIndex'] = new EmojiIndex(data, { + function() { + return true; + }, + exclude:['recent','people','nature','foods','activity','places','objects','symbols','flags'] + }); + return window['myEmojiIndex']; +} + +/** + * 查询单条记录 + * @param params + */ +export const queryById = (id) => { + let params = { id: id }; + return defHttp.get({ url: Api.queryById, params },{ isTransformResponse: false }); +}; + +/** + * 文件列表接口 + * @param params + */ +export const fileList = (params) => defHttp.get({ url: Api.fileList, params }); + +/** + * 删除单个 + */ +export const deleteOne = (params) => { + return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }); +}; + +/** + * 保存 + * @param params + */ +export const saveOne = (params) => { + let url = Api.addText; + return defHttp.post({ url: url, params }, { isTransformResponse: false }); +}; + +/** + * 数据日志列表接口 + * @param params + */ +export const getLogList = (params) => defHttp.get({ url: Api.logList, params }, {isTransformResponse: false}); + + +/** + * 文件上传接口 + */ +export const uploadFileUrl = `${baseUploadUrl}/sys/comment/addFile`; + +export function useCommentWithFile(props) { + let uploadData = { + biz: 'comment', + commentId: '', + }; + const { createMessage } = useMessage(); + const buttonLoading = ref(false); + + //确定按钮触发 + async function saveCommentAndFiles(obj, fileList) { + buttonLoading.value = true; + setTimeout(() => { + buttonLoading.value = false; + }, 500); + await saveComment(obj); + await uploadFiles(fileList); + } + + /** + * 保存评论 + */ + async function saveComment(obj) { + const {fromUserId, toUserId, commentId, commentContent} = obj; + let commentData = { + tableId: props.tableId, + tableName: props.tableName, + tableDataId: props.dataId, + fromUserId, + commentContent, + toUserId: '', + commentId: '' + }; + if(toUserId){ + commentData.toUserId = toUserId; + } + if(commentId){ + commentData.commentId = commentId; + } + uploadData.commentId = ''; + const res = await saveOne(commentData); + if (res.success) { + uploadData.commentId = res.result; + } else { + createMessage.warning(res.message); + return Promise.reject('保存评论失败'); + } + } + + async function uploadOne(file) { + let url = uploadFileUrl; + const formData = new FormData(); + formData.append('file', file); + formData.append('tableName', props.tableName); + formData.append('tableDataId', props.dataId); + Object.keys(uploadData).map((k) => { + formData.append(k, uploadData[k]); + }); + return new Promise((resolve, reject) => { + uploadMyFile(url, formData).then((res: any) => { + console.log('uploadMyFile', res); + if (res && res.data) { + if (res.data.result == 'success') { + resolve(1); + } else { + createMessage.warning(res.data.message); + reject(); + } + } else { + reject(); + } + }); + }); + } + + /** + * QQYUN-4310【文件】从文件库选择文件功能未做 + * @param file + */ + async function saveSysFormFile(file){ + let url = '/sys/comment/addFile'; + let params = { + fileId: file.id, + commentId: uploadData.commentId + } + await defHttp.post({url, params}, { joinParamsToUrl: true, isTransformResponse: false }); + } + + async function uploadFiles(fileList) { + if (fileList && fileList.length > 0) { + for (let i = 0; i < fileList.length; i++) { + let file = toRaw(fileList[i]); + if(file.exist === true){ + await saveSysFormFile(file); + }else{ + await uploadOne(file.originFileObj); + } + } + } + } + + return { + saveCommentAndFiles, + buttonLoading, + }; +} + +export function uploadMu(fileList) { + const formData = new FormData(); + // let arr = [] + for(let file of fileList){ + formData.append('files[]', file.originFileObj); + } + console.log(formData) + let url = `${baseUploadUrl}/sys/comment/addFile2`; + uploadMyFile(url, formData).then((res: any) => { + console.log('uploadMyFile', res); + }); +} + +/** + * 显示文件列表 + */ +export function useFileList() { + const imageSrcMap = reactive({}); + const typeMap = { + xls: excel, + xlsx: excel, + pdf: pdf, + txt: txt, + docx: word, + doc: word, + image + }; + function getBackground(item) { + console.log('获取文件背景图', item); + if (isImage(item)) { + return 'none' + } else { + const name = item.name; + if(!name){ + return 'none'; + } + const suffix = name.substring(name.lastIndexOf('.') + 1); + console.log('suffix', suffix) + let bg = typeMap[suffix]; + if (!bg) { + bg = other; + } + return bg; + } + } + + function getImageTypeIcon() { + return typeMap['image']; + } + + function getBase64(file, id){ + return new Promise((resolve, reject) => { + //声明js的文件流 + let reader = new FileReader(); + if(file){ + //通过文件流将文件转换成Base64字符串 + reader.readAsDataURL(file); + //转换成功后 + reader.onload = function () { + let base = reader.result; + console.log('base', base) + imageSrcMap[id] = base; + console.log('imageSrcMap', imageSrcMap) + resolve(base) + } + }else{ + reject(); + } + }) + } + function handleImageSrc(file){ + if(isImage(file)){ + let id = file.uid; + getBase64(file, id); + } + } + + function downLoad(file) { + let url = getFileAccessHttpUrl(file.url); + if (url) { + window.open(url); + } + } + + function getFileSize(item) { + let size = item.fileSize; + if (!size) { + return '0B'; + } + let temp = Math.round(size / 1024); + return temp + ' KB'; + } + + const selectFileList = ref([]); + function beforeUpload(file) { + handleImageSrc(file); + selectFileList.value = [...selectFileList.value, file]; + console.log('selectFileList', unref(selectFileList)); + return false + } + + function handleRemove(file) { + const index = selectFileList.value.indexOf(file); + const newFileList = selectFileList.value.slice(); + newFileList.splice(index, 1); + selectFileList.value = newFileList; + } + + function isImage(item){ + const type = item.type||''; + if (type.indexOf('image') >= 0) { + return true; + } + return false; + } + + function getImageSrc(file){ + if(file.exist){ + return getFileAccessHttpUrl(file.url); + } + if(isImage(file)){ + let id = file.uid; + if(id){ + if(imageSrcMap[id]){ + return imageSrcMap[id]; + } + }else if(file.url){ + //数据库中地址 + let url = getFileAccessHttpUrl(file.url); + return url; + } + } + return '' + } + + /** + * 显示图片 + * @param item + */ + function getImageAsBackground(item){ + let url; + if(item.exist){ + url = getFileAccessHttpUrl(item.url); + }else{ + url = getImageSrc(item); + } + if(url){ + return { + "backgroundImage": "url('"+url+"')" + } + } + return {} + } + + /** + * 预览列表 cell 图片 + * @param text + */ + async function viewImage(file) { + if(isImage(file)){ + let text = getImageSrc(file) + if (text) { + let imgList = [text]; + createImgPreview({ imageList: imgList }); + } + }else{ + if(file.url){ + //数据库中地址 + let url = getFileAccessHttpUrl(file.url); + await initViewDomain(); + //本地测试需要将文件地址的localhost/127.0.0.1替换成IP, 或是直接修改全局domain + //url = url.replace('localhost', '192.168.1.100') + // 代码逻辑说明: 【TV360X-952】升级到kkfileview4.1.0--- + let previewUrl = encodeURIComponent(encryptByBase64(url)); + window.open(onlinePreviewDomain+'?url='+previewUrl); + } + } + } + + /** + * 初始化domain + */ + async function initViewDomain(){ + if(!onlinePreviewDomain){ + onlinePreviewDomain = await getViewFileDomain(); + } + if(!onlinePreviewDomain.startsWith('http')){ + onlinePreviewDomain = 'http://'+ onlinePreviewDomain; + } + } + + return { + selectFileList, + getBackground, + getFileSize, + downLoad, + beforeUpload, + handleRemove, + isImage, + getImageSrc, + getImageAsBackground, + viewImage, + getImageTypeIcon + }; +} + +/** + * 用于emoji渲染 + */ +export function useEmojiHtml(globalEmojiIndex){ + const COLONS_REGEX = new RegExp('([^:]+)?(:[a-zA-Z0-9-_+]+:(:skin-tone-[2-6]:)?)','g'); + + function getHtml(text) { + if(!text){ + return '' + } + return text.replace(COLONS_REGEX, function (match, p1, p2) { + const before = p1 || '' + if (endsWith(before, 'alt="') || endsWith(before, 'data-text="')) { + return match + } + let emoji = globalEmojiIndex.findEmoji(p2) + if (!emoji) { + return match + } + return before + emoji2Html(emoji) + }) + return text; + } + + function endsWith(str, temp){ + return str.endsWith(temp) + } + + function emoji2Html(emoji) { + let style = `position: absolute;top: -3px;left: 3px;width: 18px; height: 18px;background-position: ${emoji.getPosition()}` + return ` ` + } + + return { + globalEmojiIndex, + getHtml + } +} + +/** + * 获取modal窗体高度 + */ +export function getModalHeight(){ + return window.innerHeight; +} diff --git a/src/components/jeecg/thirdApp/JThirdAppButton.vue b/src/components/jeecg/thirdApp/JThirdAppButton.vue new file mode 100644 index 0000000..c6b7f79 --- /dev/null +++ b/src/components/jeecg/thirdApp/JThirdAppButton.vue @@ -0,0 +1,180 @@ + + + + + diff --git a/src/components/jeecg/thirdApp/JThirdAppDropdown.vue b/src/components/jeecg/thirdApp/JThirdAppDropdown.vue new file mode 100644 index 0000000..bb4230c --- /dev/null +++ b/src/components/jeecg/thirdApp/JThirdAppDropdown.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/src/components/jeecg/thirdApp/jThirdApp.api.ts b/src/components/jeecg/thirdApp/jThirdApp.api.ts new file mode 100644 index 0000000..a3dfc04 --- /dev/null +++ b/src/components/jeecg/thirdApp/jThirdApp.api.ts @@ -0,0 +1,37 @@ +import { defHttp } from '/@/utils/http/axios'; +import { cloneObject } from '/@/utils/index'; + +export const backEndUrl = { + // 获取启用的第三方App + getEnabledType: '/sys/thirdApp/getEnabledType', + // 企业微信 + wechatEnterprise: { + user: '/sys/thirdApp/sync/wechatEnterprise/user', + depart: '/sys/thirdApp/sync/wechatEnterprise/depart', + }, + // 钉钉 + dingtalk: { + user: '/sys/thirdApp/sync/dingtalk/user', + depart: '/sys/thirdApp/sync/dingtalk/depart', + }, +}; +// 启用了哪些第三方App(在此缓存) +let enabledTypes = null; + +// 获取启用的第三方App +export const getEnabledTypes = async () => { + // 获取缓存 + if (enabledTypes != null) { + return cloneObject(enabledTypes); + } else { + let { success, result } = await defHttp.get({ url: backEndUrl.getEnabledType }, { isTransformResponse: false }); + if (success) { + // 在此缓存 + enabledTypes = cloneObject(result); + return result; + } else { + console.warn('getEnabledType查询失败:'); + } + } + return {}; +}; diff --git a/src/components/onlinePreview/WpsFileView.vue b/src/components/onlinePreview/WpsFileView.vue new file mode 100644 index 0000000..a8321d9 --- /dev/null +++ b/src/components/onlinePreview/WpsFileView.vue @@ -0,0 +1,63 @@ + + + + + + diff --git a/src/components/onlinePreview/open-jssdk.es.js b/src/components/onlinePreview/open-jssdk.es.js new file mode 100644 index 0000000..93ef4e9 --- /dev/null +++ b/src/components/onlinePreview/open-jssdk.es.js @@ -0,0 +1 @@ +var e={658:function(e,t,n){function r(e){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r(e)}function i(){i=function(){return e};var e={},t=Object.prototype,n=t.hasOwnProperty,o=Object.defineProperty||function(e,t,n){e[t]=n.value},a="function"==typeof Symbol?Symbol:{},s=a.iterator||"@@iterator",c=a.asyncIterator||"@@asyncIterator",u=a.toStringTag||"@@toStringTag";function l(e,t,n){return Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}),e[t]}try{l({},"")}catch(e){l=function(e,t,n){return e[t]=n}}function d(e,t,n,r){var i=t&&t.prototype instanceof v?t:v,a=Object.create(i.prototype),s=new L(r||[]);return o(a,"_invoke",{value:_(e,n,s)}),a}function f(e,t,n){try{return{type:"normal",arg:e.call(t,n)}}catch(e){return{type:"throw",arg:e}}}e.wrap=d;var p={};function v(){}function h(){}function E(){}var T={};l(T,s,(function(){return this}));var g=Object.getPrototypeOf,m=g&&g(g(D([])));m&&m!==t&&n.call(m,s)&&(T=m);var b=E.prototype=v.prototype=Object.create(T);function w(e){["next","throw","return"].forEach((function(t){l(e,t,(function(e){return this._invoke(t,e)}))}))}function y(e,t){function i(o,a,s,c){var u=f(e[o],e,a);if("throw"!==u.type){var l=u.arg,d=l.value;return d&&"object"==r(d)&&n.call(d,"__await")?t.resolve(d.__await).then((function(e){i("next",e,s,c)}),(function(e){i("throw",e,s,c)})):t.resolve(d).then((function(e){l.value=e,s(l)}),(function(e){return i("throw",e,s,c)}))}c(u.arg)}var a;o(this,"_invoke",{value:function(e,n){function r(){return new t((function(t,r){i(e,n,t,r)}))}return a=a?a.then(r,r):r()}})}function _(e,t,n){var r="suspendedStart";return function(i,o){if("executing"===r)throw new Error("Generator is already running");if("completed"===r){if("throw"===i)throw o;return{value:void 0,done:!0}}for(n.method=i,n.arg=o;;){var a=n.delegate;if(a){var s=I(a,n);if(s){if(s===p)continue;return s}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if("suspendedStart"===r)throw r="completed",n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);r="executing";var c=f(e,t,n);if("normal"===c.type){if(r=n.done?"completed":"suspendedYield",c.arg===p)continue;return{value:c.arg,done:n.done}}"throw"===c.type&&(r="completed",n.method="throw",n.arg=c.arg)}}}function I(e,t){var n=t.method,r=e.iterator[n];if(void 0===r)return t.delegate=null,"throw"===n&&e.iterator.return&&(t.method="return",t.arg=void 0,I(e,t),"throw"===t.method)||"return"!==n&&(t.method="throw",t.arg=new TypeError("The iterator does not provide a '"+n+"' method")),p;var i=f(r,e.iterator,t.arg);if("throw"===i.type)return t.method="throw",t.arg=i.arg,t.delegate=null,p;var o=i.arg;return o?o.done?(t[e.resultName]=o.value,t.next=e.nextLoc,"return"!==t.method&&(t.method="next",t.arg=void 0),t.delegate=null,p):o:(t.method="throw",t.arg=new TypeError("iterator result is not an object"),t.delegate=null,p)}function N(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.finallyLoc=e[2],t.afterLoc=e[3]),this.tryEntries.push(t)}function S(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function L(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(N,this),this.reset(!0)}function D(e){if(e){var t=e[s];if(t)return t.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var r=-1,i=function t(){for(;++r=0;--i){var o=this.tryEntries[i],a=o.completion;if("root"===o.tryLoc)return r("end");if(o.tryLoc<=this.prev){var s=n.call(o,"catchLoc"),c=n.call(o,"finallyLoc");if(s&&c){if(this.prev=0;--r){var i=this.tryEntries[r];if(i.tryLoc<=this.prev&&n.call(i,"finallyLoc")&&this.prev=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),S(n),p}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var r=n.completion;if("throw"===r.type){var i=r.arg;S(n)}return i}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:D(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=void 0),p}},e}function o(e,t,n,r,i,o,a){try{var s=e[o](a),c=s.value}catch(e){return void n(e)}s.done?t(c):Promise.resolve(c).then(r,i)}function a(e){return function(){var t=this,n=arguments;return new Promise((function(r,i){var a=e.apply(t,n);function s(e){o(a,r,i,s,c,"next",e)}function c(e){o(a,r,i,s,c,"throw",e)}s(void 0)}))}}function s(e,t){for(var n=0;n0&&void 0!==r[0]?r[0]:{},t.printTimer||t.printResolve){e.next=5;break}return e.abrupt("return",new Promise((function(e,r){t.postMessage("print.request",n),clearTimeout(t.printTimer),t.printTimer=setTimeout((function(){delete t.printResolve,delete t.printTimer,r("ERROR:导出接口超时")}),6e5),t.printResolve=e})));case 5:console.error("WARN:已存在未完成的导出任务,请稍后重试");case 6:case"end":return e.stop()}}),e)}))))})));window.litePreviewSDK={config:l.init};var d={config:l.init}},123:function(e,t,n){function r(e){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r(e)}n.r(t),n.d(t,{config:function(){return be}});var i=function(){return(i=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&i[i.length-1])&&(6===o[0]||2===o[0])){a=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]=0&&e.HANDLE_LIST.splice(n,1),window.removeEventListener("message",t,!1)},e.empty=function(){for(;e.HANDLE_LIST.length;)window.removeEventListener("message",e.HANDLE_LIST.shift(),!1)},e.parse=function(e){try{if(e&&"string"==typeof e){var t=e.indexOf('"__objName":')>=0,n=JSON.parse(e);return t&&(n.hasInnerObj=!0),n}return e}catch(e){console.log("Message.parse Error:",e)}},e.HANDLE_LIST=[],e}();function c(e){if(!e)return!1;for(var t=e;null!==Object.getPrototypeOf(t);)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t}function u(e){return"[object Function]"==={}.toString.call(e)}var l,d,f,p,v,h={origin:""};function E(e,t){h[e]=t}function T(e){return h[e]}function g(e){var t=T("whiteList")||[],n=T("origin");return!!function(e,t,n){return!n.includes(t)&&e!==t&&(e.replace(/www\./i,"").toLowerCase()!==t.replace(/www\./i,"").toLowerCase()||(e.match("www.")?void 0:(E("origin",t),!1)))}(n,e.origin,t)&&(console.warn("postMessage 域名检查不通过",{safeOrigin:n,eventOrigin:e.origin}),!0)}(v=l||(l={})).unknown="unknown",v.spreadsheet="s",v.writer="w",v.presentation="p",v.pdf="f",function(e){e.wps="w",e.et="s",e.presentation="p",e.pdf="f"}(d||(d={})),function(e){e.nomal="nomal",e.simple="simple"}(f||(f={})),function(e){e[e.requestFullscreen=1]="requestFullscreen",e[e.exitFullscreen=0]="exitFullscreen"}(p||(p={}));var m,b,w,y=(m=0,function(){return m+=1}),_=function(e,t,n){void 0===n&&(n=!0);var r=t;if(!b){var i=function e(t){var n=t.clientHeight,r=t.clientWidth;0!==n||0!==r||w?0===n&&0===r||!w||(w.disconnect(),w=null):window.ResizeObserver&&(w=new ResizeObserver((function(n){e(t)}))).observe(t),b.style.cssText+="height: "+n+"px; width: "+r+"px"}.bind(null,r);(b=document.createElement("iframe")).classList.add("web-office-iframe");var o={id:"office-iframe",src:e,scrolling:"no",frameborder:"0",allowfullscreen:"allowfullscreen",webkitallowfullscreen:"true",mozallowfullscreen:"true",allow:"clipboard-read; clipboard-write"};for(var a in r?(o.style="width: "+r.clientWidth+"px; height: "+r.clientHeight+"px;",n&&window.addEventListener("resize",i)):((r=document.createElement("div")).classList.add("web-office-default-container"),function(e){var t=document.createElement("style");document.head.appendChild(t);var n=t.sheet;n.insertRule(".web-office-default-container {position: absolute; padding: 0; margin: 0; width: 100%; height: 100%; left: 0; top: 0;}",n.cssRules.length)}(),document.body.appendChild(r),o.style="position: fixed; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%;"),H&&window.visualViewport&&window.visualViewport.addEventListener("resize",I),o)b.setAttribute(a,o[a]);r.appendChild(b),b.destroy=function(){b.parentNode.removeChild(b),b=null,window.removeEventListener("resize",i),w&&(w.disconnect(),w=null)}}return b};function I(){var e=window.visualViewport,t=e.width,n=e.height,r=document.body.clientHeight-b.clientHeight;N({eventName:"visualViewportResize",data:{width:t,height:n-r}})}var N=function(e){_().contentWindow&&_().contentWindow.postMessage(JSON.stringify(e),T("realOrigin"))};function S(e,t,n){return new Promise((function(r){var i=y();s.add((function e(t){if(!g(t)){var o=s.parse(t.data);o.eventName===n&&o.msgId===i&&(r(o.data),s.remove(e))}})),N({data:e,msgId:i,eventName:t})}))}var L=function(e){return S(e,"wps.jssdk.api","wps.api.reply")},D=function(e){return S(e,"api.basic","api.basic.reply")},A={idMap:{}},O={};function k(e,t){return o(this,void 0,void 0,(function(){var n,r,i,o;return a(this,(function(a){switch(a.label){case 0:return"api.callback"===e.eventName&&e.callbackId&&O[e.callbackId]?(n=e.data.args,e.hasInnerObj?[4,W(n,t)]:[3,2]):[3,5];case 1:return i=a.sent(),[3,3];case 2:i=n,a.label=3;case 3:return r=i,[4,O[e.callbackId].apply(O,r)];case 4:o=a.sent(),N({result:o,eventName:"api.callback.reply",callbackId:e.callbackId}),a.label=5;case 5:return[2]}}))}))}function W(e,t){return o(this,void 0,void 0,(function(){var n,i,o,s,u,l,d,f,p,v;return a(this,(function(a){switch(a.label){case 0:if(!Array.isArray(e))return[3,5];s=[],n=0,a.label=1;case 1:return n=0?"&":"?")+w.join("&")),d&&(d.isParentFullscreen||d.isBrowserViewFullscreen)&&(document.addEventListener("fullscreenchange",F),document.addEventListener("webkitfullscreenchange",F),document.addEventListener("mozfullscreenchange",F)),n.wordOptions&&(n.wpsOptions=n.wordOptions),n.excelOptions&&(n.etOptions=n.excelOptions),n.pptOptions&&(n.wppOptions=n.pptOptions),"object"==r(c.print)){var y="wpsconfig_print";"function"==typeof c.print.subscribe&&(c[y]=c.print.subscribe,n.print={callback:y},void 0!==c.print.custom&&(n.print.custom=c.print.custom)),delete c.print}return"function"==typeof c.exportPdf&&(c[y="wpsconfig_export_pdf"]=c.exportPdf,n.exportPdf={callback:y},delete c.exportPdf),n.commandBars&&j(n.commandBars,!1),i(i({},n),{subscriptions:c})},U=function(e){void 0===e&&(e="");var t="";if(!t&&e){var n=e.toLowerCase();-1!==n.indexOf("/office/s/")&&(t=l.spreadsheet),-1!==n.indexOf("/office/w/")&&(t=l.writer),-1!==n.indexOf("/office/p/")&&(t=l.presentation),-1!==n.indexOf("/office/f/")&&(t=l.pdf)}if(!t){var r=e.match(/[\?&]type=([a-z]+)/)||[];t=d[r[1]]||""}return t};function j(e,t){void 0===t&&(t=!0);var n=e.map((function(e){var t=e.attributes;if(!Array.isArray(t)){var n=[];for(var r in t)if(t.hasOwnProperty(r)){var i={name:r,value:t[r]};n.push(i)}e.attributes=n}return e}));return t&&N({data:n,eventName:"setCommandBars"}),n}var R=window.navigator.userAgent.toLowerCase(),B=/Android|webOS|iPhone|iPod|BlackBerry|iPad/i.test(R),H=/iPhone|iPod|iPad/i.test(R),Y=function(){try{return-1!==window._parent.location.search.indexOf("from=wxminiprogram")}catch(e){return!1}}();function F(){var e={status:p.requestFullscreen},t=document,n=t.fullscreenElement||t.webkitFullscreenElement||t.mozFullScreenElement;e.status=n?p.requestFullscreen:p.exitFullscreen,N({data:e,eventName:"fullscreenchange"})}var X=function(){A.idMap={}};function V(){console.group("JSSDK 事件机制调整说明"),console.warn("jssdk.on、jssdk.off 和 jssdk.Application.Sub 将在后续版本中被弃用,建议使用改进后的 ApiEvent"),console.warn("具体请参考:https://wwo.wps.cn/docs/front-end/basic-usage/events/intro/"),console.groupEnd()}var z=0,J=new Set;function K(e){return z+=1,!e&&function(e){J.forEach((function(t){return t(e)}))}(z),z}function q(){var e=new Error("");return(e.stack||e.message||"").split("\n").slice(2).join("\n")}var $={};function Z(e,t){for(var n=$.otherProps,r=$.names,i=Object.keys(r),o=-1,a=0;a=0){var c={objId:e},u=n[o];return u&&ne(c,u,n),c}}function Q(e,t){var n=this,r=t.Events,i=t.Enum,c=t.Props,u=c[0],d=c[1],f=c[2];Object.assign($,{appProps:u,otherProps:d,names:f});var p,v={objId:z};switch(ne(v,u,d),v.Events=r,v.Enum=i,e.Enum=v.Enum,e.Events=v.Events,e.Props=c,U(e.url)){case l.writer:e.WordApplication=e.WpsApplication=function(){return v};break;case l.spreadsheet:e.ExcelApplication=e.EtApplication=function(){return v};break;case l.presentation:e.PPTApplication=e.WppApplication=function(){return v};break;case l.pdf:e.PDFApplication=function(){return v}}e.Application=v,e.Free=function(e){return M("api.free",{objId:e},"")},e.Stack=v.Stack=(p=function(t){e&&e.Free(t)},function(){var e=[],t=function(t){e.push(t)};return J.add(t),{End:function(){p(e),J.delete(t)}}});var h={};s.add((function(e){return o(n,void 0,void 0,(function(){var t,n,r,i,o;return a(this,(function(a){switch(a.label){case 0:return g(e)?[2]:"api.event"===(t=s.parse(e.data)).eventName&&t.data?(n=t.data,r=n.eventName,i=n.data,(o=h[r])?[4,o(i)]:[3,2]):[3,2];case 1:a.sent(),a.label=2;case 2:return[4,k(t,(function(e,t){return Z(e,t)}))];case 3:return a.sent(),[2]}}))}))})),v.Sub={};var E=function(e){var t=r[e];Object.defineProperty(v.Sub,t,{set:function(e){V(),h[t]=e,N({eventName:"api.event.register",data:{eventName:t,register:!!e,objId:z+=1}})}})};for(var T in r)E(T)}var ee=window.FinalizationRegistry&&new FinalizationRegistry((function(e){M("api.free",{objId:e},"")})),te=["ExportAsFixedFormat","GetOperatorsInfo","ImportDataIntoFields","ReplaceText","ReplaceBookmark","GetBookmarkText","GetComments"];function ne(e,t,n){var r=t.slice(0),o=e.objId;r.includes("Destroy")||(e.Destroy=function(){return M("api.free",{objId:o},"")}),ee&&ee.register(e,o);for(var a=function(){var t=r.shift();!t.alias&&~te.indexOf(t.prop)&&r.push(i(i({},t),{alias:t.prop+"Async"}));var o=t.alias||t.prop;Object.defineProperty(e,o,{get:function(){var r=this,i=1===t.cache,o=i&&this["__"+t.prop+"CacheValue"];if(!o){var a=q(),s=K(i),c=function r(){for(var i,o=[],s=0;s=0?(t.done=null,t.then=null,e(t)):e(i.result),a.label=4;case 4:return[3,6];case 5:return s=a.sent(),n(s),[3,6];case 6:return[2]}}))}))},t.catch=function(e){return u.catch(e)}}var ie={},oe=null,ae="fileOpen",se="fileSaved",ce="fileStatus",ue="fullscreenChange",le="error",de="stage",fe="api.getToken",pe="event.toast",ve="event.hyperLinkOpen",he="api.getClipboardData";function Ee(e,t,n,r,c,u,l){var d=this;void 0===n&&(n={}),s.add((function(f){return o(d,void 0,void 0,(function(){var o,d,p,v,h,E,T,m,b,w,y,_,I,S,L,D,A,O,k;return a(this,(function(a){switch(a.label){case 0:return g(f)?[2]:(o=s.parse(f.data),d=o.eventName,p=void 0===d?"":d,v=o.data,h=void 0===v?null:v,E=o.url,T=void 0===E?null:E,-1!==["wps.jssdk.api"].indexOf(p)?[2]:"ready"!==p?[3,1]:(c.apiReadySended&&function(e){var t=[];Object.keys(ie).forEach((function(n){ie[n].forEach((function(r){var i=n;e.off(i,r),t.push({handle:r,eventName:i})})),delete ie[n]})),t.forEach((function(e){var t=e.eventName,n=e.handle;null==oe||oe.ApiEvent.AddApiEventListener(t,n)}))}(t),N({eventName:"setConfig",data:i(i({},n),{version:e.version})}),e.tokenData&&e.setToken(i(i({},e.tokenData),{hasRefreshTokenConfig:!!n.refreshToken})),e.iframeReady=!0,[3,15]));case 1:return"error"!==p?[3,2]:(t.emit(le,h),[3,15]);case 2:return"open.result"!==p?[3,3]:(void 0!==(null===(A=null==h?void 0:h.fileInfo)||void 0===A?void 0:A.officeVersion)&&(e.mainVersion=h.fileInfo.officeVersion,console.log("WebOfficeSDK Main Version: V"+e.mainVersion)),t.emit(ae,h),[3,15]);case 3:return"api.scroll"!==p?[3,4]:(window.scrollTo(h.x,h.y),[3,15]);case 4:if(p!==fe)return[3,9];m={token:!1},a.label=5;case 5:return a.trys.push([5,7,,8]),[4,c.refreshToken()];case 6:return m=a.sent(),[3,8];case 7:return b=a.sent(),console.error("refreshToken: "+(b||"fail to get")),[3,8];case 8:return N({eventName:fe+".reply",data:m}),[3,15];case 9:if(p!==he)return[3,14];w={text:"",html:""},a.label=10;case 10:return a.trys.push([10,12,,13]),[4,c.getClipboardData()];case 11:return w=a.sent(),[3,13];case 12:return y=a.sent(),console.error("getClipboardData: "+(y||"fail to get")),[3,13];case 13:return N({eventName:he+".reply",data:w}),[3,15];case 14:p===pe?c.onToast(h):p===ve?c.onHyperLinkOpen(h):"stage"===p?t.emit(de,h):"event.callback"===p?(_=h.eventName,I=h.data,S=_,_===ue&&(S="fullscreenchange"),"file.saved"===_&&(S=ce),((null===(O=n.commonOptions)||void 0===O?void 0:O.isBrowserViewFullscreen)||(null===(k=n.commonOptions)||void 0===k?void 0:k.isParentFullscreen))&&"fullscreenchange"===S&&(L=I.status,D=I.isDispatchEvent,n.commonOptions.isBrowserViewFullscreen?function(e,t,n,r){0===e?t.style="position: static; width: "+n.width+"; height: "+n.height:1===e&&(t.style="position: absolute; width: 100%; height: 100%"),r&&function(e){["fullscreen","fullscreenElement"].forEach((function(t){Object.defineProperty(document,t,{get:function(){return!!e.status},configurable:!0})}));var t=new CustomEvent("fullscreenchange");document.dispatchEvent(t)}({status:e})}(L,u,l,D):n.commonOptions.isParentFullscreen&&function(e,t,n){var r=document.querySelector(n),i=r&&1===r.nodeType?r:t;if(0===e){var o=document;(o.exitFullscreen||o.mozCancelFullScreen||o.msExitFullscreen||o.webkitCancelFullScreen||o.webkitExitFullscreen).call(document)}else if(1===e){(i.requestFullscreen||i.mozRequestFullScreen||i.msRequestFullscreen||i.webkitRequestFullscreen).call(i)}}(L,u,n.commonOptions.isParentFullscreen)),t.emit(S,I)):"api.ready"===p&&Q(e,h),a.label=15;case 15:return"function"==typeof r[p]&&r[p](e,T||h),[2]}}))}))}))}function Te(e){return new Promise((function(t){s.add((function n(r){E("realOrigin",r.origin),g(r)||s.parse(r.data).eventName===e&&(t(null),s.remove(n))}))}))}function ge(e){var t,n=this;void 0===e&&(e={}),oe&&oe.destroy();try{var r=C(e),i=r.subscriptions,c=void 0===i?{}:i,l=r.mount,d=void 0===l?null:l,f=r.url,p=r.refreshToken,v=r.onToast,h=r.onHyperLinkOpen,T=r.getClipboardData;E("whiteList",r.originWhiteList),E("origin",(f.match(/https*:\/\/[^\/]+/g)||[])[0]);var g=_(f,d),m=Te("open.result"),b=Te("api.ready"),w=d?{width:d.clientWidth+"px",height:d.clientHeight+"px"}:{width:"100vw",height:"100vh"};delete r.mount,f&&delete r.url,delete r.subscriptions;var y=(t=t||Object.create(null),{on:function(e,n){(t[e]||(t[e]=[])).push(n)},off:function(e,n){t[e]&&t[e].splice(t[e].indexOf(n)>>>0,1)},emit:function(e,n){(t[e]||[]).slice().map((function(e){e(n)})),(t["*"]||[]).slice().map((function(t){t(e,n)}))}}),I={apiReadySended:!1,apiReadySendedOnce:!1},S=function(e,t,r){return o(n,void 0,void 0,(function(){return a(this,(function(n){switch(n.label){case 0:return function(e,t,n){if(ie[e]){var r=!!ie[e].find((function(e){return e===t}));return r&&"off"===n?(y.off(e,t),ie[e]=ie[e].filter((function(e){return e!==t})),!!ie[e].length||(ie[e]=void 0,!1)):(r||"on"!==n||(ie[e].push(t),y.on(e,t)),!0)}return"on"===n?(ie[e]=[],ie[e].push(t),!1):"off"===n||void 0}(e,t,r)?[3,2]:[4,A];case 1:n.sent(),function(e,t){var n=e.eventName,r=e.type,i=e.handle;"on"===t?y.on(n,i):y.off(n,i),"base.event"===r&&N({eventName:"basic.event",data:{eventName:n,action:t}}),V()}(function(e,t){var n=e,r="base.event";switch(n){case se:console.warn("fileSaved事件监听即将弃用, 推荐使用fileStatus进行文件状态的监听"),n="fileStatus";break;case ue:n="fullscreenchange";break;case"error":case"fileOpen":r="callback.event"}return{eventName:n,type:r,handle:t}}(e,t),r),n.label=2;case 2:return[2]}}))}))};oe={url:f,iframe:g,version:"1.1.20",iframeReady:!1,tokenData:null,commandBars:null,tabs:{getTabs:function(){return o(this,void 0,void 0,(function(){return a(this,(function(e){switch(e.label){case 0:return[4,A];case 1:return e.sent(),[2,D({api:"tab.getTabs"})]}}))}))},switchTab:function(e){return o(this,void 0,void 0,(function(){return a(this,(function(t){switch(t.label){case 0:return[4,A];case 1:return t.sent(),[2,D({api:"tab.switchTab",args:{tabKey:e}})]}}))}))}},setCooperUserColor:function(e){return o(this,void 0,void 0,(function(){return a(this,(function(t){switch(t.label){case 0:return[4,A];case 1:return t.sent(),[2,D({api:"setCooperUserColor",args:e})]}}))}))},setToken:function(e){return o(this,void 0,void 0,(function(){return a(this,(function(t){switch(t.label){case 0:return[4,A];case 1:return t.sent(),oe.tokenData=e,N({eventName:"setToken",data:e}),[2]}}))}))},ready:function(){return o(this,void 0,void 0,(function(){return a(this,(function(e){switch(e.label){case 0:return I.apiReadySendedOnce?[3,2]:(I.apiReadySendedOnce=!0,[4,m]);case 1:e.sent(),I.apiReadySended=!0,N({eventName:"api.ready"}),e.label=2;case 2:return[4,b];case 3:return e.sent(),[2,new Promise((function(e){return setTimeout((function(){return e(null==oe?void 0:oe.Application)}),0)}))]}}))}))},destroy:function(){ie={},g.destroy(),s.empty(),oe=null,J=new Set,z=0,document.removeEventListener("fullscreenchange",F),X()},save:function(){return o(this,void 0,void 0,(function(){return a(this,(function(e){switch(e.label){case 0:return[4,A];case 1:return e.sent(),[2,L({api:"save"})]}}))}))},setCommandBars:function(e){return o(this,void 0,void 0,(function(){return a(this,(function(t){switch(t.label){case 0:return[4,A];case 1:return t.sent(),j(e),[2]}}))}))},updateConfig:function(e){return void 0===e&&(e={}),o(this,void 0,void 0,(function(){return a(this,(function(t){switch(t.label){case 0:return[4,A];case 1:return t.sent(),e.commandBars?(console.warn("Deprecated: `updateConfig()` 方法即将废弃,请使用`setCommandBars()`代替`updateConfig()`更新`commandBars`配置。"),[4,j(e.commandBars)]):[3,3];case 2:t.sent(),t.label=3;case 3:return[2]}}))}))},executeCommandBar:function(e){return o(this,void 0,void 0,(function(){return a(this,(function(t){switch(t.label){case 0:return[4,A];case 1:return t.sent(),j([{cmbId:e,attributes:[{name:"click",value:!0}]}]),[2]}}))}))},on:function(e,t){return o(this,void 0,void 0,(function(){return a(this,(function(n){return[2,this.ApiEvent.AddApiEventListener(e,t)]}))}))},off:function(e,t){return o(this,void 0,void 0,(function(){return a(this,(function(n){return[2,this.ApiEvent.RemoveApiEventListener(e,t)]}))}))},ApiEvent:{AddApiEventListener:function(e,t){return o(this,void 0,void 0,(function(){return a(this,(function(n){switch(n.label){case 0:return[4,S(e,t,"on")];case 1:return[2,n.sent()]}}))}))},RemoveApiEventListener:function(e,t){return o(this,void 0,void 0,(function(){return a(this,(function(n){switch(n.label){case 0:return[4,S(e,t,"off")];case 1:return[2,n.sent()]}}))}))}}},function(e,t,n,r,i,o){t&&u(t)&&(i.refreshToken=t,e.refreshToken={eventName:fe}),o&&u(o)&&(i.getClipboardData=o,e.getClipboardData={eventName:he}),n&&u(n)&&(i.onToast=n,e.onToast={eventName:pe}),r&&u(r)&&(i.onHyperLinkOpen=r,e.onHyperLinkOpen={eventName:ve})}(r,p,v,h,I,T),Ee(oe,y,r,c,I,g,w);var A=Te("ready");return oe}catch(e){console.error(e)}}console.log("WebOfficeSDK JS-SDK V1.1.20");var me=Object.freeze({__proto__:null,listener:Ee,config:ge});window.WPS=me;var be=ge;t.default={config:ge}},606:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0});var r=n(328),i=function(){function e(){this.events={},this.eventsArr=Object.values(r.AddApiListenerType),this.events={},this.handler=this.handler.bind(this),window.addEventListener("message",this.handler)}return e.prototype.handler=function(e){this.eventsArr.includes(e.data.eventName)&&this.trigger({eventName:e.data.eventName,params:e.data.data})},e.prototype.add=function(e,t){this.events[e]||(this.events[e]=[]),console.log("添加【".concat(e,"】事件成功")),this.events[e].push(t)},e.prototype.remove=function(e,t){this.events[e]&&(this.events[e]=this.events[e].filter((function(e){return e!==t})),console.log("移除【".concat(e,"】事件成功")))},e.prototype.trigger=function(e){var t=e.eventName,n=e.params;this.events[t]&&this.events[t].forEach((function(e){console.log("触发【".concat(t,"】事件,参数为:"),n),e(n)}))},e}();t.default=i},620:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function a(e){try{c(r.next(e))}catch(e){o(e)}}function s(e){try{c(r.throw(e))}catch(e){o(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}c((r=r.apply(e,t||[])).next())}))},i=this&&this.__generator||function(e,t){var n,r,i,o,a={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function s(o){return function(s){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return a.label++,{value:o[1],done:!1};case 5:a.label++,r=o[1],o=[0];continue;case 7:o=a.ops.pop(),a.trys.pop();continue;default:if(!(i=a.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){a=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]1e3*e},e.prototype.renderOpendoc=function(){var e,t=document.createElement("iframe");t.allow="clipboard-read;clipboard-write",t.allowFullscreen=!0,t.src=this.config.url,t.allowFullscreen=!0,t.frameBorder="none",t.style.width="100%",t.style.height="100%",t.id="third-iframe",null===(e=this.config.mount)||void 0===e||e.appendChild(t),s=t,this.events=new v.default,this.instance={setToken:this.setToken.bind(this),print:this.openDocPrint.bind(this),download:this.openDocDownload.bind(this),ApiEvent:{AddApiEventListener:this.addApiEventListener.bind(this),RemoveApiEventListener:this.removeApiEventListener.bind(this)},ready:this.ready.bind(this),tabs:{getTabs:this.getTabs.bind(this),switchTab:this.switchTab.bind(this)},save:this.save.bind(this),destroy:this.destroy.bind(this),iframe:t}},e.prototype.setToken=function(e){console.log("this.config",this.config),this.config.setToken=e,(0,f.setToken)(this.config,s)},e.prototype.openDocPrint=function(){if(s.src.split("/").includes("micsweb"))(0,p.openDocFunc)(p.EVENT_TYPE.PRINT,s);else{this.invoke(p.EVENT_TYPE.PRINT);var e=function(t){var n,r,i;t.data.eventName===p.EVENT_TYPE.PRINT&&(console.log(null===(i=null===(r=null===(n=t.data)||void 0===n?void 0:n.data)||void 0===r?void 0:r.result)||void 0===i?void 0:i.message),window.removeEventListener("message",e))};window.addEventListener("message",e)}},e.prototype.openDocDownload=function(e){e&&e.type?(console.log("openDocDownload: ".concat(p.EVENT_TYPE.DOWNLOAD,"-type:").concat(e.type)),this.invoke(p.EVENT_TYPE.DOWNLOAD,e.type)):(0,p.openDocFunc)(p.EVENT_TYPE.DOWNLOAD,s)},e.prototype.ready=function(){return r(this,void 0,void 0,(function(){return i(this,(function(e){return[2,new Promise((function(e,t){s.onload=function(){e(!0)}}))]}))}))},e.prototype.save=function(){return r(this,void 0,void 0,(function(){return i(this,(function(e){return this.invoke(p.EVENT_TYPE.SAVE),[2,new Promise((function(e,t){var n=function(t){t.data.eventName===p.EVENT_TYPE.SAVE&&(e(t.data.data),window.removeEventListener("message",n))};window.addEventListener("message",n)}))]}))}))},e.prototype.getTabs=function(){return console.log("触发getTabs方法"),this.invoke(p.EVENT_TYPE.GET_TABS),new Promise((function(e,t){var n=function(t){t.data.eventName===p.EVENT_TYPE.GET_TABS&&(e(t.data.data),window.removeEventListener("message",n))};window.addEventListener("message",n)}))},e.prototype.switchTab=function(e){return r(this,void 0,void 0,(function(){var t;return i(this,(function(n){return t=e.tabKey,[2,new Promise((function(e,n){(0,p.openDocFunc)("action-".concat(p.EVENT_TYPE.SWITCH_TAB),s,{tabKey:t}),e("已切换到".concat(t))}))]}))}))},e.prototype.addApiEventListener=function(e,t){this.events.add(e,t)},e.prototype.removeApiEventListener=function(e,t){this.events.remove(e,t)},e.prototype.destroy=function(){var e=document.getElementById("third-iframe");e.parentNode.removeChild(e),this.instance=null},e.prototype.invoke=function(e,t){(0,p.openDocFunc)("action-".concat(e),s,t)},e.prototype.printExecute=function(){return r(this,void 0,void 0,(function(){var e,t;return i(this,(function(n){switch(n.label){case 0:return[4,this.instance.print()];case 1:return e=n.sent(),(t=document.createElement("iframe")).setAttribute("style","display:none"),fetch(e).then((function(e){return e.blob()})).then((function(e){var n=URL.createObjectURL(e);t.src=n,document.body.appendChild(t),t.onload=function(){t.contentWindow.print()}})),[2]}}))}))},e}();t.CTX=h},328:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.openDocFunc=t.AddApiListenerType=t.EVENT_TYPE=void 0,function(e){e.PRINT="print",e.DOWNLOAD="download",e.GET_TABS="getTabs",e.SWITCH_TAB="switchTab",e.SAVE="save"}(t.EVENT_TYPE||(t.EVENT_TYPE={})),function(e){e.FILE_OPEN="fileOpen",e.FILE_STATUS="fileStatus",e.ON_USER_LIST_INIT="OnUserListInit",e.ON_USER_JOIN="OnUserJoin",e.ON_USER_QUIT="OnUserQuit"}(t.AddApiListenerType||(t.AddApiListenerType={}));var n=null;t.openDocFunc=function(e,t,r){n=t,function(e){console.log("发送事件:",e);var t=n.contentWindow;t&&t.postMessage(e,"*")}({eventName:e,data:r})}},853:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.isPromiseLike=t.setToken=t.setCookie=t.getWappId=void 0;var n,r=null,i=null,o=!0,a=null;function s(e){var t,n;t={eventName:o?"setToken":"setTokenRefresh",data:e},(n=a.contentWindow)&&n.postMessage(t,"*"),o=!1,r=e,i=(new Date).getTime()}function c(e){return e instanceof Promise||"function"==typeof(null==e?void 0:e.then)}t.getWappId=function(e){if(!e.includes("/weboffice/office/d"))return null;var t=e.match(/_w_appid=([^&]+)/);return t?t[1]:null},t.setCookie=function(e){var t=e.name,n=e.value,r=e.expires,i=new Date;i.setTime(i.getTime()+r||6e5),document.cookie="".concat(t,"=").concat(n,";expires=").concat(i.toUTCString(),";path=/")},t.setToken=function(e,t){if(!e.setToken||!e.setToken.token)return console.error("请按照文档规范设置token格式");n=e,window.addEventListener("message",(function(u){"wpsPreviewDidMount"===u.data&&(r=null,i=null,o=!0,a=t,s(e.setToken),"function"==typeof e.refreshToken&&function(e){window.document.addEventListener("visibilitychange",(function(){if("hidden"!==document.visibilityState){var e=(new Date).getTime();if(r&&e-i>r.timeout){var t=n.refreshToken();c(t)?t.then((function(e){s(e)})):s(t)}}}));var t=function(e){s(e),e.timeout&&o(e.timeout)},o=function(e){var r,i=e-3e5;setTimeout((function(){var o=(new Date).getTime(),a=n.refreshToken();if(c(a))a.then((function(n){r=n;var a=(new Date).getTime();setTimeout((function(){t(r)}),i>0?3e5-(a-o):e-(a-o))}));else{r=a;var s=(new Date).getTime();setTimeout((function(){t(r)}),i>0?3e5-(s-o):e-(s-o))}}),i)};o(e)}(e.setToken.timeout))}))},t.isPromiseLike=c},737:function(e,t){function n(e){return new RegExp(/\/weboffice\/office\//).test(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.decryptTag=t.encryptTag=t.querystring=t.isLitePreviewUrl=t.isWebofficeUrl=t.isOpenDocUrl=t.parseWebpath=t.parseUrl=t.isJSON=void 0,t.isJSON=function(e){if("string"!=typeof e)return!1;try{var t=JSON.parse(e);return!("object"!=typeof t||!t)}catch(t){return console.log("error:"+e+"!!!"+t),!1}},t.parseUrl=function(e){var t=document.createElement("a");t.href=e;var n=t.hostname,r="80"===t.port||"443"===t.port?"":t.port,i=n+(r?":".concat(r):"");return{href:e,protocol:t.protocol||"",host:i,hostname:n,port:r,search:t.search.replace(t.hash,"")||"",hash:t.hash||"",pathname:0===(t.pathname||"").indexOf("/")?t.pathname||"":"/"+(t.pathname||""),relative:(e.match(/tps?:\/\/[^\/]+(.+)/)||[,""])[1]||"",segments:t.pathname.replace(/^\//,"").split("/")||[],origin:t.protocol+"//"+i||""}},t.parseWebpath=function(e){var t=e||window.location.pathname||"";return t.substring(0,t.lastIndexOf("/docs/viewweb/"))||""},t.isOpenDocUrl=function(e){return new RegExp(/\/micsweb\/viewweb\/reader\//).test(e)},t.isWebofficeUrl=n,t.isLitePreviewUrl=function(e){return n(e)&&e.includes("wpsCachePreview")},t.querystring=function(e){var t=(e||window.location.search).replace(/^(\S*)\?/,"").split("&"),n={};return t.forEach((function(e){var t=e.split("=");n[t[0]]=t[1]})),n};t.encryptTag=function(e,t){if(!e)return"";try{var n=encodeURIComponent(e),r=encodeURIComponent(t),i=btoa(n);return btoa(r)+"_"+i}catch(e){return""}};t.decryptTag=function(e,t){if(!e)return"";var n=e.split("_");if(n.length<2)return"";try{var r=decodeURIComponent(atob(n[1]));return decodeURIComponent(atob(n[0]))!==t?"":r}catch(e){return""}}},882:function(e,t,n){var r;Object.defineProperty(t,"__esModule",{value:!0}),t.DEFAULT_PLACEHOLDER_TEXT=void 0;var i=n(153);t.DEFAULT_PLACEHOLDER_TEXT=((r={})[i.BASE_WIDGET_TYPE.SINGLE_LINE_TEXT]="请输入文本",r[i.BASE_WIDGET_TYPE.MULTILINE_TEXT]="请输入文本",r[i.BASE_WIDGET_TYPE.HTML]="请输入HTML",r[i.BASE_WIDGET_TYPE.IMAGE]="",r)},210:function(e,t,n){var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&i[i.length-1])||6!==o[0]&&2!==o[0])){a=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")},s=this&&this.__read||function(e,t){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,i,o=n.call(e),a=[];try{for(;(void 0===t||t-- >0)&&!(r=o.next()).done;)a.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return a},c=this&&this.__spreadArray||function(e,t,n){if(n||2===arguments.length)for(var r,i=0,o=t.length;i0)throw new Error("控件".concat(null==t?void 0:t.id,"不支持修改下列属性:").concat(null===(a=e.get(null==t?void 0:t.id))||void 0===a?void 0:a.join(),",设置失败"));n()}catch(e){n()}},t?[3,2]:[4,this.getWidgetList()]):[2];case 1:if(c=o.sent(),u=null==c?void 0:c.filter((function(e){return(null==e?void 0:e.tag)===n})),!((null==u?void 0:u.length)>0))throw new Error('未检索到和tag = "'.concat(n,'"匹配的控件'));l=function(e){i(e,(function(){var t=r(r({},e),s);m.setWidgetInfo(null==e?void 0:e.id,t)}))};try{for(f=a(u),p=f.next();!p.done;p=f.next())v=p.value,l(v)}catch(e){T={error:e}}finally{try{p&&!p.done&&(g=f.return)&&g.call(f)}finally{if(T)throw T.error}}return[3,5];case 2:return[4,this.getWpsWidgetItem(t)];case 3:return v=o.sent(),[4,this.getWidgetInfo(v)];case 4:if(h=o.sent(),E=r(r({},h),s),n&&(null==h?void 0:h.tag)!==n)throw new Error("未检索到id:".concat(t,'和tag:"').concat(n,'"匹配的控件'));i(E,(function(){m.setWidgetInfo(t,E)})),o.label=5;case 5:return[2]}}))}))},e.prototype.setLocate=function(e){return i(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.getWpsWidgetItem(e)];case 1:return[4,t.sent().Locate()];case 2:return t.sent(),[2]}}))}))},e.prototype.hasBasePropertyExist=function(e){var t=[];return["tag","widgetId","widgetName"].forEach((function(n){t.push(!!e[n])})),t.some((function(e){return e}))},e.prototype.deleteWidgetItem=function(e){return i(this,void 0,void 0,(function(){var t,n;return o(this,(function(r){switch(r.label){case 0:return[4,this.getWpsWidgetItem(e)];case 1:return t=r.sent(),[4,this.getWidgetInfo(t)];case 2:return n=r.sent(),[4,t.Delete()];case 3:return r.sent(),[2,n]}}))}))},e.prototype.getWidgetItem=function(e,t){return i(this,void 0,void 0,(function(){var n,r;return o(this,(function(i){switch(i.label){case 0:return t?[4,this.getWpsWidgetItemByIndex(e)]:[3,2];case 1:return r=i.sent(),[3,4];case 2:return[4,this.getWpsWidgetItem(e)];case 3:r=i.sent(),i.label=4;case 4:return n=r,[4,this.getWidgetInfo(n)];case 5:return[2,i.sent()]}}))}))},e.prototype.getWpsWidgetItem=function(e){var t;return i(this,void 0,void 0,(function(){return o(this,(function(n){switch(n.label){case 0:return[4,this.wpsInstance.ready()];case 1:return n.sent(),[4,null===(t=this.wpsInstance.Application)||void 0===t?void 0:t.ActiveDocument.ContentControls({ID:e})];case 2:return[2,n.sent()]}}))}))},e.prototype.getWpsWidgetItemByIndex=function(e){var t;return i(this,void 0,void 0,(function(){return o(this,(function(n){switch(n.label){case 0:return[4,this.wpsInstance.ready()];case 1:return n.sent(),[2,null===(t=this.wpsInstance)||void 0===t?void 0:t.Application.ActiveDocument.ContentControls.Item(e)]}}))}))},e.prototype.typeIsWidgetType=function(e){var t=[];for(var n in d.WIDGET_TYPE_ENUM)isNaN(Number(n))||t.push(Number(n));return t.includes(e)},e.prototype.initWidgetInfo=function(e){var t,n=e.baseWidgetType,r=e.widgetItem,a=e.range;return i(this,void 0,void 0,(function(){var e,f,p,v,h=this;return o(this,(function(E){switch(E.label){case 0:if(!n)throw new Error("请输入要插入的基础控件类型!");if(isNaN(d.BASE_WIDGET_CONTROL_MAP[n]))throw new Error("基础控件类型不存在!");if(a&&(null==a?void 0:a.end)<=(null==a?void 0:a.start))throw new Error("控件插入的结束位置需要大于起始位置: ".concat(null==a?void 0:a.start,"-").concat(null==a?void 0:a.end));if(n===d.BASE_WIDGET_TYPE.HTML&&!/<(\w+)[^>]*>(.*?<\/\1>)?/i.test(null==r?void 0:r.html))throw new Error("输入HTML内容不合法");f=null!==(t=d.BASE_WIDGET_CONTROL_MAP[n])&&void 0!==t?t:0,E.label=1;case 1:return E.trys.push([1,3,,4]),[4,this.wpsInstance.Application.ActiveDocument.ContentControls.Add(f,{Start:null==a?void 0:a.start,End:null==a?void 0:a.end})];case 2:return e=E.sent(),[3,4];case 3:return[2,E.sent()];case 4:switch(p=function(t){return i(h,void 0,void 0,(function(){var a,s,c=this;return o(this,(function(f){return(s={})[d.WIDGET_SETTING_ENUM.NAME]=function(){return i(c,void 0,void 0,(function(){return o(this,(function(t){return[2,e.Title=(null==r?void 0:r.widgetName)||""]}))}))},s[d.WIDGET_SETTING_ENUM.TAG]=function(){return i(c,void 0,void 0,(function(){var t,i,a,s,c,l,f;return o(this,(function(o){try{t=new URL(null===(f=this.wpsInstance)||void 0===f?void 0:f.url),i=null==t?void 0:t.searchParams,a=null==i?void 0:i.get("_w_appid"),s={tag:(null==r?void 0:r.tag)||"",appId:a,widgetId:d.BASE_WIDGET_IDS[n],dataImportWay:{dataImportType:"hand_write"},baseWidgetType:n||"",widgetType:"base_widget",originType:"base_widget"},c="".concat(JSON.stringify(s)),l=(0,u.encryptTag)(c,a),e.Tag=l}catch(e){throw new Error(e)}return[2]}))}))},s[d.WIDGET_SETTING_ENUM.PLACEHOLDER]=function(){return i(c,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,e.SetPlaceholderText({Text:(null==r?void 0:r.placeholderText)||l.DEFAULT_PLACEHOLDER_TEXT[n]})];case 1:return t.sent(),[2]}}))}))},s[d.WIDGET_SETTING_ENUM.CONTENT]=function(){return i(c,void 0,void 0,(function(){return o(this,(function(t){if(!(null==r?void 0:r.content))return[2];switch(n){case d.BASE_WIDGET_TYPE.SINGLE_LINE_TEXT:case d.BASE_WIDGET_TYPE.MULTILINE_TEXT:e.Content=null==r?void 0:r.content}return[2]}))}))},s[d.WIDGET_SETTING_ENUM.ALLOW_MULTILINE]=function(){return i(c,void 0,void 0,(function(){var t;return o(this,(function(r){return t=n===d.BASE_WIDGET_TYPE.MULTILINE_TEXT,e.MultiLine=t,[2]}))}))},s[d.WIDGET_SETTING_ENUM.IMAGE_SOURCE]=function(){return i(c,void 0,void 0,(function(){var t,i,a,s,c,u,l,f,p,v,h;return o(this,(function(o){switch(o.label){case 0:if(n!==d.BASE_WIDGET_TYPE.IMAGE||!(null==r?void 0:r.imageSource))return[3,2];if(!(null===(i=null==r?void 0:r.imageSource)||void 0===i?void 0:i.imageUrl)&&!(null===(a=null==r?void 0:r.imageSource)||void 0===a?void 0:a.imageData))throw new Error("图片控件数据源缺失!");return[4,e.Shape.Reset()];case 1:if(o.sent(),t=/^(https?:\/\/).*\.(png|jpg|jpeg|gif|webp|svg)$/i,/^\s*data:(?:[a-z]+\/[a-z0-9-+.]+(?:;[a-z-]+=[a-z0-9-]+)?)?(?:;base64)?,([a-z0-9!$&',()*+;=\-._~:@/?%\s]*?)\s*$/i.test(null===(s=null==r?void 0:r.imageSource)||void 0===s?void 0:s.imageData))e.Shape.Data=null===(c=null==r?void 0:r.imageSource)||void 0===c?void 0:c.imageData;else{if(!t.test(null===(u=null==r?void 0:r.imageSource)||void 0===u?void 0:u.imageUrl))throw new Error("图片数据源格式非法!");e.Shape.Url=null===(l=null==r?void 0:r.imageSource)||void 0===l?void 0:l.imageUrl}(null===(f=null==r?void 0:r.imageSource)||void 0===f?void 0:f.width)&&(e.Shape.Width=null===(p=null==r?void 0:r.imageSource)||void 0===p?void 0:p.width),(null===(v=null==r?void 0:r.imageSource)||void 0===v?void 0:v.height)&&(e.Shape.Height=null===(h=null==r?void 0:r.imageSource)||void 0===h?void 0:h.height),o.label=2;case 2:return[2]}}))}))},s[d.WIDGET_SETTING_ENUM.EXTENSION_DATA]=function(){return i(c,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return!0,!0==(n===d.BASE_WIDGET_TYPE.HTML)?[3,1]:[3,4];case 1:return t=null==r?void 0:r.html,t?[4,e.AddOrEditExtensionData([{key:d.EXTENSION_DATA.WIDGET_HTML_FIGMENT,value:t}])]:[3,3];case 2:i.sent(),i.label=3;case 3:case 4:return[3,5];case 5:return[2]}}))}))},s[d.WIDGET_SETTING_ENUM.HTML_PASTE]=function(){return i(c,void 0,void 0,(function(){var t,n,i,a;return o(this,(function(o){switch(o.label){case 0:return o.trys.push([0,7,,8]),[4,this.wpsInstance.ready()];case 1:return o.sent(),[4,e.LockContents];case 2:return o.sent()&&(e.LockContents=!1),[4,e.Range];case 3:return[4,(t=o.sent()).Start];case 4:return n=o.sent(),[4,t.End];case 5:return i=o.sent(),[4,this.wpsInstance.Application.ActiveDocument.Range(n,i).PasteHtml({HTML:null==r?void 0:r.html}).then((function(){e.LockContents=!0}))];case 6:return o.sent(),[3,8];case 7:throw a=o.sent(),new Error(a);case 8:return[2]}}))}))},s[d.WIDGET_SETTING_ENUM.LOCK_EDIT]=function(){return i(c,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.wpsInstance.ready()];case 1:if(t.sent(),n===d.BASE_WIDGET_TYPE.HTML)e.SetPlaceholderText({Text:(null==r?void 0:r.placeholderText)||l.DEFAULT_PLACEHOLDER_TEXT[n]}),e.LockContents=!0;return[2]}}))}))},a=s,t.forEach((function(e){var t;null===(t=a[e])||void 0===t||t.call(a)})),[2]}))}))},v=[d.WIDGET_SETTING_ENUM.NAME,d.WIDGET_SETTING_ENUM.TAG],n){case d.BASE_WIDGET_TYPE.SINGLE_LINE_TEXT:p(c(c([],s(v),!1),[d.WIDGET_SETTING_ENUM.ALLOW_MULTILINE,d.WIDGET_SETTING_ENUM.CONTENT,d.WIDGET_SETTING_ENUM.PLACEHOLDER],!1));case d.BASE_WIDGET_TYPE.MULTILINE_TEXT:p(c(c([],s(v),!1),[d.WIDGET_SETTING_ENUM.CONTENT,d.WIDGET_SETTING_ENUM.ALLOW_MULTILINE,d.WIDGET_SETTING_ENUM.PLACEHOLDER],!1));break;case d.BASE_WIDGET_TYPE.IMAGE:p(c(c([],s(v),!1),[d.WIDGET_SETTING_ENUM.IMAGE_SOURCE],!1));break;case d.BASE_WIDGET_TYPE.HTML:p(c(c([],s(v),!1),[d.WIDGET_SETTING_ENUM.EXTENSION_DATA,d.WIDGET_SETTING_ENUM.LOCK_EDIT,d.WIDGET_SETTING_ENUM.HTML_PASTE,d.WIDGET_SETTING_ENUM.PLACEHOLDER],!1))}return[2,e]}}))}))},e.prototype.getWidgetInfo=function(e){var t,n,a,s,c,l,p;return i(this,void 0,void 0,(function(){var i,v,h,E,T,g,m,b,w,y,_,I,N,S,L,D,A,O,k,W,G,P;return o(this,(function(o){switch(o.label){case 0:return[4,e.Title];case 1:return i=o.sent()||"",[4,e.Tag];case 2:v=o.sent(),h=new URL(null===(t=this.wpsInstance)||void 0===t?void 0:t.url),E=null==h?void 0:h.searchParams,T=null==E?void 0:E.get("_w_appid"),g=(0,u.decryptTag)(v,T),m="","",b="",w={},y={},_="";try{w=JSON.parse(g),m=(null==w?void 0:w.widgetId)||"",(null==w?void 0:w.widgetName)||""}catch(e){}return[4,e.ID];case 3:return I=o.sent(),[4,e.Type];case 4:return N=o.sent(),[4,e.PlaceholderText];case 5:return S=o.sent(),L=null==w?void 0:w.baseWidgetType,[d.WIDGET_TYPE_ENUM.TEXT,d.WIDGET_TYPE_ENUM.RTF_CONTENT].includes(N)&&[d.BASE_WIDGET_TYPE.SINGLE_LINE_TEXT,d.BASE_WIDGET_TYPE.MULTILINE_TEXT,d.BASE_WIDGET_TYPE.HTML].includes(L)?[4,e.Content]:[3,7];case 6:b=o.sent()||"",o.label=7;case 7:if(N!==d.WIDGET_TYPE_ENUM.IMAGE||L!==d.BASE_WIDGET_TYPE.IMAGE)return[3,16];o.label=8;case 8:return o.trys.push([8,15,,16]),D=y,[4,null===(n=null==e?void 0:e.Shape)||void 0===n?void 0:n.Width];case 9:return D.width=o.sent(),A=y,[4,null===(a=null==e?void 0:e.Shape)||void 0===a?void 0:a.Height];case 10:return A.height=o.sent(),(null===(s=null==e?void 0:e.Shape)||void 0===s?void 0:s.Url)?(O=y,[4,null===(c=null==e?void 0:e.Shape)||void 0===c?void 0:c.Url]):[3,12];case 11:O.imageUrl=o.sent(),o.label=12;case 12:return(null===(l=null==e?void 0:e.Shape)||void 0===l?void 0:l.Data)?(k=y,[4,null===(p=null==e?void 0:e.Shape)||void 0===p?void 0:p.Data]):[3,14];case 13:k.imageData=o.sent(),o.label=14;case 14:return[3,16];case 15:return W=o.sent(),console.error("Error","该控件没有对应的属性",W),[3,16];case 16:return N!==d.WIDGET_TYPE_ENUM.RTF_CONTENT||L!==d.BASE_WIDGET_TYPE.HTML?[3,18]:[4,(0,f.getExtensionData)(this.wpsInstance,I,d.EXTENSION_DATA.WIDGET_HTML_FIGMENT)];case 17:(G=o.sent())&&(_=G.value),o.label=18;case 18:switch(P={widgetId:m,widgetName:i||"",tag:(null==w?void 0:w.tag)||"",id:I,widgetType:null==w?void 0:w.widgetType,baseWidgetType:null==w?void 0:w.baseWidgetType},L){case d.BASE_WIDGET_TYPE.SINGLE_LINE_TEXT:case d.BASE_WIDGET_TYPE.MULTILINE_TEXT:return[2,r(r({},P),{content:b,placeholderText:S})];case d.BASE_WIDGET_TYPE.IMAGE:return[2,r(r({},P),{imageSource:y})];case d.BASE_WIDGET_TYPE.HTML:return[2,r(r({},P),{content:b,placeholderText:S,html:_})]}return[2]}}))}))},e.prototype.setWidgetInfo=function(e,t){return i(this,void 0,void 0,(function(){var n,a,s,c,l,f=this;return o(this,(function(p){switch(p.label){case 0:return[4,this.getWpsWidgetItem(e)];case 1:return[4,(n=p.sent()).LockContents];case 2:return p.sent()&&(n.LockContents=!1),[4,null==n?void 0:n.Tag];case 3:return a=p.sent(),[4,this.getWidgetInfo(n)];case 4:return s=p.sent(),c=null==s?void 0:s.baseWidgetType,[4,null==n?void 0:n.Type];case 5:switch(p.sent(),l=function(e){return i(f,void 0,void 0,(function(){var l,f,p=this;return o(this,(function(v){return(f={})[d.WIDGET_SETTING_ENUM.NAME]=function(){return i(p,void 0,void 0,(function(){return o(this,(function(e){return t.widgetName&&(n.Title=t.widgetName),[2]}))}))},f[d.WIDGET_SETTING_ENUM.TAG]=function(){return i(p,void 0,void 0,(function(){var e,i,s,c,l,d,f;return o(this,(function(o){try{e=new URL(null===(f=this.wpsInstance)||void 0===f?void 0:f.url),i=null==e?void 0:e.searchParams,s=null==i?void 0:i.get("_w_appid"),c=JSON.parse((0,u.decryptTag)(a,s)),l=JSON.stringify(r(r({},c),{tag:null==t?void 0:t.tag})),d=(0,u.encryptTag)(l,s),n.Tag=d}catch(e){console.error("Error",e)}return[2]}))}))},f[d.WIDGET_SETTING_ENUM.PLACEHOLDER]=function(){return i(p,void 0,void 0,(function(){return o(this,(function(e){switch(e.label){case 0:return(null==t?void 0:t.placeholderText)?(!0,!0==(c===d.BASE_WIDGET_TYPE.HTML)?[3,1]:[3,3]):[3,5];case 1:case 3:return[4,n.SetPlaceholderText({Text:t.placeholderText})];case 2:case 4:return e.sent(),[3,5];case 5:return[2]}}))}))},f[d.WIDGET_SETTING_ENUM.CONTENT]=function(){return i(p,void 0,void 0,(function(){return o(this,(function(e){return(null==t?void 0:t.content)&&c!==d.BASE_WIDGET_TYPE.HTML&&(n.Content=t.content),[2]}))}))},f[d.WIDGET_SETTING_ENUM.ALLOW_MULTILINE]=function(){return i(p,void 0,void 0,(function(){var e;return o(this,(function(r){return e=(null==t?void 0:t.baseWidgetType)===d.BASE_WIDGET_TYPE.MULTILINE_TEXT,(null==s?void 0:s.baseWidgetType)!==d.BASE_WIDGET_TYPE.IMAGE&&(n.MultiLine=e),[2]}))}))},f[d.WIDGET_SETTING_ENUM.IMAGE_SOURCE]=function(){return i(p,void 0,void 0,(function(){var e,r,i,a,s,c,u,l,d,f,p;return o(this,(function(o){switch(o.label){case 0:if(!(null===(r=null==t?void 0:t.imageSource)||void 0===r?void 0:r.imageUrl)&&!(null===(i=null==t?void 0:t.imageSource)||void 0===i?void 0:i.imageData))throw new Error("图片控件数据源缺失!");return[4,n.Shape.Reset()];case 1:if(o.sent(),e=/^(https?:\/\/).*\.(png|jpg|jpeg|gif|webp|svg)$/i,/^\s*data:(?:[a-z]+\/[a-z0-9-+.]+(?:;[a-z-]+=[a-z0-9-]+)?)?(?:;base64)?,([a-z0-9!$&',()*+;=\-._~:@/?%\s]*?)\s*$/i.test(null===(a=null==t?void 0:t.imageSource)||void 0===a?void 0:a.imageData))n.Shape.Data=null===(s=null==t?void 0:t.imageSource)||void 0===s?void 0:s.imageData;else{if(!e.test(null===(c=null==t?void 0:t.imageSource)||void 0===c?void 0:c.imageUrl))throw new Error("图片数据源格式非法!");n.Shape.Url=null===(u=null==t?void 0:t.imageSource)||void 0===u?void 0:u.imageUrl}return(null===(l=null==t?void 0:t.imageSource)||void 0===l?void 0:l.width)&&(n.Shape.Width=null===(d=null==t?void 0:t.imageSource)||void 0===d?void 0:d.width),(null===(f=null==t?void 0:t.imageSource)||void 0===f?void 0:f.height)&&(n.Shape.Height=null===(p=null==t?void 0:t.imageSource)||void 0===p?void 0:p.height),[2]}}))}))},f[d.WIDGET_SETTING_ENUM.EXTENSION_DATA]=function(){return i(p,void 0,void 0,(function(){var e;return o(this,(function(r){switch(r.label){case 0:return[4,n.LockContents];case 1:return r.sent(),!0,!0==(c===d.BASE_WIDGET_TYPE.HTML)?[3,2]:[3,5];case 2:return e=null==t?void 0:t.html,e?[4,n.AddOrEditExtensionData([{key:d.EXTENSION_DATA.WIDGET_HTML_FIGMENT,value:e}])]:[3,4];case 3:r.sent(),r.label=4;case 4:case 5:return[3,6];case 6:return[2]}}))}))},f[d.WIDGET_SETTING_ENUM.HTML_PASTE]=function(){return i(p,void 0,void 0,(function(){var e,r,i,a;return o(this,(function(o){switch(o.label){case 0:return o.trys.push([0,7,,8]),(null==t?void 0:t.html)?[4,this.wpsInstance.ready()]:[3,6];case 1:return o.sent(),[4,n.Range];case 2:return[4,(e=o.sent()).Start];case 3:return r=o.sent(),[4,e.End];case 4:return i=o.sent(),[4,this.wpsInstance.Application.ActiveDocument.Range(r,i).PasteHtml({HTML:null==t?void 0:t.html}).then((function(){n.LockContents=!0}))];case 5:o.sent(),o.label=6;case 6:return[3,8];case 7:return a=o.sent(),n.LockContents=!0,console.error(a),[3,8];case 8:return[2]}}))}))},f[d.WIDGET_SETTING_ENUM.LOCK_EDIT]=function(){return i(p,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},l=f,e.forEach((function(e){var t;null===(t=l[e])||void 0===t||t.call(l)})),[2]}))}))},c){case d.BASE_WIDGET_TYPE.SINGLE_LINE_TEXT:case d.BASE_WIDGET_TYPE.MULTILINE_TEXT:l([d.WIDGET_SETTING_ENUM.NAME,d.WIDGET_SETTING_ENUM.TAG,d.WIDGET_SETTING_ENUM.PLACEHOLDER,d.WIDGET_SETTING_ENUM.CONTENT,d.WIDGET_SETTING_ENUM.ALLOW_MULTILINE]);break;case d.BASE_WIDGET_TYPE.IMAGE:l([d.WIDGET_SETTING_ENUM.NAME,d.WIDGET_SETTING_ENUM.TAG,d.WIDGET_SETTING_ENUM.IMAGE_SOURCE]);break;case d.BASE_WIDGET_TYPE.HTML:l([d.WIDGET_SETTING_ENUM.NAME,d.WIDGET_SETTING_ENUM.TAG,d.WIDGET_SETTING_ENUM.PLACEHOLDER,d.WIDGET_SETTING_ENUM.EXTENSION_DATA,d.WIDGET_SETTING_ENUM.HTML_PASTE])}return[2]}}))}))},e}();t.Widget=p},153:function(e,t){var n,r,i,o;Object.defineProperty(t,"__esModule",{value:!0}),t.EXTENSION_DATA=t.BASE_WIDGET_IDS=t.BASE_WIDGET_CONTROL_MAP=t.WIDGET_SETTING_ENUM=t.CONTROL_ELEMENT_ENUM=t.BASE_WIDGET_TYPE=t.WIDGET_TYPE=t.WIDGET_TYPE_ENUM=void 0,function(e){e[e.RTF_CONTENT=0]="RTF_CONTENT",e[e.TEXT=1]="TEXT",e[e.IMAGE=2]="IMAGE",e[e.COMBINATION=3]="COMBINATION",e[e.DROP_DOWN=4]="DROP_DOWN",e[e.DATE=6]="DATE",e[e.CHECKBOX=8]="CHECKBOX",e[e.REPEAT=9]="REPEAT"}(i=t.WIDGET_TYPE_ENUM||(t.WIDGET_TYPE_ENUM={})),function(e){e.BASE_WIDGET="base_widget",e.BUSINESS_WIDGET="business_widget"}(t.WIDGET_TYPE||(t.WIDGET_TYPE={})),function(e){e.SINGLE_LINE_TEXT="single_line_text",e.MULTILINE_TEXT="multiline_text",e.IMAGE="image",e.HTML="html"}(o=t.BASE_WIDGET_TYPE||(t.BASE_WIDGET_TYPE={})),function(e){e.BASE_WIDGET="base_widget",e.BUSINESS_WIDGET="business_widget",e.BUSINESS_COMPONENTS="business_components",e.GROUP="group",e.REPEAT_GROUP="repeat_group"}(t.CONTROL_ELEMENT_ENUM||(t.CONTROL_ELEMENT_ENUM={})),function(e){e.NAME="标题",e.TAG="标识符",e.PLACEHOLDER="占位符",e.CONTENT="内容",e.EXTENSION_DATA="拓展属性",e.ALLOW_MULTILINE="设置是否允许换行",e.HTML_PASTE="HTML内容",e.LOCK_EDIT="限制编辑",e.IMAGE_SOURCE="图片数据源"}(t.WIDGET_SETTING_ENUM||(t.WIDGET_SETTING_ENUM={})),t.BASE_WIDGET_CONTROL_MAP=((n={})[o.SINGLE_LINE_TEXT]=i.TEXT,n[o.MULTILINE_TEXT]=i.TEXT,n[o.IMAGE]=i.IMAGE,n[o.HTML]=i.RTF_CONTENT,n),t.BASE_WIDGET_IDS=((r={})[o.SINGLE_LINE_TEXT]="baseSingleLineTextWidgetId",r[o.MULTILINE_TEXT]="baseMultiLineTextWidgetId",r[o.IMAGE]="baseImageWidgetId",r[o.HTML]="baseHTMLWidgetId",r),function(e){e.WIDGET_HTML_FIGMENT="widget_html_figment"}(t.EXTENSION_DATA||(t.EXTENSION_DATA={}))},532:function(e,t){var n=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function a(e){try{c(r.next(e))}catch(e){o(e)}}function s(e){try{c(r.throw(e))}catch(e){o(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}c((r=r.apply(e,t||[])).next())}))},r=this&&this.__generator||function(e,t){var n,r,i,o,a={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function s(o){return function(s){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return a.label++,{value:o[1],done:!1};case 5:a.label++,r=o[1],o=[0];continue;case 7:o=a.ops.pop(),a.trys.pop();continue;default:if(!(i=a.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){a=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]0)&&!(r=o.next()).done;)a.push(r.value)}catch(e){i={error:e}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return a},o=this&&this.__spreadArray||function(e,t,n){if(n||2===arguments.length)for(var r,i=0,o=t.length;i { + app.component(comp.name || comp.displayName, comp); + }); + // Space.Compact 是子组件,AntDesignVueResolver 无法自动解析,需手动注册 + app.component('ASpaceCompact', Space.Compact); + + //仪表盘依赖Tinymce,需要提前加载(没办法按需加载了) + // app.component(Editor.name, Editor); + // 代码逻辑说明: Tinymce异步加载 + // update-begin--author:liaozhiyang---date:20260227---for:【QQYUN-14751】tinymce富文本、JEasyCron、JLinkTableCard异步加载 + app.component( + 'Tinymce', + createAsyncComponent(() => import('./Tinymce/src/Editor.vue'), { + loading: true, + }) + ); + // update-end--author:liaozhiyang---date:20260227---for:【QQYUN-14751】tinymce富文本、JEasyCron、JLinkTableCard异步加载 + app + .use(registerOnlineComp) + .use(Button) + console.log("---初始化---, 全局注册Antd、仪表盘、流程设计器、online、流程等组件--------------") +} diff --git a/src/components/wordtpl/DocDesign.ts b/src/components/wordtpl/DocDesign.ts new file mode 100644 index 0000000..b96e2ee --- /dev/null +++ b/src/components/wordtpl/DocDesign.ts @@ -0,0 +1,1134 @@ +import Editor from './canvas-editor.js'; +import { reactive, ref } from 'vue'; +import { debounce } from "lodash-es"; +import { getFileAccessHttpUrl } from "@/utils/common/compUtils"; + +let instance: any = null; + +const formState = reactive({ + family: '微软雅黑', + size: '小四', + name: 'word在线编辑', + title: '正文', + highlightColor: '#ffffff00', + showCataLogDom: true, + separatorColor: '#000000', + separator: '0,0', + separatorLineWidth: 1, +}); + +const isApple = typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent); + +//搜索结果的Dom元素 +const searchCollapseDom = ref(); +const searchInputDom = ref(); +const searchResultDom = ref(); +//替换文本的Dom元素 +const replaceInputDom = ref(); + +/** + * 创建编辑器 + */ +export async function createEditor(content: any) { + let querySelector = document.querySelector('.jeecg__editor'); + // 初始化编辑器 + const editorData = { + header: content.header ? JSON.parse(content.header) : [], + main: content.main ? JSON.parse(content.main) : [], + footer: content.footer ? JSON.parse(content.footer) : [], + }; + formState.name = content.name || 'word在线编辑'; + instance = new Editor(querySelector, editorData, { scrollContainerSelector: ".editor-container", pageNumber: { format: "第{pageNo}页/共{pageCount}页"} }); + settingPaper(content); + const contextMenuList = await instance.register.getContextMenuList(); + // 修改内部右键菜单示例 + //另存为图片是base64位的,所以禁用掉 + contextMenuList.forEach((menu) => { + // 通过菜单key找到菜单项后进行属性修改 + if (menu.key === 'imageChange') { + menu.when = () => false; + } + }); + // 全屏 + const fullscreenDom:any = document.querySelector('.fullscreen') + fullscreenDom.onclick = toggleFullscreen + window.addEventListener('keydown', (evt) => { + if (evt.key === 'F11') { + toggleFullscreen(); + evt.preventDefault(); + } + }); + document.addEventListener('fullscreenchange', () => { + fullscreenDom.classList.toggle('exist') + }) + function toggleFullscreen() { + if (!document.fullscreenElement) { + document.documentElement.requestFullscreen(); + } else { + document.exitFullscreen(); + } + } + //添加监听 + addListener(); + //添加可见页发生改变监听 + addPageNoListListener(); + //添加监听当前页数发生改变监听 + addPageSizeListener(); + //添加当前页发生改变监听 + addIntersectionPageNoListener(); + //当前页面缩放比例发生改变监听 + addPageScaleListener(); + //页面模式发生改变监听 + addPageModeListener(); + //监听字数 + addWordCountListener(); +} + +/** + * 添加监听 + */ +function addListener() { + //字体 + const fontDom: any = document.querySelector('.jeecg-menu-item-font'); + const fontSelectDom = fontDom.querySelector('.select'); + const fontOptionDom = fontDom.querySelector('.options'); + //字体大小 + const jeecgSizeDom: any = document.querySelector('.jeecg-menu-item-size'); + const sizeOptionDom = jeecgSizeDom.querySelector('.options'); + //下划线 + const underlineDom: any = document.querySelector('.jeecg-menu-item-underline'); + //加粗 + const boldDom: any = document.querySelector('.jeecg-menu-item-bold'); + //倾斜 + const italicDom: any = document.querySelector('.jeecg-menu-item-italic'); + //删除线 + const strikeoutDom: any = document.querySelector('.jeecg-menu-item-strikeout'); + //上标 + const subscriptDom: any = document.querySelector('.jeecg-menu-item-subscript'); + //下标 + const superscriptDom: any = document.querySelector('.jeecg-menu-item-superscript'); + //颜色 + const colorControlDom:any = document.querySelector('#color'); + const colorDom:any = document.querySelector('.jeecg-menu-item-color'); + const colorSpanDom:any = colorDom.querySelector('span'); + //左对齐 + const leftDom:any = document.querySelector('.jeecg-menu-item-left'); + //居中对齐 + const centerDom:any = document.querySelector('.jeecg-menu-item-center'); + //右对齐 + const rightDom:any = document.querySelector('.jeecg-menu-item-right'); + //两端对齐 + const alignmentDom:any = document.querySelector('.jeecg-menu-item-alignment'); + //行间距 + const rowMarginDom:any = document.querySelector('.jeecg-menu-item-row-margin'); + const rowOptionDom = rowMarginDom.querySelector('.options') + //高亮颜色 + const highlightDom:any = document.querySelector('.jeecg-menu-item-highlight'); + const highlightSpanDom = highlightDom.querySelector('span'); + const highlightControlDom:any = document.querySelector('#highlight'); + //标题 + const titleDom:any = document.querySelector('.jeecg-menu-item-title') + const titleOptionDom = titleDom.querySelector('.options'); + //列表 + const listDom:any = document.querySelector('.jeecg-menu-item-list'); + const listOptionDom = listDom.querySelector('.options'); + //分割线 + const separatorDom:any = document.querySelector('.jeecg-menu-item-separator'); + const separatorOptionDom = separatorDom.querySelector('.options'); + //搜索查询 + searchCollapseDom.value = document.querySelector('.jeecg-menu-item-search-collapse'); + searchResultDom.value = searchCollapseDom.value.querySelector('.search-result'); + replaceInputDom.value = document.querySelector('.jeecg-menu-item-search-collapse-replace input') + searchInputDom.value = document.querySelector('.jeecg-menu-item-search-collapse-search input') + // 内部事件监听 + instance.listener.rangeStyleChange = function (payload) { + let undoDom: any = document.querySelector('.jeecg-menu-item-undo'); + // 撤销 + if (undoDom && undoDom.classList) { + payload.undo ? undoDom.classList.remove('no-allow') : undoDom.classList.add('no-allow'); + } + // 重做 + let redoDom: any = document.querySelector('.jeecg-menu-item-redo'); + if (redoDom && redoDom.classList) { + payload.redo ? redoDom.classList.remove('no-allow') : redoDom.classList.add('no-allow'); + } + let painterDom = document.querySelector('.jeecg-menu-item-painter'); + // 格式刷 + if (painterDom && painterDom.classList) { + payload.painter ? painterDom.classList.add('active') : painterDom.classList.remove('active'); + } + //字体 + fontOptionDom.querySelectorAll('li').forEach((li) => li.classList.remove('active')); + const curFontDom = fontOptionDom.querySelector(`[data-family='${payload.font}']`); + if (curFontDom) { + formState.family = curFontDom.innerText; + fontSelectDom.style.fontFamily = payload.font; + curFontDom.classList.add('active'); + } + + //字体大小 + sizeOptionDom.querySelectorAll('li').forEach((li) => li.classList.remove('active')); + const curSizeDom = sizeOptionDom.querySelector(`[data-size='${payload.size}']`); + if (curSizeDom) { + formState.size = curSizeDom.innerText; + curSizeDom.classList.add('active'); + } else { + formState.size = `${payload.size}`; + } + //加粗 + if (boldDom && boldDom.classList) { + payload.bold ? boldDom.classList.add('active') : boldDom.classList.remove('active'); + } + //倾斜 + if (italicDom && italicDom.classList) { + payload.italic ? italicDom.classList.add('active') : italicDom.classList.remove('active'); + } + + //下划线 + if (underlineDom && underlineDom.classList) { + payload.underline ? underlineDom.classList.add('active') : underlineDom.classList.remove('active'); + } + //删除线 + if (strikeoutDom && strikeoutDom.classList) { + payload.strikeout ? strikeoutDom.classList.add('active') : strikeoutDom.classList.remove('active'); + } + //上标 + payload.type === 'superscript' ? superscriptDom.classList.add('active') : superscriptDom.classList.remove('active'); + + //下标 + payload.type === 'subscript' ? subscriptDom.classList.add('active') : subscriptDom.classList.remove('active'); + + //字体颜色 + if (colorDom && colorDom.classList) { + if (payload.color) { + colorDom.classList.add('active'); + colorControlDom.value = payload.color; + colorSpanDom.style.backgroundColor = payload.color; + } else { + colorDom.classList.remove('active'); + colorControlDom.value = '#000000'; + colorSpanDom.style.backgroundColor = '#000000'; + } + } + + //对齐方式 + leftDom.classList.remove('active') + centerDom.classList.remove('active') + rightDom.classList.remove('active') + alignmentDom.classList.remove('active') + if (payload.rowFlex && payload.rowFlex === 'right') { + rightDom.classList.add('active') + } else if (payload.rowFlex && payload.rowFlex === 'center') { + centerDom.classList.add('active') + } else if (payload.rowFlex && payload.rowFlex === 'alignment') { + alignmentDom.classList.add('active') + } else { + leftDom.classList.add('active') + } + //行间距 + rowOptionDom.querySelectorAll('li').forEach(li => li.classList.remove('active')) + const curRowMarginDom = rowOptionDom.querySelector( + `[data-rowmargin='${payload.rowMargin}']` + ) + curRowMarginDom.classList.add('active') + + //高亮颜色 + if (payload.highlight) { + highlightDom.classList.add('active') + formState.highlightColor = payload.highlight + highlightControlDom.value = payload.highlight + highlightSpanDom.style.backgroundColor = payload.highlight + } else { + highlightDom.classList.remove('active') + formState.highlightColor = '#ffffff00'; + highlightControlDom.value = '#ffffff00'; + highlightSpanDom.style.backgroundColor = '#ffffff00' + } + + // 标题 + titleOptionDom.querySelectorAll('li') .forEach(li => li.classList.remove('active')); + if (payload.level) { + const curTitleDom = titleOptionDom.querySelector(`[data-level='${payload.level}']` ); + formState.title = curTitleDom.innerText; + curTitleDom.classList.add('active'); + } else { + formState.title = '正文'; + titleOptionDom.querySelector('li:first-child').classList.add('active'); + } + + //列表 + listOptionDom.querySelectorAll('li') .forEach(li => li.classList.remove('active')); + if (payload.listType) { + listDom.classList.add('active') + const listType = payload.listType + const listStyle = payload.listType === 'ol' ? 'decimal' :payload.listStyle; + const curListDom = listOptionDom.querySelector( `[data-list-type='${listType}'][data-list-style='${listStyle}']`); + if (curListDom) { + curListDom.classList.add('active'); + } + } else { + listDom.classList.remove('active'); + } + //分割线 + separatorOptionDom.querySelectorAll('li') .forEach(li => li.classList.remove('active')) + if (payload.type === 'separator') { + const separator = payload.dashArray.join(',') || '0,0' + formState.separator = separator; + const curSeparatorDom = separatorOptionDom.querySelector(`[data-separator='${separator}']`) + if (curSeparatorDom) { + curSeparatorDom.classList.add('active'); + } + if(payload.color) { + formState.separatorColor = payload.color; + } else { + formState.separatorColor = "#000000"; + } + if(payload.lineWidth) { + formState.separatorLineWidth = payload.lineWidth; + } else { + formState.separatorLineWidth = 1; + } + } + }; +} + +/** + * 可见页发生改变 + */ +function addPageNoListListener() { + instance.listener.visiblePageNoListChange = function (payload) { + const text = payload.map(i => i + 1).join('、'); + let querySelector:any = document.querySelector('.page-no-list'); + querySelector.innerText = text + } +} + +/** + * 当前页数发生改变 + */ +function addPageSizeListener(){ + instance.listener.pageSizeChange = function (payload) { + let querySelector:any = document.querySelector('.page-size'); + querySelector!.innerText = `${payload}` + } +} + +/** + * 添加当前页发生改变 + */ +function addIntersectionPageNoListener(){ + instance.listener.intersectionPageNoChange = function (payload) { + let querySelector:any = document.querySelector('.page-no'); + querySelector.innerText = `${ payload + 1 }` + } +} + +/** + * 当前页面缩放比例发生改变 + */ +function addPageScaleListener(){ + instance.listener.pageScaleChange = function (payload) { + let querySelector:any = document.querySelector('.page-scale-percentage'); + querySelector.innerText = `${Math.floor(payload * 10 * 10)}%` + } +} + +/** + * 页面模式发生改变 + */ +function addPageModeListener(){ + const pageModeDom:any = document.querySelector('.page-mode') + const pageModeOptionsDom = pageModeDom.querySelector('.options') + instance.listener.pageModeChange = function (payload) { + const activeMode = pageModeOptionsDom.querySelector( + `[data-page-mode='${payload}']` + ) + pageModeOptionsDom.querySelectorAll('li').forEach(li => li.classList.remove('active')) + activeMode.classList.add('active') + } +} + +/** + * 字数 + */ +const handleContentChange = async function () { + // 字数 + const wordCount = await instance.command.getWordCount(); + let querySelector:any = document.querySelector('.word-count'); + querySelector.innerText = `${ wordCount || 0 }`; + if(formState.showCataLogDom){ + updateCatalog(); + } +} + +/** + * 监听字数 + */ +async function addWordCountListener(){ + instance.listener.contentChange = debounce(handleContentChange,200); +} + +/** + * 设置纸张 + * @param content + */ +function settingPaper(content) { + if(content.width && content.height){ + //设置纸张大小并回显 + instance.command.executePaperSize(content.width,content.height); + const paperSizeDom:any = document.querySelector('.paper-size'); + const paperSizeDomOptionsDom = paperSizeDom.querySelector('.options') + let pagers = paperSizeDomOptionsDom.querySelectorAll('li'); + for (let index = 0; index < pagers.length; index++) { + let element = pagers[index]; + element.classList.remove('active') + if(element.dataset.paperSize == (content.height+"*"+content.width)||element.dataset.paperSize == (content.width+"*"+content.height)) { + element.classList.add('active') + } + } + //设置纸张方向并回显 + if(content.paperDirection){ + let pageDirection = content.paperDirection; + instance.command.executePaperDirection(pageDirection); + const paperDirectionDom:any = document.querySelector('.paper-direction') + const paperDirectionDomOptionsDom = paperDirectionDom.querySelector('.options') + let pagerDirections = paperDirectionDomOptionsDom.querySelectorAll('li') + for (let index = 0; index < pagerDirections.length; index++) { + let element = pagerDirections[index]; + element.classList.remove('active') + if(element.dataset.paperDirection == pageDirection){ + element.classList.add('active') + } + } + } + //设置水印 + let watermark = content.watermark; + if(watermark){ + let watermarkObj = JSON.parse(watermark); + instance.command.executeAddWatermark(watermarkObj) + } + //设置边距 + let margins = content.margins; + if(margins && margins != "[]"){ + margins = JSON.parse(margins); + instance.command.executeSetPaperMargin([ + Number(margins[0]), + Number(margins[1]), + Number(margins[2]), + Number(margins[3]) + ]) + } + } +} + +/** + * 更新日志 + */ +async function updateCatalog() { + const catalog = await instance.command.getCatalog() + const catalogMainDom:any = document.querySelector('.jeecg-catalog-main') + catalogMainDom.innerHTML = '' + if (catalog) { + const appendCatalog = ( + parent, + catalogItems + ) => { + for (let c = 0; c < catalogItems.length; c++) { + const catalogItem = catalogItems[c] + const catalogItemDom = document.createElement('div') + catalogItemDom.classList.add('catalog-item') + // 渲染 + const catalogItemContentDom = document.createElement('div') + catalogItemContentDom.classList.add('jeecg-catalog-item-content') + const catalogItemContentSpanDom = document.createElement('span') + catalogItemContentSpanDom.innerText = catalogItem.name + catalogItemContentDom.append(catalogItemContentSpanDom) + // 定位 + catalogItemContentDom.onclick = () => { + instance.command.executeLocationCatalog(catalogItem.id) + } + catalogItemDom.append(catalogItemContentDom) + if (catalogItem.subCatalog && catalogItem.subCatalog.length) { + appendCatalog(catalogItemDom, catalogItem.subCatalog) + } + // 追加 + parent.append(catalogItemDom) + } + } + appendCatalog(catalogMainDom, catalog) + } +} + +const SURROGATE_PAIR_REG = /[\uD800-\uDBFF][\uDC00-\uDFFF]/ // unicode代理对(surrogate pair) +const EMOJI_REG = + /[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26D3\uFE0F?(?:\u200D\uD83D\uDCA5)?|\u26F9(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF43\uDF45-\uDF4A\uDF4C-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDF44(?:\u200D\uD83D\uDFEB)?|\uDF4B(?:\u200D\uD83D\uDFE9)?|\uDFC3(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4\uDEB5](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE41\uDE43\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC08(?:\u200D\u2B1B)?|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC26(?:\u200D(?:\u2B1B|\uD83D\uDD25))?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE])))?))?|\uDC6F(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDD75(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?|\uDE42(?:\u200D[\u2194\u2195]\uFE0F?)?|\uDEB6(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE88\uDE90-\uDEBD\uDEBF-\uDEC2\uDECE-\uDEDB\uDEE0-\uDEE8]|\uDD3C(?:\u200D[\u2640\u2642]\uFE0F?|\uD83C[\uDFFB-\uDFFF])?|\uDDCE(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1|\uDDD1\u200D\uD83E\uDDD2(?:\u200D\uD83E\uDDD2)?|\uDDD2(?:\u200D\uD83E\uDDD2)?))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)/g + +/** + * 分割文本 + * @param text + */ +function splitText(text){ + const UNICODE_SYMBOL_REG = new RegExp( + `${EMOJI_REG.source}|${SURROGATE_PAIR_REG.source}`, + 'g' + ) + const data:any = [] + const symbolMap:any = new Map(); + for (const match of text.matchAll(UNICODE_SYMBOL_REG)) { + symbolMap.set(match.index, match[0]) + } + let t = 0 + while (t < text.length) { + const symbol = symbolMap.get(t) + if (symbol) { + data.push(symbol) + t += symbol.length + } else { + data.push(text[t]) + t++ + } + } + return data +} + +/** + * 方法 + */ +export function method() { + /** + * 清除格式 + */ + function clearFormat() { + instance.command.executeFormat(); + } + + /** + * 撤销 + */ + function undo() { + instance.command.executeUndo(); + } + + /** + * 重做 + */ + function redo() { + instance.command.executeRedo(); + } + + let isFirstClick = true; + let painterTimeout = 0; + + /** + * 格式刷(单击) + */ + function painterClick() { + if (isFirstClick) { + isFirstClick = false; + painterTimeout = window.setTimeout(() => { + isFirstClick = true; + instance.command.executePainter({ + isDblclick: false, + }); + }, 200); + } else { + window.clearTimeout(painterTimeout); + } + } + + /** + * 格式刷双击 + */ + function painterDblClick() { + isFirstClick = true; + window.clearTimeout(painterTimeout); + instance.command.executePainter({ + isDblclick: true, + }); + } + + /** + * 字体 + * @param family + * @param name + */ + function font(family, name) { + formState.family = name; + instance.command.executeFont(family); + } + + /** + * 字体大小 + * @param size + * @param name + */ + function fontSize(size, name) { + formState.size = name; + instance.command.executeSize(Number(size)); + } + + /** + * 字体+ + */ + function sizeAdd() { + instance.command.executeSizeAdd(); + } + + /** + * 字体- + */ + function sizeMinus() { + instance.command.executeSizeMinus(); + } + + /** + * 加粗 + */ + function blob() { + instance.command.executeBold(); + } + + /** + * 倾斜 + */ + function itailc() { + instance.command.executeItalic(); + } + + /** + * 下划线 + * @param value + */ + function underline(value) { + //下划线 + const underlineDom: any = document.querySelector('.jeecg-menu-item-underline'); + const underlineOptionDom = underlineDom.querySelector('.options'); + if(underlineOptionDom && underlineOptionDom.classList){ + underlineOptionDom.classList.remove('visible'); + } + if (value) { + instance.command.executeUnderline({ + style: value, + }); + } else { + instance.command.executeUnderline({ + style: '', + }); + } + } + + /** + * 删除线 + */ + function strikeout() { + instance.command.executeStrikeout(); + } + + /** + * 上标 + */ + function superscript() { + instance.command.executeSuperscript(); + } + + /** + * 下标 + */ + function subscript() { + instance.command.executeSubscript(); + } + + /** + * 颜色 + * @param e + */ + function color(e) { + instance.command.executeColor(e.target.value); + } + + /** + * 单元格背景色 + * @param e + */ + function cellColor(e) { + instance.command.executeTableTdBackgroundColor(e.target.value); + } + + /** + * 连页分页 + * @param value + */ + function pageMode(value) { + instance.command.executePageMode(value) + } + + /** + * 放大 + */ + function scaleAdd() { + instance.command.executePageScaleAdd(); + } + + /** + * 缩小 + */ + function scaleMiuns() { + instance.command.executePageScaleMinus(); + } + + /** + * 纸张大小 + * @param value + */ + function paperSize(value) { + const [width, height] = value.split('*').map(Number); + instance.command.executePaperSize(width, height); + // 纸张状态回显 + const paperSizeDom:any = document.querySelector('.paper-size'); + const paperSizeDomOptionsDom = paperSizeDom.querySelector('.options'); + paperSizeDomOptionsDom.querySelectorAll('li').forEach(child => child.classList.remove('active')); + let querySelector:any = paperSizeDomOptionsDom.querySelector( `[data-paper-size='${value}']` ); + querySelector.classList.add('active') + } + + /** + * 纸张方向 + */ + function paperDirection(value) { + instance.command.executePaperDirection(value); + // 纸张方向状态回显 + const paperDirectionDom:any = document.querySelector('.paper-direction'); + const paperDirectionDomOptionsDom = paperDirectionDom.querySelector('.options'); + paperDirectionDomOptionsDom.querySelectorAll('li').forEach(child => child.classList.remove('active')); + let querySelector:any = paperDirectionDomOptionsDom.querySelector( `[data-paper-direction='${value}']`); + querySelector.classList.add('active'); + } + + /** + * 打印 + */ + function printTemplate() { + instance.command.executePrint(); + } + + /** + * 获取边距 + */ + function getPaperMargin() { + const [topMargin, rightMargin, bottomMargin, leftMargin] = instance.command.getPaperMargin(); + return { + marginTop: topMargin, + marginBottom: bottomMargin, + marginLeft: leftMargin, + marginRight: rightMargin, + } + } + + /** + * 设置边距 + * @param values + */ + function setPaperMargin(values) { + instance.command.executeSetPaperMargin([ + values.marginTop, + values.marginRight, + values.marginBottom, + values.marginLeft + ]) + } + + /** + * 设置图片 + * @param url + */ + function setWordImage(url) { + let options = instance.command.getOptions(); + let paperDirection = options.paperDirection; + let pageWidth = options.width; + let pageHeight = options.height; + if(paperDirection == "horizontal"){ + pageWidth = options.height; + pageHeight = options.width; + } + const img = new Image(); + img.src =url; + img.onload = () => { + let width = img.width || 0; + let height = img.height || 0; + width = width>pageWidth?pageWidth:width; + height = height>pageHeight?pageHeight:height; + instance.command.executeImage({ + value: url, + width: width, + height: height + }) + }; + } + + /** + * 文件上传赋值内容 + */ + async function setWordContent(content) { + if(content){ + let main = JSON.parse(content.main); + //设置可访问的image + main = await setViewImage(main,'import'); + instance.command.executeSetValue({ + "header": JSON.parse(content.header) || [], + "main": main, + "footer": JSON.parse(content.footer) || [] + }); + settingPaper(content); + } + } + + /** + * 设置可访问的image + * @param main + * @param type + */ + function setViewImage(main,type) { + for (const item of main) { + if(item.type === 'image'){ + if(type === 'original'){ + if(item['oldValue']){ + item.value = item['oldValue']; + } + }else{ + item['oldValue'] = item.value; + item.value = getFileAccessHttpUrl(item.value); + } + } + if(item.trList && item.trList.length>0){ + for (const tr of item.trList) { + if(tr.tdList && tr.tdList.length>0){ + let tdList = tr.tdList; + for (const td of tdList) { + let tdValue = td.value; + if(tdValue && tdValue.length>0){ + for (const itemValue of tdValue) { + if(itemValue.type === 'image'){ + if(type === 'original'){ + if(itemValue.oldValue){ + itemValue.value = itemValue['oldValue']; + } + }else{ + itemValue['oldValue'] = itemValue.value; + itemValue.value = getFileAccessHttpUrl(itemValue.value); + } + } + } + } + } + } + } + } + } + return main; + } + + /** + * 左对齐 + */ + function leftAlign() { + instance.command.executeRowFlex("left"); + } + + /** + * 居中对齐 + */ + function centerAlign() { + instance.command.executeRowFlex("center"); + } + + /** + * 右对齐 + */ + function rightAlign() { + instance.command.executeRowFlex("right"); + } + + /** + * 两端对齐 + */ + function alignmentAlign() { + instance.command.executeRowFlex("alignment"); + } + + /** + * 行间距 + * @param value + */ + function rowMargin(value) { + instance.command.executeRowMargin(Number(value)); + } + + /** + * 分页符 + */ + function pageBreak() { + instance.command.executePageBreak(); + } + + /** + * 设置高亮颜色 + * @param value + */ + function setHighlightColor(value) { + let highlightColor = value.highlightColor; + if(highlightColor) { + instance.command.executeHighlight(highlightColor) + } + } + + /** + * 标题 + * @param value + */ + function title(value) { + instance.command.executeTitle(value || null); + } + + /** + * 列表 + * @param listType + * @param listStyle + */ + function list(listType,listStyle) { + instance.command.executeList(listType, listStyle); + } + + /** + * 表格 + * @param rowIndex + * @param colIndex + * @param recoveryTable + */ + function tabCell(rowIndex,colIndex,recoveryTable) { + instance.command.executeInsertTable(rowIndex, colIndex); + recoveryTable(); + } + + /** + * 分割线 + * @param value + */ + function separator() { + let payload:any = []; + const separatorDash = formState.separator?formState.separator?.split(',').map(Number): "0,0".split(',').map(Number); + if (separatorDash) { + const isSingleLine = separatorDash.every(d => d === 0) + if (!isSingleLine) { + payload = separatorDash + } + } + instance.command.executeSeparator(payload,formState.separatorLineWidth,formState.separatorColor); + } + + /** + * 超链接 + * @param value + */ + function setHyperlink(value) { + instance.command.executeHyperlink({ + type: 'hyperlink', + value: '', + url:value.url, + valueList: splitText(value.name).map(n => ({ + value: n, + size: 16 + })) + }) + } + + /** + * 获取超链接 + */ + function getHyperlink() { + let rangeText = instance.command.getRangeText(); + return { + name: rangeText, + url: '' + } + } + + /** + * 设置水印 + * @param watermark + */ + function setWatermark(watermark) { + const repeat = watermark.repeat === '1'; + instance.command.executeAddWatermark({ + data: watermark.data, + color: watermark.color, + size: watermark.size, + opacity: watermark.opacity, + repeat, + gap: + repeat && watermark.horizontalGap && watermark.verticalGap + ? [ + watermark.horizontalGap, + watermark.verticalGap + ] + : undefined + }); + } + + /** + * 删除水印 + */ + function deleteWatermarkClick() { + instance.command.executeDeleteWatermark(); + } + + /** + * 查询文本输入事件 + * @param evt + */ + function searchInput(evt) { + let value = evt.target.value; + instance.command.executeSearch(value || null); + setSearchResult(); + } + + /** + * 查询文本键盘事件 + * @param evt + */ + function searchKeyDown(evt) { + if (evt.key === 'Enter') { + let value = evt.target.value; + instance.command.executeSearch(value || null); + setSearchResult(); + } + } + + /** + * 上一个 + */ + function arrowLeft() { + instance.command.executeSearchNavigatePre(); + setSearchResult(); + } + + /** + * 下一个 + */ + function arrowRight() { + instance.command.executeSearchNavigateNext(); + setSearchResult(); + } + + /** + * 替换 + */ + function replaceText() { + console.log("searchResultDom.value:::",searchResultDom.value) + console.log("replaceInputDom.value:::",replaceInputDom.value) + const searchValue = searchInputDom.value.value; + const replaceValue = replaceInputDom.value.value; + if (searchValue && replaceValue && searchValue !== replaceValue) { + instance.command.executeReplace(replaceValue); + } + } + + /** + * 关闭搜索 + */ + function closeSearch() { + searchCollapseDom.value.style.display = 'none'; + replaceInputDom.value.value = ''; + searchInputDom.value.value = ''; + instance.command.executeSearch(null); + setSearchResult(); + } + + /** + * 设置搜索结果 + */ + function setSearchResult() { + const result = instance.command.getSearchNavigateInfo(); + if (result) { + const { index, count } = result; + searchResultDom.value.innerText = `${index}/${count}`; + } else { + searchResultDom.value.innerText = ''; + } + } + + /** + * 保存 + */ + function save() { + let tplSettings = instance.command.getValue(); + let options = instance.command.getOptions(); + let paperDirection = options.paperDirection; + let paperMargin = instance.command.getPaperMargin(); + tplSettings.data.main = setViewImage(tplSettings.data.main,'original'); + return { + header: JSON.stringify(tplSettings.data.header), + main: JSON.stringify(tplSettings.data.main), + footer: JSON.stringify(tplSettings.data.footer), + paperDirection: paperDirection, + watermark: JSON.stringify(tplSettings.options.watermark), + margins: JSON.stringify(paperMargin), + height: options.height, + width: options.width, + }; + } + + return { + formState, + clearFormat, + undo, + redo, + isApple, + painterClick, + painterDblClick, + font, + fontSize, + sizeAdd, + sizeMinus, + blob, + itailc, + underline, + strikeout, + superscript, + subscript, + color, + save, + pageMode, + scaleAdd, + scaleMiuns, + paperSize, + paperDirection, + printTemplate, + getPaperMargin, + setPaperMargin, + updateCatalog, + setWordImage, + setWordContent, + leftAlign, + centerAlign, + rightAlign, + alignmentAlign, + rowMargin, + pageBreak, + cellColor, + setHighlightColor, + title, + list, + tabCell, + separator, + setHyperlink, + getHyperlink, + setWatermark, + deleteWatermarkClick, + arrowLeft, + arrowRight, + searchInput, + searchKeyDown, + replaceText, + closeSearch, + }; +} diff --git a/src/components/wordtpl/DocDesign.vue b/src/components/wordtpl/DocDesign.vue new file mode 100644 index 0000000..b1766cd --- /dev/null +++ b/src/components/wordtpl/DocDesign.vue @@ -0,0 +1,1050 @@ + + + + + diff --git a/src/components/wordtpl/canvas-editor.js b/src/components/wordtpl/canvas-editor.js new file mode 100644 index 0000000..b902dbc --- /dev/null +++ b/src/components/wordtpl/canvas-editor.js @@ -0,0 +1,20683 @@ +(function(){"use strict";try{if(typeof document!="undefined"){var e=document.createElement("style");e.id="canvas-editor-style",e.appendChild(document.createTextNode('.ce-select-control-popup{max-width:160px;min-width:69px;max-height:225px;position:absolute;z-index:1;border:1px solid #e4e7ed;border-radius:4px;background-color:#fff;box-shadow:0 2px 12px #0000001a;box-sizing:border-box;margin:5px 0;overflow-y:auto}.ce-select-control-popup ul{list-style:none;padding:3px 0;margin:0;box-sizing:border-box}.ce-select-control-popup ul li{font-size:13px;padding:0 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#666;height:36px;line-height:36px;box-sizing:border-box;cursor:pointer}.ce-select-control-popup ul li:hover{background-color:#eef2fd}.ce-select-control-popup ul li.active{color:var(--COLOR-HOVER, #5175f4);font-weight:700}.ce-date-container{display:none;width:300px;overflow:hidden;left:0;right:0;position:absolute;z-index:1;color:#606266;background:#ffffff;border-radius:4px;padding:10px;user-select:none;border:1px solid #e4e7ed;box-shadow:0 2px 12px #0000001a}.ce-date-container.active{display:block}.ce-date-wrap{display:none}.ce-date-wrap.active{display:block}.ce-date-title{display:flex;justify-content:center;align-items:center;text-align:center;color:#606266;font-size:16px}.ce-date-title>span{display:inline-block}.ce-date-title>span:not(.ce-date-title__now){font-family:cursive;cursor:pointer}.ce-date-title>span:not(.ce-date-title__now):hover{color:#5175f4}.ce-date-title .ce-date-title__pre-year,.ce-date-title .ce-date-title__pre-month{width:15%}.ce-date-title .ce-date-title__now{width:40%}.ce-date-title .ce-date-title__next-year,.ce-date-title .ce-date-title__next-month{width:15%}.ce-date-week{width:100%;display:flex;justify-content:center;margin-top:15px;padding-bottom:5px;border-bottom:1px solid #e4e7ed}.ce-date-week>span{list-style:none;width:calc(100%/7);text-align:center;color:#606266;font-size:14px}.ce-date-day{width:100%;display:flex;flex-wrap:wrap;align-items:center;margin-top:5px}.ce-date-day>div{width:calc(100%/7);height:40px;text-align:center;color:#606266;font-size:14px;cursor:pointer;line-height:40px;border-radius:4px}.ce-date-day>div:hover{color:#5175f4;opacity:.8}.ce-date-day>div.active{color:#5175f4;font-weight:700}.ce-date-day>div.disable{color:#c0c4cc}.ce-date-day>div.select{color:#fff;background-color:#5175f4}.ce-time-wrap{display:none;padding:10px;height:286px}.ce-time-wrap ::-webkit-scrollbar{width:0}.ce-time-wrap.active{display:flex}.ce-time-wrap li{list-style:none}.ce-time-wrap>li{width:33.3%;height:100%;text-align:center}.ce-time-wrap>li>span{transform:translateY(-5px);display:inline-block}.ce-time-wrap>li>ol{height:calc(100% - 20px);overflow-y:auto;border:1px solid #e2e2e2;position:relative}.ce-time-wrap>li:first-child>ol{border-right:0}.ce-time-wrap>li:last-child>ol{border-left:0}.ce-time-wrap>li>ol>li{line-height:30px;cursor:pointer;transition:all .3s}.ce-time-wrap>li>ol>li:hover{background-color:#eaeaea}.ce-time-wrap>li>ol>li.active{color:#fff;background:#5175F4}.ce-date-menu{width:100%;height:28px;display:flex;justify-content:flex-end;align-items:center;padding-top:10px;position:relative;border-top:1px solid #e4e7ed}.ce-date-menu button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #dcdfe6;color:#606266;appearance:none;text-align:center;box-sizing:border-box;outline:none;transition:.1s;font-weight:500;user-select:none;padding:7px 15px;font-size:12px;border-radius:3px;margin:0 0 0 10px}.ce-date-menu button:hover{color:#5175f4;border-color:#5175f4}.ce-date-menu button.ce-date-menu__time{border:1px solid transparent;position:absolute;left:0;margin-left:0}.ce-date-menu button.ce-date-menu__time:hover{color:#5175f4}.ce-block-item{position:absolute;z-index:0;overflow:hidden;border-radius:8px;background-color:#fff;border:1px solid rgb(235 236 240)}.ce-table-tool__row{position:absolute;width:12px;border-radius:6.5px;overflow:hidden;background-color:#e2e6ed}.ce-table-tool__row .ce-table-tool__row__item{width:100%;position:relative}.ce-table-tool__row .ce-table-tool__row__item:after{content:"";position:absolute;bottom:0;left:2px;width:8px;height:1px;background-color:#c0c6cf}.ce-table-tool__row .ce-table-tool__row__item:last-child:after{display:none}.ce-table-tool__quick__add{width:16px;height:16px;position:absolute;border-radius:50%;background-color:#e2e6ed;cursor:pointer}.ce-table-tool__quick__add:after{content:"+";color:#fff;position:absolute;top:50%;left:50%;transform:translate(-50%,-55%)}.ce-table-tool__select{width:16px;height:18px;position:absolute;border-radius:3px;cursor:pointer}.ce-table-tool__select:hover{background-color:#e2e6ed}.ce-table-tool__select:after{content:":::";color:#aaaaab;position:absolute;top:50%;left:50%;transform:translate(-75%,-50%) rotate(-90deg)}.ce-table-tool__col{position:absolute;height:12px;border-radius:6.5px;overflow:hidden;background-color:#e2e6ed;display:flex}.ce-table-tool__col .ce-table-tool__col__item{height:100%;position:relative}.ce-table-tool__col .ce-table-tool__col__item:after{content:"";position:absolute;top:2px;left:-1px;width:1px;height:8px;z-index:1;background-color:#c0c6cf}.ce-table-tool__col .ce-table-tool__col__item:first-child:after{display:none}.ce-table-tool__row .ce-table-tool__row__item.active,.ce-table-tool__col .ce-table-tool__col__item.active{background-color:#c4d7fa}.ce-table-tool__col .ce-table-tool__anchor{right:-5px;width:10px;height:12px;z-index:9;position:absolute;cursor:col-resize}.ce-table-tool__row .ce-table-tool__anchor{bottom:-5px;left:0;width:12px;height:10px;z-index:9;position:absolute;cursor:row-resize}.ce-table-anchor__line{z-index:9;position:absolute;border:1px dotted #000000}.ce-table-tool__border{position:absolute;z-index:1;background:transparent;pointer-events:none}.ce-table-tool__border__row{position:absolute;cursor:row-resize;pointer-events:auto}.ce-table-tool__border__col{position:absolute;cursor:col-resize;pointer-events:auto}.ce-resizer-selection{position:absolute;border:1px solid;pointer-events:none}.ce-resizer-selection .resizer-handle{position:absolute;z-index:9;width:10px;height:10px;box-shadow:0 1px 4px #0000004d;border-radius:5px;border:2px solid #ffffff;box-sizing:border-box;pointer-events:initial}.ce-resizer-selection .handle-0{cursor:nw-resize}.ce-resizer-selection .handle-1{cursor:n-resize}.ce-resizer-selection .handle-2{cursor:ne-resize}.ce-resizer-selection .handle-3{cursor:e-resize}.ce-resizer-selection .handle-4{cursor:se-resize}.ce-resizer-selection .handle-5{cursor:s-resize}.ce-resizer-selection .handle-6{cursor:sw-resize}.ce-resizer-selection .handle-7{cursor:w-resize}.ce-resizer-size-view{display:flex;align-items:center;height:20px;white-space:nowrap;position:absolute;z-index:9;top:-30px;left:0;opacity:.9;background-color:#000;padding:0 5px;border-radius:4px}.ce-resizer-size-view span{color:#fff;font-size:12px}.ce-resizer-image{position:absolute;opacity:.5}.ce-image-previewer{position:fixed;left:0;top:0;z-index:1000;width:100%;height:100%;overflow:hidden;background:#f2f4f7;display:flex;align-items:center;justify-content:center;animation:previewerAnimation .3s}@keyframes previewerAnimation{0%{opacity:.1}to{opacity:1}}.ce-image-previewer .image-close{width:24px;height:24px;display:inline-block;position:absolute;right:50px;top:30px;z-index:99;cursor:pointer;background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIzLjk3IDdsMS40MTUgMS40MTQtNy43NzkgNy43NzggNy43NzkgNy43NzktMS40MTQgMS40MTQtNy43NzktNy43NzktNy43NzggNy43NzlMNyAyMy45N2w3Ljc3OC03Ljc3OUw3IDguNDE0IDguNDE0IDdsNy43NzggNy43NzhMMjMuOTcxIDd6IiBmaWxsPSIjM0Q0NzU3IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=) no-repeat;background-size:100% 100%;transition:all .3s;border-radius:50%}.ce-image-previewer .image-close:hover{background-color:#e2e6ed}.ce-image-previewer .ce-image-container{position:relative}.ce-image-previewer .ce-image-container img{cursor:move;position:relative}.ce-image-previewer .ce-image-menu{height:50px;position:absolute;bottom:50px;z-index:99;display:flex;align-items:center;justify-content:center}.ce-image-previewer .ce-image-menu i{width:32px;height:32px;margin:0 8px;cursor:pointer;display:inline-block;background-repeat:no-repeat;background-size:100% 100%;transition:all .3s;border-radius:50%}.ce-image-previewer .ce-image-menu i:hover{background-color:#e2e6ed}.ce-image-previewer .ce-image-menu i.zoom-in{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE0IDE0di00aDJ2NGg0djJoLTR2NGgtMnYtNGgtNHYtMmg0em04Ljc0OSAxMC4xNjNBMTEuOTUyIDExLjk1MiAwIDAxMTUgMjdDOC4zNzMgMjcgMyAyMS42MjcgMyAxNVM4LjM3MyAzIDE1IDNzMTIgNS4zNzMgMTIgMTJjMCAyLjk1NC0xLjA2NyA1LjY1OC0yLjgzNyA3Ljc0OWw0LjkwOCA0LjkwOC0xLjQxNCAxLjQxNC00LjkwOC00LjkwOHpNMTUgMjVjNS41MjMgMCAxMC00LjQ3NyAxMC0xMFMyMC41MjMgNSAxNSA1IDUgOS40NzcgNSAxNXM0LjQ3NyAxMCAxMCAxMHoiIGZpbGw9IiMzRDQ3NTciLz48L3N2Zz4=)}.ce-image-previewer .ce-image-menu i.zoom-out{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIyLjc0OSAyNC4xNjNBMTEuOTUyIDExLjk1MiAwIDAxMTUgMjdDOC4zNzMgMjcgMyAyMS42MjcgMyAxNVM4LjM3MyAzIDE1IDNzMTIgNS4zNzMgMTIgMTJjMCAyLjk1NC0xLjA2NyA1LjY1OC0yLjgzNyA3Ljc0OWw0LjkwOCA0LjkwOC0xLjQxNCAxLjQxNC00LjkwOC00LjkwOHpNMTUgMjVjNS41MjMgMCAxMC00LjQ3NyAxMC0xMFMyMC41MjMgNSAxNSA1IDUgOS40NzcgNSAxNXM0LjQ3NyAxMCAxMCAxMHptLTUtMTFoMTB2MkgxMHYtMnoiIGZpbGw9IiMzRDQ3NTciLz48L3N2Zz4=)}.ce-image-previewer .ce-image-menu i.rotate{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iIzNENDc1NyIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNMTYgNGM2LjYyNyAwIDEyIDUuMzczIDEyIDEyYTExLjk3IDExLjk3IDAgMDEtNCA4Ljk0NFYyM2gtLjg2QTkuOTY4IDkuOTY4IDAgMDAyNiAxNmMwLTUuNTIzLTQuNDc3LTEwLTEwLTEwUzYgMTAuNDc3IDYgMTZjMCA1LjE4NSAzLjk0NyA5LjQ0OSA5IDkuOTV2Mi4wMDlDOC44NCAyNy40NTEgNCAyMi4yOTEgNCAxNiA0IDkuMzczIDkuMzczIDQgMTYgNHoiIGZpbGwtcnVsZT0ibm9uemVybyIvPjxwYXRoIGQ9Ik0xOS44NzkgMjcuMzI4bDEuNzY3LTYuNzE3IDQuOTUgNC45NXoiLz48L2c+PC9zdmc+)}.ce-image-previewer .ce-image-menu i.original-size{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTQgNGgyNHYyNEg0VjR6bTIgMnYyMGgyMFY2SDZ6bTQgNWgydjEwaC0yVjExem01IDJoMnYyaC0ydi0yem0wIDRoMnYyaC0ydi0yem01LTZoMnYxMGgtMlYxMXoiIGZpbGw9IiMzRDQ3NTciLz48L3N2Zz4=)}.ce-image-previewer .ce-image-menu i.image-download{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTQuNSAxNXYzLjVoMTVWMTVIMjF2NUgzdi01aDEuNXptOC4yMzItMTEuMjI2djkuMTk2bDQuMDUtNC4wNSAxLjA2IDEuMDYtNS44MzQgNS44MzQtNS44MzMtNS44MzMgMS4wNi0xLjA2IDMuOTk4IDMuOTk2VjMuNzc0aDEuNXoiIGZpbGw9IiMzRDQ3NTciLz48L3N2Zz4=)}.ce-contextmenu-container{z-index:9;position:fixed;display:none;padding:4px;overflow-x:hidden;overflow-y:auto;background:#fff;box-shadow:0 2px 12px #38383833;border:1px solid #e2e6ed;border-radius:2px}.ce-contextmenu-content{display:flex;flex-direction:column}.ce-contextmenu-content .ce-contextmenu-sub-item:after{position:absolute;content:"";width:16px;height:16px;right:12px;background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNMCAwaDE2djE2SDB6Ii8+PGcgZmlsbD0iIzc2N0M4NSI+PHBhdGggZD0iTTcgMTIuMjQzbC0uNzA3LS43MDcgNC4yNDMtNC4yNDMuNzA3LjcwN3oiLz48cGF0aCBkPSJNNi4yOTMgNC40NjRMNyAzLjc1NyAxMS4yNDMgOGwtLjcwNy43MDd6Ii8+PC9nPjwvZz48L3N2Zz4=)}.ce-contextmenu-content .ce-contextmenu-item{min-width:140px;padding:0 32px 0 16px;height:30px;display:flex;align-items:center;white-space:nowrap;box-sizing:border-box;cursor:pointer}.ce-contextmenu-content .ce-contextmenu-item.hover{background:rgba(25,55,88,.04)}.ce-contextmenu-content .ce-contextmenu-item span{max-width:300px;font-size:12px;color:#3d4757;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ce-contextmenu-content .ce-contextmenu-item span.ce-shortcut{color:#767c85;height:30px;flex:1;text-align:right;line-height:30px;margin-left:20px}.ce-contextmenu-content .ce-contextmenu-item i{width:16px;height:16px;vertical-align:middle;display:inline-block;background-repeat:no-repeat;background-size:100% 100%;flex-shrink:0;margin-right:8px}.ce-contextmenu-divider{background-color:#e2e6ed;margin:4px 16px;height:1px}.ce-contextmenu-print{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjM0Q0NzU3IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0xMiA0aC0xVjJINXYySDRWMmExIDEgMCAwMTEtMWg2YTEgMSAwIDAxMSAxdjJ6bTAgNXY0YTEgMSAwIDAxLTEgMUg1YTEgMSAwIDAxLTEtMVY5aDF2NGg2VjloMXoiLz48cGF0aCBkPSJNMTIgMTJ2LTFoMlY1SDJ2NmgydjFIMmExIDEgMCAwMS0xLTFWNWExIDEgMCAwMTEtMWgxMmExIDEgMCAwMTEgMXY2YTEgMSAwIDAxLTEgMWgtMnoiLz48cGF0aCBkPSJNMyA4aDEwdjFIM3ptOC0yaDJ2MWgtMnoiLz48L2c+PC9zdmc+)}.ce-contextmenu-image{background-image:url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSLlm77lsYJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4PSIwIiB5PSIwIiB2aWV3Qm94PSIwIDAgMTYgMTYiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxzdHlsZT4uc3Qwe2ZpbGw6IzNkNDc1N308L3N0eWxlPjxnIGlkPSJfeDMwXzAt5YWs5YWxX3gyRl8wMuW3peWFt+agj194MkZf5o+S5YWl5Zu+54mHLTE2cHgtIj48ZyBpZD0iR3JvdXAtMTkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEgMSkiPjxwYXRoIGlkPSJDb21iaW5lZC1TaGFwZSIgY2xhc3M9InN0MCIgZD0iTTEgMGgxMmMuNiAwIDEgLjQgMSAxdjExYzAgLjYtLjQgMS0xIDFIMWMtLjYgMC0xLS40LTEtMVYxYzAtLjYuNC0xIDEtMXptMCAxdjExaDEyVjFIMXoiLz48Y2lyY2xlIGlkPSLmpK3lnIblvaIiIGNsYXNzPSJzdDAiIGN4PSIxMCIgY3k9IjQiIHI9IjEiLz48cGF0aCBpZD0iUGF0aCIgY2xhc3M9InN0MCIgZD0iTTguNSAxMS4ybC00LTQuMUwxIDEwLjdWOS4yYzEuNy0xLjYgMi43LTIuNSAzLTIuOC40LS41LjctLjQgMSAwTDguNSAxMCAxMSA3LjNjLjQtLjUuNi0uNSAxLS4xbDIgMi44djEuNWwtMi41LTMuNC0zIDMuMXoiLz48L2c+PC9nPjwvc3ZnPg==)}.ce-contextmenu-image-change{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyIDQpIiBmaWxsPSIjM0Q0NzU3Ij48Y2lyY2xlIGZpbGwtcnVsZT0ibm9uemVybyIgY3g9IjMiIGN5PSIxIiByPSIxIi8+PHBhdGggZD0iTTcuNDczIDguMjIzTDMuNDcgNC4xMDcgMCA3LjY2N3YtMS41QzEuNzE1IDQuNiAyLjcwNyAzLjY2NCAyLjk3NSAzLjM1OGMuNDAyLS40NTcuNjUxLS4zOSAxLjA0MiAwTDcuNDczIDcgOS45NiA0LjM0OWMuNDE0LS40NjIuNjItLjQ2MiAxLjAxMS0uMDcxTDEzIDcuMDZ2MS41bC0yLjUxLTMuNDEtMy4wMTcgMy4wNzJ6Ii8+PC9nPjxwYXRoIGQ9Ik02IDEuNUgxLjV2MTJoMTN2LTRWMTNhLjUuNSAwIDAxLS41LjVIMmEuNS41IDAgMDEtLjUtLjVWMmEuNS41IDAgMDEuNS0uNWg0em04LjUgOFY2bC0uNS41aDFsLS41LS41djMuNXpNNiAxLjVoNEw5LjUgMXYxbC41LS41SDZ6IiBzdHJva2U9IiMzRDQ3NTciLz48cGF0aCBkPSJNMTMuMDg1IDEuMzE2bC0zLjgxNCA0YTEgMSAwIDAwMS40NTggMS4zNjhsMy44MTUtNGExIDEgMCAxMC0xLjQ1OS0xLjM2OHoiIGZpbGw9IiMzRDQ3NTciIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvZz48L3N2Zz4=)}.ce-contextmenu-insert-row-col{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBzdHJva2U9IiMzRDQ3NTciIGQ9Ik04LjUgNS41aDZ2NGgtNnoiLz48cGF0aCBmaWxsPSIjM0Q0NzU3IiBkPSJNNCA3djFoMlY3em0tMyAuNUw0IDV2NXpNMSAxaDEydjFIMXptMCAxMmgxMnYxSDF6Ii8+PC9nPjwvc3ZnPg==)}.ce-contextmenu-insert-top-row{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBmaWxsPSIjM0Q0NzU3IiBkPSJNOCA1SDd2M2gxem0tLjUtM0wxMCA1SDV6Ii8+PHJlY3Qgc3Ryb2tlPSIjM0Q0NzU3IiB4PSIxLjUiIHk9IjEwLjUiIHdpZHRoPSIxMiIgaGVpZ2h0PSIzIiByeD0iMSIvPjwvZz48L3N2Zz4=)}.ce-contextmenu-insert-bottom-row{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBmaWxsPSIjM0Q0NzU3IiBkPSJNNyAxMWgxVjhIN3ptLjUgM0w1IDExaDV6Ii8+PHJlY3Qgc3Ryb2tlPSIjM0Q0NzU3IiB4PSIxLjUiIHk9IjIuNSIgd2lkdGg9IjEyIiBoZWlnaHQ9IjMiIHJ4PSIxIi8+PC9nPjwvc3ZnPg==)}.ce-contextmenu-insert-left-col{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBmaWxsPSIjM0Q0NzU3IiBkPSJNMTEgN3YxaDNWN3ptLTMgLjVMMTEgNXY1eiIvPjxyZWN0IHN0cm9rZT0iIzNENDc1NyIgdHJhbnNmb3JtPSJyb3RhdGUoOTAgNCA3LjUpIiB4PSItMiIgeT0iNiIgd2lkdGg9IjEyIiBoZWlnaHQ9IjMiIHJ4PSIxIi8+PC9nPjwvc3ZnPg==)}.ce-contextmenu-insert-right-col{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBmaWxsPSIjM0Q0NzU3IiBkPSJNNSA4VjdIMnYxem0zLS41TDUgMTBWNXoiLz48cmVjdCBzdHJva2U9IiMzRDQ3NTciIHRyYW5zZm9ybT0icm90YXRlKDkwIDEyIDcuNSkiIHg9IjYiIHk9IjYiIHdpZHRoPSIxMiIgaGVpZ2h0PSIzIiByeD0iMSIvPjwvZz48L3N2Zz4=)}.ce-contextmenu-delete-row-col{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBzdHJva2U9IiM5MjlBQTgiIGQ9Ik04LjUgNi41aDZ2MmgtNnoiLz48cGF0aCBmaWxsPSIjM0Q0NzU3IiBkPSJNMiAxMmgxMXYxSDJ6TTIgMmgxMXYxSDJ6bS42MyAzTDcgOS4zNWwtLjYzNS42NUwyIDUuNjN6Ii8+PHBhdGggZmlsbD0iIzNENDc1NyIgZD0iTTIgOS4zNjNMNi4zNTUgNSA3IDUuNzA3IDIuNjk1IDEweiIvPjwvZz48L3N2Zz4=)}.ce-contextmenu-delete-row{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBzdHJva2U9IiM5MjlBQTgiIGQ9Ik04LjUgNS41aDZ2NGgtNnoiLz48cGF0aCBmaWxsPSIjM0Q0NzU3IiBkPSJNMSAxM2gxMnYxSDF6TTEgMWgxMnYxSDF6bTAgNGgxdjFIMXptMSAxaDF2MUgyem0xIDFoMXYxSDN6bTEtMWgxdjFINHptMS0xaDF2MUg1ek00IDhoMXYxSDR6TTIgOGgxdjFIMnptMyAxaDF2MUg1ek0xIDloMXYxSDF6Ii8+PC9nPjwvc3ZnPg==)}.ce-contextmenu-delete-col{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBzdHJva2U9IiM5MjlBQTgiIGQ9Ik01LjUgNy41di02aDR2NnoiLz48cGF0aCBmaWxsPSIjM0Q0NzU3IiBkPSJNMTMgMTVWM2gxdjEyek0xIDE1VjNoMXYxMnptNCAwdi0xaDF2MXptMS0xdi0xaDF2MXptMS0xdi0xaDF2MXptLTEtMXYtMWgxdjF6bS0xLTF2LTFoMXYxem0zIDF2LTFoMXYxem0wIDJ2LTFoMXYxem0xLTN2LTFoMXYxem0wIDR2LTFoMXYxeiIvPjwvZz48L3N2Zz4=)}.ce-contextmenu-delete-table{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iIzNENDc1NyIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNMTQgMTNoLTF2LTNIMnYzSDF2LTNhMSAxIDAgMDExLTFoMTFhMSAxIDAgMDExIDF2M3oiIGZpbGwtcnVsZT0ibm9uemVybyIvPjxwYXRoIGQ9Ik01LjYyNSAyTDEwIDYuMzc1IDkuMzc1IDcgNSAyLjYyNXoiLz48cGF0aCBkPSJNNSA2LjM3NUw5LjM3NSAybC42MjUuNjI1TDUuNjI1IDd6Ii8+PC9nPjwvc3ZnPg==)}.ce-contextmenu-merge-cell{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iIzNENDc1NyIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNNiAxdjFIMnYxMWg0djFIMmExIDEgMCAwMS0xLTFWMmExIDEgMCAwMTEtMWg0em0zIDBoNGExIDEgMCAwMTEgMXYxMWExIDEgMCAwMS0xIDFIOXYtMWg0VjJIOVYxeiIvPjxwYXRoIGZpbGwtcnVsZT0ibm9uemVybyIgZD0iTTYgMWgxdjRINnptMiAwaDF2NEg4eiIvPjxwYXRoIGQ9Ik04IDcuNUwxMCA2djN6bS0xIDBMNSA2djN6Ii8+PHBhdGggZD0iTTkgN2gzdjFIOXpNMyA3aDN2MUgzeiIvPjxwYXRoIGZpbGwtcnVsZT0ibm9uemVybyIgZD0iTTggMTBoMXY0SDh6bS0yIDBoMXY0SDZ6Ii8+PC9nPjwvc3ZnPg==)}.ce-contextmenu-merge-cancel-cell{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iIzNENDc1NyIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNNiAxdjFIMnYxMWg0djFIMmExIDEgMCAwMS0xLTFWMmExIDEgMCAwMTEtMWg0em0zIDBoNGExIDEgMCAwMTEgMXYxMWExIDEgMCAwMS0xIDFIOXYtMWg0VjJIOVYxeiIvPjxwYXRoIGZpbGwtcnVsZT0ibm9uemVybyIgZD0iTTYgMWgxdjRINnptMiAwaDF2NEg4eiIvPjxwYXRoIGQ9Ik0zIDcuNUw1IDZ2M3ptOSAwTDEwIDZ2M3oiLz48cGF0aCBkPSJNNCA3aDN2MUg0em00IDBoM3YxSDh6Ii8+PHBhdGggZmlsbC1ydWxlPSJub256ZXJvIiBkPSJNOCAxMGgxdjRIOHptLTIgMGgxdjRINnoiLz48L2c+PC9zdmc+)}.ce-contextmenu-vertical-align{background-image:url(data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMiAxM2gxMnYxSDJ6bTAtM2g4djFIMnptMC0zaDEydjFIMnptMC02aDEydjFIMnptMCAzaDh2MUgyeiIgZmlsbD0iIzNkNDc1NyIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9zdmc+)}.ce-contextmenu-vertical-align-top{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTggOEg3djZoMXptLS41LTNMMTAgOEg1ek0yIDNoMTF2MUgyeiIgZmlsbD0iIzNENDc1NyIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9zdmc+)}.ce-contextmenu-vertical-align-middle{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNOCAxMkg3djNoMXptLS41LTNsMi41IDNINXpNNyAzaDFWMEg3em0uNSAzTDUgM2g1ek0yIDdoMTF2MUgyeiIgZmlsbD0iIzNENDc1NyIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9zdmc+)}.ce-contextmenu-vertical-align-bottom{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTcgOWgxVjNIN3ptLjUgM0w1IDloNXpNMiAxM2gxMXYxSDJ6IiBmaWxsPSIjM0Q0NzU3IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=)}.ce-contextmenu-border-all{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuNSAzYS41LjUgMCAwMS41LS41aDExYS41LjUgMCAwMS41LjV2MTFhLjUuNSAwIDAxLS41LjVIM2EuNS41IDAgMDEtLjUtLjVWM3oiIHN0cm9rZT0iIzNENDc1NyIvPjxwYXRoIGZpbGw9IiMzRDQ3NTciIGQ9Ik0zIDhoMTF2MUgzeiIvPjxwYXRoIGZpbGw9IiMzRDQ3NTciIGQ9Ik05IDN2MTFIOFYzeiIvPjwvc3ZnPg==)}.ce-contextmenu-border-empty{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMyAzaC0xVjJoMWExIDEgMCAwMTEgMXYxaC0xVjN6bS0zLTF2MUg4LjV2MmgtMVYzSDZWMmg0ek00IDJ2MUgzdjFIMlYzYTEgMSAwIDAxMS0xaDF6TTIgNmgxdjEuNWgydjFIM1YxMEgyVjZ6bTAgNmgxdjFoMXYxSDNhMSAxIDAgMDEtMS0xdi0xem00IDJ2LTFoMS41di0yaDF2MkgxMHYxSDZ6bTYgMHYtMWgxdi0xaDF2MWExIDEgMCAwMS0xIDFoLTF6bTItNGgtMVY4LjVoLTJ2LTFoMlY2aDF2NHpNOC41IDcuNXYtMWgtMXYxaC0xdjFoMXYxaDF2LTFoMXYtMWgtMXoiIGZpbGw9IiNBQUFDQjAiLz48L3N2Zz4=)}.ce-contextmenu-border-external{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuNSAzYS41LjUgMCAwMS41LS41aDExYS41LjUgMCAwMS41LjV2MTFhLjUuNSAwIDAxLS41LjVIM2EuNS41IDAgMDEtLjUtLjVWM3oiIHN0cm9rZT0iIzNENDc1NyIvPjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOSA1VjNIOHYyaDF6bTAgOXYtMkg4djJoMXpNNSA4SDN2MWgyVjh6bTkgMGgtMnYxaDJWOHpNOSA3djFoMXYxSDl2MUg4VjlIN1Y4aDFWN2gxeiIgZmlsbD0iI0FBQUNCMCIvPjwvc3ZnPg==)}.ce-contextmenu-border-td{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuNSAzYS41LjUgMCAwMS41LS41aDExYS41LjUgMCAwMS41LjV2MTFhLjUuNSAwIDAxLS41LjVIM2EuNS41IDAgMDEtLjUtLjVWM3oiIHN0cm9rZT0iI0FBQUNCMCIvPjxwYXRoIHN0cm9rZT0iIzNENDc1NyIgZD0iTTguNSAyLjUgdjYgaC02Ii8+PC9zdmc+)}.ce-contextmenu-border-td-top{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuNSAzYS41LjUgMCAwMS41LS41aDExYS41LjUgMCAwMS41LjV2MTFhLjUuNSAwIDAxLS41LjVIM2EuNS41IDAgMDEtLjUtLjVWM3oiIHN0cm9rZT0iI0FBQUNCMCIvPjxwYXRoIHN0cm9rZT0iIzNENDc1NyIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNMi41IDMgaDEyIi8+PC9zdmc+)}.ce-contextmenu-border-td-left{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuNSAzYS41LjUgMCAwMS41LS41aDExYS41LjUgMCAwMS41LjV2MTFhLjUuNSAwIDAxLS41LjVIM2EuNS41IDAgMDEtLjUtLjVWM3oiIHN0cm9rZT0iI0FBQUNCMCIvPjxwYXRoIHN0cm9rZT0iIzNENDc1NyIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNMyAzIHYxMSIvPjwvc3ZnPg==)}.ce-contextmenu-border-td-bottom{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuNSAzYS41LjUgMCAwMS41LS41aDExYS41LjUgMCAwMS41LjV2MTFhLjUuNSAwIDAxLS41LjVIM2EuNS41IDAgMDEtLjUtLjVWM3oiIHN0cm9rZT0iI0FBQUNCMCIvPjxwYXRoIHN0cm9rZT0iIzNENDc1NyIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNMi41IDE0IGgxMiIvPjwvc3ZnPg==)}.ce-contextmenu-border-td-right{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuNSAzYS41LjUgMCAwMS41LS41aDExYS41LjUgMCAwMS41LjV2MTFhLjUuNSAwIDAxLS41LjVIM2EuNS41IDAgMDEtLjUtLjVWM3oiIHN0cm9rZT0iI0FBQUNCMCIvPjxwYXRoIHN0cm9rZT0iIzNENDc1NyIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNMTQgMyB2MTEiLz48L3N2Zz4=)}.ce-contextmenu-border-td-forward{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuNSAzYS41LjUgMCAwMS41LS41aDExYS41LjUgMCAwMS41LjV2MTFhLjUuNSAwIDAxLS41LjVIM2EuNS41IDAgMDEtLjUtLjVWM3oiIHN0cm9rZT0iI0FBQUNCMCIgLz48cGF0aCBzdHJva2U9IiMzRDQ3NTciIGQ9Ik0xNCAzIGwtMTEgMTEiIC8+PC9zdmc+)}.ce-contextmenu-border-td-back{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuNSAzYS41LjUgMCAwMS41LS41aDExYS41LjUgMCAwMS41LjV2MTFhLjUuNSAwIDAxLS41LjVIM2EuNS41IDAgMDEtLjUtLjVWM3oiIHN0cm9rZT0iI0FBQUNCMCIgLz48cGF0aCBzdHJva2U9IiMzRDQ3NTciIGQ9Ik0zIDMgbDExIDExIiAvPjwvc3ZnPg==)}.ce-hyperlink-popup{background:#fff;box-shadow:0 2px 12px #626b8433;border-radius:2px;color:#3d4757;padding:12px 16px;position:absolute;z-index:1;text-align:center;display:none}.ce-hyperlink-popup a{min-width:100px;max-width:300px;font-size:12px;display:inline-block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;text-decoration:none;border-bottom-width:1px;border-bottom-style:solid;color:#00f}.ce-zone-indicator>div{padding:3px 6px;color:#000;font-size:12px;background:rgb(218 231 252);position:absolute;transform-origin:0 0}.ce-zone-indicator-border__top,.ce-zone-indicator-border__bottom,.ce-zone-indicator-border__left,.ce-zone-indicator-border__right{display:block;position:absolute;z-index:0}.ce-zone-indicator-border__top{border-top:2px dashed rgb(238,238,238)}.ce-zone-indicator-border__bottom{border-top:2px dashed rgb(238,238,238);width:100%}.ce-zone-indicator-border__left{border-left:2px dashed rgb(238,238,238)}.ce-zone-indicator-border__right{border-right:2px dashed rgb(238,238,238)}.ce-zone-tip{display:none;align-items:center;height:30px;white-space:nowrap;position:fixed;opacity:.9;background-color:#000;padding:0 5px;border-radius:4px;z-index:9;transition:all .3s;outline:none;user-select:none;pointer-events:none;transform:translate(10px,10px)}.ce-zone-tip.show{display:flex}.ce-zone-tip span{color:#fff;font-size:12px}.ce-inputarea{width:100px;height:30px;min-width:0;min-height:0;margin:0;padding:0;left:0;right:0;letter-spacing:0;font-size:12px;position:absolute;z-index:-1;outline:none;resize:none;border:none;overflow:hidden;color:transparent;user-select:none;caret-color:transparent;background-color:transparent}.ce-cursor{width:1px;height:20px;left:0;right:0;position:absolute;outline:none;background-color:#000;pointer-events:none}.ce-cursor.ce-cursor--animation{animation-duration:1s;animation-iteration-count:infinite;animation-name:cursorAnimation}@keyframes cursorAnimation{0%{opacity:1}13%{opacity:0}50%{opacity:0}63%{opacity:1}to{opacity:1}}.ce-float-image{position:absolute;opacity:.5;pointer-events:none}')),document.head.appendChild(e)}}catch(i){console.error("vite-plugin-css-injected-by-js",i)}})(); +var index = ""; +const version = "0.9.94"; +var MaxHeightRatio; +(function(MaxHeightRatio2) { + MaxHeightRatio2["HALF"] = "half"; + MaxHeightRatio2["ONE_THIRD"] = "one-third"; + MaxHeightRatio2["QUARTER"] = "quarter"; +})(MaxHeightRatio || (MaxHeightRatio = {})); +var NumberType; +(function(NumberType2) { + NumberType2["ARABIC"] = "arabic"; + NumberType2["CHINESE"] = "chinese"; +})(NumberType || (NumberType = {})); +var ImageDisplay; +(function(ImageDisplay2) { + ImageDisplay2["INLINE"] = "inline"; + ImageDisplay2["BLOCK"] = "block"; + ImageDisplay2["SURROUND"] = "surround"; + ImageDisplay2["FLOAT_TOP"] = "float-top"; + ImageDisplay2["FLOAT_BOTTOM"] = "float-bottom"; +})(ImageDisplay || (ImageDisplay = {})); +var LocationPosition; +(function(LocationPosition2) { + LocationPosition2["BEFORE"] = "before"; + LocationPosition2["AFTER"] = "after"; +})(LocationPosition || (LocationPosition = {})); +const ZERO = "\u200B"; +const WRAP = "\n"; +const NBSP = " "; +const NON_BREAKING_SPACE = " "; +const PUNCTUATION_LIST = [ + "\xB7", + "\u3001", + ":", + "\uFF1A", + ",", + "\uFF0C", + ".", + "\u3002", + ";", + "\uFF1B", + "?", + "\uFF1F", + "!", + "\uFF01" +]; +const maxHeightRadioMapping = { + [MaxHeightRatio.HALF]: 1 / 2, + [MaxHeightRatio.ONE_THIRD]: 1 / 3, + [MaxHeightRatio.QUARTER]: 1 / 4 +}; +const LETTER_CLASS = { + ENGLISH: "A-Za-z", + SPANISH: "A-Za-z\xC1\xC9\xCD\xD3\xDA\xE1\xE9\xED\xF3\xFA\xD1\xF1\xDC\xFC", + FRENCH: "A-Za-z\xC0\xC2\xC7\xE0\xE2\xE7\xC9\xE9\xC8\xE8\xCA\xEA\xCB\xEB\xCE\xEE\xCF\xEF\xD4\xF4\xD9\xF9\xDB\xFB\u0178\xFF", + GERMAN: "A-Za-z\xC4\xE4\xD6\xF6\xDC\xFC\xDF", + RUSSIAN: "\u0410-\u042F\u0430-\u044F\u0401\u0451", + PORTUGUESE: "A-Za-z\xC1\xC9\xCD\xD3\xDA\xE1\xE9\xED\xF3\xFA\xC3\xD5\xE3\xF5\xC7\xE7", + ITALIAN: "A-Za-z\xC0\xE0\xC8\xE8\xC9\xE9\xCC\xEC\xCD\xED\xCE\xEE\xD3\xF3\xD2\xF2\xD9\xF9", + DUTCH: "A-Za-z\xC0\xE0\xC1\xE1\xC2\xE2\xC4\xE4\xC8\xE8\xC9\xE9\xCA\xEA\xCB\xEB\xCC\xEC\xCD\xED\xCE\xEE\xCF\xEF\xD3\xF3\xD2\xF2\xD4\xF4\xD6\xF6\xD9\xF9\xDB\xFB\xDC\xFC", + SWEDISH: "A-Za-z\xC5\xE5\xC4\xE4\xD6\xF6", + GREEK: "\u0391\u03B1\u0392\u03B2\u0393\u03B3\u0394\u03B4\u0395\u03B5\u0396\u03B6\u0397\u03B7\u0398\u03B8\u0399\u03B9\u039A\u03BA\u039B\u03BB\u039C\u03BC\u039D\u03BD\u039E\u03BE\u039F\u03BF\u03A0\u03C0\u03A1\u03C1\u03A3\u03C3\u03C2\u03A4\u03C4\u03A5\u03C5\u03A6\u03C6\u03A7\u03C7\u03A8\u03C8\u03A9\u03C9" +}; +const METRICS_BASIS_TEXT = "\u65E5"; +var RowFlex; +(function(RowFlex2) { + RowFlex2["LEFT"] = "left"; + RowFlex2["CENTER"] = "center"; + RowFlex2["RIGHT"] = "right"; + RowFlex2["ALIGNMENT"] = "alignment"; + RowFlex2["JUSTIFY"] = "justify"; +})(RowFlex || (RowFlex = {})); +const NUMBER_LIKE_REG = /[0-9.]/; +const SURROGATE_PAIR_REG = /[\uD800-\uDBFF][\uDC00-\uDFFF]/; +const EMOJI_REG = /[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26D3\uFE0F?(?:\u200D\uD83D\uDCA5)?|\u26F9(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF43\uDF45-\uDF4A\uDF4C-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDF44(?:\u200D\uD83D\uDFEB)?|\uDF4B(?:\u200D\uD83D\uDFE9)?|\uDFC3(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4\uDEB5](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE41\uDE43\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC08(?:\u200D\u2B1B)?|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC26(?:\u200D(?:\u2B1B|\uD83D\uDD25))?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE])))?))?|\uDC6F(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDD75(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?|\uDE42(?:\u200D[\u2194\u2195]\uFE0F?)?|\uDEB6(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE88\uDE90-\uDEBD\uDEBF-\uDEC2\uDECE-\uDEDB\uDEE0-\uDEE8]|\uDD3C(?:\u200D[\u2640\u2642]\uFE0F?|\uD83C[\uDFFB-\uDFFF])?|\uDDCE(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1|\uDDD1\u200D\uD83E\uDDD2(?:\u200D\uD83E\uDDD2)?|\uDDD2(?:\u200D\uD83E\uDDD2)?))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)/g; +const UNICODE_SYMBOL_REG = new RegExp(`${EMOJI_REG.source}|${SURROGATE_PAIR_REG.source}`, "g"); +const PUNCTUATION_REG = /[、,。?!;:……「」“”‘’*()【】〔〕〖〗〘〙〚〛《》———﹝﹞–—\\/·.,!?;:`~<>()[\]{}'"|]/; +const START_LINE_BREAK_REG = new RegExp(`^[${ZERO} +]`); +function debounce(func, delay) { + let timer; + return function(...args) { + if (timer) { + window.clearTimeout(timer); + } + timer = window.setTimeout(() => { + func.apply(this, args); + }, delay); + }; +} +function throttle(func, delay) { + let lastExecTime = 0; + let timer; + return function(...args) { + const currentTime = Date.now(); + if (currentTime - lastExecTime >= delay) { + window.clearTimeout(timer); + func.apply(this, args); + lastExecTime = currentTime; + } else { + window.clearTimeout(timer); + timer = window.setTimeout(() => { + func.apply(this, args); + lastExecTime = currentTime; + }, delay); + } + }; +} +function deepCloneOmitKeys(obj, omitKeys) { + if (!obj || typeof obj !== "object") { + return obj; + } + let newObj = {}; + if (Array.isArray(obj)) { + newObj = obj.map((item) => deepCloneOmitKeys(item, omitKeys)); + } else { + Object.keys(obj).forEach((key) => { + if (omitKeys.includes(key)) + return; + return newObj[key] = deepCloneOmitKeys(obj[key], omitKeys); + }); + } + return newObj; +} +function deepClone(obj) { + if (!obj || typeof obj !== "object") { + return obj; + } + let newObj = {}; + if (Array.isArray(obj)) { + newObj = obj.map((item) => deepClone(item)); + } else { + Object.keys(obj).forEach((key) => { + return newObj[key] = deepClone(obj[key]); + }); + } + return newObj; +} +function isBody(node) { + return node && node.nodeType === 1 && node.tagName.toLowerCase() === "body"; +} +function findParent(node, filterFn, includeSelf) { + if (node && !isBody(node)) { + node = includeSelf ? node : node.parentNode; + while (node) { + if (!filterFn || filterFn(node) || isBody(node)) { + return filterFn && !filterFn(node) && isBody(node) ? null : node; + } + node = node.parentNode; + } + } + return null; +} +function getUUID() { + function S4() { + return ((1 + Math.random()) * 65536 | 0).toString(16).substring(1); + } + return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4(); +} +function splitText(text) { + const data2 = []; + if (Intl.Segmenter) { + const segmenter = new Intl.Segmenter(); + const segments = segmenter.segment(text); + for (const { segment } of segments) { + data2.push(segment); + } + } else { + const symbolMap = /* @__PURE__ */ new Map(); + for (const match of text.matchAll(UNICODE_SYMBOL_REG)) { + symbolMap.set(match.index, match[0]); + } + let t = 0; + while (t < text.length) { + const symbol = symbolMap.get(t); + if (symbol) { + data2.push(symbol); + t += symbol.length; + } else { + data2.push(text[t]); + t++; + } + } + } + return data2; +} +function downloadFile(href, fileName) { + const a = document.createElement("a"); + a.href = href; + a.download = fileName; + a.click(); +} +function threeClick$1(dom, fn) { + nClickEvent(3, dom, fn); +} +function nClickEvent(n, dom, fn) { + let count = 0; + let lastTime = 0; + const handler = function(evt) { + const currentTime = new Date().getTime(); + count = currentTime - lastTime < 300 ? count + 1 : 0; + lastTime = new Date().getTime(); + if (count >= n - 1) { + fn(evt); + count = 0; + } + }; + dom.addEventListener("click", handler); +} +function isObject(type) { + return Object.prototype.toString.call(type) === "[object Object]"; +} +function isArray(type) { + return Array.isArray(type); +} +function mergeObject(source, target) { + if (isObject(source) && isObject(target)) { + const objectTarget = target; + for (const [key, val] of Object.entries(source)) { + if (!objectTarget[key]) { + objectTarget[key] = val; + } else { + objectTarget[key] = mergeObject(val, objectTarget[key]); + } + } + } else if (isArray(source) && isArray(target)) { + target.push(...source); + } + return target; +} +function nextTick(fn) { + setTimeout(() => { + fn(); + }, 0); +} +function convertNumberToChinese(num) { + const chineseNum = [ + "\u96F6", + "\u4E00", + "\u4E8C", + "\u4E09", + "\u56DB", + "\u4E94", + "\u516D", + "\u4E03", + "\u516B", + "\u4E5D" + ]; + const chineseUnit = [ + "", + "\u5341", + "\u767E", + "\u5343", + "\u4E07", + "\u5341", + "\u767E", + "\u5343", + "\u4EBF", + "\u5341", + "\u767E", + "\u5343", + "\u4E07", + "\u5341", + "\u767E", + "\u5343", + "\u4EBF" + ]; + if (!num || isNaN(num)) + return "\u96F6"; + const numStr = num.toString().split(""); + let result = ""; + for (let i = 0; i < numStr.length; i++) { + const desIndex = numStr.length - 1 - i; + result = `${chineseUnit[i]}${result}`; + result = `${chineseNum[Number(numStr[desIndex])]}${result}`; + } + result = result.replace(/零(千|百|十)/g, "\u96F6").replace(/十零/g, "\u5341"); + result = result.replace(/零+/g, "\u96F6"); + result = result.replace(/零亿/g, "\u4EBF").replace(/零万/g, "\u4E07"); + result = result.replace(/亿万/g, "\u4EBF"); + result = result.replace(/零+$/, ""); + result = result.replace(/^一十/g, "\u5341"); + return result; +} +function cloneProperty(properties, sourceElement, targetElement) { + for (let i = 0; i < properties.length; i++) { + const property = properties[i]; + const value = sourceElement[property]; + if (value !== void 0) { + targetElement[property] = value; + } else { + delete targetElement[property]; + } + } +} +function pickObject(object, pickKeys) { + const newObject = {}; + for (const key in object) { + if (pickKeys.includes(key)) { + newObject[key] = object[key]; + } + } + return newObject; +} +function omitObject(object, omitKeys) { + const newObject = {}; + for (const key in object) { + if (!omitKeys.includes(key)) { + newObject[key] = object[key]; + } + } + return newObject; +} +function convertStringToBase64(input2) { + const encoder = new TextEncoder(); + const data2 = encoder.encode(input2); + const charArray = Array.from(data2, (byte) => String.fromCharCode(byte)); + const base64 = window.btoa(charArray.join("")); + return base64; +} +function findScrollContainer(element) { + let parent = element.parentElement; + while (parent) { + const style = window.getComputedStyle(parent); + const overflowY = style.getPropertyValue("overflow-y"); + if (parent.scrollHeight > parent.clientHeight && (overflowY === "auto" || overflowY === "scroll")) { + return parent; + } + parent = parent.parentElement; + } + return document.documentElement; +} +function isArrayEqual(arr1, arr2) { + if (arr1.length !== arr2.length) { + return false; + } + return !arr1.some((item) => !arr2.includes(item)); +} +function isObjectEqual(obj1, obj2) { + if (!isObject(obj1) || !isObject(obj2)) + return false; + const obj1Keys = Object.keys(obj1); + const obj2Keys = Object.keys(obj2); + if (obj1Keys.length !== obj2Keys.length) { + return false; + } + return !obj1Keys.some((key) => obj2[key] !== obj1[key]); +} +function isRectIntersect(rect1, rect2) { + const rect1Left = rect1.x; + const rect1Right = rect1.x + rect1.width; + const rect1Top = rect1.y; + const rect1Bottom = rect1.y + rect1.height; + const rect2Left = rect2.x; + const rect2Right = rect2.x + rect2.width; + const rect2Top = rect2.y; + const rect2Bottom = rect2.y + rect2.height; + if (rect1Left > rect2Right || rect1Right < rect2Left || rect1Top > rect2Bottom || rect1Bottom < rect2Top) { + return false; + } + return true; +} +const CURSOR_AGENT_OFFSET_HEIGHT = 12; +const defaultCursorOption = { + width: 1, + color: "#000000", + dragWidth: 2, + dragColor: "#0000FF" +}; +const EDITOR_COMPONENT = "editor-component"; +const EDITOR_PREFIX = "ce"; +const EDITOR_CLIPBOARD = `${EDITOR_PREFIX}-clipboard`; +var MoveDirection; +(function(MoveDirection2) { + MoveDirection2["UP"] = "top"; + MoveDirection2["DOWN"] = "down"; + MoveDirection2["LEFT"] = "left"; + MoveDirection2["RIGHT"] = "right"; +})(MoveDirection || (MoveDirection = {})); +var ElementType; +(function(ElementType2) { + ElementType2["TEXT"] = "text"; + ElementType2["IMAGE"] = "image"; + ElementType2["TABLE"] = "table"; + ElementType2["HYPERLINK"] = "hyperlink"; + ElementType2["SUPERSCRIPT"] = "superscript"; + ElementType2["SUBSCRIPT"] = "subscript"; + ElementType2["SEPARATOR"] = "separator"; + ElementType2["PAGE_BREAK"] = "pageBreak"; + ElementType2["CONTROL"] = "control"; + ElementType2["CHECKBOX"] = "checkbox"; + ElementType2["RADIO"] = "radio"; + ElementType2["LATEX"] = "latex"; + ElementType2["TAB"] = "tab"; + ElementType2["DATE"] = "date"; + ElementType2["BLOCK"] = "block"; + ElementType2["TITLE"] = "title"; + ElementType2["LIST"] = "list"; +})(ElementType || (ElementType = {})); +const EDITOR_ELEMENT_STYLE_ATTR = [ + "bold", + "color", + "highlight", + "font", + "size", + "italic", + "underline", + "strikeout", + "textDecoration" +]; +const EDITOR_ROW_ATTR = ["rowFlex", "rowMargin"]; +const EDITOR_ELEMENT_COPY_ATTR = [ + "type", + "font", + "size", + "bold", + "color", + "italic", + "highlight", + "underline", + "strikeout", + "rowFlex", + "url", + "hyperlinkId", + "dateId", + "dateFormat", + "groupIds", + "rowMargin", + "textDecoration" +]; +const EDITOR_ELEMENT_ZIP_ATTR = [ + "type", + "font", + "size", + "bold", + "color", + "lineWidth", + "italic", + "highlight", + "underline", + "strikeout", + "rowFlex", + "rowMargin", + "dashArray", + "trList", + "borderType", + "width", + "height", + "url", + "colgroup", + "valueList", + "control", + "checkbox", + "radio", + "dateFormat", + "block", + "level", + "title", + "listType", + "listStyle", + "listWrap", + "groupIds", + "conceptId", + "imgDisplay", + "imgFloatPosition", + "textDecoration", + "extension", + "externalId" +]; +const TABLE_TD_ZIP_ATTR = [ + "conceptId", + "extension", + "externalId", + "verticalAlign", + "backgroundColor", + "borderTypes", + "slashTypes" +]; +const TABLE_CONTEXT_ATTR = [ + "tdId", + "trId", + "tableId" +]; +const TITLE_CONTEXT_ATTR = [ + "level", + "titleId", + "title" +]; +const LIST_CONTEXT_ATTR = [ + "listId", + "listType", + "listStyle" +]; +const CONTROL_CONTEXT_ATTR = [ + "control", + "controlId", + "controlComponent" +]; +const CONTROL_STYLE_ATTR = [ + "font", + "size", + "bold", + "highlight", + "italic", + "strikeout" +]; +const EDITOR_ELEMENT_CONTEXT_ATTR = [ + ...TABLE_CONTEXT_ATTR, + ...TITLE_CONTEXT_ATTR, + ...LIST_CONTEXT_ATTR +]; +const TEXTLIKE_ELEMENT_TYPE = [ + ElementType.TEXT, + ElementType.HYPERLINK, + ElementType.SUBSCRIPT, + ElementType.SUPERSCRIPT, + ElementType.CONTROL, + ElementType.DATE +]; +const IMAGE_ELEMENT_TYPE = [ + ElementType.IMAGE, + ElementType.LATEX +]; +const BLOCK_ELEMENT_TYPE = [ + ElementType.BLOCK, + ElementType.PAGE_BREAK, + ElementType.SEPARATOR, + ElementType.TABLE +]; +const INLINE_NODE_NAME = ["HR", "TABLE", "UL", "OL"]; +const VIRTUAL_ELEMENT_TYPE = [ + ElementType.TITLE, + ElementType.LIST +]; +class ImageParticle { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + this.container = draw.getContainer(); + this.imageCache = /* @__PURE__ */ new Map(); + this.floatImageContainer = null; + this.floatImage = null; + } + createFloatImage(element) { + const { scale } = this.options; + let floatImageContainer = this.floatImageContainer; + let floatImage = this.floatImage; + if (!floatImageContainer) { + floatImageContainer = document.createElement("div"); + floatImageContainer.classList.add(`${EDITOR_PREFIX}-float-image`); + this.container.append(floatImageContainer); + this.floatImageContainer = floatImageContainer; + } + if (!floatImage) { + floatImage = document.createElement("img"); + floatImageContainer.append(floatImage); + this.floatImage = floatImage; + } + floatImageContainer.style.display = "none"; + floatImage.style.width = `${element.width * scale}px`; + floatImage.style.height = `${element.height * scale}px`; + const height = this.draw.getHeight(); + const pageGap = this.draw.getPageGap(); + const preY = this.draw.getPageNo() * (height + pageGap); + const imgFloatPosition = element.imgFloatPosition; + floatImageContainer.style.left = `${imgFloatPosition.x}px`; + floatImageContainer.style.top = `${preY + imgFloatPosition.y}px`; + floatImage.src = element.value; + } + dragFloatImage(movementX, movementY) { + if (!this.floatImageContainer) + return; + this.floatImageContainer.style.display = "block"; + const x = parseFloat(this.floatImageContainer.style.left) + movementX; + const y = parseFloat(this.floatImageContainer.style.top) + movementY; + this.floatImageContainer.style.left = `${x}px`; + this.floatImageContainer.style.top = `${y}px`; + } + destroyFloatImage() { + if (this.floatImageContainer) { + this.floatImageContainer.style.display = "none"; + } + } + addImageObserver(promise) { + this.draw.getImageObserver().add(promise); + } + getFallbackImage(width, height) { + const tileSize = 8; + const x = (width - Math.ceil(width / tileSize) * tileSize) / 2; + const y = (height - Math.ceil(height / tileSize) * tileSize) / 2; + const svg = ` + + + + + + + + `; + const fallbackImage = new Image(); + fallbackImage.src = `data:image/svg+xml;base64,${convertStringToBase64(svg)}`; + return fallbackImage; + } + render(ctx, element, x, y) { + const { scale } = this.options; + const width = element.width * scale; + const height = element.height * scale; + if (this.imageCache.has(element.id)) { + const img = this.imageCache.get(element.id); + ctx.drawImage(img, x, y, width, height); + } else { + const imageLoadPromise = new Promise((resolve, reject) => { + const img = new Image(); + img.setAttribute("crossOrigin", "Anonymous"); + img.src = element.value; + img.onload = () => { + this.imageCache.set(element.id, img); + resolve(element); + if (element.imgDisplay === ImageDisplay.FLOAT_BOTTOM) { + this.draw.render({ + isCompute: false, + isSetCursor: false, + isSubmitHistory: false + }); + } else { + ctx.drawImage(img, x, y, width, height); + } + }; + img.onerror = (error) => { + const fallbackImage = this.getFallbackImage(width, height); + fallbackImage.onload = () => { + ctx.drawImage(fallbackImage, x, y, width, height); + this.imageCache.set(element.id, fallbackImage); + }; + reject(error); + }; + }); + this.addImageObserver(imageLoadPromise); + } + } +} +const ordR = "R".charCodeAt(0); +function HERSHEY(i) { + if (data[i] == null) { + compile(i); + } + return data[i]; +} +function compile(i) { + const entry = raw[i]; + if (entry == null) { + return; + } + const bound = entry.substring(3, 5); + const xmin = 1 * bound.charCodeAt(0) - ordR; + const xmax = 1 * bound.charCodeAt(1) - ordR; + const content = entry.substring(5); + const polylines = [[]]; + let ymin = Infinity; + let ymax = -Infinity; + let zmin = Infinity; + let zmax = -Infinity; + let j = 0; + while (j < content.length) { + const digit = content.substring(j, j + 2); + if (digit == " R") { + polylines.push([]); + } else { + const x = digit.charCodeAt(0) - ordR - xmin; + const y = digit.charCodeAt(1) - ordR; + ymin = Math.min(y, ymin); + ymax = Math.max(y, ymax); + zmin = Math.min(x, zmin); + zmax = Math.max(x, zmax); + polylines[polylines.length - 1].push([x, y]); + } + j += 2; + } + data[i] = { + w: xmax - xmin, + xmin: zmin, + xmax: zmax, + ymin, + ymax, + polylines + }; +} +const data = {}; +const raw = { + 1: " 9MWRMNV RRMVV RPSTS", + 2: " 16MWOMOV ROMSMUNUPSQ ROQSQURUUSVOV", + 3: " 11MXVNTMRMPNOPOSPURVTVVU", + 4: " 12MWOMOV ROMRMTNUPUSTURVOV", + 5: " 12MWOMOV ROMUM ROQSQ ROVUV", + 6: " 9MVOMOV ROMUM ROQSQ", + 7: " 15MXVNTMRMPNOPOSPURVTVVUVR RSRVR", + 8: " 9MWOMOV RUMUV ROQUQ", + 9: " 3PTRMRV", + 10: " 7NUSMSTRVPVOTOS", + 11: " 9MWOMOV RUMOS RQQUV", + 12: " 6MVOMOV ROVUV", + 13: " 12LXNMNV RNMRV RVMRV RVMVV", + 14: " 9MWOMOV ROMUV RUMUV", + 15: " 14MXRMPNOPOSPURVSVUUVSVPUNSMRM", + 16: " 10MWOMOV ROMSMUNUQSROR", + 17: " 17MXRMPNOPOSPURVSVUUVSVPUNSMRM RSTVW", + 18: " 13MWOMOV ROMSMUNUQSROR RRRUV", + 19: " 13MWUNSMQMONOOPPTRUSUUSVQVOU", + 20: " 6MWRMRV RNMVM", + 21: " 9MXOMOSPURVSVUUVSVM", + 22: " 6MWNMRV RVMRV", + 23: " 12LXNMPV RRMPV RRMTV RVMTV", + 24: " 6MWOMUV RUMOV", + 25: " 7MWNMRQRV RVMRQ", + 26: " 9MWUMOV ROMUM ROVUV", + 27: " 9MWRMNV RRMVV RPSTS", + 28: " 16MWOMOV ROMSMUNUPSQ ROQSQURUUSVOV", + 29: " 6MVOMOV ROMUM", + 30: " 9MWRMNV RRMVV RNVVV", + 31: " 12MWOMOV ROMUM ROQSQ ROVUV", + 32: " 9MWUMOV ROMUM ROVUV", + 33: " 9MWOMOV RUMUV ROQUQ", + 34: " 20MXRMPNOPOSPURVSVUUVSVPUNSMRM RQQTR RTQQR", + 35: " 3PTRMRV", + 36: " 9MWOMOV RUMOS RQQUV", + 37: " 6MWRMNV RRMVV", + 38: " 12LXNMNV RNMRV RVMRV RVMVV", + 39: " 9MWOMOV ROMUV RUMUV", + 40: " 12MWOMUM RPQTR RTQPR ROVUV", + 41: " 14MXRMPNOPOSPURVSVUUVSVPUNSMRM", + 42: " 9MWOMOV RUMUV ROMUM", + 43: " 10MWOMOV ROMSMUNUQSROR", + 44: " 10MWOMRQOV ROMUM ROVUV", + 45: " 6MWRMRV RNMVM", + 46: " 15MWNONNOMPMQNRPRV RVOVNUMTMSNRP", + 47: " 13LXRMRV RPONPNSPTTTVSVPTOPO", + 48: " 6MWOMUV RUMOV", + 49: " 12LXRMRV RNOOPOSQTSTUSUPVO", + 50: " 13MXOVQVOROPPNRMSMUNVPVRTVVV", + 200: " 12MWRMPNOPOSPURVTUUSUPTNRM", + 201: " 4MWPORMRV", + 202: " 9MWONQMSMUNUPTROVUV", + 203: " 15MWONQMSMUNUPSQ RRQSQURUUSVQVOU", + 204: " 7MWSMSV RSMNSVS", + 205: " 14MWPMOQQPRPTQUSTURVQVOU RPMTM", + 206: " 14MWTMRMPNOPOSPURVTUUSTQRPPQOS", + 207: " 6MWUMQV ROMUM", + 208: " 19MWQMONOPQQSQUPUNSMQM RQQOROUQVSVUUURSQ", + 209: " 14MWUPTRRSPROPPNRMTNUPUSTURVPV", + 210: " 6PURURVSVSURU", + 211: " 7PUSVRVRUSUSWRY", + 212: " 12PURPRQSQSPRP RRURVSVSURU", + 213: " 13PURPRQSQSPRP RSVRVRUSUSWRY", + 214: " 12PURMRR RSMSR RRURVSVSURU", + 215: " 17NWPNRMSMUNUPRQRRSRSQUP RRURVSVSURU", + 216: " 3PTRMRQ", + 217: " 6NVPMPQ RTMTQ", + 218: " 10NVQMPNPPQQSQTPTNSMQM", + 219: " 16MWUNSMQMONOPQQTRUSUUSVQVOU RRLRW", + 220: " 3MWVLNW", + 221: " 7OVTLRNQPQSRUTW", + 222: " 7NUPLRNSPSSRUPW", + 223: " 3PTRLRW", + 224: " 3LXNRVR", + 225: " 6LXRNRV RNRVR", + 226: " 6LXNPVP RNTVT", + 227: " 6MWOOUU RUOOU", + 228: " 9MWRORU ROPUT RUPOT", + 229: " 6PURQRRSRSQRQ", + 230: " 7PUSMRORQSQSPRP", + 231: " 7PUSNRNRMSMSORQ", + 232: " 7LXSOVRSU RNRVR", + 233: " 12MXRLPW RULSW ROPVP ROSVS", + 234: " 21LXVRURTSSURVOVNUNSORRQSPSNRMPMONOPQSSUUVVV", + 235: " 20LXNNOQOSNV RVNUQUSVV RNNQOSOVN RNVQUSUVV", + 501: " 9I[RFJ[ RRFZ[ RMTWT", + 502: " 24G\\KFK[ RKFTFWGXHYJYLXNWOTP RKPTPWQXRYTYWXYWZT[K[", + 503: " 19H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZV", + 504: " 16G\\KFK[ RKFRFUGWIXKYNYSXVWXUZR[K[", + 505: " 12H[LFL[ RLFYF RLPTP RL[Y[", + 506: " 9HZLFL[ RLFYF RLPTP", + 507: " 23H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZVZS RUSZS", + 508: " 9G]KFK[ RYFY[ RKPYP", + 509: " 3NVRFR[", + 510: " 11JZVFVVUYTZR[P[NZMYLVLT", + 511: " 9G\\KFK[ RYFKT RPOY[", + 512: " 6HYLFL[ RL[X[", + 513: " 12F^JFJ[ RJFR[ RZFR[ RZFZ[", + 514: " 9G]KFK[ RKFY[ RYFY[", + 515: " 22G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF", + 516: " 14G\\KFK[ RKFTFWGXHYJYMXOWPTQKQ", + 517: " 25G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF RSWY]", + 518: " 17G\\KFK[ RKFTFWGXHYJYLXNWOTPKP RRPY[", + 519: " 21H\\YIWGTFPFMGKIKKLMMNOOUQWRXSYUYXWZT[P[MZKX", + 520: " 6JZRFR[ RKFYF", + 521: " 11G]KFKULXNZQ[S[VZXXYUYF", + 522: " 6I[JFR[ RZFR[", + 523: " 12F^HFM[ RRFM[ RRFW[ R\\FW[", + 524: " 6H\\KFY[ RYFK[", + 525: " 7I[JFRPR[ RZFRP", + 526: " 9H\\YFK[ RKFYF RK[Y[", + 527: " 9I[RFJ[ RRFZ[ RMTWT", + 528: " 24G\\KFK[ RKFTFWGXHYJYLXNWOTP RKPTPWQXRYTYWXYWZT[K[", + 529: " 6HYLFL[ RLFXF", + 530: " 9I[RFJ[ RRFZ[ RJ[Z[", + 531: " 12H[LFL[ RLFYF RLPTP RL[Y[", + 532: " 9H\\YFK[ RKFYF RK[Y[", + 533: " 9G]KFK[ RYFY[ RKPYP", + 534: " 25G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF ROPUP", + 535: " 3NVRFR[", + 536: " 9G\\KFK[ RYFKT RPOY[", + 537: " 6I[RFJ[ RRFZ[", + 538: " 12F^JFJ[ RJFR[ RZFR[ RZFZ[", + 539: " 9G]KFK[ RKFY[ RYFY[", + 540: " 9I[KFYF ROPUP RK[Y[", + 541: " 22G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF", + 542: " 9G]KFK[ RYFY[ RKFYF", + 543: " 14G\\KFK[ RKFTFWGXHYJYMXOWPTQKQ", + 544: " 10I[KFRPK[ RKFYF RK[Y[", + 545: " 6JZRFR[ RKFYF", + 546: " 19I[KKKILGMFOFPGQIRMR[ RYKYIXGWFUFTGSIRM", + 547: " 21H\\RFR[ RPKMLLMKOKRLTMUPVTVWUXTYRYOXMWLTKPK", + 548: " 6H\\KFY[ RK[YF", + 549: " 18G]RFR[ RILJLKMLQMSNTQUSUVTWSXQYMZL[L", + 550: " 17H\\K[O[LTKPKLLINGQFSFVGXIYLYPXTU[Y[", + 551: " 20G[G[IZLWOSSLVFV[UXSUQSNQLQKRKTLVNXQZT[Y[", + 552: " 41F]SHTITLSPRSQUOXMZK[J[IZIWJRKOLMNJPHRGUFXFZG[I[KZMYNWOTP RSPTPWQXRYTYWXYWZU[R[PZOX", + 553: " 24H\\TLTMUNWNYMZKZIYGWFTFQGOIMLLNKRKVLYMZO[Q[TZVXWV", + 554: " 35G^TFRGQIPMOSNVMXKZI[G[FZFXGWIWKXMZP[S[VZXXZT[O[KZHYGWFTFRHRJSMUPWRZT\\U", + 555: " 28H\\VJVKWLYLZKZIYGVFRFOGNINLONPOSPPPMQLRKTKWLYMZP[S[VZXXYV", + 556: " 28H\\RLPLNKMINGQFTFXG[G]F RXGVNTTRXPZN[L[JZIXIVJULUNV RQPZP", + 557: " 29G^G[IZMVPQQNRJRGQFPFOGNINLONQOUOXNYMZKZQYVXXVZS[O[LZJXIVIT", + 558: " 38F^MMKLJJJIKGMFNFPGQIQKPONULYJ[H[GZGX RMRVOXN[L]J^H^G]F\\FZHXLVRUWUZV[W[YZZY\\V", + 559: " 25IZWVUTSQROQLQIRGSFUFVGWIWLVQTVSXQZO[M[KZJXJVKUMUOV", + 560: " 25JYT^R[PVOPOJPGRFTFUGVJVMURR[PaOdNfLgKfKdLaN^P\\SZWX", + 561: " 39F^MMKLJJJIKGMFNFPGQIQKPONULYJ[H[GZGX R^I^G]F\\FZGXIVLTNROPO RROSQSXTZU[V[XZYY[V", + 562: " 29I\\MRORSQVOXMYKYHXFVFUGTISNRSQVPXNZL[J[IZIXJWLWNXQZT[V[YZ[X", + 563: " 45@aEMCLBJBICGEFFFHGIIIKHPGTE[ RGTJLLHMGOFPFRGSISKRPQTO[ RQTTLVHWGYFZF\\G]I]K\\PZWZZ[[\\[^Z_YaV", + 564: " 32E]JMHLGJGIHGJFKFMGNINKMPLTJ[ RLTOLQHRGTFVFXGYIYKXPVWVZW[X[ZZ[Y]V", + 565: " 29H]TFQGOIMLLNKRKVLYMZO[Q[TZVXXUYSZOZKYHXGVFTFRHRKSNUQWSZU\\V", + 566: " 31F_SHTITLSPRSQUOXMZK[J[IZIWJRKOLMNJPHRGUFZF\\G]H^J^M]O\\PZQWQUPTO", + 567: " 32H^ULTNSOQPOPNNNLOIQGTFWFYGZIZMYPWSSWPYNZK[I[HZHXIWKWMXPZS[V[YZ[X", + 568: " 38F_SHTITLSPRSQUOXMZK[J[IZIWJRKOLMNJPHRGUFYF[G\\H]J]M\\O[PYQVQSPTQUSUXVZX[ZZ[Y]V", + 569: " 28H\\H[JZLXOTQQSMTJTGSFRFQGPIPKQMSOVQXSYUYWXYWZT[P[MZKXJVJT", + 570: " 25H[RLPLNKMINGQFTFXG[G]F RXGVNTTRXPZN[L[JZIXIVJULUNV", + 571: " 33E]JMHLGJGIHGJFKFMGNINKMOLRKVKXLZN[P[RZSYUUXMZF RXMWQVWVZW[X[ZZ[Y]V", + 572: " 32F]KMILHJHIIGKFLFNGOIOKNOMRLVLYM[O[QZTWVTXPYMZIZGYFXFWGVIVKWNYP[Q", + 573: " 25C_HMFLEJEIFGHFIFKGLILLK[ RUFK[ RUFS[ RaF_G\\JYNVTS[", + 574: " 36F^NLLLKKKILGNFPFRGSISLQUQXRZT[V[XZYXYVXUVU R]I]G\\FZFXGVITLPUNXLZJ[H[GZGX", + 575: " 38F]KMILHJHIIGKFLFNGOIOKNOMRLVLXMZN[P[RZTXVUWSYM R[FYMVWT]RbPfNgMfMdNaP^S[VY[V", + 576: " 40H]ULTNSOQPOPNNNLOIQGTFWFYGZIZMYPWTTWPZN[K[JZJXKWNWPXQYR[R^QaPcNfLgKfKdLaN^Q[TYZV", + 583: " 9I[JFR[ RZFR[ RJFZF", + 601: " 18I\\XMX[ RXPVNTMQMONMPLSLUMXOZQ[T[VZXX", + 602: " 18H[LFL[ RLPNNPMSMUNWPXSXUWXUZS[P[NZLX", + 603: " 15I[XPVNTMQMONMPLSLUMXOZQ[T[VZXX", + 604: " 18I\\XFX[ RXPVNTMQMONMPLSLUMXOZQ[T[VZXX", + 605: " 18I[LSXSXQWOVNTMQMONMPLSLUMXOZQ[T[VZXX", + 606: " 9MYWFUFSGRJR[ ROMVM", + 607: " 23I\\XMX]W`VaTbQbOa RXPVNTMQMONMPLSLUMXOZQ[T[VZXX", + 608: " 11I\\MFM[ RMQPNRMUMWNXQX[", + 609: " 9NVQFRGSFREQF RRMR[", + 610: " 12MWRFSGTFSERF RSMS^RaPbNb", + 611: " 9IZMFM[ RWMMW RQSX[", + 612: " 3NVRFR[", + 613: " 19CaGMG[ RGQJNLMOMQNRQR[ RRQUNWMZM\\N]Q][", + 614: " 11I\\MMM[ RMQPNRMUMWNXQX[", + 615: " 18I\\QMONMPLSLUMXOZQ[T[VZXXYUYSXPVNTMQM", + 616: " 18H[LMLb RLPNNPMSMUNWPXSXUWXUZS[P[NZLX", + 617: " 18I\\XMXb RXPVNTMQMONMPLSLUMXOZQ[T[VZXX", + 618: " 9KXOMO[ ROSPPRNTMWM", + 619: " 18J[XPWNTMQMNNMPNRPSUTWUXWXXWZT[Q[NZMX", + 620: " 9MYRFRWSZU[W[ ROMVM", + 621: " 11I\\MMMWNZP[S[UZXW RXMX[", + 622: " 6JZLMR[ RXMR[", + 623: " 12G]JMN[ RRMN[ RRMV[ RZMV[", + 624: " 6J[MMX[ RXMM[", + 625: " 10JZLMR[ RXMR[P_NaLbKb", + 626: " 9J[XMM[ RMMXM RM[X[", + 627: " 24H]QMONMPLRKUKXLZN[P[RZUWWTYPZM RQMSMTNUPWXXZY[Z[", + 628: " 31I\\UFSGQIOMNPMTLZKb RUFWFYHYKXMWNUORO RROTPVRWTWWVYUZS[Q[OZNYMV", + 629: " 17I\\JPLNNMOMQNROSRSVR[ RZMYPXRR[P_Ob", + 630: " 24I[TMQMONMPLSLVMYNZP[R[TZVXWUWRVOTMRKQIQGRFTFVGXI", + 631: " 19JZWOVNTMQMONOPPRSS RSSOTMVMXNZP[S[UZWX", + 632: " 23JYTFRGQHQIRJUKXK RXKTMQONRMUMWNYP[S]T_TaSbQbP`", + 633: " 19H\\IQJOLMNMONOPNTL[ RNTPPRNTMVMXOXRWWTb", + 634: " 27G\\HQIOKMMMNNNPMUMXNZO[Q[SZUWVUWRXMXJWGUFSFRHRJSMUPWRZT", + 635: " 9LWRMPTOXOZP[R[TYUW", + 636: " 19I[OMK[ RYNXMWMUNQROSNS RNSPTQUSZT[U[VZ", + 637: " 9JZKFMFOGPHX[ RRML[", + 638: " 21H]OMIb RNQMVMYO[Q[SZUXWT RYMWTVXVZW[Y[[Y\\W", + 639: " 14I[LMOMNSMXL[ RYMXPWRUURXOZL[", + 640: " 29JZTFRGQHQIRJUKXK RUKRLPMOOOQQSTTVT RTTPUNVMXMZO\\S^T_TaRbPb", + 641: " 18J[RMPNNPMSMVNYOZQ[S[UZWXXUXRWOVNTMRM", + 642: " 13G]PML[ RUMVSWXX[ RIPKNNM[M", + 643: " 19I[MSMVNYOZQ[S[UZWXXUXRWOVNTMRMPNNPMSIb", + 644: " 18I][MQMONMPLSLVMYNZP[R[TZVXWUWRVOUNSM", + 645: " 8H\\SMP[ RJPLNOMZM", + 646: " 16H\\IQJOLMNMONOPMVMYO[Q[TZVXXTYPYM", + 647: " 21G]ONMOKQJTJWKYLZN[Q[TZWXYUZRZOXMVMTORSPXMb", + 648: " 14I[KMMMOOU`WbYb RZMYOWRM]K`Jb", + 649: " 20F]VFNb RGQHOJMLMMNMPLULXMZO[Q[TZVXXUZP[M", + 650: " 23F]NMLNJQITIWJZK[M[OZQW RRSQWRZS[U[WZYWZTZQYNXM", + 651: " 22L\\UUTSRRPRNSMTLVLXMZO[Q[SZTXVRUWUZV[W[YZZY\\V", + 652: " 23M[MVOSRNSLTITGSFQGPIOMNTNZO[P[RZTXUUURVVWWYW[V", + 653: " 14MXTTTSSRQROSNTMVMXNZP[S[VYXV", + 654: " 24L\\UUTSRRPRNSMTLVLXMZO[Q[SZTXZF RVRUWUZV[W[YZZY\\V", + 655: " 17NXOYQXRWSUSSRRQROSNUNXOZQ[S[UZVYXV", + 656: " 24OWOVSQUNVLWIWGVFTGSIQQNZKaJdJfKgMfNcOZP[R[TZUYWV", + 657: " 28L[UUTSRRPRNSMTLVLXMZO[Q[SZTY RVRTYPdOfMgLfLdMaP^S\\U[XY[V", + 658: " 29M\\MVOSRNSLTITGSFQGPIOMNSM[ RM[NXOVQSSRURVSVUUXUZV[W[YZZY\\V", + 659: " 16PWSMSNTNTMSM RPVRRPXPZQ[R[TZUYWV", + 660: " 20PWSMSNTNTMSM RPVRRLdKfIgHfHdIaL^O\\Q[TYWV", + 661: " 33M[MVOSRNSLTITGSFQGPIOMNSM[ RM[NXOVQSSRURVSVUTVQV RQVSWTZU[V[XZYY[V", + 662: " 18OWOVQSTNULVIVGUFSGRIQMPTPZQ[R[TZUYWV", + 663: " 33E^EVGSIRJSJTIXH[ RIXJVLSNRPRQSQTPXO[ RPXQVSSURWRXSXUWXWZX[Y[[Z\\Y^V", + 664: " 23J\\JVLSNROSOTNXM[ RNXOVQSSRURVSVUUXUZV[W[YZZY\\V", + 665: " 23LZRRPRNSMTLVLXMZO[Q[SZTYUWUUTSRRQSQURWTXWXYWZV", + 666: " 24KZKVMSNQMUGg RMUNSPRRRTSUUUWTYSZQ[ RMZO[R[UZWYZV", + 667: " 27L[UUTSRRPRNSMTLVLXMZO[Q[SZ RVRUUSZPaOdOfPgRfScS\\U[XY[V", + 668: " 15MZMVOSPQPSSSTTTVSYSZT[U[WZXYZV", + 669: " 16NYNVPSQQQSSVTXTZR[ RNZP[T[VZWYYV", + 670: " 16OXOVQSSO RVFPXPZQ[S[UZVYXV RPNWN", + 671: " 19L[LVNRLXLZM[O[QZSXUU RVRTXTZU[V[XZYY[V", + 672: " 17L[LVNRMWMZN[O[RZTXUUUR RURVVWWYW[V", + 673: " 25I^LRJTIWIYJ[L[NZPX RRRPXPZQ[S[UZWXXUXR RXRYVZW\\W^V", + 674: " 20JZJVLSNRPRQSQZR[U[XYZV RWSVRTRSSOZN[L[KZ", + 675: " 23L[LVNRLXLZM[O[QZSXUU RVRPdOfMgLfLdMaP^S\\U[XY[V", + 676: " 23LZLVNSPRRRTTTVSXQZN[P\\Q^QaPdOfMgLfLdMaP^S\\WYZV", + 677: " 22J\\K[NZQXSVUSWOXKXIWGUFSGRHQJPOPTQXRZT[V[XZYY", + 683: " 26I[WUWRVOUNSMQMONMPLSLVMYNZP[R[TZVXWUXPXKWHVGTFRFPGNI", + 684: " 16JZWNUMRMPNNPMSMVNYOZQ[T[VZ RMTUT", + 685: " 23J[TFRGPJOLNOMTMXNZO[Q[SZUWVUWRXMXIWGVFTF RNPWP", + 686: " 21H\\VFNb RQMNNLPKSKVLXNZQ[S[VZXXYUYRXPVNSMQM", + 687: " 16I[XOWNTMQMNNMOLQLSMUOWSZT\\T^S_Q_", + 700: " 18H\\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF", + 701: " 5H\\NJPISFS[", + 702: " 15H\\LKLJMHNGPFTFVGWHXJXLWNUQK[Y[", + 703: " 16H\\MFXFRNUNWOXPYSYUXXVZS[P[MZLYKW", + 704: " 7H\\UFKTZT RUFU[", + 705: " 18H\\WFMFLOMNPMSMVNXPYSYUXXVZS[P[MZLYKW", + 706: " 24H\\XIWGTFRFOGMJLOLTMXOZR[S[VZXXYUYTXQVOSNRNOOMQLT", + 707: " 6H\\YFO[ RKFYF", + 708: " 30H\\PFMGLILKMMONSOVPXRYTYWXYWZT[P[MZLYKWKTLRNPQOUNWMXKXIWGTFPF", + 709: " 24H\\XMWPURRSQSNRLPKMKLLINGQFRFUGWIXMXRWWUZR[P[MZLX", + 710: " 6MWRYQZR[SZRY", + 711: " 9MWSZR[QZRYSZS\\R^Q_", + 712: " 12MWRMQNROSNRM RRYQZR[SZRY", + 713: " 15MWRMQNROSNRM RSZR[QZRYSZS\\R^Q_", + 714: " 9MWRFRT RRYQZR[SZRY", + 715: " 21I[LKLJMHNGPFTFVGWHXJXLWNVORQRT RRYQZR[SZRY", + 716: " 3NVRFRM", + 717: " 6JZNFNM RVFVM", + 718: " 14KYQFOGNINKOMQNSNUMVKVIUGSFQF", + 719: " 27H\\PBP_ RTBT_ RYIWGTFPFMGKIKKLMMNOOUQWRXSYUYXWZT[P[MZKX", + 720: " 3G][BIb", + 721: " 11KYVBTDRGPKOPOTPYR]T`Vb", + 722: " 11KYNBPDRGTKUPUTTYR]P`Nb", + 723: " 3NVRBRb", + 724: " 3E_IR[R", + 725: " 6E_RIR[ RIR[R", + 726: " 6E_IO[O RIU[U", + 727: " 6G]KKYY RYKKY", + 728: " 9JZRLRX RMOWU RWOMU", + 729: " 6MWRQQRRSSRRQ", + 730: " 8MWSFRGQIQKRLSKRJ", + 731: " 8MWRHQGRFSGSIRKQL", + 732: " 9E_UMXP[RXTUW RIR[R", + 733: " 12H]SBLb RYBRb RLOZO RKUYU", + 734: " 35E_\\O\\N[MZMYNXPVUTXRZP[L[JZIYHWHUISJRQNRMSKSIRGPFNGMIMKNNPQUXWZY[[[\\Z\\Y", + 735: " 28G]IIJKKOKUJYI[ R[IZKYOYUZY[[ RIIKJOKUKYJ[I RI[KZOYUYYZ[[", + 737: " 6KYOBO[ RUBU[", + 738: " 6F^RBR[ RI[[[", + 739: " 4F^[BI[[[", + 740: " 18E_RIQJRKSJRI RIYHZI[JZIY R[YZZ[[\\Z[Y", + 741: " 33F^RHNLKPJSJUKWMXOXQWRU RRHVLYPZSZUYWWXUXSWRU RRUQYP\\ RRUSYT\\ RP\\T\\", + 742: " 26F^RNQKPINHMHKIJKJOKRLTNWR\\ RRNSKTIVHWHYIZKZOYRXTVWR\\", + 743: " 20F^RGPJLOIR RRGTJXO[R RIRLUPZR] R[RXUTZR]", + 744: " 48F^RTTWVXXXZW[U[SZQXPVPSQ RSQUOVMVKUISHQHOINKNMOOQQ RQQNPLPJQISIUJWLXNXPWRT RRTQYP\\ RRTSYT\\ RP\\T\\", + 745: " 55F^RRR[Q\\ RRVQ\\ RRIQHOHNINKONRR RRISHUHVIVKUNRR RRRNOLNJNIOIQJR RRRVOXNZN[O[QZR RRRNULVJVIUISJR RRRVUXVZV[U[SZR", + 746: " 55F^ISJSLTMVMXLZ RISIRJQLQMRNTNWMYLZ RRGPIOLOOQUQXPZR\\ RRGTIULUOSUSXTZR\\ R[S[RZQXQWRVTVWWYXZ R[SZSXTWVWXXZ RKVYV", + 750: " 18PSSRRSQSPRPQQPRPSQSSRUQV RQQQRRRRQQQ", + 751: " 16PTQPPQPSQTSTTSTQSPQP RRQQRRSSRRQ", + 752: " 9NVPOTU RTOPU RNRVR", + 753: " 28MWRKQMOPMR RRKSMUPWR RRMOQ RRMUQ RROPQ RROTQ RQQSQ RMRWR", + 754: " 26MWMRMQNOONQMSMUNVOWQWR RPNTN ROOUO RNPVP RNQVQ RMRWR", + 755: " 14LRLFLRRRLF RLIPQ RLLOR RLOMQ", + 756: " 10MWRKQMOPMR RRKSMUPWR", + 757: " 11MWWRWQVOUNSMQMONNOMQMR", + 758: " 13G]]R]P\\MZJWHTGPGMHJJHMGPGR", + 759: " 11MWMRMSNUOVQWSWUVVUWSWR", + 760: " 7LXLPNRQSSSVRXP", + 761: " 6RURUTTURTPRO", + 762: " 7RVRRUPVNVLUKTK", + 763: " 7NRRROPNNNLOKPK", + 764: " 21MWWHVGTFQFOGNHMJMLNNOOUSVTWVWXVZU[S\\P\\N[MZ", + 765: " 21G]IWHVGTGQHOINKMMMONPOTUUVWWYW[V\\U]S]P\\N[M", + 766: " 31G]RRTUUVWWYW[V\\U]S]Q\\O[NYMWMUNTOPUOVMWKWIVHUGSGQHOINKMMMONPORR", + 767: " 22H\\KFK[ RHF[FQP[Z RZV[Y\\[ RZVZY RWYZY RWYZZ\\[", + 768: " 30KYUARBPCNELHKLKRLUNWQXSXVWXUYR RKPLMNKQJSJVKXMYPYVXZV]T_R`Oa", + 796: " 3>f>RfR", + 797: " 3D`D``D", + 798: " 3RRR>Rf", + 799: " 3D`DD``", + 800: " 3D`DR`R", + 801: " 3F^FY^K", + 802: " 3KYK^YF", + 803: " 3RRRDR`", + 804: " 3KYKFY^", + 805: " 3F^FK^Y", + 806: " 3KYKRYR", + 807: " 3MWMWWM", + 808: " 3RRRKRY", + 809: " 3MWMMWW", + 810: " 8GRRGPGMHJJHMGPGR", + 811: " 8GRGRGTHWJZM\\P]R]", + 812: " 8R]R]T]W\\ZZ\\W]T]R", + 813: " 8R]]R]P\\MZJWHTGRG", + 814: " 9D`DOGQKSPTTTYS]Q`O", + 815: " 9PUUDSGQKPPPTQYS]U`", + 816: " 9OTODQGSKTPTTSYQ]O`", + 817: " 9D`DUGSKQPPTPYQ]S`U", + 818: " 5KYRJYNKVRZ", + 819: " 5JZJRNKVYZR", + 820: " 5KYKVKNYVYN", + 821: " 5JZLXJPZTXL", + 822: " 23JZJ]L]O\\Q[TXUVVSVOULTJSIQIPJOLNONSOVPXS[U\\X]Z]", + 823: " 23I]]Z]X\\U[SXPVOSNONLOJPIQISJTLUOVSVVUXT[Q\\O]L]J", + 824: " 23JZZGXGUHSIPLONNQNUOXPZQ[S[TZUXVUVQUNTLQIOHLGJG", + 825: " 23G[GJGLHOIQLTNUQVUVXUZT[S[QZPXOUNQNNOLPISHUGXGZ", + 826: " 21E[EPFRHTJUMVQVUUXSZP[NZLWLSMQNNPLSKVKYL\\M^", + 827: " 19EYETHVKWPWSVVTXQYNYLXKVKSLPNNQMTMYN\\P_", + 828: " 26OUQOOQOSQUSUUSUQSOQO RQPPQPSQTSTTSTQSPQP RRQQRRSSRRQ", + 829: " 11RWRMSMUNVOWQWSVUUVSWRW", + 830: " 9D`DRJR RORUR RZR`R", + 831: " 5D`DUDO`O`U", + 832: " 6JZRDJR RRDZR", + 833: " 9D`DR`R RJYZY RP`T`", + 834: " 9D`DR`R RDRRb R`RRb", + 840: " 18KYQKNLLNKQKSLVNXQYSYVXXVYSYQXNVLSKQK", + 841: " 6LXLLLXXXXLLL", + 842: " 5KYRJKVYVRJ", + 843: " 6LXRHLRR\\XRRH", + 844: " 12JZRIPOJOOSMYRUWYUSZOTORI", + 845: " 6KYRKRY RKRYR", + 846: " 6MWMMWW RWMMW", + 847: " 9MWRLRX RMOWU RWOMU", + 850: " 35NVQNOONQNSOUQVSVUUVSVQUOSNQN ROQOS RPPPT RQOQU RRORU RSOSU RTPTT RUQUS", + 851: " 27NVNNNVVVVNNN ROOOU RPOPU RQOQU RRORU RSOSU RTOTU RUOUU", + 852: " 17MWRLMUWURL RROOT RROUT RRRQT RRRST", + 853: " 17LULRUWUMLR RORTU RORTO RRRTS RRRTQ", + 854: " 17MWRXWOMORX RRUUP RRUOP RRRSP RRRQP", + 855: " 17OXXROMOWXR RURPO RURPU RRRPQ RRRPS", + 856: " 22LXRLNWXPLPVWRL RRRRL RRRLP RRRNW RRRVW RRRXP", + 857: " 11RYRKRY RRKYNRQ RSMVNSO", + 860: " 13MWRLRX ROOUO RMUOWQXSXUWWU", + 861: " 11LXRLRX RLQMOWOXQ RPWTW", + 862: " 14KYMNWX RWNMX ROLLOKQ RULXOYQ", + 863: " 18I[NII[ RVI[[ RMM[[ RWMI[ RNIVI RMMWM", + 864: " 21I[RGRV RMJWP RWJMP RIVL\\ R[VX\\ RIV[V RL\\X\\", + 865: " 11G[MJSV RKPSL RG\\[\\[RG\\", + 866: " 14LXPLPPLPLTPTPXTXTTXTXPTPTLPL", + 867: " 32KYYPXNVLSKQKNLLNKQKSLVNXQYSYVXXVYT RYPWNUMSMQNPOOQOSPUQVSWUWWVYT", + 868: " 10KYRJKVYVRJ RRZYNKNRZ", + 869: " 34G]PIPGQFSFTGTI RGZHXJVKTLPLKMJOIUIWJXKXPYTZV\\X]Z RGZ]Z RQZP[Q\\S\\T[SZ", + 870: " 64JZRMRS RRSQ\\ RRSS\\ RQ\\S\\ RRMQJPHNG RQJNG RRMSJTHVG RSJVG RRMNKLKJM RPLLLJM RRMVKXKZM RTLXLZM RRMPNOOOR RRMPOOR RRMTNUOUR RRMTOUR", + 871: " 94JZRIRK RRNRP RRSRU RRYQ\\ RRYS\\ RQ\\S\\ RRGQIPJ RRGSITJ RPJRITJ RRKPNNOMN RRKTNVOWN RNOPORNTOVO RRPPSNTLTKRKSLT RRPTSVTXTYRYSXT RNTPTRSTTVT RRUPXOYMZLZKYJWJYLZ RRUTXUYWZXZYYZWZYXZ RMZOZRYUZWZ", + 872: " 40JZRYQ\\ RRYS\\ RQ\\S\\ RRYUZXZZXZUYTWTYRZOYMWLUMVJUHSGQGOHNJOMMLKMJOKRMTKTJUJXLZOZRY", + 873: " 32JZRYQ\\ RRYS\\ RQ\\S\\ RRYVXVVXUXRZQZLYIXHVHTGPGNHLHKIJLJQLRLUNVNXRY", + 874: " 15I[IPKR RLKNP RRGRO RXKVP R[PYR", + 899: " 6QSRQQRRSSRRQ", + 900: " 10PTQPPQPSQTSTTSTQSPQP", + 901: " 14NVQNOONQNSOUQVSVUUVSVQUOSNQN", + 902: " 18MWQMONNOMQMSNUOVQWSWUVVUWSWQVOUNSMQM", + 903: " 18KYQKNLLNKQKSLVNXQYSYVXXVYSYQXNVLSKQK", + 904: " 22G]PGMHJJHMGPGTHWJZM\\P]T]W\\ZZ\\W]T]P\\MZJWHTGPG", + 905: " 34AcPALBJCGEEGCJBLAPATBXCZE]G_JaLbPcTcXbZa]__]aZbXcTcPbLaJ_G]EZCXBTAPA", + 906: " 34fRAPCMDJDGCEA>H@JAMAZB]D_G`M`PaRc RRATCWDZD]C_AfHdJcMcZb]`_]`W`TaRc", + 909: " 33AcRAPCMDJDGCEABGAKAPBTDXG\\L`Rc RRATCWDZD]C_AbGcKcPbT`X]\\X`Rc RBHbH", + 997: " 3MWMXWX", + 998: " 3JZJZZZ", + 999: " 3JZJ]Z]", + 1001: " 18KYRKMX RRNVX RRKWX ROTTT RKXPX RTXYX", + 1002: " 35JZNKNX ROKOX RLKSKVLWNVPSQ RSKULVNUPSQ ROQSQVRWTWUVWSXLX RSQURVTVUUWSX", + 1003: " 24KYVLWKWOVLTKQKOLNMMPMSNVOWQXTXVWWU RQKOMNPNSOVQX", + 1004: " 26JZNKNX ROKOX RLKSKVLWMXPXSWVVWSXLX RSKULVMWPWSVVUWSX", + 1005: " 22JYNKNX ROKOX RSOSS RLKVKVOUK ROQSQ RLXVXVTUX", + 1006: " 20JXNKNX ROKOX RSOSS RLKVKVOUK ROQSQ RLXQX", + 1007: " 36K[VLWKWOVLTKQKOLNMMPMSNVOWQXTXVW RQKOMNPNSOVQX RTXUWVU RVSVX RWSWX RTSYS", + 1008: " 27J[NKNX ROKOX RVKVX RWKWX RLKQK RTKYK ROQVQ RLXQX RTXYX", + 1009: " 12NWRKRX RSKSX RPKUK RPXUX", + 1010: " 19LXSKSURWQX RTKTUSWQXPXNWMUNTOUNV RQKVK", + 1011: " 27JZNKNX ROKOX RWKOS RQQVX RRQWX RLKQK RTKYK RLXQX RTXYX", + 1012: " 14KXOKOX RPKPX RMKRK RMXWXWTVX", + 1013: " 30I\\MKMX RNNRX RNKRU RWKRX RWKWX RXKXX RKKNK RWKZK RKXOX RUXZX", + 1014: " 21JZNKNX ROMVX ROKVV RVKVX RLKOK RTKXK RLXPX", + 1015: " 32KZQKOLNMMPMSNVOWQXTXVWWVXSXPWMVLTKQK RQKOMNPNSOVQX RTXVVWSWPVMTK", + 1016: " 25JYNKNX ROKOX RLKSKVLWNWOVQSROR RSKULVNVOUQSR RLXQX", + 1017: " 47KZQKOLNMMPMSNVOWQXTXVWWVXSXPWMVLTKQK RQKOMNPNSOVQX RTXVVWSWPVMTK RPWPUQTSTTUUZV[W[XZ RTUUXVZW[", + 1018: " 37JZNKNX ROKOX RLKSKVLWNWOVQSROR RSKULVNVOUQSR RLXQX RSRTSUWVXWXXW RSRUSVWWX", + 1019: " 32KZVMWKWOVMULSKQKOLNMNOOPQQTRVSWT RNNOOQPTQVRWSWVVWTXRXPWOVNTNXOV", + 1020: " 16KZRKRX RSKSX RNKMOMKXKXOWK RPXUX", + 1021: " 20J[NKNUOWQXTXVWWUWK ROKOUPWQX RLKQK RUKYK", + 1022: " 15KYMKRX RNKRU RWKRX RKKPK RTKYK", + 1023: " 24I[LKOX RMKOT RRKOX RRKUX RSKUT RXKUX RJKOK RVKZK", + 1024: " 21KZNKVX ROKWX RWKNX RLKQK RTKYK RLXQX RTXYX", + 1025: " 20LYNKRRRX ROKSR RWKSRSX RLKQK RTKYK RPXUX", + 1026: " 16LYVKNX RWKOX ROKNONKWK RNXWXWTVX", + 1027: " 18KYRKMX RRNVX RRKWX ROTTT RKXPX RTXYX", + 1028: " 35JZNKNX ROKOX RLKSKVLWNVPSQ RSKULVNUPSQ ROQSQVRWTWUVWSXLX RSQURVTVUUWSX", + 1029: " 14KXOKOX RPKPX RMKWKWOVK RMXRX", + 1030: " 15KYRKLX RRMWX RRKXX RMWVW RLXXX", + 1031: " 22JYNKNX ROKOX RSOSS RLKVKVOUK ROQSQ RLXVXVTUX", + 1032: " 16LYVKNX RWKOX ROKNONKWK RNXWXWTVX", + 1033: " 27J[NKNX ROKOX RVKVX RWKWX RLKQK RTKYK ROQVQ RLXQX RTXYX", + 1034: " 44KZQKOLNMMPMSNVOWQXTXVWWVXSXPWMVLTKQK RQKOMNPNSOVQX RTXVVWSWPVMTK RQOQT RTOTT RQQTQ RQRTR", + 1035: " 12NWRKRX RSKSX RPKUK RPXUX", + 1036: " 27JZNKNX ROKOX RWKOS RQQVX RRQWX RLKQK RTKYK RLXQX RTXYX", + 1037: " 15KYRKMX RRNVX RRKWX RKXPX RTXYX", + 1038: " 30I\\MKMX RNNRX RNKRU RWKRX RWKWX RXKXX RKKNK RWKZK RKXOX RUXZX", + 1039: " 21JZNKNX ROMVX ROKVV RVKVX RLKOK RTKXK RLXPX", + 1040: " 36JZMJLM RXJWM RPPOS RUPTS RMVLY RXVWY RMKWK RMLWL RPQTQ RPRTR RMWWW RMXWX", + 1041: " 32KZQKOLNMMPMSNVOWQXTXVWWVXSXPWMVLTKQK RQKOMNPNSOVQX RTXVVWSWPVMTK", + 1042: " 21J[NKNX ROKOX RVKVX RWKWX RLKYK RLXQX RTXYX", + 1043: " 25JYNKNX ROKOX RLKSKVLWNWOVQSROR RSKULVNVOUQSR RLXQX", + 1044: " 20K[MKRQ RNKSQMX RMKWKXOVK RNWWW RMXWXXTVX", + 1045: " 16KZRKRX RSKSX RNKMOMKXKXOWK RPXUX", + 1046: " 33KZMONLOKPKQLRORX RXOWLVKUKTLSOSX RMONMOLPLQMRO RXOWMVLULTMSO RPXUX", + 1047: " 40KZRKRX RSKSX RQNNOMQMRNTQUTUWTXRXQWOTNQN RQNOONQNROTQU RTUVTWRWQVOTN RPKUK RPXUX", + 1048: " 21KZNKVX ROKWX RWKNX RLKQK RTKYK RLXQX RTXYX", + 1049: " 33J[RKRX RSKSX RLPMONOOSQU RTUVSWOXOYP RMONROTQUTUVTWRXO RPKUK RPXUX", + 1050: " 35KZMVNXQXMRMONMOLQKTKVLWMXOXRTXWXXV ROUNRNOOMQK RTKVMWOWRVU RNWPW RUWWW", + 1051: " 18KYTKKX RSMTX RTKUX RNTTT RIXNX RRXWX", + 1052: " 34JYPKLX RQKMX RNKUKWLWNVPSQ RUKVLVNUPSQ ROQRQTRUSUUTWQXJX RRQTSTUSWQX", + 1053: " 25KXVLWLXKWNVLTKRKPLOMNOMRMUNWPXRXTWUU RRKPMOONRNVPX", + 1054: " 26JYPKLX RQKMX RNKTKVLWNWQVTUVTWQXJX RTKULVNVQUTTVSWQX", + 1055: " 22JYPKLX RQKMX RSORS RNKXKWNWK ROQRQ RJXTXUUSX", + 1056: " 20JXPKLX RQKMX RSORS RNKXKWNWK ROQRQ RJXOX", + 1057: " 33KYVLWLXKWNVLTKRKPLOMNOMRMUNWPXRXTWUVVS RRKPMOONRNVPX RRXTVUS RSSXS", + 1058: " 27J[PKLX RQKMX RXKTX RYKUX RNKSK RVK[K ROQVQ RJXOX RRXWX", + 1059: " 12NWTKPX RUKQX RRKWK RNXSX", + 1060: " 19LXUKRUQWPX RVKSURWPXOXMWLUMTNUMV RSKXK", + 1061: " 27JZPKLX RQKMX RYKOR RRPTX RSPUX RNKSK RVK[K RJXOX RRXWX", + 1062: " 14KXQKMX RRKNX ROKTK RKXUXVUTX", + 1063: " 30I\\OKKX ROMPX RPKQV RYKPX RYKUX RZKVX RMKPK RYK\\K RIXMX RSXXX", + 1064: " 21JZPKLX RPKTX RQKTU RXKTX RNKQK RVKZK RJXNX", + 1065: " 32KYRKPLOMNOMRMUNWPXRXTWUVVTWQWNVLTKRK RRKPMOONRNVPX RRXTVUTVQVMTK", + 1066: " 24JYPKLX RQKMX RNKUKWLXMXOWQTROR RUKWMWOVQTR RJXOX", + 1067: " 46KYRKPLOMNOMRMUNWPXRXTWUVVTWQWNVLTKRK RRKPMOONRNVPX RRXTVUTVQVMTK ROWOVPUQURVRZS[T[UZ RRVSZT[", + 1068: " 35JZPKLX RQKMX RNKUKWLXMXOWQTROR RUKWMWOVQTR RSRTWUXVXWW RSRTSUWVX RJXOX", + 1069: " 28KZWLXLYKXNWLUKRKPLOMOOPPUSVT RONPOURVSVVUWSXPXNWMULXMWNW", + 1070: " 16KZTKPX RUKQX RPKNNOKZKYNYK RNXSX", + 1071: " 20J[PKMUMWOXSXUWVUYK RQKNUNWOX RNKSK RWK[K", + 1072: " 15KYOKPX RPKQV RYKPX RMKRK RVK[K", + 1073: " 24I[NKMX ROKNV RTKMX RTKSX RUKTV RZKSX RLKQK RXK\\K", + 1074: " 21KZPKTX RQKUX RYKLX RNKSK RVK[K RJXOX RRXWX", + 1075: " 20LYPKRQPX RQKSQ RYKSQQX RNKSK RVK[K RNXSX", + 1076: " 16LYXKLX RYKMX RQKONPKYK RLXUXVUTX", + 1101: " 32LZQOPPPQOQOPQOTOVQVWWXXX RTOUQUWWX RURRSPTOUOWPXSXTWUU RRSPUPWQX", + 1102: " 29JYNKNX ROKOX RORPPROTOVPWRWUVWTXRXPWOU RTOUPVRVUUWTX RLKOK", + 1103: " 24LXVQUQURVRVQUPSOQOOPNRNUOWQXSXUWVV RQOPPOROUPWQX", + 1104: " 32L[VKVX RWKWX RVRUPSOQOOPNRNUOWQXSXUWVU RQOPPOROUPWQX RTKWK RVXYX", + 1105: " 26LXOSVSVRUPSOQOOPNRNUOWQXSXUWVV RUSUQSO RQOPPOROUPWQX", + 1106: " 20LWTKULUMVMVLTKRKPMPX RRKQMQX RNOSO RNXSX", + 1107: " 42LYQOOQOSQUSUUSUQSOQO RQOPQPSQU RSUTSTQSO RTPUOVO RPTOUOXPYTYVZ ROWPXTXVYV[T\\P\\N[NYPX", + 1108: " 28J[NKNX ROKOX RORPPROTOVPWRWX RTOUPVRVX RLKOK RLXQX RTXYX", + 1109: " 18NWRKRLSLSKRK RRORX RSOSX RPOSO RPXUX", + 1110: " 23NWSKSLTLTKSK RSOSZR\\ RTOTZR\\P\\O[OZPZP[O[ RQOTO", + 1111: " 27JZNKNX ROKOX RWOOU RRSVX RSSWX RLKOK RTOYO RLXQX RTXYX", + 1112: " 12NWRKRX RSKSX RPKSK RPXUX", + 1113: " 44F_JOJX RKOKX RKRLPNOPORPSRSX RPOQPRRRX RSRTPVOXOZP[R[X RXOYPZRZX RHOKO RHXMX RPXUX RXX]X", + 1114: " 28J[NONX ROOOX RORPPROTOVPWRWX RTOUPVRVX RLOOO RLXQX RTXYX", + 1115: " 28LYQOOPNRNUOWQXTXVWWUWRVPTOQO RQOPPOROUPWQX RTXUWVUVRUPTO", + 1116: " 32JYNON\\ ROOO\\ RORPPROTOVPWRWUVWTXRXPWOU RTOUPVRVUUWTX RLOOO RL\\Q\\", + 1117: " 29KYUOU\\ RVOV\\ RURTPROPONPMRMUNWPXRXTWUU RPOOPNRNUOWPX RS\\X\\", + 1118: " 22KXOOOX RPOPX RPRQPSOUOVPVQUQUPVP RMOPO RMXRX", + 1119: " 26LYTOUPUQVQVPTOQOOPORQSTTVU ROQQRTSVTVWTXQXOWOVPVPWQX", + 1120: " 14LWPKPVRXTXUWUV RQKQVRX RNOTO", + 1121: " 28J[NONUOWQXSXUWVU ROOOUPWQX RVOVX RWOWX RLOOO RTOWO RVXYX", + 1122: " 15KYNORX ROORV RVORX RLOQO RTOXO", + 1123: " 24I[LOOX RMOOU RROOX RROUX RSOUU RXOUX RJOOO RVOZO", + 1124: " 21KYNOUX ROOVX RVONX RLOQO RTOXO RLXPX RSXXX", + 1125: " 23KYNORX ROORV RVORXP[N\\M\\L[LZMZM[L[ RLOQO RTOXO", + 1126: " 16LXUONX RVOOX ROONQNOVO RNXVXVVUX", + 1127: " 32K[QOOPNQMSMUNWPXQXSWUUWRXO RQOOQNSNUOWPX RQOSOUPWWXX RSOTPVWXXYX", + 1128: " 40KXRKPMOOMUK\\ RQLPNNTL\\ RRKTKVLVNUPRQ RTKULUNTPRQ RRQTRUTUVTWRXQXOWNT RRQSRTTTVRX", + 1129: " 19KYLQNOPORPSSSXR\\ RLQNPPPRQSS RWOVRSXQ\\", + 1130: " 39KYSOQOOPNQMSMUNWPXRXTWUVVTVRUPRNQLQKRJTJUKVM RQOOQNSNVPX RRXTVUTUQSO RQLRKTKVM", + 1131: " 27LXVPTOQOOPOQPRRS RQOPPPQRS RRSOTNUNWPXSXUW RRSPTOUOWPX", + 1132: " 28LWRKQLQMSNVNVMSNPOOPNRNTOVPWRXSYS[R\\P\\O[ RSNQOPPOROTPVRX", + 1133: " 26IYJRKPLONOOPOQMX RMONPNQLX ROQPPROTOVPVRS\\ RTOUPURR\\", + 1134: " 35IYJSKQLPNPOQOVPX RMPNQNUOWPXQXSWTVUTVQVNULTKRKQLQNRPURWS RQXSVTTUQUNTK", + 1135: " 13NWROPVPWQXSXUWVU RSOQVQWRX", + 1136: " 26KYOOLX RPOMX RUOVPWPVOTORQOR RORPSRWTXVWWU RORQSSWTX", + 1137: " 15LXLKNKPLWX RNKOLVX RRPMX RRPNX", + 1138: " 26KZOOK\\ RPOL\\ RNUNWOXQXSWTV RVOTVTWUXWXXWYU RWOUVUWVX", + 1139: " 19JYNOMX ROONUMX RVRVOWOVRTUQWNXMX RLOOO", + 1140: " 36MXRKQLQMSNVN RTNQOPPPRRSUS RTNROQPQRRS RSSPTOUOWQXSYTZT[S\\Q\\ RSSQTPUPWQX", + 1141: " 28KXQOOPNQMSMUNWPXRXTWUVVTVRUPSOQO RQOOQNSNVPX RRXTVUTUQSO", + 1142: " 20IZPPMX RPPNX RTPSX RTPTX RKQMOXO RKQMPXP", + 1143: " 29JXSOQOOPNQMSJ\\ RQOOQNSK\\ RSOUPVRVTUVTWRXPXNWMU RSOUQUTTVRX", + 1144: " 28K[YOQOOPNQMSMUNWPXRXTWUVVTVRUPYP RQOOQNSNVPX RRXTVUTUQSO", + 1145: " 14KZSPQX RSPRX RMQOOXO RMQOPXP", + 1146: " 24JXKRLPMOOOPPPROUOWPX RNOOPORNUNWPXQXSWUUVRVOUOVP", + 1147: " 35KZOPNQMSMUNWPXRXUWWUXRXPWOUOTPSRRUO\\ RMUNVPWRWUVWTXR RXQWPUPSR RRUQXP\\", + 1148: " 17KXMONOPPS[T\\ RNOOPR[T\\U\\ RVOTRNYL\\", + 1149: " 28I[TKQ\\ RUKP\\ RJRKPLONOOPOVPWSWUVWT RMONPNTOWPXSXUWWTXRYO", + 1150: " 36JZNPPPPONPMQLSLUMWNXPXQWRUSR RLUNWPWRU RRRRWSXUXWVXTXRWPVOVPWP RRUSWUWWV", + 1151: " 32KZVOTVTWUXWXXWYU RWOUVUWVX RUSUQSOQOOPNQMSMUNWPXRXTV RQOOQNSNVPX", + 1152: " 32JXOKMR RPKNRNVPX RNROPQOSOUPVRVTUVTWRXPXNWMUMR RSOUQUTTVRX RMKPK", + 1153: " 22KXUPUQVQUPSOQOOPNQMSMUNWPXRXTWUV RQOOQNSNVPX", + 1154: " 35KZWKTVTWUXWXXWYU RXKUVUWVX RUSUQSOQOOPNQMSMUNWPXRXTV RQOOQNSNVPX RUKXK", + 1155: " 23KWNURTTSURUPSOQOOPNQMSMUNWPXRXTWUV RQOOQNSNVPX", + 1156: " 23MXWKXLXKVKTLSNPYO[N\\ RVKULTNQYP[N\\L\\L[M\\ RPOVO", + 1157: " 34KYVOTVSYR[ RWOUVTYR[P\\M\\L[M[N\\ RUSUQSOQOOPNQMSMUNWPXRXTV RQOOQNSNVPX", + 1158: " 29KZPKLX RQKMX ROQPPROTOVPVRUUUWVX RTOUPURTUTWUXWXXWYU RNKQK", + 1159: " 26MWSKSLTLTKSK RNROPPOROSPSRRURWSX RQORPRRQUQWRXTXUWVU", + 1160: " 26MWTKTLULUKTK RORPPQOSOTPTRRYQ[O\\M\\M[N\\ RROSPSRQYP[O\\", + 1161: " 32KXPKLX RQKMX RVPUQVQVPUOTORQPROR RORPSQWRXTXUWVU RORQSRWSX RNKQK", + 1162: " 16NVSKPVPWQXSXTWUU RTKQVQWRX RQKTK", + 1163: " 46F^GRHPIOKOLPLQJX RJOKPKQIX RLQMPOOQOSPSQQX RQORPRQPX RSQTPVOXOZPZRYUYWZX RXOYPYRXUXWYX[X\\W]U", + 1164: " 33J[KRLPMOOOPPPQNX RNOOPOQMX RPQQPSOUOWPWRVUVWWX RUOVPVRUUUWVXXXYWZU", + 1165: " 28KXQOOPNQMSMUNWPXRXTWUVVTVRUPSOQO RQOOQNSNVPX RRXTVUTUQSO", + 1166: " 35JYKRLPMOOOPPPQM\\ RNOOPOQL\\ RPQROTOVPWRWTVVUWSXQXOVOT RTOVQVTUVSX RJ\\O\\", + 1167: " 28KYVOR\\ RWOS\\ RUSUQSOQOOPNQMSMUNWPXRXTV RQOOQNSNVPX RP\\U\\", + 1168: " 22LXMRNPOOQORPRQPX RPOQPQQOX RRQSPUOVOWPWQVQWP", + 1169: " 24LYVPVQWQVPTOQOOPORQSTTVU ROQQRTSVTVWTXQXOWNVOVOW", + 1170: " 16NWSKPVPWQXSXTWUU RTKQVQWRX RPOUO", + 1171: " 33IZJRKPLONOOPORNUNWOX RMONPNRMUMWOXQXSWTV RVOTVTWUXWXXWYU RWOUVUWVX", + 1172: " 24JXKRLPMOOOPPPROUOWPX RNOOPORNUNWPXQXSWUUVRVOUOVP", + 1173: " 37H\\IRJPKOMONPNRMUMWNX RLOMPMRLULWNXOXQWRV RTORVRWTX RUOSVSWTXUXWWYUZRZOYOZP", + 1174: " 38JZMRNPPOROSPSR RQORPRRQUPWNXMXLWLVMVLW RXPWQXQXPWOVOTPSRRURWSX RQUQWRXTXVWWU", + 1175: " 35IYJRKPLONOOPORNUNWOX RMONPNRMUMWOXQXSWTV RVOTVSYR[ RWOUVTYR[P\\M\\L[M[N\\", + 1176: " 27KYWOWPVQNVMWMX RNQOOROUQ ROPRPUQVQ RNVOVRWUW ROVRXUXVV", + 1177: " 39H[RKSLSMTMTLRKOKMLLNLX ROKNLMNMX RXKYLYMZMZLXKVKTMTX RVKUMUX RJOWO RJXOX RRXWX", + 1178: " 29J[UKVLWLWKQKOLNNNX RQKPLONOX RVOVX RWOWX RLOWO RLXQX RTXYX", + 1179: " 27J[WKQKOLNNNX RQKPLONOX RUKVLVX RWKWX RLOVO RLXQX RTXYX", + 1180: " 48F_PKQLQMRMRLPKMKKLJNJX RMKLLKNKX RYKZL[L[KUKSLRNRX RUKTLSNSX RZOZX R[O[X RHO[O RHXMX RPXUX RXX]X", + 1181: " 46F_PKQLQMRMRLPKMKKLJNJX RMKLLKNKX R[KUKSLRNRX RUKTLSNSX RYKZLZX R[K[X RHOZO RHXMX RPXUX RXX]X", + 1182: " 12NWRORX RSOSX RPOSO RPXUX", + 1184: " 21LXVPTOROPPOQNSNUOWQXSXUW RROPQOSOVQX ROSSS", + 1185: " 35LYSKQLPMOONRNUOWPXRXTWUVVTWQWNVLUKSK RSKQMPOOSOVPX RRXTVUTVPVMUK ROQVQ", + 1186: " 34KZTKQ\\ RUKP\\ RQONPMRMUNWQXTXWWXUXRWPTOQO RQOOPNRNUOWQX RTXVWWUWRVPTO", + 1187: " 22LXUPVRVQUPSOQOOPNRNTOVRX RQOOQOTPVRXSYS[R\\P\\", + 1191: " 45I[VKWLXLVKSKQLPMOOLYK[J\\ RSKQMPOMYL[J\\H\\H[I\\ RZK[L[KYKWLVNSYR[Q\\ RYKXLWNTYS[Q\\O\\O[P\\ RLOYO", + 1192: " 38IZVKWLXLXKSKQLPMOOLYK[J\\ RSKQMPOMYL[J\\H\\H[I\\ RVOTVTWUXWXXWYU RWOUVUWVX RLOWO", + 1193: " 38IZVKWL RXKSKQLPMOOLYK[J\\ RSKQMPOMYL[J\\H\\H[I\\ RWKTVTWUXWXXWYU RXKUVUWVX RLOVO", + 1194: " 63F^SKTLTM RULSKPKNLMMLOIYH[G\\ RPKNMMOJYI[G\\E\\E[F\\ RZK[L\\L\\KWKUL RTMSOPYO[N\\ RWKUMTOQYP[N\\L\\L[M\\ RZOXVXWYX[X\\W]U R[OYVYWZX RIO[O", + 1195: " 63F^SKTLTM RULSKPKNLMMLOIYH[G\\ RPKNMMOJYI[G\\E\\E[F\\ RZK[L R\\KWKUL RTMSOPYO[N\\ RWKUMTOQYP[N\\L\\L[M\\ R[KXVXWYX[X\\W]U R\\KYVYWZX RIOZO", + 1196: " 20MWNROPPOROSPSRRURWSX RQORPRRQUQWRXTXUWVU", + 1200: " 28LYQKOLNONTOWQXTXVWWTWOVLTKQK RQKPLOOOTPWQX RTXUWVTVOULTK", + 1201: " 10LYPNSKSX RRLRX ROXVX", + 1202: " 35LYOMONNNNMOLQKTKVLWNVPTQQROSNUNX RTKULVNUPTQ RNWOVPVSWVWWV RPVSXVXWVWU", + 1203: " 39LYOMONNNNMOLQKTKVLWNVPTQ RTKULVNUPTQ RRQTQVRWTWUVWTXQXOWNVNUOUOV RTQURVTVUUWTX", + 1204: " 13LYSMSX RTKTX RTKMTXT RQXVX", + 1205: " 33LYOKNQ ROKVK ROLSLVK RNQOPQOTOVPWRWUVWTXQXOWNVNUOUOV RTOUPVRVUUWTX", + 1206: " 36LYVMVNWNWMVLTKRKPLOMNPNUOWQXTXVWWUWSVQTPQPNR RRKPMOPOUPWQX RTXUWVUVSUQTP", + 1207: " 22LYNKNO RVMRTPX RWKTQQX RNMPKRKUM RNMPLRLUMVM", + 1208: " 51LYQKOLNNOPQQTQVPWNVLTKQK RQKPLONPPQQ RTQUPVNULTK RQQORNTNUOWQXTXVWWUWTVRTQ RQQPROTOUPWQX RTXUWVUVTURTQ", + 1209: " 36LYOVOUNUNVOWQXSXUWVVWSWNVLTKQKOLNNNPORQSTSWQ RSXUVVSVNULTK RQKPLONOPPRQS", + 1210: " 6NVRVQWRXSWRV", + 1211: " 8NVSWRXQWRVSWSYQ[", + 1212: " 12NVROQPRQSPRO RRVQWRXSWRV", + 1213: " 14NVROQPRQSPRO RSWRXQWRVSWSYQ[", + 1214: " 15NVRKQLRSSLRK RRLRO RRVQWRXSWRV", + 1215: " 29LYNNONOONONNOLQKTKVLWNWOVQSRRSRTST RTKVMVPUQSR RRWRXSXSWRW", + 1216: " 6OVRKRP RSKRP", + 1217: " 12LXOKOP RPKOP RUKUP RVKUP", + 1218: " 10MWQKPLPNQOSOTNTLSKQK", + 1219: " 9MWRJRP ROKUO RUKOO", + 1220: " 3KZXHM\\", + 1221: " 16MWUHSJQMPPPTQWSZU\\ RSJRLQPQTRXSZ", + 1222: " 16MWOHQJSMTPTTSWQZO\\ RQJRLSPSTRXQZ", + 1223: " 12MWPHP\\ RQHQ\\ RPHUH RP\\U\\", + 1224: " 12MWSHS\\ RTHT\\ ROHTH RO\\T\\", + 1225: " 38LWSHQIPJPLRNSP RQIPL RSNRQ RPJQLSNSPRQPRRSSTSVQXPZ RRSSV RPXQ[ RSTRVPXPZQ[S\\", + 1226: " 38MXQHSITJTLRNQP RSITL RQNRQ RTJSLQNQPRQTRRSQTQVSXTZ RRSQV RTXS[ RQTRVTXTZS[Q\\", + 1227: " 4MWTHPRT\\", + 1228: " 4MWPHTRP\\", + 1229: " 3OURHR\\", + 1230: " 6MWPHP\\ RTHT\\", + 1231: " 3I[LRXR", + 1232: " 6I[RLRX RLRXR", + 1233: " 9JZRMRX RMRWR RMXWX", + 1234: " 9JZRMRX RMMWM RMRWR", + 1235: " 6JZMMWW RWMMW", + 1236: " 6NVRQQRRSSRRQ", + 1237: " 15I[RLQMRNSMRL RLRXR RRVQWRXSWRV", + 1238: " 6I[LPXP RLTXT", + 1239: " 9I[WLMX RLPXP RLTXT", + 1240: " 9I[LNXN RLRXR RLVXV", + 1241: " 4JZWLMRWX", + 1242: " 4JZMLWRMX", + 1243: " 10JZWKMOWS RMTWT RMXWX", + 1244: " 10JZMKWOMS RMTWT RMXWX", + 1245: " 21H[YUWUUTTSRPQOONNNLOKQKRLTNUOUQTRSTPUOWNYN", + 1246: " 16JZLTLRMPOPUSWSXR RLRMQOQUTWTXRXP", + 1247: " 8JZMSRPWS RMSRQWS", + 1248: " 7NVSKPO RSKTLPO", + 1249: " 7NVQKTO RQKPLTO", + 1250: " 14LXNKOMQNSNUMVK RNKONQOSOUNVK", + 1251: " 8NVSLRMQLRKSLSNQP", + 1252: " 8NVSKQMQORPSORNQO", + 1253: " 8NVQLRMSLRKQLQNSP", + 1254: " 8NVQKSMSORPQORNSO", + 1256: " 11JZWMQMONNOMQMSNUOVQWWW", + 1257: " 11JZMMMSNUOVQWSWUVVUWSWM", + 1258: " 11JZMMSMUNVOWQWSVUUVSWMW", + 1259: " 11JZMWMQNOONQMSMUNVOWQWW", + 1260: " 14JZWMQMONNOMQMSNUOVQWWW RMRUR", + 1261: " 13I[TOUPXRUTTU RUPWRUT RLRWR", + 1262: " 13MWRMRX ROPPORLTOUP RPORMTO", + 1263: " 13I[POOPLROTPU ROPMROT RMRXR", + 1264: " 13MWRLRW ROTPURXTUUT RPURWTU", + 1265: " 37KYVSUPSOQOOPNQMSMUNWPXRXTWUVVTWQWNVLTKQKPLQLRK RQOOQNSNVPX RRXTVUTVQVNULTK", + 1266: " 15JZLKRX RMKRV RXKRX RLKXK RNLWL", + 1267: " 10G[IOLORW RKORX R[FRX", + 1268: " 26I[XIXJYJYIXHVHTJSLROQUPYO[ RUITKSORUQXPZN\\L\\K[KZLZL[", + 1269: " 40I[XIXJYJYIXHVHTJSLROQUPYO[ RUITKSORUQXPZN\\L\\K[KZLZL[ RQNOONQNSOUQVSVUUVSVQUOSNQN", + 1270: " 26H\\ZRYTWUVUTTSSQPPONNMNKOJQJRKTMUNUPTQSSPTOVNWNYOZQZR", + 1271: " 26JZXKLX ROKPLPNOOMOLNLLMKOKSLVLXK RUTTUTWUXWXXWXUWTUT", + 1272: " 41J[YPXPXQYQYPXOWOVPUTTVSWQXOXMWLVLTMSORRPSNSLRKPKOLONPQUWWXXXYW ROXMVMTOR RONPPVWWX", + 1273: " 29J[UPSOQOPQPRQTSTUS RUOUSVTXTYRYQXNVLSKRKOLMNLQLRMUOWRXSXVW", + 1274: " 34KZQHQ\\ RTHT\\ RWLVLVMWMWLUKPKNLNNOPVSWT RNNOOVRWTWVVWTXQXOWNVNUOUOVNV", + 1275: " 12KYRKN\\ RVKR\\ RNQWQ RMVVV", + 1276: " 40LXTLSLSMTMTLSKQKPLPNQPTRUS RPNQOTQUSUUSW RQPOROTPVSXTY ROTPUSWTYT[S\\Q\\P[PZQZQ[P[", + 1277: " 29LXRKQLRMSLRK RRMRQ RRQQSRVSSRQ RRVR\\ RPOONNOOPPOTOUNVOUPTO", + 1278: " 42LXRMSLRKQLRMRQQRSURV RRQSRQURVRZQ[R\\S[RZ RPOONNOOPPOTOUNVOUPTO RPXOWNXOYPXTXUWVXUYTX", + 1279: " 12LYVKVX RNKVK RQQVQ RNXVX", + 1281: " 24H\\QKNLLNKQKSLVNXQYSYVXXVYSYQXNVLSKQK RRQQRRSSRRQ", + 1282: " 33LYQKPLPMQN RTKULUMTN RRNPOOQORPTRUSUUTVRVQUOSNRN RRURY RSUSY ROWVW", + 1283: " 23LYRKPLONOOPQRRSRUQVOVNULSKRK RRRRX RSRSX ROUVU", + 1284: " 24H\\QKNLLNKQKSLVNXQYSYVXXVYSYQXNVLSKQK RRKRY RKRYR", + 1285: " 25JYRRPQOQMRLTLUMWOXPXRWSUSTRR RWMRR RRMWMWR RRMVNWR", + 1286: " 25JZLLMKOKQLRNRPQRPSNT ROKPLQNQQPS RVKUX RWKTX RNTXT", + 1287: " 27JYNKNU ROKNR RNROPQOSOUPVQVTTVTXUYVYWX RSOUQUTTV RLKOK", + 1288: " 27LYONRKRQ RVNSKSQ RRQPROTOUPWRXSXUWVUVTURSQ RRTRUSUSTRT", + 1289: " 27JZRKRY RMKMPNRPSTSVRWPWK RLMMKNM RQMRKSM RVMWKXM ROVUV", + 1290: " 27JYNKNX ROKOX RLKSKVLWNWOVQSROR RSKULVNVOUQSR RLXVXVUUX", + 1291: " 20LYWKTKQLONNQNSOVQXTYWY RWKTLRNQQQSRVTXWY", + 1292: " 23JZRRPQOQMRLTLUMWOXPXRWSUSTRR RSLQQ RWMRR RXQSS", + 1293: " 12KYPMTW RTMPW RMPWT RWPMT", + 1294: " 34J[OUMULVLXMYOYPXPVNTMRMONMOLQKTKVLWMXOXRWTUVUXVYXYYXYVXUVU RNMPLULWM", + 1295: " 34J[OOMOLNLLMKOKPLPNNPMRMUNWOXQYTYVXWWXUXRWPUNULVKXKYLYNXOVO RNWPXUXWW", + 1401: " 21F^KHK\\ RLHL\\ RXHX\\ RYHY\\ RHH\\H RH\\O\\ RU\\\\\\", + 1402: " 20H]KHRQJ\\ RJHQQ RJHYHZMXH RK[X[ RJ\\Y\\ZWX\\", + 1403: " 20KYVBTDRGPKOPOTPYR]T`Vb RTDRHQKPPPTQYR\\T`", + 1404: " 20KYNBPDRGTKUPUTTYR]P`Nb RPDRHSKTPTTSYR\\P`", + 1405: " 12KYOBOb RPBPb ROBVB RObVb", + 1406: " 12KYTBTb RUBUb RNBUB RNbUb", + 1407: " 40KYTBRCQDPFPHQJRKSMSOQQ RRCQEQGRISJTLTNSPORSTTVTXSZR[Q]Q_Ra RQSSUSWRYQZP\\P^Q`RaTb", + 1408: " 40KYPBRCSDTFTHSJRKQMQOSQ RRCSESGRIQJPLPNQPURQTPVPXQZR[S]S_Ra RSSQUQWRYSZT\\T^S`RaPb", + 1409: " 24KYU@RCPFOIOLPOSVTYT\\S_Ra RRCQEPHPKQNTUUXU[T^RaOd", + 1410: " 24KYO@RCTFUIULTOQVPYP\\Q_Ra RRCSETHTKSNPUOXO[P^RaUd", + 1411: " 13AXCRGRR` RGSRa RFSRb RX:Rb", + 1412: " 32F^[CZD[E\\D\\C[BYBWCUETGSJRNPZO^N` RVDUFTJRVQZP]O_MaKbIbHaH`I_J`Ia", + 2001: " 18H\\RFK[ RRFY[ RRIX[ RMUVU RI[O[ RU[[[", + 2002: " 45G]LFL[ RMFM[ RIFUFXGYHZJZLYNXOUP RUFWGXHYJYLXNWOUP RMPUPXQYRZTZWYYXZU[I[ RUPWQXRYTYWXYWZU[", + 2003: " 32G\\XIYLYFXIVGSFQFNGLIKKJNJSKVLXNZQ[S[VZXXYV RQFOGMILKKNKSLVMXOZQ[", + 2004: " 30G]LFL[ RMFM[ RIFSFVGXIYKZNZSYVXXVZS[I[ RSFUGWIXKYNYSXVWXUZS[", + 2005: " 22G\\LFL[ RMFM[ RSLST RIFYFYLXF RMPSP RI[Y[YUX[", + 2006: " 20G[LFL[ RMFM[ RSLST RIFYFYLXF RMPSP RI[P[", + 2007: " 40G^XIYLYFXIVGSFQFNGLIKKJNJSKVLXNZQ[S[VZXX RQFOGMILKKNKSLVMXOZQ[ RXSX[ RYSY[ RUS\\S", + 2008: " 27F^KFK[ RLFL[ RXFX[ RYFY[ RHFOF RUF\\F RLPXP RH[O[ RU[\\[", + 2009: " 12MXRFR[ RSFS[ ROFVF RO[V[", + 2010: " 20KZUFUWTZR[P[NZMXMVNUOVNW RTFTWSZR[ RQFXF", + 2011: " 27F\\KFK[ RLFL[ RYFLS RQOY[ RPOX[ RHFOF RUF[F RH[O[ RU[[[", + 2012: " 14I[NFN[ ROFO[ RKFRF RK[Z[ZUY[", + 2013: " 30F_KFK[ RLFRX RKFR[ RYFR[ RYFY[ RZFZ[ RHFLF RYF]F RH[N[ RV[][", + 2014: " 21G^LFL[ RMFYY RMHY[ RYFY[ RIFMF RVF\\F RI[O[", + 2015: " 44G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF RQFOGMILKKOKRLVMXOZQ[ RS[UZWXXVYRYOXKWIUGSF", + 2016: " 29G]LFL[ RMFM[ RIFUFXGYHZJZMYOXPUQMQ RUFWGXHYJYMXOWPUQ RI[P[", + 2017: " 64G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF RQFOGMILKKOKRLVMXOZQ[ RS[UZWXXVYRYOXKWIUGSF RNYNXOVQURUTVUXV_W`Y`Z^Z] RUXV\\W^X_Y_Z^", + 2018: " 45G]LFL[ RMFM[ RIFUFXGYHZJZLYNXOUPMP RUFWGXHYJYLXNWOUP RI[P[ RRPTQURXYYZZZ[Y RTQUSWZX[Z[[Y[X", + 2019: " 34H\\XIYFYLXIVGSFPFMGKIKKLMMNOOUQWRYT RKKMMONUPWQXRYTYXWZT[Q[NZLXKUK[LX", + 2020: " 16I\\RFR[ RSFS[ RLFKLKFZFZLYF RO[V[", + 2021: " 23F^KFKULXNZQ[S[VZXXYUYF RLFLUMXOZQ[ RHFOF RVF\\F", + 2022: " 15H\\KFR[ RLFRX RYFR[ RIFOF RUF[F", + 2023: " 24F^JFN[ RKFNV RRFN[ RRFV[ RSFVV RZFV[ RGFNF RWF]F", + 2024: " 21H\\KFX[ RLFY[ RYFK[ RIFOF RUF[F RI[O[ RU[[[", + 2025: " 20H]KFRQR[ RLFSQS[ RZFSQ RIFOF RVF\\F RO[V[", + 2026: " 16H\\XFK[ RYFL[ RLFKLKFYF RK[Y[YUX[", + 2027: " 18H\\RFK[ RRFY[ RRIX[ RMUVU RI[O[ RU[[[", + 2028: " 45G]LFL[ RMFM[ RIFUFXGYHZJZLYNXOUP RUFWGXHYJYLXNWOUP RMPUPXQYRZTZWYYXZU[I[ RUPWQXRYTYWXYWZU[", + 2029: " 14I[NFN[ ROFO[ RKFZFZLYF RK[R[", + 2030: " 15H\\RFJ[ RRFZ[ RRIY[ RKZYZ RJ[Z[", + 2031: " 22G\\LFL[ RMFM[ RSLST RIFYFYLXF RMPSP RI[Y[YUX[", + 2032: " 16H\\XFK[ RYFL[ RLFKLKFYF RK[Y[YUX[", + 2033: " 27F^KFK[ RLFL[ RXFX[ RYFY[ RHFOF RUF\\F RLPXP RH[O[ RU[\\[", + 2034: " 56G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF RQFOGMILKKOKRLVMXOZQ[ RS[UZWXXVYRYOXKWIUGSF ROMOT RUMUT ROPUP ROQUQ", + 2035: " 12MXRFR[ RSFS[ ROFVF RO[V[", + 2036: " 27F\\KFK[ RLFL[ RYFLS RQOY[ RPOX[ RHFOF RUF[F RH[O[ RU[[[", + 2037: " 15H\\RFK[ RRFY[ RRIX[ RI[O[ RU[[[", + 2038: " 30F_KFK[ RLFRX RKFR[ RYFR[ RYFY[ RZFZ[ RHFLF RYF]F RH[N[ RV[][", + 2039: " 21G^LFL[ RMFYY RMHY[ RYFY[ RIFMF RVF\\F RI[O[", + 2040: " 36G]KEJJ RZEYJ RONNS RVNUS RKWJ\\ RZWY\\ RKGYG RKHYH ROPUP ROQUQ RKYYY RKZYZ", + 2041: " 44G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF RQFOGMILKKOKRLVMXOZQ[ RS[UZWXXVYRYOXKWIUGSF", + 2042: " 21F^KFK[ RLFL[ RXFX[ RYFY[ RHF\\F RH[O[ RU[\\[", + 2043: " 29G]LFL[ RMFM[ RIFUFXGYHZJZMYOXPUQMQ RUFWGXHYJYMXOWPUQ RI[P[", + 2044: " 20H]KFRPJ[ RJFQP RJFYFZLXF RKZXZ RJ[Y[ZUX[", + 2045: " 16I\\RFR[ RSFS[ RLFKLKFZFZLYF RO[V[", + 2046: " 33I\\KKKILGMFOFPGQIRMR[ RKIMGOGQI RZKZIYGXFVFUGTISMS[ RZIXGVGTI RO[V[", + 2047: " 48H]RFR[ RSFS[ RPKMLLMKOKRLTMUPVUVXUYTZRZOYMXLUKPK RPKNLMMLOLRMTNUPV RUVWUXTYRYOXMWLUK ROFVF RO[V[", + 2048: " 21H\\KFX[ RLFY[ RYFK[ RIFOF RUF[F RI[O[ RU[[[", + 2049: " 41G^RFR[ RSFS[ RIMJLLMMQNSOTQU RJLKMLQMSNTQUTUWTXSYQZM[L RTUVTWSXQYM[L\\M ROFVF RO[V[", + 2050: " 43G]JXK[O[MWKSJPJLKIMGPFTFWGYIZLZPYSWWU[Y[ZX RMWLTKPKLLINGPF RTFVGXIYLYPXTWW RKZNZ RVZYZ", + 2051: " 18H\\UFH[ RUFV[ RTHU[ RLUUU RF[L[ RR[X[", + 2052: " 41F^OFI[ RPFJ[ RLFWFZG[I[KZNYOVP RWFYGZIZKYNXOVP RMPVPXQYSYUXXVZR[F[ RVPWQXSXUWXUZR[", + 2053: " 34H]ZH[H\\F[L[JZHYGWFTFQGOIMLLOKSKVLYMZP[S[UZWXXV RTFRGPINLMOLSLVMYNZP[", + 2054: " 30F]OFI[ RPFJ[ RLFUFXGYHZKZOYSWWUYSZO[F[ RUFWGXHYKYOXSVWTYRZO[", + 2055: " 22F]OFI[ RPFJ[ RTLRT RLF[FZLZF RMPSP RF[U[WVT[", + 2056: " 20F\\OFI[ RPFJ[ RTLRT RLF[FZLZF RMPSP RF[M[", + 2057: " 42H^ZH[H\\F[L[JZHYGWFTFQGOIMLLOKSKVLYMZP[R[UZWXYT RTFRGPINLMOLSLVMYNZP[ RR[TZVXXT RUT\\T", + 2058: " 27E_NFH[ ROFI[ R[FU[ R\\FV[ RKFRF RXF_F RLPXP RE[L[ RR[Y[", + 2059: " 12LYUFO[ RVFP[ RRFYF RL[S[", + 2060: " 21I[XFSWRYQZO[M[KZJXJVKULVKW RWFRWQYO[ RTF[F", + 2061: " 27F]OFI[ RPFJ[ R]FLS RSOW[ RROV[ RLFSF RYF_F RF[M[ RS[Y[", + 2062: " 14H\\QFK[ RRFL[ RNFUF RH[W[YUV[", + 2063: " 30E`NFH[ RNFO[ ROFPY R\\FO[ R\\FV[ R]FW[ RKFOF R\\F`F RE[K[ RS[Z[", + 2064: " 21F_OFI[ ROFVX ROIV[ R\\FV[ RLFOF RYF_F RF[L[", + 2065: " 42G]SFPGNILLKOJSJVKYLZN[Q[TZVXXUYRZNZKYHXGVFSF RSFQGOIMLLOKSKVLYN[ RQ[SZUXWUXRYNYKXHVF", + 2066: " 27F]OFI[ RPFJ[ RLFXF[G\\I\\K[NYPUQMQ RXFZG[I[KZNXPUQ RF[M[", + 2067: " 61G]SFPGNILLKOJSJVKYLZN[Q[TZVXXUYRZNZKYHXGVFSF RSFQGOIMLLOKSKVLYN[ RQ[SZUXWUXRYNYKXHVF RLYLXMVOUPURVSXS_T`V`W^W] RSXT^U_V_W^", + 2068: " 42F^OFI[ RPFJ[ RLFWFZG[I[KZNYOVPMP RWFYGZIZKYNXOVP RRPTQURVZW[Y[ZYZX RURWYXZYZZY RF[M[", + 2069: " 35G^ZH[H\\F[L[JZHYGVFRFOGMIMKNMONVRXT RMKOMVQWRXTXWWYVZS[O[LZKYJWJUI[JYKY", + 2070: " 16H]UFO[ RVFP[ ROFLLNF]F\\L\\F RL[S[", + 2071: " 25F_NFKQJUJXKZN[R[UZWXXU\\F ROFLQKUKXLZN[ RKFRF RYF_F", + 2072: " 15H\\NFO[ ROFPY R\\FO[ RLFRF RXF^F", + 2073: " 24E_MFK[ RNFLY RUFK[ RUFS[ RVFTY R]FS[ RJFQF RZF`F", + 2074: " 21G]NFU[ ROFV[ R\\FH[ RLFRF RXF^F RF[L[ RR[X[", + 2075: " 20H]NFRPO[ ROFSPP[ R]FSP RLFRF RYF_F RL[S[", + 2076: " 16G][FH[ R\\FI[ ROFLLNF\\F RH[V[XUU[", + 2077: " 46H\\KILKXWYYY[ RLLXX RKIKKLMXYY[ RPPLTKVKXLZK[ RKVMZ RLTLVMXMZK[ RSSXN RVIVLWNYNYLWKVI RVIWLYN", + 2101: " 39I]NONPMPMONNPMTMVNWOXQXXYZZ[ RWOWXXZZ[[[ RWQVRPSMTLVLXMZP[S[UZWX RPSNTMVMXNZP[", + 2102: " 33G\\LFL[ RMFM[ RMPONQMSMVNXPYSYUXXVZS[Q[OZMX RSMUNWPXSXUWXUZS[ RIFMF", + 2103: " 28H[WPVQWRXQXPVNTMQMNNLPKSKULXNZQ[S[VZXX RQMONMPLSLUMXOZQ[", + 2104: " 36H]WFW[ RXFX[ RWPUNSMQMNNLPKSKULXNZQ[S[UZWX RQMONMPLSLUMXOZQ[ RTFXF RW[[[", + 2105: " 31H[LSXSXQWOVNTMQMNNLPKSKULXNZQ[S[VZXX RWSWPVN RQMONMPLSLUMXOZQ[", + 2106: " 22KXUGTHUIVHVGUFSFQGPIP[ RSFRGQIQ[ RMMUM RM[T[", + 2107: " 60I\\QMONNOMQMSNUOVQWSWUVVUWSWQVOUNSMQM RONNPNTOV RUVVTVPUN RVOWNYMYNWN RNUMVLXLYM[P\\U\\X]Y^ RLYMZP[U[X\\Y^Y_XaUbObLaK_K^L\\O[", + 2108: " 28G]LFL[ RMFM[ RMPONRMTMWNXPX[ RTMVNWPW[ RIFMF RI[P[ RT[[[", + 2109: " 18MXRFQGRHSGRF RRMR[ RSMS[ ROMSM RO[V[", + 2110: " 25MXSFRGSHTGSF RTMT_SaQbObNaN`O_P`Oa RSMS_RaQb RPMTM", + 2111: " 27G\\LFL[ RMFM[ RWMMW RRSX[ RQSW[ RIFMF RTMZM RI[P[ RT[Z[", + 2112: " 12MXRFR[ RSFS[ ROFSF RO[V[", + 2113: " 44BcGMG[ RHMH[ RHPJNMMOMRNSPS[ ROMQNRPR[ RSPUNXMZM]N^P^[ RZM\\N]P][ RDMHM RD[K[ RO[V[ RZ[a[", + 2114: " 28G]LML[ RMMM[ RMPONRMTMWNXPX[ RTMVNWPW[ RIMMM RI[P[ RT[[[", + 2115: " 36H\\QMNNLPKSKULXNZQ[S[VZXXYUYSXPVNSMQM RQMONMPLSLUMXOZQ[ RS[UZWXXUXSWPUNSM", + 2116: " 36G\\LMLb RMMMb RMPONQMSMVNXPYSYUXXVZS[Q[OZMX RSMUNWPXSXUWXUZS[ RIMMM RIbPb", + 2117: " 33H\\WMWb RXMXb RWPUNSMQMNNLPKSKULXNZQ[S[UZWX RQMONMPLSLUMXOZQ[ RTb[b", + 2118: " 23IZNMN[ ROMO[ ROSPPRNTMWMXNXOWPVOWN RKMOM RK[R[", + 2119: " 32J[WOXMXQWOVNTMPMNNMOMQNRPSUUWVXW RMPNQPRUTWUXVXYWZU[Q[OZNYMWM[NY", + 2120: " 16KZPFPWQZS[U[WZXX RQFQWRZS[ RMMUM", + 2121: " 28G]LMLXMZP[R[UZWX RMMMXNZP[ RWMW[ RXMX[ RIMMM RTMXM RW[[[", + 2122: " 15I[LMR[ RMMRY RXMR[ RJMPM RTMZM", + 2123: " 24F^JMN[ RKMNX RRMN[ RRMV[ RSMVX RZMV[ RGMNM RWM]M", + 2124: " 21H\\LMW[ RMMX[ RXML[ RJMPM RTMZM RJ[P[ RT[Z[", + 2125: " 22H[LMR[ RMMRY RXMR[P_NaLbKbJaK`La RJMPM RTMZM", + 2126: " 16I[WML[ RXMM[ RMMLQLMXM RL[X[XWW[", + 2127: " 40G^QMNNLPKRJUJXKZN[P[RZUWWTYPZM RQMONMPLRKUKXLZN[ RQMSMUNVPXXYZZ[ RSMTNUPWXXZZ[[[", + 2128: " 57G\\TFQGOIMMLPKTJZIb RTFRGPINMMPLTKZJb RTFVFXGYHYKXMWNTOPO RVFXHXKWMVNTO RPOTPVRWTWWVYUZR[P[NZMYLV RPOSPURVTVWUYTZR[", + 2129: " 28H\\IPKNMMOMQNROSRSVRZOb RJOLNPNRO RZMYPXRSYP^Nb RYMXPWRSY", + 2130: " 44I\\VNTMRMONMQLTLWMYNZP[R[UZWWXTXQWOSJRHRFSEUEWFYH RRMPNNQMTMXNZ RR[TZVWWTWPVNTKSISGTFVFYH", + 2131: " 32I[XPVNTMPMNNNPPRSS RPMONOPQRSS RSSNTLVLXMZP[S[UZWX RSSOTMVMXNZP[", + 2132: " 31I[TFRGQHQIRJUKZKZJWKSMPOMRLULWMYP[S]T_TaSbQbPa RULQONRMUMWNYP[", + 2133: " 32G]HQIOKMNMONOPNTL[ RMMNNNPMTK[ RNTPPRNTMVMXNYOYRXWUb RVMXOXRWWTb", + 2134: " 44F]GQHOJMMMNNNPMUMXNZO[ RLMMNMPLULXMZO[Q[SZUXWUXRYMYIXGVFTFRHRJSMUPWRZT RSZUWVUWRXMXIWGVF", + 2135: " 15LXRMPTOXOZP[S[UYVW RSMQTPXPZQ[", + 2136: " 29H\\NMJ[ ROMK[ RXMYNZNYMWMUNQROSMS ROSQTSZT[ ROSPTRZS[U[WZYW", + 2137: " 23H\\KFMFOGPHQJWXXZY[ RMFOHPJVXWZY[Z[ RRMJ[ RRMK[", + 2138: " 28F]MMGb RNMHb RMPLVLYN[P[RZTXVU RXMUXUZV[Y[[Y\\W RYMVXVZW[", + 2139: " 24H\\NML[ ROMNSMXL[ RYMXQVU RZMYPXRVUTWQYOZL[ RKMOM", + 2140: " 45IZTFRGQHQIRJUKXK RUKQLOMNONQPSSTVT RUKRLPMOOOQQSST RSTOUMVLXLZN\\S^T_TaRbPb RSTPUNVMXMZO\\S^", + 2141: " 32I[RMONMQLTLWMYNZP[R[UZWWXTXQWOVNTMRM RRMPNNQMTMXNZ RR[TZVWWTWPVN", + 2142: " 22G]PNL[ RPNM[ RVNV[ RVNW[ RIPKNNM[M RIPKONN[N", + 2143: " 31H[LVMYNZP[R[UZWWXTXQWOVNTMRMONMQLTHb RR[TZVWWTWPVN RRMPNNQMTIb", + 2144: " 35H][MQMNNLQKTKWLYMZO[Q[TZVWWTWQVOUNSM RQMONMQLTLXMZ RQ[SZUWVTVPUN RUN[N", + 2145: " 16H\\SNP[ RSNQ[ RJPLNOMZM RJPLOONZN", + 2146: " 31H\\IQJOLMOMPNPPNVNYP[ RNMONOPMVMYNZP[Q[TZVXXUYRYOXMWNXOYR RXUYO", + 2147: " 37G]ONMOKQJTJWKYLZN[Q[TZWXYUZRZOXMVMTORSPXMb RJWLYNZQZTYWWYU RZOXNVNTPRSPYNb", + 2148: " 23I[KMMMONPPU_VaWb RMMNNOPT_UaWbYb RZMYOWRM]K`Jb", + 2149: " 34F]UFOb RVFNb RGQHOJMMMNNNPMUMXOZRZTYWVYS RLMMNMPLULXMZO[R[TZVXXUYS[M", + 2150: " 44F]JQLOONNMLNJQITIWJZK[M[OZQWRT RIWJYKZMZOYQW RQTQWRZS[U[WZYWZTZQYNXMWNYOZQ RQWRYSZUZWYYW", + 2151: " 39H]XMVTUXUZV[Y[[Y\\W RYMWTVXVZW[ RVTVQUNSMQMNNLQKTKWLYMZO[Q[SZUWVT RQMONMQLTLXMZ", + 2152: " 36H[PFLSLVMYNZ RQFMS RMSNPPNRMTMVNWOXQXTWWUZR[P[NZMWMS RVNWPWTVWTZR[ RMFQF", + 2153: " 25I[WPWQXQXPWNUMRMONMQLTLWMYNZP[R[UZWW RRMPNNQMTMXNZ", + 2154: " 42H]ZFVTUXUZV[Y[[Y\\W R[FWTVXVZW[ RVTVQUNSMQMNNLQKTKWLYMZO[Q[SZUWVT RQMONMQLTLXMZ RWF[F", + 2155: " 26I[MVQUTTWRXPWNUMRMONMQLTLWMYNZP[R[UZWX RRMPNNQMTMXNZ", + 2156: " 35KZZGYHZI[H[GZFXFVGUHTJSMP[O_Na RXFVHUJTNRWQ[P^O`NaLbJbIaI`J_K`Ja ROMYM", + 2157: " 43H\\YMU[T^RaObLbJaI`I_J^K_J` RXMT[S^QaOb RVTVQUNSMQMNNLQKTKWLYMZO[Q[SZUWVT RQMONMQLTLXMZ", + 2158: " 31H]PFJ[ RQFK[ RMTOPQNSMUMWNXOXQVWVZW[ RUMWOWQUWUZV[Y[[Y\\W RMFQF", + 2159: " 26LYUFTGUHVGUF RMQNOPMSMTNTQRWRZS[ RRMSNSQQWQZR[U[WYXW", + 2160: " 32LYVFUGVHWGVF RNQOOQMTMUNUQR[Q^P`OaMbKbJaJ`K_L`Ka RSMTNTQQ[P^O`Mb", + 2161: " 34H\\PFJ[ RQFK[ RXNWOXPYOYNXMWMUNQROSMS ROSQTSZT[ ROSPTRZS[U[WZYW RMFQF", + 2162: " 18MYUFQTPXPZQ[T[VYWW RVFRTQXQZR[ RRFVF", + 2163: " 52AbBQCOEMHMINIPHTF[ RGMHNHPGTE[ RHTJPLNNMPMRNSOSQP[ RPMRORQO[ RRTTPVNXMZM\\N]O]Q[W[Z\\[ RZM\\O\\QZWZZ[[^[`YaW", + 2164: " 37F]GQHOJMMMNNNPMTK[ RLMMNMPLTJ[ RMTOPQNSMUMWNXOXQVWVZW[ RUMWOWQUWUZV[Y[[Y\\W", + 2165: " 32I[RMONMQLTLWMYNZP[R[UZWWXTXQWOVNTMRM RRMPNNQMTMXNZ RR[TZVWWTWPVN", + 2166: " 42G\\HQIOKMNMONOPNTJb RMMNNNPMTIb RNTOQQNSMUMWNXOYQYTXWVZS[Q[OZNWNT RWNXPXTWWUZS[ RFbMb", + 2167: " 33H\\XMRb RYMSb RVTVQUNSMQMNNLQKTKWLYMZO[Q[SZUWVT RQMONMQLTLXMZ RObVb", + 2168: " 26IZJQKOMMPMQNQPPTN[ ROMPNPPOTM[ RPTRPTNVMXMYNYOXPWOXN", + 2169: " 28J[XOXPYPYOXNUMRMONNONQORVVWW RNPOQVUWVWYVZS[P[MZLYLXMXMY", + 2170: " 18KYTFPTOXOZP[S[UYVW RUFQTPXPZQ[ RNMWM", + 2171: " 37F]GQHOJMMMNNNQLWLYN[ RLMMNMQKWKYLZN[P[RZTXVT RXMVTUXUZV[Y[[Y\\W RYMWTVXVZW[", + 2172: " 26H\\IQJOLMOMPNPQNWNYP[ RNMONOQMWMYNZP[Q[TZVXXUYQYMXMYO", + 2173: " 41C`DQEOGMJMKNKQIWIYK[ RIMJNJQHWHYIZK[M[OZQXRV RTMRVRYSZU[W[YZ[X\\V]R]M\\M]O RUMSVSYU[", + 2174: " 42H\\KQMNOMRMSOSR RQMRORRQVPXNZL[K[JZJYKXLYKZ RQVQYR[U[WZYW RYNXOYPZOZNYMXMVNTPSRRVRYS[", + 2175: " 41G\\HQIOKMNMONOQMWMYO[ RMMNNNQLWLYMZO[Q[SZUXWT RZMV[U^SaPbMbKaJ`J_K^L_K` RYMU[T^RaPb", + 2176: " 31H\\YMXOVQNWLYK[ RLQMOOMRMVO RMOONRNVOXO RLYNYRZUZWY RNYR[U[WYXW", + 2177: " 43G^VGUHVIWHWGUFRFOGMILLL[ RRFPGNIMLM[ R\\G[H\\I]H]G\\FZFXGWIW[ RZFYGXIX[ RIM[M RI[P[ RT[[[", + 2178: " 33G]WGVHWIXHWGUFRFOGMILLL[ RRFPGNIMLM[ RWMW[ RXMX[ RIMXM RI[P[ RT[[[", + 2179: " 35G]VGUHVIWHWGUF RXFRFOGMILLL[ RRFPGNIMLM[ RWHW[ RXFX[ RIMWM RI[P[ RT[[[", + 2180: " 54BcRGQHRISHRGPFMFJGHIGLG[ RMFKGIIHLH[ R]G\\H]I^H]G[FXFUGSIRLR[ RXFVGTISLS[ R]M][ R^M^[ RDM^M RD[K[ RO[V[ RZ[a[", + 2181: " 56BcRGQHRISHRGPFMFJGHIGLG[ RMFKGIIHLH[ R\\G[H\\I]H]G[F R^FXFUGSIRLR[ RXFVGTISLS[ R]H][ R^F^[ RDM]M RD[K[ RO[V[ RZ[a[", + 2182: " 12MXRMR[ RSMS[ ROMSM RO[V[", + 2184: " 25IZWNUMRMONMPLSLVMYNZQ[T[VZ RRMPNNPMSMVNYOZQ[ RMTUT", + 2185: " 43I\\TFQGOJNLMOLTLXMZO[Q[TZVWWUXRYMYIXGVFTF RTFRGPJOLNOMTMXNZO[ RQ[SZUWVUWRXMXIWGVF RNPWP", + 2186: " 42G]UFOb RVFNb RQMMNKPJSJVKXMZP[S[WZYXZUZRYPWNTMQM RQMNNLPKSKVLXNZP[ RS[VZXXYUYRXPVNTM", + 2187: " 27I[TMVNXPXOWNTMQMNNMOLQLSMUOWSZ RQMONNOMQMSNUSZT\\T^S_Q_", + 2190: " 45G]LMKNJPJRKUOYP[ RJRKTOXP[P]O`MbLbKaJ_J\\KXMTOQRNTMVMYNZPZTYXWZU[T[SZSXTWUXTY RVMXNYPYTXXWZ", + 2191: " 69E_YGXHYIZHYGWFTFQGOINKMNLRJ[I_Ha RTFRGPIOKNNLWK[J^I`HaFbDbCaC`D_E`Da R_G^H_I`H`G_F]F[GZHYJXMU[T_Sa R]F[HZJYNWWV[U^T`SaQbObNaN`O_P`Oa RIM^M", + 2192: " 52F^[GZH[I\\H[GXFUFRGPIOKNNMRK[J_Ia RUFSGQIPKONMWL[K^J`IaGbEbDaD`E_F`Ea RYMWTVXVZW[Z[\\Y]W RZMXTWXWZX[ RJMZM", + 2193: " 54F^YGXHYIZHZGXF R\\FUFRGPIOKNNMRK[J_Ia RUFSGQIPKONMWL[K^J`IaGbEbDaD`E_F`Ea R[FWTVXVZW[Z[\\Y]W R\\FXTWXWZX[ RJMYM", + 2194: " 86@cTGSHTIUHTGRFOFLGJIIKHNGRE[D_Ca ROFMGKIJKINGWF[E^D`CaAb?b>a>`?_@`?a R`G_H`IaH`G]FZFWGUITKSNRRP[O_Na RZFXGVIUKTNRWQ[P^O`NaLbJbIaI`J_K`Ja R^M\\T[X[Z\\[_[aYbW R_M]T\\X\\Z][ RDM_M", + 2195: " 88@cTGSHTIUHTGRFOFLGJIIKHNGRE[D_Ca ROFMGKIJKINGWF[E^D`CaAb?b>a>`?_@`?a R^G]H^I_H_G]F RaFZFWGUITKSNRRP[O_Na RZFXGVIUKTNRWQ[P^O`NaLbJbIaI`J_K`Ja R`F\\T[X[Z\\[_[aYbW RaF]T\\X\\Z][ RDM^M", + 2196: " 20LYMQNOPMSMTNTQRWRZS[ RRMSNSQQWQZR[U[WYXW", + 2200: " 40H\\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF RQFOGNHMJLOLRMWNYOZQ[ RS[UZVYWWXRXOWJVHUGSF", + 2201: " 11H\\NJPISFS[ RRGR[ RN[W[", + 2202: " 45H\\LJMKLLKKKJLHMGPFTFWGXHYJYLXNUPPRNSLUKXK[ RTFVGWHXJXLWNTPPR RKYLXNXSZVZXYYX RNXS[W[XZYXYV", + 2203: " 47H\\LJMKLLKKKJLHMGPFTFWGXIXLWNTOQO RTFVGWIWLVNTO RTOVPXRYTYWXYWZT[P[MZLYKWKVLUMVLW RWQXTXWWYVZT[", + 2204: " 13H\\THT[ RUFU[ RUFJUZU RQ[X[", + 2205: " 39H\\MFKP RKPMNPMSMVNXPYSYUXXVZS[P[MZLYKWKVLUMVLW RSMUNWPXSXUWXUZS[ RMFWF RMGRGWF", + 2206: " 48H\\WIVJWKXJXIWGUFRFOGMILKKOKULXNZQ[S[VZXXYUYTXQVOSNRNOOMQLT RRFPGNIMKLOLUMXOZQ[ RS[UZWXXUXTWQUOSN", + 2207: " 31H\\KFKL RKJLHNFPFUIWIXHYF RLHNGPGUI RYFYIXLTQSSRVR[ RXLSQRSQVQ[", + 2208: " 63H\\PFMGLILLMNPOTOWNXLXIWGTFPF RPFNGMIMLNNPO RTOVNWLWIVGTF RPOMPLQKSKWLYMZP[T[WZXYYWYSXQWPTO RPONPMQLSLWMYNZP[ RT[VZWYXWXSWQVPTO", + 2209: " 48H\\XMWPURRSQSNRLPKMKLLINGQFSFVGXIYLYRXVWXUZR[O[MZLXLWMVNWMX RQSORMPLMLLMIOGQF RSFUGWIXLXRWVVXTZR[", + 2210: " 6MWRYQZR[SZRY", + 2211: " 8MWR[QZRYSZS\\R^Q_", + 2212: " 12MWRMQNROSNRM RRYQZR[SZRY", + 2213: " 14MWRMQNROSNRM RR[QZRYSZS\\R^Q_", + 2214: " 15MWRFQHRTSHRF RRHRN RRYQZR[SZRY", + 2215: " 32I[MJNKMLLKLJMHNGPFSFVGWHXJXLWNVORQRT RSFUGVHWJWLVNTP RRYQZR[SZRY", + 2216: " 6NVRFQM RSFQM", + 2217: " 12JZNFMM ROFMM RVFUM RWFUM", + 2218: " 14KYQFOGNINKOMQNSNUMVKVIUGSFQF", + 2219: " 9JZRFRR RMIWO RWIMO", + 2220: " 3G][BIb", + 2221: " 20KYVBTDRGPKOPOTPYR]T`Vb RTDRHQKPPPTQYR\\T`", + 2222: " 20KYNBPDRGTKUPUTTYR]P`Nb RPDRHSKTPTTSYR\\P`", + 2223: " 12KYOBOb RPBPb ROBVB RObVb", + 2224: " 12KYTBTb RUBUb RNBUB RNbUb", + 2225: " 40KYTBRCQDPFPHQJRKSMSOQQ RRCQEQGRISJTLTNSPORSTTVTXSZR[Q]Q_Ra RQSSUSWRYQZP\\P^Q`RaTb", + 2226: " 40KYPBRCSDTFTHSJRKQMQOSQ RRCSESGRIQJPLPNQPURQTPVPXQZR[S]S_Ra RSSQUQWRYSZT\\T^S`RaPb", + 2227: " 4KYUBNRUb", + 2228: " 4KYOBVROb", + 2229: " 3NVRBRb", + 2230: " 6KYOBOb RUBUb", + 2231: " 3E_IR[R", + 2232: " 6E_RIR[ RIR[R", + 2233: " 9F^RJR[ RJRZR RJ[Z[", + 2234: " 9F^RJR[ RJJZJ RJRZR", + 2235: " 6G]KKYY RYKKY", + 2236: " 6MWRQQRRSSRRQ", + 2237: " 15E_RIQJRKSJRI RIR[R RRYQZR[SZRY", + 2238: " 6E_IO[O RIU[U", + 2239: " 9E_YIK[ RIO[O RIU[U", + 2240: " 9E_IM[M RIR[R RIW[W", + 2241: " 4F^ZIJRZ[", + 2242: " 4F^JIZRJ[", + 2243: " 10F^ZFJMZT RJVZV RJ[Z[", + 2244: " 10F^JFZMJT RJVZV RJ[Z[", + 2245: " 21F_[WYWWVUTRPQOONMNKOJQJSKUMVOVQURTUPWNYM[M", + 2246: " 24F^IUISJPLONOPPTSVTXTZS[Q RISJQLPNPPQTTVUXUZT[Q[O", + 2247: " 8G]JTROZT RJTRPZT", + 2248: " 7LXTFOL RTFUGOL", + 2249: " 7LXPFUL RPFOGUL", + 2250: " 18H\\KFLHNJQKSKVJXHYF RKFLINKQLSLVKXIYF", + 2251: " 8MWRHQGRFSGSIRKQL", + 2252: " 8MWSFRGQIQKRLSKRJ", + 2253: " 8MWRHSGRFQGQIRKSL", + 2254: " 8MWQFRGSISKRLQKRJ", + 2255: " 10E[HMLMRY RKMR[ R[BR[", + 2256: " 13F^ZJSJOKMLKNJQJSKVMXOYSZZZ", + 2257: " 13F^JJJQKULWNYQZSZVYXWYUZQZJ", + 2258: " 13F^JJQJUKWLYNZQZSYVWXUYQZJZ", + 2259: " 13F^JZJSKOLMNKQJSJVKXMYOZSZZ", + 2260: " 16F^ZJSJOKMLKNJQJSKVMXOYSZZZ RJRVR", + 2261: " 11E_XP[RXT RUMZRUW RIRZR", + 2262: " 11JZPLRITL RMORJWO RRJR[", + 2263: " 11E_LPIRLT ROMJROW RJR[R", + 2264: " 11JZPXR[TX RMURZWU RRIRZ", + 2265: " 44I\\XRWOVNTMRMONMQLTLWMYNZP[R[UZWXXUYPYKXHWGUFRFPGOHOIPIPH RRMPNNQMTMXNZ RR[TZVXWUXPXKWHUF", + 2266: " 15H\\JFR[ RKFRY RZFR[ RJFZF RKGYG", + 2267: " 10AbDMIMRY RHNR[ Rb:R[", + 2268: " 32F^[CZD[E\\D\\C[BYBWCUETGSJRNPZO^N` RVDUFTJRVQZP]O_MaKbIbHaH`I_J`Ia", + 2269: " 50F^[CZD[E\\D\\C[BYBWCUETGSJRNPZO^N` RVDUFTJRVQZP]O_MaKbIbHaH`I_J`Ia RQKNLLNKQKSLVNXQYSYVXXVYSYQXNVLSKQK", + 2270: " 26F_\\S[UYVWVUUTTQPPONNLNJOIQISJULVNVPUQTTPUOWNYN[O\\Q\\S", + 2271: " 32F^[FI[ RNFPHPJOLMMKMIKIIJGLFNFPGSHVHYG[F RWTUUTWTYV[X[ZZ[X[VYTWT", + 2272: " 49F_[NZO[P\\O\\N[MZMYNXPVUTXRZP[M[JZIXIUJSPORMSKSIRGPFNGMIMKNNPQUXWZZ[[[\\Z\\Y RM[KZJXJUKSMQ RMKNMVXXZZ[", + 2273: " 56E`WNVLTKQKOLNMMPMSNUPVSVUUVS RQKOMNPNSOUPV RWKVSVUXVZV\\T]Q]O\\L[JYHWGTFQFNGLHJJILHOHRIUJWLYNZQ[T[WZYYZX RXKWSWUXV", + 2274: " 42H\\PBP_ RTBT_ RXIWJXKYJYIWGTFPFMGKIKKLMMNOOUQWRYT RKKMMONUPWQXRYTYXWZT[P[MZKXKWLVMWLX", + 2275: " 12H]SFLb RYFRb RLQZQ RKWYW", + 2276: " 46JZUITJUKVJVIUGSFQFOGNINKOMQOVR ROMTPVRWTWVVXTZ RPNNPMRMTNVPXU[ RNVSYU[V]V_UaSbQbOaN_N^O]P^O_", + 2277: " 30JZRFQHRJSHRF RRFRb RRQQTRbSTRQ RLMNNPMNLLM RLMXM RTMVNXMVLTM", + 2278: " 56JZRFQHRJSHRF RRFRT RRPQRSVRXQVSRRP RRTRb RR^Q`RbS`R^ RLMNNPMNLLM RLMXM RTMVNXMVLTM RL[N\\P[NZL[ RL[X[ RT[V\\X[VZT[", + 2279: " 12I\\XFX[ RKFXF RPPXP RK[X[", + 2281: " 38E`QFNGKIILHOHRIUKXNZQ[T[WZZX\\U]R]O\\LZIWGTFQF RROQPQQRRSRTQTPSORO RRPRQSQSPRP", + 2282: " 45J[PFNGOIQJ RPFOGOI RUFWGVITJ RUFVGVI RQJOKNLMNMQNSOTQUTUVTWSXQXNWLVKTJQJ RRUR[ RSUS[ RNXWX", + 2283: " 27I\\RFOGMILLLMMPORRSSSVRXPYMYLXIVGSFRF RRSR[ RSSS[ RNWWW", + 2284: " 28D`PFMGJIHLGOGSHVJYM[P\\T\\W[ZY\\V]S]O\\LZIWGTFPF RRFR\\ RGQ]Q", + 2285: " 31G`PMMNKPJSJTKWMYPZQZTYVWWTWSVPTNQMPM R]GWG[HUN R]G]M\\IVO R\\HVN", + 2286: " 28F\\IIJGLFOFQGRIRLQOPQNSKU ROFPGQIQMPPNS RVFT[ RWFS[ RKUYU", + 2287: " 30I\\MFMU RNFMQ RMQNOONQMTMWNXPXRWTUV RTMVNWPWRTXTZU[W[YY RKFNF", + 2288: " 44I\\RNOOMQLTLUMXOZR[S[VZXXYUYTXQVOSNRN RRHNJRFRN RSHWJSFSN RRSQTQURVSVTUTTSSRS RRTRUSUSTRT", + 2289: " 37G^QHRFR[ RTHSFS[ RJHKFKMLPNRQSRS RMHLFLNMQ R[HZFZMYPWRTSSS RXHYFYNXQ RNWWW", + 2290: " 31G]LFL[ RMFM[ RIFUFXGYHZJZMYOXPUQMQ RUFWGXHYJYMXOWPUQ RI[Y[YVX[", + 2291: " 24H[YGUGQHNJLMKPKSLVNYQ[U\\Y\\ RYGVHSJQMPPPSQVSYV[Y\\", + 2292: " 27F_OQMQKRJSIUIWJYKZM[O[QZRYSWSURSQROQ RSHPQ RZJRR R\\QST", + 2293: " 12H\\OKUY RUKOY RKOYU RYOKU", + 2294: " 48F^NVLUKUIVHXHYI[K\\L\\N[OYOXNVKRJOJMKJMHPGTGWHYJZMZOYRVVUXUYV[X\\Y\\[[\\Y\\X[VYUXUVV RJMKKMIPHTHWIYKZM", + 2295: " 48F^NMLNKNIMHKHJIHKGLGNHOJOKNMKQJTJVKYM[P\\T\\W[YYZVZTYQVMUKUJVHXGYG[H\\J\\K[MYNXNVM RJVKXMZP[T[WZYXZV", + 2301: " 40F_JMILIJJHLGNGPHQIRKSP RIJKHMHOIPJQLRPR[ R[M\\L\\J[HYGWGUHTISKRP R\\JZHXHVIUJTLSPS[", + 2302: " 51F^IGJKKMMOPPTPWOYMZK[G RIGJJKLMNPOTOWNYLZJ[G RPONPMQLSLVMXOZQ[S[UZWXXVXSWQVPTO RPPNQMSMVNY RVYWVWSVQTP", + 2303: " 30F^MJMV RNKNU RVKVU RWJWV RIGKIMJPKTKWJYI[G RIYKWMVPUTUWVYW[Y", + 2304: " 48F^[ILIJJILINJPLQNQPPQNQLPJ[J RIMJOKPMQ RQMPKOJMI RIXXXZW[U[SZQXPVPTQSSSUTWIW R[TZRYQWP RSTTVUWWX", + 2305: " 48F]OUMTLTJUIWIXJZL[M[OZPXPWOUJPINIKJILHOGSGWHYJZLZOYRVUUWUYV[X[YZZX RMSKPJNJKKILH RSGVHXJYLYOXRVU", + 2306: " 48G_HKKHMKMV RJILLLV RMKPHRKRU ROIQLQU RRKUHWKW[ RTIVLV[ RWKZH[J\\M\\P[SZUXWUYP[ RYIZJ[M[PZSYUWWTYP[", + 2307: " 41F^ISMSLRKOKMLJNHQGSGVHXJYMYOXRWS[S RITOTMRLOLMMJOHQG RSGUHWJXMXOWRUT[T RKXYX RKYYY", + 2308: " 30F_GLJIMLMX RIJLMLX RMLPISLSX ROJRMRX RSLVIYLYW[Y RUJXMXXZZ]W", + 2309: " 33G]ZIJY RZIWJQJ RXKUKQJ RZIYLYR RXKXNYR RQRJR RPSMSJR RQRQY RPSPVQY", + 2310: " 33F^HOJKOU RJMOWRPWPZO[M[KZIXHWHUITKTMUPVRWUWXUZ RWHVIUKUMWQXTXWWYUZ", + 2311: " 36F^IOLLPN RKMOORLUN RQMTOWLYN RVMXO[L RIULRPT RKSOURRUT RQSTUWRYT RVSXU[R", + 2312: " 48F^JHNJPLQOQRPUNWJY RJHMIOJQLRO RRRQUOWMXJY RZHWIUJSLRO RRRSUUWWXZY RZHVJTLSOSRTUVWZY RIP[P RIQ[Q", + 2317: " 12NVQQQSSSSQQQ RQQSS RSQQS", + 2318: " 18JZMPQRTTVVWYW[V]U^ RMQST RMRPSTUVWWY", + 2319: " 18JZWKVMTOPQMR RSPMS RUFVGWIWKVNTPQRMT", + 2320: " 36H\\SMONLPKRKTLVNWQWUVXTYRYPXNVMSM RXNSM RVMQNLP RONKR RLVQW RNWSVXT RUVYR", + 2321: " 36H\\SMONLPKRKTLVNWQWUVXTYRYPXNVMSM RXNSM RVMQNLP RONKR RLVQW RNWSVXT RUVYR", + 2322: " 34J[SMPNNPMRMTNVPWRWUVWTXRXPWNUMSM ROPUM RNRVN RMTWO RNUXP ROVWR RPWVT", + 2323: " 18JZOGO^ RUFU] RMNWL RMOWM RMWWU RMXWV", + 2324: " 18JZNFNX RVLV^ RNNVL RNOVM RNWVU RNXVV", + 2325: " 25JZNBNW RNNQLTLVMWOWQVSSUQVNW RNNQMTMVN RUMVOVQUSSU", + 2326: " 18E_HIHL R\\I\\L RHI\\I RHJ\\J RHK\\K RHL\\L", + 2327: " 18JZMNMQ RWNWQ RMNWN RMOWO RMPWP RMQWQ", + 2328: " 49JZMLWX RMLONQOTOVNWMWKUKUMTO RONTO RQOWM RVKVN RULWL RWXUVSUPUNVMWMYOYOWPU RUVPU RSUMW RNVNY RMXOX", + 2329: " 26JZPOOMOKMKMMNNPOSOUNWL RNKNN RMLOL RMMSO RPOUN RWLWY", + 2330: " 86A^GfHfIeIdHcGcFdFfGhIiKiNhPfQdR`RUQ;Q4R/S-U,V,X-Y/Y3X6W8U;P?JCHEFHEJDNDREVGYJ[N\\R\\V[XZZW[T[PZMYKWITHPHMIKKJNJRKUMW RGdGeHeHdGd RU;Q?LCIFGIFKENERFVGXJ[ RR\\U[WZYWZTZPYMXKVITH", + 2331: "103EfNSOUQVSVUUVSVQUOSNQNOONPMSMVNYP[S\\V\\Y[[Y\\W]T]P\\MZJXIUHRHOIMJKLIOHSHXI]KaMcPeTfYf]e`cba RKLJNIRIXJ\\L`NbQdUeYe]d_cba RPOTO ROPUP RNQVQ RNRVR RNSVS ROTUT RPUTU RaLaNcNcLaL RbLbN RaMcM RaVaXcXcVaV RbVbX RaWcW", + 2332: " 30D`H@Hd RM@Md RW@Wd R\\@\\d RMMWK RMNWL RMOWM RMWWU RMXWV RMYWW", + 2367: " 12NVQQQSSSSQQQ RQQSS RSQQS", + 2368: " 18JZMPQRTTVVWYW[V]U^ RMQST RMRPSTUVWWY", + 2369: " 18JZWKVMTOPQMR RSPMS RUFVGWIWKVNTPQRMT", + 2370: " 32H\\PMMNLOKQKSLUMVPWTWWVXUYSYQXOWNTMPM RMNLPLSMUNVPW RWVXTXQWOVNTM", + 2371: " 36H\\SMONLPKRKTLVNWQWUVXTYRYPXNVMSM RXNSM RVMQNLP RONKR RLVQW RNWSVXT RUVYR", + 2372: " 34J[SMPNNPMRMTNVPWRWUVWTXRXPWNUMSM ROPUM RNRVN RMTWO RNUXP ROVWR RPWVT", + 2373: " 18JZOGO^ RUFU] RMNWL RMOWM RMWWU RMXWV", + 2374: " 18JZNFNX RVLV^ RNNVL RNOVM RNWVU RNXVV", + 2375: " 25JZNBNW RNNQLTLVMWOWQVSSUQVNW RNNQMTMVN RUMVOVQUSSU", + 2376: " 18E_HIHL R\\I\\L RHI\\I RHJ\\J RHK\\K RHL\\L", + 2377: " 18JZMNMQ RWNWQ RMNWN RMOWO RMPWP RMQWQ", + 2378: " 36JZQCVMRTRU RULQS RTITKPRRUUY RW\\UYSXQXOYN[N]O_Ra RW\\UZSYOYO]P_Ra RSXPZN]", + 2379: " 26JZPOOMOKMKMMNNPOSOUNWL RNKNN RMLOL RMMSO RPOUN RWLSY", + 2380: " 86A^GfHfIeIdHcGcFdFfGhIiKiNhPfQdR`RUQ;Q4R/S-U,V,X-Y/Y3X6W8U;P?JCHEFHEJDNDREVGYJ[N\\R\\V[XZZW[T[PZMYKWITHPHMIKKJNJRKUMW RGdGeHeHdGd RU;Q?LCIFGIFKENERFVGXJ[ RR\\U[WZYWZTZPYMXKVITH", + 2381: " 89IjNQOOQNSNUOVQVSUUSVQVOUNTMQMNNKPISHWH[I^K`NaRaW`[_]]`ZcVfQiMk RWHZI]K_N`R`W_[^]\\`YcTgQi RPOTO ROPUP RNQVQ RNRVR RNSVS ROTUT RPUTU ReLeNgNgLeL RfLfN ReMgM ReVeXgXgVeV RfVfX ReWgW", + 2382: " 85D`H>Hf RI>If RM>Mf RQBSBSDQDQAR?T>W>Y?[A\\D\\I[LYNWOUOSNRLQNOQNROSQVRXSVUUWUYV[X\\[\\`[cYeWfTfReQcQ`S`SbQb RRBRD RQCSC RY?ZA[D[IZLYN RRLRNPQNRPSRVRX RYVZX[[[`ZcYe RR`Rb RQaSa", + 2401: " 21AcHBHb RIBIb R[B[b R\\B\\b RDB`B RDbMb RWb`b", + 2402: " 23BaGBQPFb RFBPP REBPQ REB\\B^I[B RGa\\a RFb\\b^[[b", + 2403: " 28I[X+U1R8P=OANFMNMVN^OcPgRlUsXy RU1S6Q": 2242, + "~": 2246, + "@": 2273, + "\\": 804 + }[x]; +} +const CONFIG = { + SUB_SUP_SCALE: 0.5, + SQRT_MAG_SCALE: 0.5, + FRAC_SCALE: 0.85, + LINE_SPACING: 0.5, + FRAC_SPACING: 0.4 +}; +function tokenize(str) { + str = str.replace(/\n/g, " "); + let i = 0; + const tokens = []; + let curr = ""; + while (i < str.length) { + if (str[i] == " ") { + if (curr.length) { + tokens.push(curr); + curr = ""; + } + } else if (str[i] == "\\") { + if (curr.length == 1 && curr[0] == "\\") { + curr += str[i]; + tokens.push(curr); + curr = ""; + } else { + if (curr.length) { + tokens.push(curr); + } + curr = str[i]; + } + } else if (/[A-Za-z0-9\.]/.test(str[i])) { + curr += str[i]; + } else { + if (curr.length && curr != "\\") { + tokens.push(curr); + curr = ""; + } + curr += str[i]; + tokens.push(curr); + curr = ""; + } + i++; + } + if (curr.length) + tokens.push(curr); + return tokens; +} +function parseAtom(x) { + return { + type: SYMB[x] ? "symb" : "char", + mode: "math", + text: x, + chld: [], + bbox: null + }; +} +function parse(tokens) { + let i = 0; + let expr = { + type: "node", + text: "", + mode: "math", + chld: [], + bbox: null + }; + function takeOpt() { + if (tokens[i] != "[") { + return null; + } + let lvl = 0; + let j = i; + while (j < tokens.length) { + if (tokens[j] == "[") { + lvl++; + } else if (tokens[j] == "]") { + lvl--; + if (!lvl) { + break; + } + } + j++; + } + const ret = parse(tokens.slice(i + 1, j)); + i = j; + return ret; + } + function takeN(n) { + let j = i; + let j0 = j; + let lvl = 0; + let cnt = 0; + const ret = []; + while (j < tokens.length) { + if (tokens[j] == "{") { + if (!lvl) { + j0 = j; + } + lvl++; + } else if (tokens[j] == "}") { + lvl--; + if (!lvl) { + ret.push(parse(tokens.slice(j0 + 1, j))); + cnt++; + if (cnt == n) { + break; + } + } + } else { + if (lvl == 0) { + ret.push(parseAtom(tokens[j])); + cnt++; + if (cnt == n) { + break; + } + } + } + j++; + } + i = j; + return ret; + } + for (i = 0; i < tokens.length; i++) { + const s = SYMB[tokens[i]]; + const e = { + type: "", + text: tokens[i], + mode: "math", + chld: [], + bbox: null + }; + if (s) { + if (s.arity) { + i++; + e.type = "func"; + let opt = null; + if (s.flags.opt) { + opt = takeOpt(); + if (opt) + i++; + } + const chld = takeN(s.arity); + e.chld = chld; + if (opt) { + e.chld.push(opt); + } + } else { + e.type = "symb"; + } + } else { + if (tokens[i] == "{") { + e.type = "node"; + e.text = ""; + e.chld = takeN(1); + } else { + e.type = "char"; + } + } + expr.chld.push(e); + } + if (expr.chld.length == 1) { + expr = expr.chld[0]; + } + return expr; +} +function environments(exprs) { + let i = 0; + while (i < exprs.length) { + if (exprs[i].text == "\\begin") { + let j; + for (j = i; j < exprs.length; j++) { + if (exprs[j].text == "\\end") { + break; + } + } + const es = exprs.splice(i + 1, j - (i + 1)); + environments(es); + exprs[i].text = exprs[i].chld[0].text; + exprs[i].chld = es; + exprs.splice(i + 1, 1); + } + i++; + } +} +function transform(expr, sclx, scly, x, y, notFirst) { + if (scly == null) { + scly = sclx; + } + if (!expr.bbox) + return; + if (notFirst) { + expr.bbox.x *= sclx; + expr.bbox.y *= scly; + } + expr.bbox.w *= sclx; + expr.bbox.h *= scly; + for (let i = 0; i < expr.chld.length; i++) { + transform(expr.chld[i], sclx, scly, 0, 0, true); + } + expr.bbox.x += x; + expr.bbox.y += y; +} +function computeBbox(exprs) { + let xmin = Infinity; + let xmax = -Infinity; + let ymin = Infinity; + let ymax = -Infinity; + for (let i = 0; i < exprs.length; i++) { + if (!exprs[i].bbox) { + continue; + } + xmin = Math.min(xmin, exprs[i].bbox.x); + ymin = Math.min(ymin, exprs[i].bbox.y); + xmax = Math.max(xmax, exprs[i].bbox.x + exprs[i].bbox.w); + ymax = Math.max(ymax, exprs[i].bbox.y + exprs[i].bbox.h); + } + return { x: xmin, y: ymin, w: xmax - xmin, h: ymax - ymin }; +} +function group(exprs) { + if (!exprs.length) { + return null; + } + const bbox = computeBbox(exprs); + for (let i = 0; i < exprs.length; i++) { + if (!exprs[i].bbox) { + continue; + } + exprs[i].bbox.x -= bbox.x; + exprs[i].bbox.y -= bbox.y; + } + const expr = { + type: "node", + text: "", + mode: "math", + chld: exprs, + bbox + }; + return expr; +} +function align(exprs, alignment = "center") { + for (let i = 0; i < exprs.length; i++) { + if (exprs[i].text == "^" || exprs[i].text == "'") { + let h = 0; + let j = i; + while (j > 0 && (exprs[j].text == "^" || exprs[j].text == "_" || exprs[j].text == "'")) { + j--; + } + h = exprs[j].bbox.y; + if (exprs[i].text == "'") { + exprs[i].bbox.y = h; + } else { + transform(exprs[i], CONFIG.SUB_SUP_SCALE, null, 0, 0); + if (SYMB[exprs[j].text] && SYMB[exprs[j].text].flags.big) { + exprs[i].bbox.y = h - exprs[i].bbox.h; + } else if (exprs[j].text == "\\int") { + exprs[i].bbox.y = h; + } else { + exprs[i].bbox.y = h - exprs[i].bbox.h / 2; + } + } + } else if (exprs[i].text == "_") { + let h = 1; + let j = i; + while (j > 0 && (exprs[j].text == "^" || exprs[j].text == "_" || exprs[j].text == "'")) { + j--; + } + h = exprs[j].bbox.y + exprs[j].bbox.h; + transform(exprs[i], CONFIG.SUB_SUP_SCALE, null, 0, 0); + if (SYMB[exprs[j].text] && SYMB[exprs[j].text].flags.big) { + exprs[i].bbox.y = h; + } else if (exprs[j].text == "\\int") { + exprs[i].bbox.y = h - exprs[i].bbox.h; + } else { + exprs[i].bbox.y = h - exprs[i].bbox.h / 2; + } + } + } + function searchHigh(i, l, r, dir, lvl0) { + let j = i; + let lvl = lvl0; + let ymin = Infinity; + let ymax = -Infinity; + while (dir > 0 ? j < exprs.length : j >= 0) { + if (exprs[j].text == l) { + lvl++; + } else if (exprs[j].text == r) { + lvl--; + if (lvl == 0) { + break; + } + } else if (exprs[j].text == "^" || exprs[j].text == "_") + ; + else if (exprs[j].bbox) { + ymin = Math.min(ymin, exprs[j].bbox.y); + ymax = Math.max(ymax, exprs[j].bbox.y + exprs[j].bbox.h); + } + j += dir; + } + return [ymin, ymax]; + } + for (let i = 0; i < exprs.length; i++) { + if (exprs[i].text == "\\left") { + const [ymin, ymax] = searchHigh(i, "\\left", "\\right", 1, 0); + if (ymin != Infinity && ymax != -Infinity) { + exprs[i].bbox.y = ymin; + transform(exprs[i], 1, (ymax - ymin) / exprs[i].bbox.h, 0, 0); + } + } else if (exprs[i].text == "\\right") { + const [ymin, ymax] = searchHigh(i, "\\right", "\\left", -1, 0); + if (ymin != Infinity && ymax != -Infinity) { + exprs[i].bbox.y = ymin; + transform(exprs[i], 1, (ymax - ymin) / exprs[i].bbox.h, 0, 0); + } + } else if (exprs[i].text == "\\middle") { + const [lmin, lmax] = searchHigh(i, "\\right", "\\left", -1, 1); + const [rmin, rmax] = searchHigh(i, "\\left", "\\right", 1, 1); + const ymin = Math.min(lmin, rmin); + const ymax = Math.max(lmax, rmax); + if (ymin != Infinity && ymax != -Infinity) { + exprs[i].bbox.y = ymin; + transform(exprs[i], 1, (ymax - ymin) / exprs[i].bbox.h, 0, 0); + } + } + } + if (!exprs.some((x) => x.text == "&" || x.text == "\\\\")) { + return; + } + const rows = []; + let row = []; + let cell = []; + for (let i = 0; i < exprs.length; i++) { + if (exprs[i].text == "&") { + row.push(cell); + cell = []; + } else if (exprs[i].text == "\\\\") { + if (cell.length) { + row.push(cell); + cell = []; + } + rows.push(row); + row = []; + } else { + cell.push(exprs[i]); + } + } + if (cell.length) { + row.push(cell); + } + if (row.length) { + rows.push(row); + } + const colws = []; + const erows = []; + for (let i = 0; i < rows.length; i++) { + const erow = []; + for (let j = 0; j < rows[i].length; j++) { + const e = group(rows[i][j]); + if (e) { + colws[j] = colws[j] || 0; + colws[j] = Math.max(e.bbox.w + 1, colws[j]); + } + erow[j] = e; + } + erows.push(erow); + } + const ybds = []; + for (let i = 0; i < erows.length; i++) { + let ymin = Infinity; + let ymax = -Infinity; + for (let j = 0; j < erows[i].length; j++) { + if (!erows[i][j]) { + continue; + } + ymin = Math.min(ymin, erows[i][j].bbox.y); + ymax = Math.max(ymax, erows[i][j].bbox.y + erows[i][j].bbox.h); + } + ybds.push([ymin, ymax]); + } + for (let i = 0; i < ybds.length; i++) { + if (ybds[i][0] == Infinity || ybds[i][1] == Infinity) { + ybds[i][0] = i == 0 ? 0 : ybds[i - 1][1]; + ybds[i][1] = ybds[i][0] + 2; + } + } + for (let i = 1; i < erows.length; i++) { + const shft = ybds[i - 1][1] - ybds[i][0] + CONFIG.LINE_SPACING; + for (let j = 0; j < erows[i].length; j++) { + if (erows[i][j]) { + erows[i][j].bbox.y += shft; + } + } + ybds[i][0] += shft; + ybds[i][1] += shft; + } + exprs.splice(0, exprs.length); + for (let i = 0; i < erows.length; i++) { + let dx = 0; + for (let j = 0; j < erows[i].length; j++) { + const e = erows[i][j]; + if (!e) { + dx += colws[j]; + continue; + } + e.bbox.x += dx; + dx += colws[j] - e.bbox.w; + if (alignment == "center") { + e.bbox.x += (colws[j] - e.bbox.w) / 2; + } else if (alignment == "left") + ; + else if (alignment == "right") { + e.bbox.x += colws[j] - e.bbox.w; + } else if (alignment == "equation") { + if (j != erows[i].length - 1) { + e.bbox.x += colws[j] - e.bbox.w; + } + } + exprs.push(e); + } + } +} +function plan(expr, mode = "math") { + var _a, _b, _c; + const tmd = (_a = { + "\\text": "text", + "\\mathnormal": "math", + "\\mathrm": "rm", + "\\mathit": "it", + "\\mathbf": "bf", + "\\mathsf": "sf", + "\\mathtt": "tt", + "\\mathfrak": "frak", + "\\mathcal": "cal", + "\\mathbb": "bb", + "\\mathscr": "scr", + "\\rm": "rm", + "\\it": "it", + "\\bf": "bf", + "\\sf": "tt", + "\\tt": "tt", + "\\frak": "frak", + "\\cal": "cal", + "\\bb": "bb", + "\\scr": "scr" + }[expr.text]) != null ? _a : mode; + if (!expr.chld.length) { + if (SYMB[expr.text]) { + if (SYMB[expr.text].flags.big) { + if (expr.text == "\\lim") { + expr.bbox = { x: 0, y: 0, w: 3.5, h: 2 }; + } else { + expr.bbox = { x: 0, y: -0.5, w: 3, h: 3 }; + } + } else if (SYMB[expr.text].flags.txt) { + let w = 0; + for (let i = 1; i < expr.text.length; i++) { + w += HERSHEY(asciiMap(expr.text[i], "text")).w; + } + w /= 16; + expr.bbox = { x: 0, y: 0, w, h: 2 }; + } else if (SYMB[expr.text].glyph) { + let w = HERSHEY(SYMB[expr.text].glyph).w; + w /= 16; + if (expr.text == "\\int" || expr.text == "\\oint") { + expr.bbox = { x: 0, y: -1.5, w, h: 5 }; + } else { + expr.bbox = { x: 0, y: 0, w, h: 2 }; + } + } else { + expr.bbox = { x: 0, y: 0, w: 1, h: 2 }; + } + } else { + let w = 0; + for (let i = 0; i < expr.text.length; i++) { + if (!HERSHEY(asciiMap(expr.text[i], tmd))) { + continue; + } + if (tmd == "tt") { + w += 16; + } else { + w += HERSHEY(asciiMap(expr.text[i], tmd)).w; + } + } + w /= 16; + expr.bbox = { x: 0, y: 0, w, h: 2 }; + } + expr.mode = tmd; + return; + } + if (expr.text == "\\frac") { + const a = expr.chld[0]; + const b = expr.chld[1]; + const s = CONFIG.FRAC_SCALE; + plan(a); + plan(b); + a.bbox.x = 0; + a.bbox.y = 0; + b.bbox.x = 0; + b.bbox.y = 0; + const mw = Math.max(a.bbox.w, b.bbox.w) * s; + transform(a, s, null, (mw - a.bbox.w * s) / 2, 0); + transform( + b, + s, + null, + (mw - b.bbox.w * s) / 2, + a.bbox.h + CONFIG.FRAC_SPACING + ); + expr.bbox = { + x: 0, + y: -a.bbox.h + 1 - CONFIG.FRAC_SPACING / 2, + w: mw, + h: a.bbox.h + b.bbox.h + CONFIG.FRAC_SPACING + }; + } else if (expr.text == "\\binom") { + const a = expr.chld[0]; + const b = expr.chld[1]; + plan(a); + plan(b); + a.bbox.x = 0; + a.bbox.y = 0; + b.bbox.x = 0; + b.bbox.y = 0; + const mw = Math.max(a.bbox.w, b.bbox.w); + transform(a, 1, null, (mw - a.bbox.w) / 2 + 1, 0); + transform(b, 1, null, (mw - b.bbox.w) / 2 + 1, a.bbox.h); + expr.bbox = { x: 0, y: -a.bbox.h + 1, w: mw + 2, h: a.bbox.h + b.bbox.h }; + } else if (expr.text == "\\sqrt") { + const e = expr.chld[0]; + plan(e); + const f = expr.chld[1]; + let pl = 0; + if (f) { + plan(f); + pl = Math.max(f.bbox.w * CONFIG.SQRT_MAG_SCALE - 0.5, 0); + transform(f, CONFIG.SQRT_MAG_SCALE, null, 0, 0.5); + } + transform(e, 1, null, 1 + pl, 0.5); + expr.bbox = { + x: 0, + y: 2 - e.bbox.h - 0.5, + w: e.bbox.w + 1 + pl, + h: e.bbox.h + 0.5 + }; + } else if (SYMB[expr.text] && SYMB[expr.text].flags.hat) { + const e = expr.chld[0]; + plan(e); + const y0 = e.bbox.y - 0.5; + e.bbox.y = 0.5; + expr.bbox = { x: 0, y: y0, w: e.bbox.w, h: e.bbox.h + 0.5 }; + } else if (SYMB[expr.text] && SYMB[expr.text].flags.mat) { + const e = expr.chld[0]; + plan(e); + expr.bbox = { x: 0, y: 0, w: e.bbox.w, h: e.bbox.h + 0.5 }; + } else { + let dx = 0; + let dy = 0; + let mh = 1; + for (let i = 0; i < expr.chld.length; i++) { + const c = expr.chld[i]; + const spac = (_b = { + "\\quad": 2, + "\\,": 2 * 3 / 18, + "\\:": 2 * 4 / 18, + "\\;": 2 * 5 / 18, + "\\!": 2 * -3 / 18 + }[c.text]) != null ? _b : null; + if (c.text == "\\\\") { + dy += mh; + dx = 0; + mh = 1; + continue; + } else if (c.text == "&") { + continue; + } else if (spac != null) { + dx += spac; + continue; + } else { + plan(c, tmd); + transform(c, 1, null, dx, dy); + if (c.text == "^" || c.text == "_" || c.text == "'") { + let j = i; + while (j > 0 && (expr.chld[j].text == "^" || expr.chld[j].text == "_" || expr.chld[j].text == "'")) { + j--; + } + const wasBig = SYMB[expr.chld[j].text] && SYMB[expr.chld[j].text].flags.big; + if (c.text == "'") { + let k = j + 1; + let nth = 0; + while (k < i) { + if (expr.chld[k].text == "'") { + nth++; + } + k++; + } + c.bbox.x = expr.chld[j].bbox.x + expr.chld[j].bbox.w + c.bbox.w * nth; + dx = Math.max(dx, c.bbox.x + c.bbox.w); + } else { + if (wasBig) { + const ex = expr.chld[j].bbox.x + (expr.chld[j].bbox.w - c.bbox.w * CONFIG.SUB_SUP_SCALE) / 2; + c.bbox.x = ex; + dx = Math.max(dx, expr.chld[j].bbox.x + expr.chld[j].bbox.w + (c.bbox.w * CONFIG.SUB_SUP_SCALE - expr.chld[j].bbox.w) / 2); + } else { + c.bbox.x = expr.chld[j].bbox.x + expr.chld[j].bbox.w; + dx = Math.max(dx, c.bbox.x + c.bbox.w * CONFIG.SUB_SUP_SCALE); + } + } + } else { + dx += c.bbox.w; + } + if (mode == "text") { + dx += 1; + } + mh = Math.max(c.bbox.y + c.bbox.h - dy, mh); + } + } + dy += mh; + const m2s = { + bmatrix: ["[", "]"], + pmatrix: ["(", ")"], + Bmatrix: ["\\{", "\\}"], + cases: ["\\{"] + }; + const alt = (_c = { + bmatrix: "center", + pmatrix: "center", + Bmatrix: "center", + cases: "left", + matrix: "center", + aligned: "equation" + }[expr.text]) != null ? _c : "left"; + const hasLp = !!m2s[expr.text]; + const hasRp = !!m2s[expr.text] && m2s[expr.text].length > 1; + align(expr.chld, alt); + const bb = computeBbox(expr.chld); + if (expr.text == "\\text") { + bb.x -= 1; + bb.w += 2; + } + for (let i = 0; i < expr.chld.length; i++) { + transform(expr.chld[i], 1, null, -bb.x + (hasLp ? 1.5 : 0), -bb.y); + } + expr.bbox = { + x: 0, + y: 0, + w: bb.w + 1.5 * Number(hasLp) + 1.5 * Number(hasRp), + h: bb.h + }; + if (hasLp) { + expr.chld.unshift({ + type: "symb", + text: m2s[expr.text][0], + mode: expr.mode, + chld: [], + bbox: { x: 0, y: 0, w: 1, h: bb.h } + }); + } + if (hasRp) { + expr.chld.push({ + type: "symb", + text: m2s[expr.text][1], + mode: expr.mode, + chld: [], + bbox: { x: bb.w + 2, y: 0, w: 1, h: bb.h } + }); + } + if (hasLp || hasRp || expr.text == "matrix") { + expr.type = "node"; + expr.text = ""; + expr.bbox.y -= (expr.bbox.h - 2) / 2; + } + } +} +function flatten(expr) { + function flat(expr2, dx, dy) { + const ff = []; + if (expr2.bbox) { + dx += expr2.bbox.x; + dy += expr2.bbox.y; + if (expr2.text == "\\frac") { + const h = expr2.chld[1].bbox.y - (expr2.chld[0].bbox.y + expr2.chld[0].bbox.h); + const e = { + type: "symb", + mode: expr2.mode, + text: "\\bar", + bbox: { + x: dx, + y: dy + (expr2.chld[1].bbox.y - h / 2) - h / 2, + w: expr2.bbox.w, + h + }, + chld: [] + }; + ff.push(e); + } else if (expr2.text == "\\sqrt") { + const h = expr2.chld[0].bbox.y; + const xx = Math.max(0, expr2.chld[0].bbox.x - expr2.chld[0].bbox.h / 2); + const e = { + type: "symb", + mode: expr2.mode, + text: "\\sqrt", + bbox: { + x: dx + xx, + y: dy + h / 2, + w: expr2.chld[0].bbox.x - xx, + h: expr2.bbox.h - h / 2 + }, + chld: [] + }; + ff.push(e); + ff.push({ + type: "symb", + text: "\\bar", + mode: expr2.mode, + bbox: { + x: dx + expr2.chld[0].bbox.x, + y: dy, + w: expr2.bbox.w - expr2.chld[0].bbox.x, + h + }, + chld: [] + }); + } else if (expr2.text == "\\binom") { + const w = Math.min(expr2.chld[0].bbox.x, expr2.chld[1].bbox.x); + const e = { + type: "symb", + mode: expr2.mode, + text: "(", + bbox: { + x: dx, + y: dy, + w, + h: expr2.bbox.h + }, + chld: [] + }; + ff.push(e); + ff.push({ + type: "symb", + text: ")", + mode: expr2.mode, + bbox: { + x: dx + expr2.bbox.w - w, + y: dy, + w, + h: expr2.bbox.h + }, + chld: [] + }); + } else if (SYMB[expr2.text] && SYMB[expr2.text].flags.hat) { + const h = expr2.chld[0].bbox.y; + const e = { + type: "symb", + mode: expr2.mode, + text: expr2.text, + bbox: { + x: dx, + y: dy, + w: expr2.bbox.w, + h + }, + chld: [] + }; + ff.push(e); + } else if (SYMB[expr2.text] && SYMB[expr2.text].flags.mat) { + const h = expr2.chld[0].bbox.h; + const e = { + type: "symb", + text: expr2.text, + mode: expr2.mode, + bbox: { + x: dx, + y: dy + h, + w: expr2.bbox.w, + h: expr2.bbox.h - h + }, + chld: [] + }; + ff.push(e); + } else if (expr2.type != "node" && expr2.text != "^" && expr2.text != "_") { + const e = { + type: expr2.type == "func" ? "symb" : expr2.type, + text: expr2.text, + mode: expr2.mode, + bbox: { + x: dx, + y: dy, + w: expr2.bbox.w, + h: expr2.bbox.h + }, + chld: [] + }; + ff.push(e); + } + } + for (let i = 0; i < expr2.chld.length; i++) { + const f2 = flat(expr2.chld[i], dx, dy); + ff.push(...f2); + } + return ff; + } + const f = flat(expr, -expr.bbox.x, -expr.bbox.y); + expr.type = "node"; + expr.text = ""; + expr.chld = f; +} +function render(expr) { + const o = []; + for (let i = 0; i < expr.chld.length; i++) { + const e = expr.chld[i]; + let s = e.bbox.h / 2; + let isSmallHat = false; + if (SYMB[e.text] && SYMB[e.text].flags.hat && !SYMB[e.text].flags.xfl && !SYMB[e.text].flags.yfl) { + s *= 4; + isSmallHat = true; + } + if (SYMB[e.text] && SYMB[e.text].glyph) { + const d = HERSHEY(SYMB[e.text].glyph); + for (let j = 0; j < d.polylines.length; j++) { + const l = []; + for (let k = 0; k < d.polylines[j].length; k++) { + let x = d.polylines[j][k][0]; + let y = d.polylines[j][k][1]; + if (SYMB[e.text].flags.xfl) { + x = (x - d.xmin) / Math.max(d.xmax - d.xmin, 1) * e.bbox.w; + x += e.bbox.x; + } else if (d.w / 16 * s > e.bbox.w) { + x = x / Math.max(d.w, 1) * e.bbox.w; + x += e.bbox.x; + } else { + x = x / 16 * s; + const p = (e.bbox.w - d.w / 16 * s) / 2; + x += e.bbox.x + p; + } + if (SYMB[e.text].flags.yfl) { + y = (y - d.ymin) / Math.max(d.ymax - d.ymin, 1) * e.bbox.h; + y += e.bbox.y; + } else { + y = y / 16 * s; + if (isSmallHat) { + const p = (d.ymax + d.ymin) / 2; + y -= p / 16 * s; + } + y += e.bbox.y + e.bbox.h / 2; + } + l.push([x, y]); + } + o.push(l); + } + } else if (SYMB[e.text] && SYMB[e.text].flags.txt || e.type == "char") { + let x0 = e.bbox.x; + const isVerb = !!(SYMB[e.text] && SYMB[e.text].flags.txt); + for (let n = Number(isVerb); n < e.text.length; n++) { + const d = HERSHEY(asciiMap(e.text[n], isVerb ? "text" : e.mode)); + if (!d) { + console.warn("unmapped character: " + e.text[n]); + continue; + } + for (let j = 0; j < d.polylines.length; j++) { + const l = []; + for (let k = 0; k < d.polylines[j].length; k++) { + let x = d.polylines[j][k][0]; + let y = d.polylines[j][k][1]; + x /= 16; + y /= 16; + x *= s; + y *= s; + if (e.mode == "tt") { + if (d.w > 16) { + x *= 16 / d.w; + } else { + x += (16 - d.w) / 2 / 16; + } + } + x += x0; + y += e.bbox.y + e.bbox.h / 2; + l.push([x, y]); + } + o.push(l); + } + if (e.mode == "tt") { + x0 += s; + } else { + x0 += d.w / 16 * s; + } + } + } + } + return o; +} +function nf(x) { + return Math.round(x * 100) / 100; +} +class LaTexUtils { + constructor(latex) { + this._latex = latex; + this._tokens = tokenize(latex); + this._tree = parse(this._tokens); + environments(this._tree.chld); + plan(this._tree); + flatten(this._tree); + this._polylines = render(this._tree); + } + resolveScale(opt) { + var _a, _b, _c, _d; + if (opt == void 0) { + return [16, 16, 16, 16]; + } + let sclx = (_a = opt.SCALE_X) != null ? _a : 16; + let scly = (_b = opt.SCALE_Y) != null ? _b : 16; + if (opt.MIN_CHAR_H != void 0) { + let mh = 0; + for (let i = 0; i < this._tree.chld.length; i++) { + const c = this._tree.chld[i]; + if (c.type == "char" || SYMB[c.text] && (SYMB[c.text].flags.txt || !Object.keys(SYMB[c.text].flags).length)) { + mh = Math.min(c.bbox.h, mh); + } + } + const s = Math.max(1, opt.MIN_CHAR_H / mh); + sclx *= s; + scly *= s; + } + if (opt.MAX_W != void 0) { + const s0 = sclx; + sclx = Math.min(sclx, opt.MAX_W / this._tree.bbox.w); + scly *= sclx / s0; + } + if (opt.MAX_H != void 0) { + const s0 = scly; + scly = Math.min(scly, opt.MAX_H / this._tree.bbox.h); + sclx *= scly / s0; + } + const px = (_c = opt.MARGIN_X) != null ? _c : sclx; + const py = (_d = opt.MARGIN_Y) != null ? _d : scly; + return [px, py, sclx, scly]; + } + polylines(opt) { + if (!opt) + opt = {}; + const polylines = []; + const [px, py, sclx, scly] = this.resolveScale(opt); + for (let i = 0; i < this._polylines.length; i++) { + polylines.push([]); + for (let j = 0; j < this._polylines[i].length; j++) { + const [x, y] = this._polylines[i][j]; + polylines[polylines.length - 1].push([px + x * sclx, py + y * scly]); + } + } + return polylines; + } + pathd(opt) { + if (!opt) + opt = {}; + let d = ""; + const [px, py, sclx, scly] = this.resolveScale(opt); + for (let i = 0; i < this._polylines.length; i++) { + for (let j = 0; j < this._polylines[i].length; j++) { + const [x, y] = this._polylines[i][j]; + d += !j ? "M" : "L"; + d += `${nf(px + x * sclx)} ${nf(py + y * scly)}`; + } + } + return d; + } + svg(opt) { + var _a, _b; + if (!opt) + opt = {}; + const [px, py, sclx, scly] = this.resolveScale(opt); + const w = nf(this._tree.bbox.w * sclx + px * 2); + const h = nf(this._tree.bbox.h * scly + py * 2); + let o = ``; + if (opt.BG_COLOR) { + o += ``; + } + o += ``; + o += ``; + return { + svg: `data:image/svg+xml;base64,${window.btoa(o)}`, + width: Math.ceil(w), + height: Math.ceil(h) + }; + } + pdf(opt) { + var _a; + if (!opt) + opt = {}; + const [px, py, sclx, scly] = this.resolveScale(opt); + const width = nf(this._tree.bbox.w * sclx + px * 2); + const height = nf(this._tree.bbox.h * scly + py * 2); + let head = `%PDF-1.1 +%%\xA5\xB1\xEB +1 0 obj +<< /Type /Catalog +/Pages 2 0 R +>>endobj + 2 0 obj +<< /Type /Pages +/Kids [3 0 R] +/Count 1 +/MediaBox [0 0 ${width} ${height}] +>> +endobj + 3 0 obj +<< /Type /Page +/Parent 2 0 R +/Resources +<< /Font +<< /F1 +<< /Type /Font + /Subtype /Type1 +/BaseFont /Times-Roman +>> +>> +>> +/Contents [`; + let pdf = ""; + let count = 4; + for (let i = 0; i < this._polylines.length; i++) { + pdf += `${count} 0 obj +<< /Length 0 >> + stream + 1 j 1 J ${(_a = opt.STROKE_W) != null ? _a : 1} w +`; + for (let j = 0; j < this._polylines[i].length; j++) { + const [x, y] = this._polylines[i][j]; + pdf += `${nf(px + x * sclx)} ${nf(height - (py + y * scly))} ${j ? "l" : "m"} `; + } + pdf += "\nS\nendstream\nendobj\n"; + head += `${count} 0 R `; + count++; + } + head += "]\n>>\nendobj\n"; + pdf += "\ntrailer\n<< /Root 1 0 R \n /Size 0\n >>startxref\n\n%%EOF\n"; + return head + pdf; + } + boxes(opt) { + if (!opt) + opt = {}; + const [px, py, sclx, scly] = this.resolveScale(opt); + const bs = []; + for (let i = 0; i < this._tree.chld.length; i++) { + const { x, y, w, h } = this._tree.chld[i].bbox; + bs.push({ x: px + x * sclx, y: py + y * scly, w: w * sclx, h: h * scly }); + } + return bs; + } + box(opt) { + if (!opt) + opt = {}; + const [px, py, sclx, scly] = this.resolveScale(opt); + return { + x: px + this._tree.bbox.x * sclx, + y: py + this._tree.bbox.y * scly, + w: this._tree.bbox.w * sclx, + h: this._tree.bbox.h * scly + }; + } +} +class LaTexParticle extends ImageParticle { + static convertLaTextToSVG(laTex) { + return new LaTexUtils(laTex).svg({ + SCALE_X: 10, + SCALE_Y: 10, + MARGIN_X: 0, + MARGIN_Y: 0 + }); + } + render(ctx, element, x, y) { + const { scale } = this.options; + const width = element.width * scale; + const height = element.height * scale; + if (this.imageCache.has(element.value)) { + const img = this.imageCache.get(element.value); + ctx.drawImage(img, x, y, width, height); + } else { + const laTexLoadPromise = new Promise((resolve, reject) => { + const img = new Image(); + img.src = element.laTexSVG; + img.onload = () => { + ctx.drawImage(img, x, y, width, height); + this.imageCache.set(element.value, img); + resolve(element); + }; + img.onerror = (error) => { + reject(error); + }; + }); + this.addImageObserver(laTexLoadPromise); + } + } +} +var ListType; +(function(ListType2) { + ListType2["UL"] = "ul"; + ListType2["OL"] = "ol"; +})(ListType || (ListType = {})); +var UlStyle; +(function(UlStyle2) { + UlStyle2["DISC"] = "disc"; + UlStyle2["CIRCLE"] = "circle"; + UlStyle2["SQUARE"] = "square"; + UlStyle2["CHECKBOX"] = "checkbox"; +})(UlStyle || (UlStyle = {})); +var OlStyle; +(function(OlStyle2) { + OlStyle2["DECIMAL"] = "decimal"; +})(OlStyle || (OlStyle = {})); +var ListStyle; +(function(ListStyle2) { + ListStyle2["DISC"] = "disc"; + ListStyle2["CIRCLE"] = "circle"; + ListStyle2["SQUARE"] = "square"; + ListStyle2["DECIMAL"] = "decimal"; + ListStyle2["CHECKBOX"] = "checkbox"; +})(ListStyle || (ListStyle = {})); +const ulStyleMapping = { + [UlStyle.DISC]: "\u2022", + [UlStyle.CIRCLE]: "\u25E6", + [UlStyle.SQUARE]: "\u25AB\uFE0E", + [UlStyle.CHECKBOX]: "\u2611\uFE0F" +}; +const listTypeElementMapping = { + [ListType.OL]: "ol", + [ListType.UL]: "ul" +}; +const listStyleCSSMapping = { + [ListStyle.DISC]: "disc", + [ListStyle.CIRCLE]: "circle", + [ListStyle.SQUARE]: "square", + [ListStyle.DECIMAL]: "decimal", + [ListStyle.CHECKBOX]: "checkbox" +}; +var TitleLevel; +(function(TitleLevel2) { + TitleLevel2["FIRST"] = "first"; + TitleLevel2["SECOND"] = "second"; + TitleLevel2["THIRD"] = "third"; + TitleLevel2["FOURTH"] = "fourth"; + TitleLevel2["FIFTH"] = "fifth"; + TitleLevel2["SIXTH"] = "sixth"; +})(TitleLevel || (TitleLevel = {})); +const defaultTitleOption = { + defaultFirstSize: 26, + defaultSecondSize: 24, + defaultThirdSize: 22, + defaultFourthSize: 20, + defaultFifthSize: 18, + defaultSixthSize: 16 +}; +const titleSizeMapping = { + [TitleLevel.FIRST]: "defaultFirstSize", + [TitleLevel.SECOND]: "defaultSecondSize", + [TitleLevel.THIRD]: "defaultThirdSize", + [TitleLevel.FOURTH]: "defaultFourthSize", + [TitleLevel.FIFTH]: "defaultFifthSize", + [TitleLevel.SIXTH]: "defaultSixthSize" +}; +const titleOrderNumberMapping = { + [TitleLevel.FIRST]: 1, + [TitleLevel.SECOND]: 2, + [TitleLevel.THIRD]: 3, + [TitleLevel.FOURTH]: 4, + [TitleLevel.FIFTH]: 5, + [TitleLevel.SIXTH]: 6 +}; +const titleNodeNameMapping = { + H1: TitleLevel.FIRST, + H2: TitleLevel.SECOND, + H3: TitleLevel.THIRD, + H4: TitleLevel.FOURTH, + H5: TitleLevel.FIFTH, + H6: TitleLevel.SIXTH +}; +var ControlType; +(function(ControlType2) { + ControlType2["TEXT"] = "text"; + ControlType2["SELECT"] = "select"; + ControlType2["CHECKBOX"] = "checkbox"; + ControlType2["RADIO"] = "radio"; + ControlType2["DATE"] = "date"; +})(ControlType || (ControlType = {})); +var ControlComponent; +(function(ControlComponent2) { + ControlComponent2["PREFIX"] = "prefix"; + ControlComponent2["POSTFIX"] = "postfix"; + ControlComponent2["PLACEHOLDER"] = "placeholder"; + ControlComponent2["VALUE"] = "value"; + ControlComponent2["CHECKBOX"] = "checkbox"; + ControlComponent2["RADIO"] = "radio"; +})(ControlComponent || (ControlComponent = {})); +var ControlIndentation; +(function(ControlIndentation2) { + ControlIndentation2["ROW_START"] = "rowStart"; + ControlIndentation2["VALUE_START"] = "valueStart"; +})(ControlIndentation || (ControlIndentation = {})); +var BackgroundSize; +(function(BackgroundSize2) { + BackgroundSize2["CONTAIN"] = "contain"; + BackgroundSize2["COVER"] = "cover"; +})(BackgroundSize || (BackgroundSize = {})); +var BackgroundRepeat; +(function(BackgroundRepeat2) { + BackgroundRepeat2["REPEAT"] = "repeat"; + BackgroundRepeat2["NO_REPEAT"] = "no-repeat"; + BackgroundRepeat2["REPEAT_X"] = "repeat-x"; + BackgroundRepeat2["REPEAT_Y"] = "repeat-y"; +})(BackgroundRepeat || (BackgroundRepeat = {})); +const defaultBackground = { + color: "#FFFFFF", + image: "", + size: BackgroundSize.COVER, + repeat: BackgroundRepeat.NO_REPEAT, + applyPageNumbers: [] +}; +var VerticalAlign; +(function(VerticalAlign2) { + VerticalAlign2["TOP"] = "top"; + VerticalAlign2["MIDDLE"] = "middle"; + VerticalAlign2["BOTTOM"] = "bottom"; +})(VerticalAlign || (VerticalAlign = {})); +const defaultCheckboxOption = { + width: 14, + height: 14, + gap: 5, + lineWidth: 1, + fillStyle: "#5175f4", + strokeStyle: "#ffffff", + verticalAlign: VerticalAlign.BOTTOM +}; +const defaultControlOption = { + placeholderColor: "#9c9b9b", + bracketColor: "#000000", + prefix: "{", + postfix: "}", + borderWidth: 1, + borderColor: "#000000", + activeBackgroundColor: "" +}; +const defaultFooterOption = { + bottom: 30, + maxHeightRadio: MaxHeightRatio.HALF, + disabled: false, + editable: true +}; +const defaultGroupOption = { + opacity: 0.1, + backgroundColor: "#E99D00", + activeOpacity: 0.5, + activeBackgroundColor: "#E99D00", + disabled: false +}; +const defaultHeaderOption = { + top: 30, + maxHeightRadio: MaxHeightRatio.HALF, + disabled: false, + editable: true +}; +const defaultLineBreak = { + disabled: true, + color: "#CCCCCC", + lineWidth: 1.5 +}; +const defaultPageBreakOption = { + font: "Microsoft YaHei", + fontSize: 12, + lineDash: [3, 1] +}; +const FORMAT_PLACEHOLDER = { + PAGE_NO: "{pageNo}", + PAGE_COUNT: "{pageCount}" +}; +const defaultPageNumberOption = { + bottom: 60, + size: 12, + font: "Microsoft YaHei", + color: "#000000", + rowFlex: RowFlex.CENTER, + format: FORMAT_PLACEHOLDER.PAGE_NO, + numberType: NumberType.ARABIC, + disabled: false, + startPageNo: 1, + fromPageNo: 0, + maxPageNo: null +}; +const defaultPlaceholderOption = { + data: "", + color: "#DCDFE6", + opacity: 1, + size: 16, + font: "Microsoft YaHei" +}; +const defaultRadioOption = { + width: 14, + height: 14, + gap: 5, + lineWidth: 1, + fillStyle: "#5175f4", + strokeStyle: "#000000", + verticalAlign: VerticalAlign.BOTTOM +}; +const defaultSeparatorOption = { + lineWidth: 1, + strokeStyle: "#000000" +}; +const defaultTableOption = { + tdPadding: [0, 5, 5, 5], + defaultTrMinHeight: 42, + defaultColMinWidth: 40 +}; +const defaultWatermarkOption = { + data: "", + color: "#AEB5C0", + opacity: 0.3, + size: 200, + font: "Microsoft YaHei", + repeat: false, + gap: [10, 10] +}; +const defaultZoneOption = { + tipDisabled: true +}; +var LineNumberType; +(function(LineNumberType2) { + LineNumberType2["PAGE"] = "page"; + LineNumberType2["CONTINUITY"] = "continuity"; +})(LineNumberType || (LineNumberType = {})); +const defaultLineNumberOption = { + size: 12, + font: "Microsoft YaHei", + color: "#000000", + disabled: true, + right: 20, + type: LineNumberType.CONTINUITY +}; +const defaultPageBorderOption = { + color: "#000000", + lineWidth: 1, + padding: [0, 5, 0, 5], + disabled: true +}; +var EditorComponent; +(function(EditorComponent2) { + EditorComponent2["COMPONENT"] = "component"; + EditorComponent2["MENU"] = "menu"; + EditorComponent2["MAIN"] = "main"; + EditorComponent2["FOOTER"] = "footer"; + EditorComponent2["CONTEXTMENU"] = "contextmenu"; + EditorComponent2["POPUP"] = "popup"; + EditorComponent2["CATALOG"] = "catalog"; + EditorComponent2["COMMENT"] = "comment"; +})(EditorComponent || (EditorComponent = {})); +var EditorContext; +(function(EditorContext2) { + EditorContext2["PAGE"] = "page"; + EditorContext2["TABLE"] = "table"; +})(EditorContext || (EditorContext = {})); +var EditorMode; +(function(EditorMode2) { + EditorMode2["EDIT"] = "edit"; + EditorMode2["CLEAN"] = "clean"; + EditorMode2["READONLY"] = "readonly"; + EditorMode2["FORM"] = "form"; + EditorMode2["PRINT"] = "print"; + EditorMode2["DESIGN"] = "design"; +})(EditorMode || (EditorMode = {})); +var EditorZone; +(function(EditorZone2) { + EditorZone2["HEADER"] = "header"; + EditorZone2["MAIN"] = "main"; + EditorZone2["FOOTER"] = "footer"; +})(EditorZone || (EditorZone = {})); +var PageMode; +(function(PageMode2) { + PageMode2["PAGING"] = "paging"; + PageMode2["CONTINUITY"] = "continuity"; +})(PageMode || (PageMode = {})); +var PaperDirection; +(function(PaperDirection2) { + PaperDirection2["VERTICAL"] = "vertical"; + PaperDirection2["HORIZONTAL"] = "horizontal"; +})(PaperDirection || (PaperDirection = {})); +var WordBreak; +(function(WordBreak2) { + WordBreak2["BREAK_ALL"] = "break-all"; + WordBreak2["BREAK_WORD"] = "break-word"; +})(WordBreak || (WordBreak = {})); +var RenderMode; +(function(RenderMode2) { + RenderMode2["SPEED"] = "speed"; + RenderMode2["COMPATIBILITY"] = "compatibility"; +})(RenderMode || (RenderMode = {})); +function mergeOption(options = {}) { + const tableOptions = { + ...defaultTableOption, + ...options.table + }; + const headerOptions = { + ...defaultHeaderOption, + ...options.header + }; + const footerOptions = { + ...defaultFooterOption, + ...options.footer + }; + const pageNumberOptions = { + ...defaultPageNumberOption, + ...options.pageNumber + }; + const waterMarkOptions = { + ...defaultWatermarkOption, + ...options.watermark + }; + const controlOptions = { + ...defaultControlOption, + ...options.control + }; + const checkboxOptions = { + ...defaultCheckboxOption, + ...options.checkbox + }; + const radioOptions = { + ...defaultRadioOption, + ...options.radio + }; + const cursorOptions = { + ...defaultCursorOption, + ...options.cursor + }; + const titleOptions = { + ...defaultTitleOption, + ...options.title + }; + const placeholderOptions = { + ...defaultPlaceholderOption, + ...options.placeholder + }; + const groupOptions = { + ...defaultGroupOption, + ...options.group + }; + const pageBreakOptions = { + ...defaultPageBreakOption, + ...options.pageBreak + }; + const zoneOptions = { + ...defaultZoneOption, + ...options.zone + }; + const backgroundOptions = { + ...defaultBackground, + ...options.background + }; + const lineBreakOptions = { + ...defaultLineBreak, + ...options.lineBreak + }; + const separatorOptions = { + ...defaultSeparatorOption, + ...options.separator + }; + const lineNumberOptions = { + ...defaultLineNumberOption, + ...options.lineNumber + }; + const pageBorderOptions = { + ...defaultPageBorderOption, + ...options.pageBorder + }; + return { + mode: EditorMode.EDIT, + defaultType: "TEXT", + defaultColor: "#000000", + defaultFont: "Microsoft YaHei", + defaultSize: 16, + minSize: 5, + maxSize: 72, + defaultRowMargin: 1, + defaultBasicRowMarginHeight: 8, + defaultTabWidth: 32, + width: 794, + height: 1123, + scale: 1, + pageGap: 20, + underlineColor: "#000000", + strikeoutColor: "#FF0000", + rangeAlpha: 0.6, + rangeColor: "#AECBFA", + rangeMinWidth: 5, + searchMatchAlpha: 0.6, + searchMatchColor: "#FFFF00", + searchNavigateMatchColor: "#AAD280", + highlightAlpha: 0.6, + resizerColor: "#4182D9", + resizerSize: 5, + marginIndicatorSize: 35, + marginIndicatorColor: "#BABABA", + margins: [100, 120, 100, 120], + pageMode: PageMode.PAGING, + renderMode: RenderMode.SPEED, + defaultHyperlinkColor: "#0000FF", + paperDirection: PaperDirection.VERTICAL, + inactiveAlpha: 0.6, + historyMaxRecordCount: 100, + wordBreak: WordBreak.BREAK_WORD, + printPixelRatio: 3, + maskMargin: [0, 0, 0, 0], + letterClass: [LETTER_CLASS.ENGLISH], + contextMenuDisableKeys: [], + scrollContainerSelector: "", + ...options, + table: tableOptions, + header: headerOptions, + footer: footerOptions, + pageNumber: pageNumberOptions, + watermark: waterMarkOptions, + control: controlOptions, + checkbox: checkboxOptions, + radio: radioOptions, + cursor: cursorOptions, + title: titleOptions, + placeholder: placeholderOptions, + group: groupOptions, + pageBreak: pageBreakOptions, + zone: zoneOptions, + background: backgroundOptions, + lineBreak: lineBreakOptions, + separator: separatorOptions, + lineNumber: lineNumberOptions, + pageBorder: pageBorderOptions + }; +} +function unzipElementList(elementList) { + const result = []; + for (let v = 0; v < elementList.length; v++) { + const valueItem = elementList[v]; + const textList = splitText(valueItem.value); + for (let d = 0; d < textList.length; d++) { + result.push({ ...valueItem, value: textList[d] }); + } + } + return result; +} +function formatElementList(elementList, options) { + const { isHandleFirstElement = true, isForceCompensation = false, editorOptions } = options; + const startElement = elementList[0]; + if (isForceCompensation || isHandleFirstElement && (startElement == null ? void 0 : startElement.type) !== ElementType.LIST && ((startElement == null ? void 0 : startElement.type) && startElement.type !== ElementType.TEXT || !START_LINE_BREAK_REG.test(startElement == null ? void 0 : startElement.value))) { + elementList.unshift({ + value: ZERO + }); + } + let i = 0; + while (i < elementList.length) { + let el = elementList[i]; + if (el.type === ElementType.TITLE) { + elementList.splice(i, 1); + const valueList = el.valueList || []; + formatElementList(valueList, { + ...options, + isHandleFirstElement: false, + isForceCompensation: false + }); + if (valueList.length) { + const titleId = getUUID(); + const titleOptions = editorOptions.title; + for (let v = 0; v < valueList.length; v++) { + const value = valueList[v]; + value.title = el.title; + if (el.level) { + value.titleId = titleId; + value.level = el.level; + } + if (isTextLikeElement(value)) { + if (!value.size) { + value.size = titleOptions[titleSizeMapping[value.level]]; + } + if (value.bold === void 0) { + value.bold = true; + } + } + elementList.splice(i, 0, value); + i++; + } + } + i--; + } else if (el.type === ElementType.LIST) { + elementList.splice(i, 1); + const valueList = el.valueList || []; + formatElementList(valueList, { + ...options, + isHandleFirstElement: true, + isForceCompensation: false + }); + if (valueList.length) { + const listId = getUUID(); + for (let v = 0; v < valueList.length; v++) { + const value = valueList[v]; + value.listId = listId; + value.listType = el.listType; + value.listStyle = el.listStyle; + elementList.splice(i, 0, value); + i++; + } + } + i--; + } else if (el.type === ElementType.TABLE) { + const tableId = getUUID(); + el.id = tableId; + if (el.trList) { + const { defaultTrMinHeight } = editorOptions.table; + for (let t = 0; t < el.trList.length; t++) { + const tr = el.trList[t]; + const trId = getUUID(); + tr.id = trId; + if (!tr.minHeight || tr.minHeight < defaultTrMinHeight) { + tr.minHeight = defaultTrMinHeight; + } + if (tr.height < tr.minHeight) { + tr.height = tr.minHeight; + } + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const tdId = getUUID(); + td.id = tdId; + formatElementList(td.value, { + ...options, + isHandleFirstElement: true, + isForceCompensation: true + }); + for (let v = 0; v < td.value.length; v++) { + const value = td.value[v]; + value.tdId = tdId; + value.trId = trId; + value.tableId = tableId; + } + } + } + } + } else if (el.type === ElementType.HYPERLINK) { + elementList.splice(i, 1); + const valueList = unzipElementList(el.valueList || []); + if (valueList.length) { + const hyperlinkId = getUUID(); + for (let v = 0; v < valueList.length; v++) { + const value = valueList[v]; + value.type = el.type; + value.url = el.url; + value.hyperlinkId = hyperlinkId; + elementList.splice(i, 0, value); + i++; + } + } + i--; + } else if (el.type === ElementType.DATE) { + elementList.splice(i, 1); + const valueList = unzipElementList(el.valueList || []); + if (valueList.length) { + const dateId = getUUID(); + for (let v = 0; v < valueList.length; v++) { + const value = valueList[v]; + value.type = el.type; + value.dateFormat = el.dateFormat; + value.dateId = dateId; + elementList.splice(i, 0, value); + i++; + } + } + i--; + } else if (el.type === ElementType.CONTROL) { + if (!el.control) { + i++; + continue; + } + const { prefix, postfix, value, placeholder, code, type, valueSets } = el.control; + const { editorOptions: { control: controlOption, checkbox: checkboxOption, radio: radioOption } } = options; + const controlId = getUUID(); + elementList.splice(i, 1); + const controlContext = pickObject(el, [ + ...EDITOR_ELEMENT_CONTEXT_ATTR, + ...EDITOR_ROW_ATTR + ]); + const controlDefaultStyle = pickObject(el.control, CONTROL_STYLE_ATTR); + const thePrePostfixArg = { + ...controlDefaultStyle, + color: editorOptions.control.bracketColor + }; + const prefixStrList = splitText(prefix || controlOption.prefix); + for (let p = 0; p < prefixStrList.length; p++) { + const value2 = prefixStrList[p]; + elementList.splice(i, 0, { + ...controlContext, + ...thePrePostfixArg, + controlId, + value: value2, + type: el.type, + control: el.control, + controlComponent: ControlComponent.PREFIX + }); + i++; + } + if (value && value.length || type === ControlType.CHECKBOX || type === ControlType.RADIO || type === ControlType.SELECT && code && (!value || !value.length)) { + let valueList = value || []; + if (type === ControlType.CHECKBOX) { + const codeList = code ? code.split(",") : []; + if (Array.isArray(valueSets) && valueSets.length) { + const valueStyleList = valueList.reduce((pre, cur) => pre.concat(cur.value.split("").map((v) => ({ ...cur, value: v }))), []); + let valueStyleIndex = 0; + for (let v = 0; v < valueSets.length; v++) { + const valueSet = valueSets[v]; + elementList.splice(i, 0, { + ...controlContext, + ...controlDefaultStyle, + controlId, + value: "", + type: el.type, + control: el.control, + controlComponent: ControlComponent.CHECKBOX, + checkbox: { + code: valueSet.code, + value: codeList.includes(valueSet.code) + } + }); + i++; + const valueStrList = splitText(valueSet.value); + for (let e = 0; e < valueStrList.length; e++) { + const value2 = valueStrList[e]; + const isLastLetter = e === valueStrList.length - 1; + elementList.splice(i, 0, { + ...controlContext, + ...controlDefaultStyle, + ...valueStyleList[valueStyleIndex], + controlId, + value: value2 === "\n" ? ZERO : value2, + letterSpacing: isLastLetter ? checkboxOption.gap : 0, + control: el.control, + controlComponent: ControlComponent.VALUE + }); + valueStyleIndex++; + i++; + } + } + } + } else if (type === ControlType.RADIO) { + if (Array.isArray(valueSets) && valueSets.length) { + const valueStyleList = valueList.reduce((pre, cur) => pre.concat(cur.value.split("").map((v) => ({ ...cur, value: v }))), []); + let valueStyleIndex = 0; + for (let v = 0; v < valueSets.length; v++) { + const valueSet = valueSets[v]; + elementList.splice(i, 0, { + ...controlContext, + ...controlDefaultStyle, + controlId, + value: "", + type: el.type, + control: el.control, + controlComponent: ControlComponent.RADIO, + radio: { + code: valueSet.code, + value: code === valueSet.code + } + }); + i++; + const valueStrList = splitText(valueSet.value); + for (let e = 0; e < valueStrList.length; e++) { + const value2 = valueStrList[e]; + const isLastLetter = e === valueStrList.length - 1; + elementList.splice(i, 0, { + ...controlContext, + ...controlDefaultStyle, + ...valueStyleList[valueStyleIndex], + controlId, + value: value2 === "\n" ? ZERO : value2, + letterSpacing: isLastLetter ? radioOption.gap : 0, + control: el.control, + controlComponent: ControlComponent.VALUE + }); + valueStyleIndex++; + i++; + } + } + } + } else { + if (!value || !value.length) { + if (Array.isArray(valueSets) && valueSets.length) { + const valueSet = valueSets.find((v) => v.code === code); + if (valueSet) { + valueList = [ + { + value: valueSet.value + } + ]; + } + } + } + formatElementList(valueList, { + ...options, + isHandleFirstElement: false, + isForceCompensation: false + }); + for (let v = 0; v < valueList.length; v++) { + const element = valueList[v]; + const value2 = element.value; + elementList.splice(i, 0, { + ...controlContext, + ...controlDefaultStyle, + ...element, + controlId, + value: value2 === "\n" ? ZERO : value2, + type: element.type || ElementType.TEXT, + control: el.control, + controlComponent: ControlComponent.VALUE + }); + i++; + } + } + } else if (placeholder) { + const thePlaceholderArgs = { + ...controlDefaultStyle, + color: editorOptions.control.placeholderColor + }; + const placeholderStrList = splitText(placeholder); + for (let p = 0; p < placeholderStrList.length; p++) { + const value2 = placeholderStrList[p]; + elementList.splice(i, 0, { + ...controlContext, + ...thePlaceholderArgs, + controlId, + value: value2 === "\n" ? ZERO : value2, + type: el.type, + control: el.control, + controlComponent: ControlComponent.PLACEHOLDER + }); + i++; + } + } + const postfixStrList = splitText(postfix || controlOption.postfix); + for (let p = 0; p < postfixStrList.length; p++) { + const value2 = postfixStrList[p]; + elementList.splice(i, 0, { + ...controlContext, + ...thePrePostfixArg, + controlId, + value: value2, + type: el.type, + control: el.control, + controlComponent: ControlComponent.POSTFIX + }); + i++; + } + i--; + } else if ((!el.type || TEXTLIKE_ELEMENT_TYPE.includes(el.type)) && el.value.length > 1) { + elementList.splice(i, 1); + const valueList = splitText(el.value); + for (let v = 0; v < valueList.length; v++) { + elementList.splice(i + v, 0, { ...el, value: valueList[v] }); + } + el = elementList[i]; + } + if (el.value === "\n" || el.value == "\r\n") { + el.value = ZERO; + } + if (el.type === ElementType.IMAGE || el.type === ElementType.BLOCK) { + el.id = getUUID(); + } + if (el.type === ElementType.LATEX) { + const { svg, width, height } = LaTexParticle.convertLaTextToSVG(el.value); + el.width = el.width || width; + el.height = el.height || height; + el.laTexSVG = svg; + el.id = getUUID(); + } + i++; + } +} +function isSameElementExceptValue(source, target) { + const sourceKeys = Object.keys(source); + const targetKeys = Object.keys(target); + if (sourceKeys.length !== targetKeys.length) + return false; + for (let s = 0; s < sourceKeys.length; s++) { + const key = sourceKeys[s]; + if (key === "value") + continue; + if (key === "groupIds" && Array.isArray(source[key]) && Array.isArray(target[key]) && isArrayEqual(source[key], target[key])) { + continue; + } + if (source[key] !== target[key]) { + return false; + } + } + return true; +} +function pickElementAttr(payload, option = {}) { + const { extraPickAttrs } = option; + const zipAttrs = EDITOR_ELEMENT_ZIP_ATTR; + if (extraPickAttrs) { + zipAttrs.push(...extraPickAttrs); + } + const element = { + value: payload.value === ZERO ? ` +` : payload.value + }; + zipAttrs.forEach((attr) => { + const value = payload[attr]; + if (value !== void 0) { + element[attr] = value; + } + }); + return element; +} +function zipElementList(payload, options = {}) { + const { extraPickAttrs } = options; + const elementList = deepClone(payload); + const zipElementListData = []; + let e = 0; + while (e < elementList.length) { + let element = elementList[e]; + if (e === 0 && element.value === ZERO && !element.listId && (!element.type || element.type === ElementType.TEXT)) { + e++; + continue; + } + if (element.titleId && element.level) { + const titleId = element.titleId; + if (titleId) { + const level = element.level; + const titleElement = { + type: ElementType.TITLE, + title: element.title, + value: "", + level + }; + const valueList = []; + while (e < elementList.length) { + const titleE = elementList[e]; + if (titleId !== titleE.titleId) { + e--; + break; + } + delete titleE.level; + delete titleE.title; + valueList.push(titleE); + e++; + } + titleElement.valueList = zipElementList(valueList, options); + element = titleElement; + } + } else if (element.listId && element.listType) { + const listId = element.listId; + if (listId) { + const listType = element.listType; + const listStyle = element.listStyle; + const listElement = { + type: ElementType.LIST, + value: "", + listId, + listType, + listStyle + }; + const valueList = []; + while (e < elementList.length) { + const listE = elementList[e]; + if (listId !== listE.listId) { + e--; + break; + } + delete listE.listType; + delete listE.listStyle; + valueList.push(listE); + e++; + } + listElement.valueList = zipElementList(valueList, options); + element = listElement; + } + } else if (element.type === ElementType.TABLE) { + if (element.pagingId) { + let tableIndex = e + 1; + let combineCount = 0; + while (tableIndex < elementList.length) { + const nextElement = elementList[tableIndex]; + if (nextElement.pagingId === element.pagingId) { + element.height += nextElement.height; + element.trList.push(...nextElement.trList); + tableIndex++; + combineCount++; + } else { + break; + } + } + e += combineCount; + } + if (element.trList) { + for (let t = 0; t < element.trList.length; t++) { + const tr = element.trList[t]; + delete tr.id; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const zipTd = { + colspan: td.colspan, + rowspan: td.rowspan, + value: zipElementList(td.value, options) + }; + TABLE_TD_ZIP_ATTR.forEach((attr) => { + const value = td[attr]; + if (value !== void 0) { + zipTd[attr] = value; + } + }); + tr.tdList[d] = zipTd; + } + } + } + } else if (element.type === ElementType.HYPERLINK) { + const hyperlinkId = element.hyperlinkId; + if (hyperlinkId) { + const hyperlinkElement = { + type: ElementType.HYPERLINK, + value: "", + url: element.url + }; + const valueList = []; + while (e < elementList.length) { + const hyperlinkE = elementList[e]; + if (hyperlinkId !== hyperlinkE.hyperlinkId) { + e--; + break; + } + delete hyperlinkE.type; + delete hyperlinkE.url; + valueList.push(hyperlinkE); + e++; + } + hyperlinkElement.valueList = zipElementList(valueList, options); + element = hyperlinkElement; + } + } else if (element.type === ElementType.DATE) { + const dateId = element.dateId; + if (dateId) { + const dateElement = { + type: ElementType.DATE, + value: "", + dateFormat: element.dateFormat + }; + const valueList = []; + while (e < elementList.length) { + const dateE = elementList[e]; + if (dateId !== dateE.dateId) { + e--; + break; + } + delete dateE.type; + delete dateE.dateFormat; + valueList.push(dateE); + e++; + } + dateElement.valueList = zipElementList(valueList, options); + element = dateElement; + } + } else if (element.controlId) { + const controlId = element.controlId; + if (controlId) { + const controlDefaultStyle = pickObject(element, CONTROL_STYLE_ATTR); + const control = { + ...element.control, + ...controlDefaultStyle + }; + const controlElement = { + ...pickObject(element, EDITOR_ROW_ATTR), + type: ElementType.CONTROL, + value: "", + control, + controlId + }; + const valueList = []; + while (e < elementList.length) { + const controlE = elementList[e]; + if (controlId !== controlE.controlId) { + e--; + break; + } + if (controlE.controlComponent === ControlComponent.VALUE) { + delete controlE.control; + delete controlE.controlId; + valueList.push(controlE); + } + e++; + } + controlElement.control.value = zipElementList(valueList, options); + element = pickElementAttr(controlElement, { extraPickAttrs }); + } + } + const pickElement = pickElementAttr(element, { extraPickAttrs }); + if (!element.type || element.type === ElementType.TEXT || element.type === ElementType.SUBSCRIPT || element.type === ElementType.SUPERSCRIPT) { + while (e < elementList.length) { + const nextElement = elementList[e + 1]; + e++; + if (nextElement && isSameElementExceptValue(pickElement, pickElementAttr(nextElement, { extraPickAttrs }))) { + const nextValue = nextElement.value === ZERO ? "\n" : nextElement.value; + pickElement.value += nextValue; + } else { + break; + } + } + } else { + e++; + } + zipElementListData.push(pickElement); + } + return zipElementListData; +} +function convertTextAlignToRowFlex(node) { + const textAlign = window.getComputedStyle(node).textAlign; + switch (textAlign) { + case "left": + case "start": + return RowFlex.LEFT; + case "center": + return RowFlex.CENTER; + case "right": + case "end": + return RowFlex.RIGHT; + case "justify": + return RowFlex.ALIGNMENT; + case "justify-all": + return RowFlex.JUSTIFY; + default: + return RowFlex.LEFT; + } +} +function convertRowFlexToTextAlign(rowFlex) { + return rowFlex === RowFlex.ALIGNMENT ? "justify" : rowFlex; +} +function convertRowFlexToJustifyContent(rowFlex) { + switch (rowFlex) { + case RowFlex.LEFT: + return "flex-start"; + case RowFlex.CENTER: + return "center"; + case RowFlex.RIGHT: + return "flex-end"; + case RowFlex.ALIGNMENT: + case RowFlex.JUSTIFY: + return "space-between"; + default: + return "flex-start"; + } +} +function isTextLikeElement(element) { + return !element.type || TEXTLIKE_ELEMENT_TYPE.includes(element.type); +} +function getAnchorElement(elementList, anchorIndex) { + const anchorElement = elementList[anchorIndex]; + if (!anchorElement) + return null; + const anchorNextElement = elementList[anchorIndex + 1]; + return !anchorElement.listId && anchorElement.value === ZERO && anchorNextElement && anchorNextElement.value !== ZERO ? anchorNextElement : anchorElement; +} +function formatElementContext(sourceElementList, formatElementList2, anchorIndex, options) { + var _a, _b, _c; + let copyElement = getAnchorElement(sourceElementList, anchorIndex); + if (!copyElement) + return; + const { isBreakWhenWrap = false, editorOptions } = options || {}; + const { mode } = editorOptions || {}; + if (mode !== EditorMode.DESIGN && ((_a = copyElement.title) == null ? void 0 : _a.disabled)) { + copyElement = omitObject(copyElement, TITLE_CONTEXT_ATTR); + } + let isBreakWarped = false; + for (let e = 0; e < formatElementList2.length; e++) { + const targetElement = formatElementList2[e]; + if (isBreakWhenWrap && !copyElement.listId && START_LINE_BREAK_REG.test(targetElement.value)) { + isBreakWarped = true; + } + if (isBreakWarped || !copyElement.listId && targetElement.type === ElementType.LIST) { + const cloneAttr2 = [...TABLE_CONTEXT_ATTR, ...EDITOR_ROW_ATTR]; + cloneProperty(cloneAttr2, copyElement, targetElement); + (_b = targetElement.valueList) == null ? void 0 : _b.forEach((valueItem) => { + cloneProperty(cloneAttr2, copyElement, valueItem); + }); + continue; + } + if ((_c = targetElement.valueList) == null ? void 0 : _c.length) { + formatElementContext(sourceElementList, targetElement.valueList, anchorIndex, options); + } + const cloneAttr = [...EDITOR_ELEMENT_CONTEXT_ATTR]; + if (!getIsBlockElement(targetElement)) { + cloneAttr.push(...EDITOR_ROW_ATTR); + } + cloneProperty(cloneAttr, copyElement, targetElement); + } +} +function convertElementToDom(element, options) { + let tagName = "span"; + if (element.type === ElementType.SUPERSCRIPT) { + tagName = "sup"; + } else if (element.type === ElementType.SUBSCRIPT) { + tagName = "sub"; + } + const dom = document.createElement(tagName); + dom.style.fontFamily = element.font || options.defaultFont; + if (element.rowFlex) { + dom.style.textAlign = convertRowFlexToTextAlign(element.rowFlex); + } + if (element.color) { + dom.style.color = element.color; + } + if (element.bold) { + dom.style.fontWeight = "600"; + } + if (element.italic) { + dom.style.fontStyle = "italic"; + } + dom.style.fontSize = `${element.size || options.defaultSize}px`; + if (element.highlight) { + dom.style.backgroundColor = element.highlight; + } + if (element.underline) { + dom.style.textDecoration = "underline"; + } + if (element.strikeout) { + dom.style.textDecoration += " line-through"; + } + dom.innerText = element.value.replace(new RegExp(`${ZERO}`, "g"), "\n"); + return dom; +} +function splitListElement(elementList) { + let curListIndex = 0; + const listElementListMap = /* @__PURE__ */ new Map(); + for (let e = 0; e < elementList.length; e++) { + const element = elementList[e]; + if (e === 0) { + if (element.checkbox) + continue; + element.value = element.value.replace(START_LINE_BREAK_REG, ""); + } + if (element.listWrap) { + const listElementList = listElementListMap.get(curListIndex) || []; + listElementList.push(element); + listElementListMap.set(curListIndex, listElementList); + } else { + const valueList = element.value.split("\n"); + for (let c = 0; c < valueList.length; c++) { + if (c > 0) { + curListIndex += 1; + } + const value = valueList[c]; + const listElementList = listElementListMap.get(curListIndex) || []; + listElementList.push({ + ...element, + value + }); + listElementListMap.set(curListIndex, listElementList); + } + } + } + return listElementListMap; +} +function groupElementListByRowFlex(elementList) { + var _a; + const elementListGroupList = []; + if (!elementList.length) + return elementListGroupList; + let currentRowFlex = ((_a = elementList[0]) == null ? void 0 : _a.rowFlex) || null; + elementListGroupList.push({ + rowFlex: currentRowFlex, + data: [elementList[0]] + }); + for (let e = 1; e < elementList.length; e++) { + const element = elementList[e]; + const rowFlex = element.rowFlex || null; + if (currentRowFlex === rowFlex && !getIsBlockElement(element) && !getIsBlockElement(elementList[e - 1])) { + const lastElementListGroup = elementListGroupList[elementListGroupList.length - 1]; + lastElementListGroup.data.push(element); + } else { + elementListGroupList.push({ + rowFlex, + data: [element] + }); + currentRowFlex = rowFlex; + } + } + for (let g = 0; g < elementListGroupList.length; g++) { + const elementListGroup = elementListGroupList[g]; + elementListGroup.data = zipElementList(elementListGroup.data); + } + return elementListGroupList; +} +function createDomFromElementList(elementList, options) { + const editorOptions = mergeOption(options); + function buildDom(payload) { + var _a, _b, _c, _d, _e, _f, _g, _h, _i; + const clipboardDom2 = document.createElement("div"); + for (let e = 0; e < payload.length; e++) { + const element = payload[e]; + if (element.type === ElementType.TABLE) { + const tableDom = document.createElement("table"); + tableDom.setAttribute("cellSpacing", "0"); + tableDom.setAttribute("cellpadding", "0"); + tableDom.setAttribute("border", "0"); + const borderStyle = "1px solid #000000"; + if (!element.borderType || element.borderType === TableBorder.ALL) { + tableDom.style.borderTop = borderStyle; + tableDom.style.borderLeft = borderStyle; + } else if (element.borderType === TableBorder.EXTERNAL) { + tableDom.style.border = borderStyle; + } + tableDom.style.width = `${element.width}px`; + const colgroupDom = document.createElement("colgroup"); + for (let c = 0; c < element.colgroup.length; c++) { + const colgroup = element.colgroup[c]; + const colDom = document.createElement("col"); + colDom.setAttribute("width", `${colgroup.width}`); + colgroupDom.append(colDom); + } + tableDom.append(colgroupDom); + const trList = element.trList; + for (let t = 0; t < trList.length; t++) { + const trDom = document.createElement("tr"); + const tr = trList[t]; + trDom.style.height = `${tr.height}px`; + for (let d = 0; d < tr.tdList.length; d++) { + const tdDom = document.createElement("td"); + if (!element.borderType || element.borderType === TableBorder.ALL) { + tdDom.style.borderBottom = tdDom.style.borderRight = "1px solid"; + } + const td = tr.tdList[d]; + tdDom.colSpan = td.colspan; + tdDom.rowSpan = td.rowspan; + tdDom.style.verticalAlign = td.verticalAlign || "top"; + if ((_a = td.borderTypes) == null ? void 0 : _a.includes(TdBorder.TOP)) { + tdDom.style.borderTop = borderStyle; + } + if ((_b = td.borderTypes) == null ? void 0 : _b.includes(TdBorder.RIGHT)) { + tdDom.style.borderRight = borderStyle; + } + if ((_c = td.borderTypes) == null ? void 0 : _c.includes(TdBorder.BOTTOM)) { + tdDom.style.borderBottom = borderStyle; + } + if ((_d = td.borderTypes) == null ? void 0 : _d.includes(TdBorder.LEFT)) { + tdDom.style.borderLeft = borderStyle; + } + const childDom = createDomFromElementList(td.value, options); + tdDom.innerHTML = childDom.innerHTML; + if (td.backgroundColor) { + tdDom.style.backgroundColor = td.backgroundColor; + } + trDom.append(tdDom); + } + tableDom.append(trDom); + } + clipboardDom2.append(tableDom); + } else if (element.type === ElementType.HYPERLINK) { + const a = document.createElement("a"); + a.innerText = element.valueList.map((v) => v.value).join(""); + if (element.url) { + a.href = element.url; + } + clipboardDom2.append(a); + } else if (element.type === ElementType.TITLE) { + const h = document.createElement(`h${titleOrderNumberMapping[element.level]}`); + const childDom = buildDom(element.valueList); + h.innerHTML = childDom.innerHTML; + clipboardDom2.append(h); + } else if (element.type === ElementType.LIST) { + const list = document.createElement(listTypeElementMapping[element.listType]); + if (element.listStyle) { + list.style.listStyleType = listStyleCSSMapping[element.listStyle]; + } + const zipList = zipElementList(element.valueList); + const listElementListMap = splitListElement(zipList); + listElementListMap.forEach((listElementList) => { + const li = document.createElement("li"); + const childDom = buildDom(listElementList); + li.innerHTML = childDom.innerHTML; + list.append(li); + }); + clipboardDom2.append(list); + } else if (element.type === ElementType.IMAGE) { + const img = document.createElement("img"); + if (element.value) { + img.src = element.value; + img.width = element.width; + img.height = element.height; + } + clipboardDom2.append(img); + } else if (element.type === ElementType.SEPARATOR) { + const hr = document.createElement("hr"); + clipboardDom2.append(hr); + } else if (element.type === ElementType.CHECKBOX) { + const checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + if ((_e = element.checkbox) == null ? void 0 : _e.value) { + checkbox.setAttribute("checked", "true"); + } + clipboardDom2.append(checkbox); + } else if (element.type === ElementType.RADIO) { + const radio = document.createElement("input"); + radio.type = "radio"; + if ((_f = element.radio) == null ? void 0 : _f.value) { + radio.setAttribute("checked", "true"); + } + clipboardDom2.append(radio); + } else if (element.type === ElementType.TAB) { + const tab2 = document.createElement("span"); + tab2.innerHTML = `${NON_BREAKING_SPACE}${NON_BREAKING_SPACE}`; + clipboardDom2.append(tab2); + } else if (element.type === ElementType.CONTROL) { + const controlElement = document.createElement("span"); + const childDom = buildDom(((_g = element.control) == null ? void 0 : _g.value) || []); + controlElement.innerHTML = childDom.innerHTML; + clipboardDom2.append(controlElement); + } else if (!element.type || element.type === ElementType.LATEX || TEXTLIKE_ELEMENT_TYPE.includes(element.type)) { + let text = ""; + if (element.type === ElementType.DATE) { + text = ((_h = element.valueList) == null ? void 0 : _h.map((v) => v.value).join("")) || ""; + } else { + text = element.value; + } + if (!text) + continue; + const dom = convertElementToDom(element, editorOptions); + if (((_i = payload[e - 1]) == null ? void 0 : _i.type) === ElementType.TITLE) { + text = text.replace(/^\n/, ""); + } + dom.innerText = text.replace(new RegExp(`${ZERO}`, "g"), "\n"); + clipboardDom2.append(dom); + } + } + return clipboardDom2; + } + const clipboardDom = document.createElement("div"); + const groupElementList = groupElementListByRowFlex(elementList); + for (let g = 0; g < groupElementList.length; g++) { + const elementGroupRowFlex = groupElementList[g]; + const isDefaultRowFlex = !elementGroupRowFlex.rowFlex || elementGroupRowFlex.rowFlex === RowFlex.LEFT; + const rowFlexDom = document.createElement("div"); + if (!isDefaultRowFlex) { + const firstElement = elementGroupRowFlex.data[0]; + if (getIsBlockElement(firstElement)) { + rowFlexDom.style.display = "flex"; + rowFlexDom.style.justifyContent = convertRowFlexToJustifyContent(firstElement.rowFlex); + } else { + rowFlexDom.style.textAlign = convertRowFlexToTextAlign(elementGroupRowFlex.rowFlex); + } + } + rowFlexDom.innerHTML = buildDom(elementGroupRowFlex.data).innerHTML; + if (!isDefaultRowFlex) { + clipboardDom.append(rowFlexDom); + } else { + rowFlexDom.childNodes.forEach((child) => { + clipboardDom.append(child.cloneNode(true)); + }); + } + } + return clipboardDom; +} +function convertTextNodeToElement(textNode) { + if (!textNode || textNode.nodeType !== 3) + return null; + const parentNode = textNode.parentNode; + const anchorNode = parentNode.nodeName === "FONT" ? parentNode.parentNode : parentNode; + const rowFlex = convertTextAlignToRowFlex(anchorNode); + const value = textNode.textContent; + const style = window.getComputedStyle(anchorNode); + if (!value || anchorNode.nodeName === "STYLE") + return null; + const element = { + value, + color: style.color, + bold: Number(style.fontWeight) > 500, + italic: style.fontStyle.includes("italic"), + size: Math.floor(parseFloat(style.fontSize)) + }; + if (anchorNode.nodeName === "SUB" || style.verticalAlign === "sub") { + element.type = ElementType.SUBSCRIPT; + } else if (anchorNode.nodeName === "SUP" || style.verticalAlign === "super") { + element.type = ElementType.SUPERSCRIPT; + } + if (rowFlex !== RowFlex.LEFT) { + element.rowFlex = rowFlex; + } + if (style.backgroundColor !== "rgba(0, 0, 0, 0)") { + element.highlight = style.backgroundColor; + } + if (style.textDecorationLine.includes("underline")) { + element.underline = true; + } + if (style.textDecorationLine.includes("line-through")) { + element.strikeout = true; + } + return element; +} +function getElementListByHTML(htmlText, options) { + const elementList = []; + function findTextNode(dom) { + if (dom.nodeType === 3) { + const element = convertTextNodeToElement(dom); + if (element) { + elementList.push(element); + } + } else if (dom.nodeType === 1) { + const childNodes = dom.childNodes; + for (let n = 0; n < childNodes.length; n++) { + const node = childNodes[n]; + if (node.nodeName === "BR") { + elementList.push({ + value: "\n" + }); + } else if (node.nodeName === "A") { + const aElement = node; + const value = aElement.innerText; + if (value) { + elementList.push({ + type: ElementType.HYPERLINK, + value: "", + valueList: [ + { + value + } + ], + url: aElement.href + }); + } + } else if (/H[1-6]/.test(node.nodeName)) { + const hElement = node; + const valueList = getElementListByHTML(replaceHTMLElementTag(hElement, "div").outerHTML, options); + elementList.push({ + value: "", + type: ElementType.TITLE, + level: titleNodeNameMapping[node.nodeName], + valueList + }); + if (node.nextSibling && !INLINE_NODE_NAME.includes(node.nextSibling.nodeName)) { + elementList.push({ + value: "\n" + }); + } + } else if (node.nodeName === "UL" || node.nodeName === "OL") { + const listNode = node; + const listElement = { + value: "", + type: ElementType.LIST, + valueList: [] + }; + if (node.nodeName === "OL") { + listElement.listType = ListType.OL; + } else { + listElement.listType = ListType.UL; + listElement.listStyle = listNode.style.listStyleType; + } + listNode.querySelectorAll("li").forEach((li) => { + const liValueList = getElementListByHTML(li.innerHTML, options); + liValueList.forEach((list) => { + if (list.value === "\n") { + list.listWrap = true; + } + }); + liValueList.unshift({ + value: "\n" + }); + listElement.valueList.push(...liValueList); + }); + elementList.push(listElement); + } else if (node.nodeName === "HR") { + elementList.push({ + value: "\n", + type: ElementType.SEPARATOR + }); + } else if (node.nodeName === "IMG") { + const { src, width, height } = node; + if (src && width && height) { + elementList.push({ + width, + height, + value: src, + type: ElementType.IMAGE + }); + } + } else if (node.nodeName === "TABLE") { + const tableElement = node; + const element = { + type: ElementType.TABLE, + value: "\n", + colgroup: [], + trList: [] + }; + tableElement.querySelectorAll("tr").forEach((trElement) => { + const trHeightStr = window.getComputedStyle(trElement).height.replace("px", ""); + const tr = { + height: Number(trHeightStr), + tdList: [] + }; + trElement.querySelectorAll("th,td").forEach((tdElement) => { + const tableCell = tdElement; + const valueList = getElementListByHTML(tableCell.innerHTML, options); + const td = { + colspan: tableCell.colSpan, + rowspan: tableCell.rowSpan, + value: valueList + }; + if (tableCell.style.backgroundColor) { + td.backgroundColor = tableCell.style.backgroundColor; + } + tr.tdList.push(td); + }); + element.trList.push(tr); + }); + if (element.trList.length) { + const tdCount = element.trList[0].tdList.reduce((pre, cur) => pre + cur.colspan, 0); + const width = Math.ceil(options.innerWidth / tdCount); + for (let i = 0; i < tdCount; i++) { + element.colgroup.push({ + width + }); + } + elementList.push(element); + } + } else if (node.nodeName === "INPUT" && node.type === ControlComponent.CHECKBOX) { + elementList.push({ + type: ElementType.CHECKBOX, + value: "", + checkbox: { + value: node.checked + } + }); + } else if (node.nodeName === "INPUT" && node.type === ControlComponent.RADIO) { + elementList.push({ + type: ElementType.RADIO, + value: "", + radio: { + value: node.checked + } + }); + } else { + findTextNode(node); + if (node.nodeType === 1 && n !== childNodes.length - 1) { + const display = window.getComputedStyle(node).display; + if (display === "block") { + elementList.push({ + value: "\n" + }); + } + } + } + } + } + } + const clipboardDom = document.createElement("div"); + clipboardDom.innerHTML = htmlText; + document.body.appendChild(clipboardDom); + const deleteNodes = []; + clipboardDom.childNodes.forEach((child) => { + var _a; + if (child.nodeType !== 1 && !((_a = child.textContent) == null ? void 0 : _a.trim())) { + deleteNodes.push(child); + } + }); + deleteNodes.forEach((node) => node.remove()); + findTextNode(clipboardDom); + clipboardDom.remove(); + return elementList; +} +function getTextFromElementList(elementList) { + function buildText(payload) { + var _a, _b, _c, _d, _e; + let text = ""; + for (let e = 0; e < payload.length; e++) { + const element = payload[e]; + if (element.type === ElementType.TABLE) { + text += ` +`; + const trList = element.trList; + for (let t = 0; t < trList.length; t++) { + const tr = trList[t]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const tdText = buildText(zipElementList(td.value)); + const isFirst = d === 0; + const isLast = tr.tdList.length - 1 === d; + text += `${!isFirst ? ` ` : ``}${tdText}${isLast ? ` +` : ``}`; + } + } + } else if (element.type === ElementType.TAB) { + text += ` `; + } else if (element.type === ElementType.HYPERLINK) { + text += element.valueList.map((v) => v.value).join(""); + } else if (element.type === ElementType.TITLE) { + text += `${buildText(zipElementList(element.valueList))}`; + } else if (element.type === ElementType.LIST) { + const zipList = zipElementList(element.valueList); + const listElementListMap = splitListElement(zipList); + let ulListStyleText = ""; + if (element.listType === ListType.UL) { + ulListStyleText = ulStyleMapping[element.listStyle]; + } + listElementListMap.forEach((listElementList, listIndex) => { + const isLast = listElementListMap.size - 1 === listIndex; + text += ` +${ulListStyleText || `${listIndex + 1}.`}${buildText(listElementList)}${isLast ? ` +` : ``}`; + }); + } else if (element.type === ElementType.CHECKBOX) { + text += ((_a = element.checkbox) == null ? void 0 : _a.value) ? `\u2611` : `\u25A1`; + } else if (element.type === ElementType.RADIO) { + text += ((_b = element.radio) == null ? void 0 : _b.value) ? `\u2609` : `\u25CB`; + } else if (!element.type || element.type === ElementType.LATEX || TEXTLIKE_ELEMENT_TYPE.includes(element.type)) { + let textLike = ""; + if (element.type === ElementType.CONTROL) { + textLike = ((_d = (_c = element.control.value) == null ? void 0 : _c[0]) == null ? void 0 : _d.value) || ""; + } else if (element.type === ElementType.DATE) { + textLike = ((_e = element.valueList) == null ? void 0 : _e.map((v) => v.value).join("")) || ""; + } else { + textLike = element.value; + } + text += textLike.replace(new RegExp(`${ZERO}`, "g"), "\n"); + } + } + return text; + } + return buildText(zipElementList(elementList)); +} +function getSlimCloneElementList(elementList) { + return deepCloneOmitKeys(elementList, [ + "metrics", + "style" + ]); +} +function getIsBlockElement(element) { + return !!(element == null ? void 0 : element.type) && (BLOCK_ELEMENT_TYPE.includes(element.type) || element.imgDisplay === ImageDisplay.INLINE); +} +function replaceHTMLElementTag(oldDom, tagName) { + const newDom = document.createElement(tagName); + for (let i = 0; i < oldDom.attributes.length; i++) { + const attr = oldDom.attributes[i]; + newDom.setAttribute(attr.name, attr.value); + } + newDom.innerHTML = oldDom.innerHTML; + return newDom; +} +function pickSurroundElementList(elementList) { + const surroundElementList = []; + for (let e = 0; e < elementList.length; e++) { + const element = elementList[e]; + if (element.imgDisplay === ImageDisplay.SURROUND) { + surroundElementList.push(element); + } + } + return surroundElementList; +} +function deleteSurroundElementList(elementList, pageNo) { + var _a; + for (let s = elementList.length - 1; s >= 0; s--) { + const surroundElement = elementList[s]; + if (((_a = surroundElement.imgFloatPosition) == null ? void 0 : _a.pageNo) === pageNo) { + elementList.splice(s, 1); + } + } +} +function setClipboardData(data2) { + localStorage.setItem(EDITOR_CLIPBOARD, JSON.stringify({ + text: data2.text, + elementList: data2.elementList + })); +} +function getClipboardData() { + const clipboardText = localStorage.getItem(EDITOR_CLIPBOARD); + return clipboardText ? JSON.parse(clipboardText) : null; +} +function removeClipboardData() { + localStorage.removeItem(EDITOR_CLIPBOARD); +} +function writeClipboardItem(text, html, elementList) { + if (!text && !html && !elementList.length) + return; + const plainText = new Blob([text], { type: "text/plain" }); + const htmlText = new Blob([html], { type: "text/html" }); + if (window.ClipboardItem) { + const item = new ClipboardItem({ + [plainText.type]: plainText, + [htmlText.type]: htmlText + }); + window.navigator.clipboard.write([item]); + } else { + const fakeElement = document.createElement("div"); + fakeElement.setAttribute("contenteditable", "true"); + fakeElement.innerHTML = html; + document.body.append(fakeElement); + const selection = window.getSelection(); + const range = document.createRange(); + const br = document.createElement("span"); + br.innerText = "\n"; + fakeElement.append(br); + range.selectNodeContents(fakeElement); + selection == null ? void 0 : selection.removeAllRanges(); + selection == null ? void 0 : selection.addRange(range); + document.execCommand("copy"); + fakeElement.remove(); + } + setClipboardData({ text, elementList }); +} +function writeElementList(elementList, options) { + const clipboardDom = createDomFromElementList(elementList, options); + document.body.append(clipboardDom); + const text = clipboardDom.innerText; + clipboardDom.remove(); + const html = clipboardDom.innerHTML; + if (!text && !html && !elementList.length) + return; + writeClipboardItem(text, html, zipElementList(elementList)); +} +function getIsClipboardContainFile(clipboardData) { + let isFile = false; + for (let i = 0; i < clipboardData.items.length; i++) { + const item = clipboardData.items[i]; + if (item.kind === "file") { + isFile = true; + break; + } + } + return isFile; +} +function pasteElement(host, elementList) { + const draw = host.getDraw(); + if (draw.isReadonly() || draw.isDisabled()) + return; + const rangeManager = draw.getRange(); + const { startIndex } = rangeManager.getRange(); + const originalElementList = draw.getElementList(); + if (~startIndex && !rangeManager.getIsSelectAll()) { + const anchorElement = originalElementList[startIndex]; + if ((anchorElement == null ? void 0 : anchorElement.titleId) || (anchorElement == null ? void 0 : anchorElement.listId)) { + let start = 0; + while (start < elementList.length) { + const pasteElement2 = elementList[start]; + if (anchorElement.titleId && /^\n/.test(pasteElement2.value)) { + break; + } + if (VIRTUAL_ELEMENT_TYPE.includes(pasteElement2.type)) { + elementList.splice(start, 1); + if (pasteElement2.valueList) { + for (let v = 0; v < pasteElement2.valueList.length; v++) { + const element = pasteElement2.valueList[v]; + if (element.value === ZERO || element.value === "\n") { + continue; + } + elementList.splice(start, 0, element); + start++; + } + } + start--; + } + start++; + } + } + formatElementContext(originalElementList, elementList, startIndex, { + isBreakWhenWrap: true, + editorOptions: draw.getOptions() + }); + } + draw.insertElementList(elementList); +} +function pasteHTML(host, htmlText) { + const draw = host.getDraw(); + if (draw.isReadonly() || draw.isDisabled()) + return; + const elementList = getElementListByHTML(htmlText, { + innerWidth: draw.getOriginalInnerWidth() + }); + pasteElement(host, elementList); +} +function pasteImage(host, file) { + const draw = host.getDraw(); + if (draw.isReadonly() || draw.isDisabled()) + return; + const rangeManager = draw.getRange(); + const { startIndex } = rangeManager.getRange(); + const elementList = draw.getElementList(); + const fileReader = new FileReader(); + fileReader.readAsDataURL(file); + fileReader.onload = () => { + const image = new Image(); + const value = fileReader.result; + image.src = value; + image.onload = () => { + const imageElement = { + value, + type: ElementType.IMAGE, + width: image.width, + height: image.height + }; + if (~startIndex) { + formatElementContext(elementList, [imageElement], startIndex, { + editorOptions: draw.getOptions() + }); + } + draw.insertElementList([imageElement]); + }; + }; +} +function pasteByEvent(host, evt) { + const draw = host.getDraw(); + if (draw.isReadonly() || draw.isDisabled()) + return; + const clipboardData = evt.clipboardData; + if (!clipboardData) + return; + const { paste } = draw.getOverride(); + if (paste) { + const overrideResult = paste(evt); + if ((overrideResult == null ? void 0 : overrideResult.preventDefault) !== false) + return; + } + if (!getIsClipboardContainFile(clipboardData)) { + const clipboardText = clipboardData.getData("text"); + const editorClipboardData = getClipboardData(); + if (clipboardText === (editorClipboardData == null ? void 0 : editorClipboardData.text)) { + pasteElement(host, editorClipboardData.elementList); + return; + } + } + removeClipboardData(); + let isHTML = false; + for (let i = 0; i < clipboardData.items.length; i++) { + const item = clipboardData.items[i]; + if (item.type === "text/html") { + isHTML = true; + break; + } + } + for (let i = 0; i < clipboardData.items.length; i++) { + const item = clipboardData.items[i]; + if (item.kind === "string") { + if (item.type === "text/plain" && !isHTML) { + item.getAsString((plainText) => { + host.input(plainText); + }); + break; + } + if (item.type === "text/html" && isHTML) { + item.getAsString((htmlText) => { + pasteHTML(host, htmlText); + }); + break; + } + } else if (item.kind === "file") { + if (item.type.includes("image")) { + const file = item.getAsFile(); + if (file) { + pasteImage(host, file); + } + } + } + } +} +async function pasteByApi(host, options) { + const draw = host.getDraw(); + if (draw.isReadonly() || draw.isDisabled()) + return; + const { paste } = draw.getOverride(); + if (paste) { + const overrideResult = paste(); + if ((overrideResult == null ? void 0 : overrideResult.preventDefault) !== false) + return; + } + const clipboardText = await navigator.clipboard.readText(); + const editorClipboardData = getClipboardData(); + if (clipboardText === (editorClipboardData == null ? void 0 : editorClipboardData.text)) { + pasteElement(host, editorClipboardData.elementList); + return; + } + removeClipboardData(); + if (options == null ? void 0 : options.isPlainText) { + if (clipboardText) { + host.input(clipboardText); + } + } else { + const clipboardData = await navigator.clipboard.read(); + let isHTML = false; + for (const item of clipboardData) { + if (item.types.includes("text/html")) { + isHTML = true; + break; + } + } + for (const item of clipboardData) { + if (item.types.includes("text/plain") && !isHTML) { + const textBlob = await item.getType("text/plain"); + const text = await textBlob.text(); + if (text) { + host.input(text); + } + } else if (item.types.includes("text/html") && isHTML) { + const htmlTextBlob = await item.getType("text/html"); + const htmlText = await htmlTextBlob.text(); + if (htmlText) { + pasteHTML(host, htmlText); + } + } else if (item.types.some((type) => type.startsWith("image/"))) { + const type = item.types.find((type2) => type2.startsWith("image/")); + const imageBlob = await item.getType(type); + pasteImage(host, imageBlob); + } + } + } +} +class CursorAgent { + constructor(draw, canvasEvent) { + this.draw = draw; + this.container = draw.getContainer(); + this.canvasEvent = canvasEvent; + const agentCursorDom = document.createElement("textarea"); + agentCursorDom.autocomplete = "off"; + agentCursorDom.classList.add(`${EDITOR_PREFIX}-inputarea`); + agentCursorDom.innerText = ""; + this.container.append(agentCursorDom); + this.agentCursorDom = agentCursorDom; + agentCursorDom.onkeydown = (evt) => this._keyDown(evt); + agentCursorDom.oninput = debounce(this._input.bind(this), 0); + agentCursorDom.onpaste = (evt) => this._paste(evt); + agentCursorDom.addEventListener("compositionstart", this._compositionstart.bind(this)); + agentCursorDom.addEventListener("compositionend", this._compositionend.bind(this)); + } + getAgentCursorDom() { + return this.agentCursorDom; + } + _keyDown(evt) { + this.canvasEvent.keydown(evt); + } + _input(evt) { + const data2 = evt.data; + if (!data2) + return; + this.canvasEvent.input(data2); + } + _paste(evt) { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + const clipboardData = evt.clipboardData; + if (!clipboardData) + return; + pasteByEvent(this.canvasEvent, evt); + evt.preventDefault(); + } + _compositionstart() { + this.canvasEvent.compositionstart(); + } + _compositionend(evt) { + this.canvasEvent.compositionend(evt); + } +} +class Cursor { + constructor(draw, canvasEvent) { + this.ANIMATION_CLASS = `${EDITOR_PREFIX}-cursor--animation`; + this.draw = draw; + this.container = draw.getContainer(); + this.position = draw.getPosition(); + this.options = draw.getOptions(); + this.cursorDom = document.createElement("div"); + this.cursorDom.classList.add(`${EDITOR_PREFIX}-cursor`); + this.container.append(this.cursorDom); + this.cursorAgent = new CursorAgent(draw, canvasEvent); + this.blinkTimeout = null; + } + getCursorDom() { + return this.cursorDom; + } + getAgentDom() { + return this.cursorAgent.getAgentCursorDom(); + } + getAgentIsActive() { + return this.getAgentDom() === document.activeElement; + } + getAgentDomValue() { + return this.getAgentDom().value; + } + clearAgentDomValue() { + this.getAgentDom().value = ""; + } + _blinkStart() { + this.cursorDom.classList.add(this.ANIMATION_CLASS); + } + _blinkStop() { + this.cursorDom.classList.remove(this.ANIMATION_CLASS); + } + _setBlinkTimeout() { + this._clearBlinkTimeout(); + this.blinkTimeout = window.setTimeout(() => { + this._blinkStart(); + }, 500); + } + _clearBlinkTimeout() { + if (this.blinkTimeout) { + this._blinkStop(); + window.clearTimeout(this.blinkTimeout); + this.blinkTimeout = null; + } + } + drawCursor(payload) { + let cursorPosition = this.position.getCursorPosition(); + if (!cursorPosition) + return; + const { scale, cursor } = this.options; + const { color, width, isShow = true, isBlink = true, isFocus = true, hitLineStartIndex } = { ...cursor, ...payload }; + const height = this.draw.getHeight(); + const pageGap = this.draw.getPageGap(); + if (hitLineStartIndex) { + const positionList = this.position.getPositionList(); + cursorPosition = positionList[hitLineStartIndex]; + } + const { metrics, coordinate: { leftTop, rightTop }, ascent, pageNo } = cursorPosition; + const zoneManager = this.draw.getZone(); + const curPageNo = zoneManager.isMainActive() ? pageNo : this.draw.getPageNo(); + const preY = curPageNo * (height + pageGap); + const defaultOffsetHeight = CURSOR_AGENT_OFFSET_HEIGHT * scale; + const increaseHeight = Math.min(metrics.height / 4, defaultOffsetHeight); + const cursorHeight = metrics.height + increaseHeight * 2; + const agentCursorDom = this.cursorAgent.getAgentCursorDom(); + if (isFocus) { + setTimeout(() => { + if (document.activeElement !== agentCursorDom) { + agentCursorDom.focus(); + agentCursorDom.setSelectionRange(0, 0); + } + }); + } + const descent = metrics.boundingBoxDescent < 0 ? 0 : metrics.boundingBoxDescent; + const cursorTop = leftTop[1] + ascent + descent - (cursorHeight - increaseHeight) + preY; + const cursorLeft = hitLineStartIndex ? leftTop[0] : rightTop[0]; + agentCursorDom.style.left = `${cursorLeft}px`; + agentCursorDom.style.top = `${cursorTop + cursorHeight - defaultOffsetHeight}px`; + if (!isShow) { + this.recoveryCursor(); + return; + } + const isReadonly = this.draw.isReadonly(); + this.cursorDom.style.width = `${width * scale}px`; + this.cursorDom.style.backgroundColor = color; + this.cursorDom.style.left = `${cursorLeft}px`; + this.cursorDom.style.top = `${cursorTop}px`; + this.cursorDom.style.display = isReadonly ? "none" : "block"; + this.cursorDom.style.height = `${cursorHeight}px`; + if (isBlink) { + this._setBlinkTimeout(); + } else { + this._clearBlinkTimeout(); + } + } + recoveryCursor() { + this.cursorDom.style.display = "none"; + this._clearBlinkTimeout(); + } + moveCursorToVisible(payload) { + const { cursorPosition, direction } = payload; + if (!cursorPosition || !direction) + return; + const { pageNo, coordinate: { leftTop, leftBottom } } = cursorPosition; + const prePageY = pageNo * (this.draw.getHeight() + this.draw.getPageGap()) + this.container.getBoundingClientRect().top; + const isUp = direction === MoveDirection.UP; + const x = leftBottom[0]; + const y = isUp ? leftTop[1] + prePageY : leftBottom[1] + prePageY; + const scrollContainer = findScrollContainer(this.container); + const rect = { + left: 0, + right: 0, + top: 0, + bottom: 0 + }; + if (scrollContainer === document.documentElement) { + rect.right = window.innerWidth; + rect.bottom = window.innerHeight; + } else { + const { left: left2, right: right2, top, bottom } = scrollContainer.getBoundingClientRect(); + rect.left = left2; + rect.right = right2; + rect.top = top; + rect.bottom = bottom; + } + const { maskMargin } = this.options; + rect.top += maskMargin[0]; + rect.bottom -= maskMargin[2]; + if (!(x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom)) { + const { scrollLeft, scrollTop } = scrollContainer; + isUp ? scrollContainer.scroll(scrollLeft, scrollTop - (rect.top - y)) : scrollContainer.scroll(scrollLeft, scrollTop + y - rect.bottom); + } + } +} +var MouseEventButton; +(function(MouseEventButton2) { + MouseEventButton2[MouseEventButton2["LEFT"] = 0] = "LEFT"; + MouseEventButton2[MouseEventButton2["CENTER"] = 1] = "CENTER"; + MouseEventButton2[MouseEventButton2["RIGHT"] = 2] = "RIGHT"; +})(MouseEventButton || (MouseEventButton = {})); +const isApple = typeof navigator !== "undefined" && /Mac OS X/.test(navigator.userAgent); +const isIOS = typeof navigator !== "undefined" && /iPad|iPhone/.test(navigator.userAgent); +function isMod(evt) { + return isApple ? evt.metaKey : evt.ctrlKey; +} +var KeyMap; +(function(KeyMap2) { + KeyMap2["Delete"] = "Delete"; + KeyMap2["Backspace"] = "Backspace"; + KeyMap2["Enter"] = "Enter"; + KeyMap2["Left"] = "ArrowLeft"; + KeyMap2["Right"] = "ArrowRight"; + KeyMap2["Up"] = "ArrowUp"; + KeyMap2["Down"] = "ArrowDown"; + KeyMap2["ESC"] = "Escape"; + KeyMap2["TAB"] = "Tab"; + KeyMap2["META"] = "Meta"; + KeyMap2["LEFT_BRACKET"] = "["; + KeyMap2["RIGHT_BRACKET"] = "]"; + KeyMap2["COMMA"] = ","; + KeyMap2["PERIOD"] = "."; + KeyMap2["LEFT_ANGLE_BRACKET"] = "<"; + KeyMap2["RIGHT_ANGLE_BRACKET"] = ">"; + KeyMap2["EQUAL"] = "="; + KeyMap2["MINUS"] = "-"; + KeyMap2["PLUS"] = "+"; + KeyMap2["A"] = "a"; + KeyMap2["B"] = "b"; + KeyMap2["C"] = "c"; + KeyMap2["D"] = "d"; + KeyMap2["E"] = "e"; + KeyMap2["F"] = "f"; + KeyMap2["G"] = "g"; + KeyMap2["H"] = "h"; + KeyMap2["I"] = "i"; + KeyMap2["J"] = "j"; + KeyMap2["K"] = "k"; + KeyMap2["L"] = "l"; + KeyMap2["M"] = "m"; + KeyMap2["N"] = "n"; + KeyMap2["O"] = "o"; + KeyMap2["P"] = "p"; + KeyMap2["Q"] = "q"; + KeyMap2["R"] = "r"; + KeyMap2["S"] = "s"; + KeyMap2["T"] = "t"; + KeyMap2["U"] = "u"; + KeyMap2["V"] = "v"; + KeyMap2["W"] = "w"; + KeyMap2["X"] = "x"; + KeyMap2["Y"] = "y"; + KeyMap2["Z"] = "z"; + KeyMap2["A_UPPERCASE"] = "A"; + KeyMap2["B_UPPERCASE"] = "B"; + KeyMap2["C_UPPERCASE"] = "C"; + KeyMap2["D_UPPERCASE"] = "D"; + KeyMap2["E_UPPERCASE"] = "E"; + KeyMap2["F_UPPERCASE"] = "F"; + KeyMap2["G_UPPERCASE"] = "G"; + KeyMap2["H_UPPERCASE"] = "H"; + KeyMap2["I_UPPERCASE"] = "I"; + KeyMap2["J_UPPERCASE"] = "J"; + KeyMap2["K_UPPERCASE"] = "K"; + KeyMap2["L_UPPERCASE"] = "L"; + KeyMap2["M_UPPERCASE"] = "M"; + KeyMap2["N_UPPERCASE"] = "N"; + KeyMap2["O_UPPERCASE"] = "O"; + KeyMap2["P_UPPERCASE"] = "P"; + KeyMap2["Q_UPPERCASE"] = "Q"; + KeyMap2["R_UPPERCASE"] = "R"; + KeyMap2["S_UPPERCASE"] = "S"; + KeyMap2["T_UPPERCASE"] = "T"; + KeyMap2["U_UPPERCASE"] = "U"; + KeyMap2["V_UPPERCASE"] = "V"; + KeyMap2["W_UPPERCASE"] = "W"; + KeyMap2["X_UPPERCASE"] = "X"; + KeyMap2["Y_UPPERCASE"] = "Y"; + KeyMap2["Z_UPPERCASE"] = "Z"; + KeyMap2["ZERO"] = "0"; + KeyMap2["ONE"] = "1"; + KeyMap2["TWO"] = "2"; + KeyMap2["THREE"] = "3"; + KeyMap2["FOUR"] = "4"; + KeyMap2["FIVE"] = "5"; + KeyMap2["SIX"] = "6"; + KeyMap2["SEVEN"] = "7"; + KeyMap2["EIGHT"] = "8"; + KeyMap2["NINE"] = "9"; +})(KeyMap || (KeyMap = {})); +class CheckboxControl { + constructor(element, control) { + this.element = element; + this.control = control; + } + setElement(element) { + this.element = element; + } + getElement() { + return this.element; + } + getCode() { + var _a; + return ((_a = this.element.control) == null ? void 0 : _a.code) || null; + } + getValue() { + const elementList = this.control.getElementList(); + const { startIndex } = this.control.getRange(); + const startElement = elementList[startIndex]; + const data2 = []; + let preIndex = startIndex; + while (preIndex > 0) { + const preElement = elementList[preIndex]; + if (preElement.controlId !== startElement.controlId || preElement.controlComponent === ControlComponent.PREFIX) { + break; + } + if (preElement.controlComponent === ControlComponent.VALUE) { + data2.unshift(preElement); + } + preIndex--; + } + let nextIndex = startIndex + 1; + while (nextIndex < elementList.length) { + const nextElement = elementList[nextIndex]; + if (nextElement.controlId !== startElement.controlId || nextElement.controlComponent === ControlComponent.POSTFIX) { + break; + } + if (nextElement.controlComponent === ControlComponent.VALUE) { + data2.push(nextElement); + } + nextIndex++; + } + return data2; + } + setValue() { + return -1; + } + setSelect(codes, context = {}, options = {}) { + if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl(context)) { + return; + } + const { control } = this.element; + const elementList = context.elementList || this.control.getElementList(); + const { startIndex } = context.range || this.control.getRange(); + const startElement = elementList[startIndex]; + let preIndex = startIndex; + while (preIndex > 0) { + const preElement = elementList[preIndex]; + if (preElement.controlId !== startElement.controlId || preElement.controlComponent === ControlComponent.PREFIX) { + break; + } + if (preElement.controlComponent === ControlComponent.CHECKBOX) { + const checkbox = preElement.checkbox; + checkbox.value = codes.includes(checkbox.code); + } + preIndex--; + } + let nextIndex = startIndex + 1; + while (nextIndex < elementList.length) { + const nextElement = elementList[nextIndex]; + if (nextElement.controlId !== startElement.controlId || nextElement.controlComponent === ControlComponent.POSTFIX) { + break; + } + if (nextElement.controlComponent === ControlComponent.CHECKBOX) { + const checkbox = nextElement.checkbox; + checkbox.value = codes.includes(checkbox.code); + } + nextIndex++; + } + control.code = codes.join(","); + this.control.repaintControl({ + curIndex: startIndex, + isSetCursor: false + }); + } + keydown(evt) { + if (this.control.getIsDisabledControl()) { + return null; + } + const range = this.control.getRange(); + this.control.shrinkBoundary(); + const { startIndex, endIndex } = range; + if (evt.key === KeyMap.Backspace || evt.key === KeyMap.Delete) { + return this.control.removeControl(startIndex); + } + return endIndex; + } + cut() { + return -1; + } +} +class RadioControl extends CheckboxControl { + setSelect(codes, context = {}, options = {}) { + if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl(context)) { + return; + } + const { control } = this.element; + const elementList = context.elementList || this.control.getElementList(); + const { startIndex } = context.range || this.control.getRange(); + const startElement = elementList[startIndex]; + let preIndex = startIndex; + while (preIndex > 0) { + const preElement = elementList[preIndex]; + if (preElement.controlId !== startElement.controlId || preElement.controlComponent === ControlComponent.PREFIX) { + break; + } + if (preElement.controlComponent === ControlComponent.RADIO) { + const radio = preElement.radio; + radio.value = codes.includes(radio.code); + } + preIndex--; + } + let nextIndex = startIndex + 1; + while (nextIndex < elementList.length) { + const nextElement = elementList[nextIndex]; + if (nextElement.controlId !== startElement.controlId || nextElement.controlComponent === ControlComponent.POSTFIX) { + break; + } + if (nextElement.controlComponent === ControlComponent.RADIO) { + const radio = nextElement.radio; + radio.value = codes.includes(radio.code); + } + nextIndex++; + } + control.code = codes.join(","); + this.control.repaintControl({ + curIndex: startIndex, + isSetCursor: false + }); + } +} +function setRangeCache(host) { + const draw = host.getDraw(); + const position = draw.getPosition(); + const rangeManager = draw.getRange(); + host.isAllowDrag = true; + host.cacheRange = deepClone(rangeManager.getRange()); + host.cacheElementList = draw.getElementList(); + host.cachePositionList = position.getPositionList(); + host.cachePositionContext = position.getPositionContext(); +} +function hitCheckbox(element, draw) { + const { checkbox, control } = element; + if (!control) { + draw.getCheckboxParticle().setSelect(element); + } else { + const codes = (control == null ? void 0 : control.code) ? control.code.split(",") : []; + if (checkbox == null ? void 0 : checkbox.value) { + const codeIndex = codes.findIndex((c) => c === checkbox.code); + codes.splice(codeIndex, 1); + } else { + if (checkbox == null ? void 0 : checkbox.code) { + codes.push(checkbox.code); + } + } + const activeControl = draw.getControl().getActiveControl(); + if (activeControl instanceof CheckboxControl) { + activeControl.setSelect(codes); + } + } +} +function hitRadio(element, draw) { + const { radio, control } = element; + if (!control) { + draw.getRadioParticle().setSelect(element); + } else { + const codes = (radio == null ? void 0 : radio.code) ? [radio.code] : []; + const activeControl = draw.getControl().getActiveControl(); + if (activeControl instanceof RadioControl) { + activeControl.setSelect(codes); + } + } +} +function mousedown(evt, host) { + var _a, _b; + if (evt.button === MouseEventButton.RIGHT) + return; + const draw = host.getDraw(); + const isReadonly = draw.isReadonly(); + const rangeManager = draw.getRange(); + const position = draw.getPosition(); + if (!host.isAllowDrag) { + const range = rangeManager.getRange(); + if (!isReadonly && range.startIndex !== range.endIndex) { + const isPointInRange = rangeManager.getIsPointInRange(evt.offsetX, evt.offsetY); + if (isPointInRange) { + setRangeCache(host); + return; + } + } + } + const target = evt.target; + const pageIndex = target.dataset.index; + if (pageIndex) { + draw.setPageNo(Number(pageIndex)); + } + host.isAllowSelection = true; + const oldPositionContext = deepClone(position.getPositionContext()); + const positionResult = position.adjustPositionContext({ + x: evt.offsetX, + y: evt.offsetY + }); + if (!positionResult) + return; + const { index: index2, isDirectHit, isCheckbox, isRadio, isImage, isTable, tdValueIndex, hitLineStartIndex } = positionResult; + host.mouseDownStartPosition = { + ...positionResult, + index: isTable ? tdValueIndex : index2, + x: evt.offsetX, + y: evt.offsetY + }; + const elementList = draw.getElementList(); + const positionList = position.getPositionList(); + const curIndex = isTable ? tdValueIndex : index2; + const curElement = elementList[curIndex]; + const isDirectHitImage = !!(isDirectHit && isImage); + const isDirectHitCheckbox = !!(isDirectHit && isCheckbox); + const isDirectHitRadio = !!(isDirectHit && isRadio); + if (~index2) { + let startIndex = curIndex; + let endIndex = curIndex; + if (evt.shiftKey) { + const { startIndex: oldStartIndex } = rangeManager.getRange(); + if (~oldStartIndex) { + const newPositionContext = position.getPositionContext(); + if (newPositionContext.tdId === oldPositionContext.tdId) { + if (curIndex > oldStartIndex) { + startIndex = oldStartIndex; + } else { + endIndex = oldStartIndex; + } + } + } + } + rangeManager.setRange(startIndex, endIndex); + position.setCursorPosition(positionList[curIndex]); + if (isDirectHitCheckbox && !isReadonly) { + hitCheckbox(curElement, draw); + } else if (isDirectHitRadio && !isReadonly) { + hitRadio(curElement, draw); + } else if (curElement.controlComponent === ControlComponent.VALUE && (((_a = curElement.control) == null ? void 0 : _a.type) === ControlType.CHECKBOX || ((_b = curElement.control) == null ? void 0 : _b.type) === ControlType.RADIO)) { + let preIndex = curIndex; + while (preIndex > 0) { + const preElement = elementList[preIndex]; + if (preElement.controlComponent === ControlComponent.CHECKBOX) { + hitCheckbox(preElement, draw); + break; + } else if (preElement.controlComponent === ControlComponent.RADIO) { + hitRadio(preElement, draw); + break; + } + preIndex--; + } + } else { + draw.render({ + curIndex, + isCompute: false, + isSubmitHistory: false, + isSetCursor: !isDirectHitImage && !isDirectHitCheckbox && !isDirectHitRadio + }); + } + if (hitLineStartIndex) { + host.getDraw().getCursor().drawCursor({ + hitLineStartIndex + }); + } + } + const previewer = draw.getPreviewer(); + previewer.clearResizer(); + if (isDirectHitImage) { + const previewerDrawOption = { + dragDisable: isReadonly || !curElement.controlId && draw.getMode() === EditorMode.FORM + }; + if (curElement.type === ElementType.LATEX) { + previewerDrawOption.mime = "svg"; + previewerDrawOption.srcKey = "laTexSVG"; + } + previewer.drawResizer(curElement, positionList[curIndex], previewerDrawOption); + draw.getCursor().drawCursor({ + isShow: false + }); + setRangeCache(host); + if (curElement.imgDisplay === ImageDisplay.SURROUND || curElement.imgDisplay === ImageDisplay.FLOAT_TOP || curElement.imgDisplay === ImageDisplay.FLOAT_BOTTOM) { + draw.getImageParticle().createFloatImage(curElement); + } + } + const tableTool = draw.getTableTool(); + tableTool.dispose(); + if (isTable && !isReadonly && draw.getMode() !== EditorMode.FORM) { + tableTool.render(); + } + const hyperlinkParticle = draw.getHyperlinkParticle(); + hyperlinkParticle.clearHyperlinkPopup(); + if (curElement.type === ElementType.HYPERLINK) { + if (isMod(evt)) { + hyperlinkParticle.openHyperlink(curElement); + } else { + hyperlinkParticle.drawHyperlinkPopup(curElement, positionList[curIndex]); + } + } + const dateParticle = draw.getDateParticle(); + dateParticle.clearDatePicker(); + if (curElement.type === ElementType.DATE && !isReadonly) { + dateParticle.renderDatePicker(curElement, positionList[curIndex]); + } +} +function createDragId(element) { + const dragId = getUUID(); + Reflect.set(element, "dragId", dragId); + return dragId; +} +function getElementIndexByDragId(dragId, elementList) { + return elementList.findIndex((el) => el.dragId === dragId); +} +function moveImgPosition(element, evt, host) { + const draw = host.getDraw(); + if (element.imgDisplay === ImageDisplay.SURROUND || element.imgDisplay === ImageDisplay.FLOAT_TOP || element.imgDisplay === ImageDisplay.FLOAT_BOTTOM) { + const moveX = evt.offsetX - host.mouseDownStartPosition.x; + const moveY = evt.offsetY - host.mouseDownStartPosition.y; + const imgFloatPosition = element.imgFloatPosition; + element.imgFloatPosition = { + x: imgFloatPosition.x + moveX, + y: imgFloatPosition.y + moveY, + pageNo: draw.getPageNo() + }; + } + draw.getImageParticle().destroyFloatImage(); +} +function mouseup(evt, host) { + var _a, _b, _c, _d; + if (host.isAllowDrop) { + const draw = host.getDraw(); + if (draw.isReadonly() || draw.isDisabled()) { + host.mousedown(evt); + return; + } + const position = draw.getPosition(); + const positionList = position.getPositionList(); + const positionContext = position.getPositionContext(); + const rangeManager = draw.getRange(); + const cacheRange = host.cacheRange; + const cacheElementList = host.cacheElementList; + const cachePositionList = host.cachePositionList; + const range = rangeManager.getRange(); + const isCacheRangeCollapsed = cacheRange.startIndex === cacheRange.endIndex; + const cacheStartIndex = isCacheRangeCollapsed ? cacheRange.startIndex - 1 : cacheRange.startIndex; + const cacheEndIndex = cacheRange.endIndex; + if (range.startIndex >= cacheStartIndex && range.endIndex <= cacheEndIndex && ((_a = host.cachePositionContext) == null ? void 0 : _a.tdId) === positionContext.tdId) { + draw.clearSideEffect(); + let isSubmitHistory = false; + let isCompute = false; + if (isCacheRangeCollapsed) { + const dragElement = cacheElementList[cacheEndIndex]; + if (dragElement.type === ElementType.IMAGE || dragElement.type === ElementType.LATEX) { + moveImgPosition(dragElement, evt, host); + if (dragElement.imgDisplay === ImageDisplay.SURROUND || dragElement.imgDisplay === ImageDisplay.FLOAT_TOP || dragElement.imgDisplay === ImageDisplay.FLOAT_BOTTOM) { + draw.getPreviewer().drawResizer(dragElement); + isSubmitHistory = true; + } else { + const cachePosition = cachePositionList[cacheEndIndex]; + draw.getPreviewer().drawResizer(dragElement, cachePosition); + } + isCompute = dragElement.imgDisplay === ImageDisplay.SURROUND; + } + } + rangeManager.replaceRange({ + ...cacheRange + }); + draw.render({ + isCompute, + isSubmitHistory, + isSetCursor: false + }); + return; + } + const dragElementList = cacheElementList.slice(cacheStartIndex + 1, cacheEndIndex + 1); + const isContainControl = dragElementList.find((element) => element.controlId); + if (isContainControl) { + const cacheStartElement2 = cacheElementList[cacheStartIndex + 1]; + const cacheEndElement2 = cacheElementList[cacheEndIndex]; + const isAllowDragControl = (!cacheStartElement2.controlId || cacheStartElement2.controlComponent === ControlComponent.PREFIX) && (!cacheEndElement2.controlId || cacheEndElement2.controlComponent === ControlComponent.POSTFIX) || cacheStartElement2.controlId === cacheEndElement2.controlId && cacheStartElement2.controlComponent === ControlComponent.PREFIX && cacheEndElement2.controlComponent === ControlComponent.POSTFIX || ((_b = cacheStartElement2.control) == null ? void 0 : _b.type) === ControlType.TEXT && cacheStartElement2.controlComponent === ControlComponent.VALUE && ((_c = cacheEndElement2.control) == null ? void 0 : _c.type) === ControlType.TEXT && cacheEndElement2.controlComponent === ControlComponent.VALUE; + if (!isAllowDragControl) { + draw.render({ + curIndex: range.startIndex, + isCompute: false, + isSubmitHistory: false + }); + return; + } + } + const control = draw.getControl(); + const elementList = draw.getElementList(); + const isOmitControlAttr = !isContainControl || !!elementList[range.startIndex].controlId || !control.getIsElementListContainFullControl(dragElementList); + const editorOptions = draw.getOptions(); + const replaceElementList = dragElementList.map((el) => { + if (!el.type || el.type === ElementType.TEXT) { + const newElement = { + value: el.value + }; + const copyAttr = EDITOR_ELEMENT_STYLE_ATTR; + if (!isOmitControlAttr) { + copyAttr.push(...CONTROL_CONTEXT_ATTR); + } + copyAttr.forEach((attr) => { + const value = el[attr]; + if (value !== void 0) { + newElement[attr] = value; + } + }); + return newElement; + } else { + let newElement = deepClone(el); + if (isOmitControlAttr) { + newElement = omitObject(newElement, CONTROL_CONTEXT_ATTR); + } + formatElementList([newElement], { + isHandleFirstElement: false, + editorOptions + }); + return newElement; + } + }); + formatElementContext(elementList, replaceElementList, range.startIndex, { + editorOptions: draw.getOptions() + }); + const cacheStartElement = cacheElementList[cacheStartIndex]; + const cacheStartPosition = cachePositionList[cacheStartIndex]; + const cacheRangeStartId = createDragId(cacheElementList[cacheStartIndex]); + const cacheRangeEndId = createDragId(cacheElementList[cacheEndIndex]); + const replaceLength = replaceElementList.length; + let rangeStart = range.startIndex; + let rangeEnd = rangeStart + replaceLength; + const activeControl = control.getActiveControl(); + if (activeControl && cacheElementList[rangeStart].controlComponent !== ControlComponent.POSTFIX) { + rangeEnd = activeControl.setValue(replaceElementList); + rangeStart = rangeEnd - replaceLength; + } else { + draw.spliceElementList(elementList, rangeStart + 1, 0, ...replaceElementList); + } + if (!~rangeEnd) { + draw.render({ + isSetCursor: false + }); + return; + } + const rangeStartId = createDragId(elementList[rangeStart]); + const rangeEndId = createDragId(elementList[rangeEnd]); + const cacheRangeStartIndex = getElementIndexByDragId(cacheRangeStartId, cacheElementList); + const cacheRangeEndIndex = getElementIndexByDragId(cacheRangeEndId, cacheElementList); + const cacheEndElement = cacheElementList[cacheRangeEndIndex]; + if (cacheEndElement.controlId && cacheEndElement.controlComponent !== ControlComponent.POSTFIX) { + rangeManager.replaceRange({ + ...cacheRange, + startIndex: cacheRangeStartIndex, + endIndex: cacheRangeEndIndex + }); + (_d = control.getActiveControl()) == null ? void 0 : _d.cut(); + } else { + draw.spliceElementList(cacheElementList, cacheRangeStartIndex + 1, cacheRangeEndIndex - cacheRangeStartIndex); + } + const startElement = elementList[range.startIndex]; + const startPosition = positionList[range.startIndex]; + let positionContextIndex = positionContext.index; + if (positionContextIndex) { + if (startElement.tableId && !cacheStartElement.tableId) { + if (cacheStartPosition.index < positionContextIndex) { + positionContextIndex -= replaceLength; + } + } else if (!startElement.tableId && cacheStartElement.tableId) { + if (startPosition.index < positionContextIndex) { + positionContextIndex += replaceLength; + } + } + position.setPositionContext({ + ...positionContext, + index: positionContextIndex + }); + } + const rangeStartIndex = getElementIndexByDragId(rangeStartId, elementList); + const rangeEndIndex = getElementIndexByDragId(rangeEndId, elementList); + rangeManager.setRange(isCacheRangeCollapsed ? rangeEndIndex : rangeStartIndex, rangeEndIndex, range.tableId, range.startTdIndex, range.endTdIndex, range.startTrIndex, range.endTrIndex); + draw.clearSideEffect(); + let imgElement = null; + if (isCacheRangeCollapsed) { + const elementList2 = draw.getElementList(); + const dragElement = elementList2[rangeEndIndex]; + if (dragElement.type === ElementType.IMAGE || dragElement.type === ElementType.LATEX) { + moveImgPosition(dragElement, evt, host); + imgElement = dragElement; + } + } + draw.render({ + isSetCursor: false + }); + if (imgElement) { + if (imgElement.imgDisplay === ImageDisplay.SURROUND || imgElement.imgDisplay === ImageDisplay.FLOAT_TOP || imgElement.imgDisplay === ImageDisplay.FLOAT_BOTTOM) { + draw.getPreviewer().drawResizer(imgElement); + } else { + const dragPositionList = position.getPositionList(); + const dragPosition = dragPositionList[rangeEndIndex]; + draw.getPreviewer().drawResizer(imgElement, dragPosition); + } + } + } else if (host.isAllowDrag) { + host.mousedown(evt); + } +} +function mouseleave(evt, host) { + const draw = host.getDraw(); + const pageContainer = draw.getPageContainer(); + const { x, y, width, height } = pageContainer.getBoundingClientRect(); + if (evt.x >= x && evt.x <= x + width && evt.y >= y && evt.y <= y + height) { + return; + } + host.setIsAllowSelection(false); +} +function mousemove(evt, host) { + var _a; + const draw = host.getDraw(); + if (host.isAllowDrag) { + const x = evt.offsetX; + const y = evt.offsetY; + const { startIndex: startIndex2, endIndex: endIndex2 } = host.cacheRange; + const positionList = host.cachePositionList; + for (let p = startIndex2 + 1; p <= endIndex2; p++) { + const { coordinate: { leftTop, rightBottom } } = positionList[p]; + if (x >= leftTop[0] && x <= rightBottom[0] && y >= leftTop[1] && y <= rightBottom[1]) { + return; + } + } + const cacheStartIndex = (_a = host.cacheRange) == null ? void 0 : _a.startIndex; + if (cacheStartIndex) { + const dragElement = host.cacheElementList[cacheStartIndex]; + if ((dragElement == null ? void 0 : dragElement.type) === ElementType.IMAGE && (dragElement.imgDisplay === ImageDisplay.SURROUND || dragElement.imgDisplay === ImageDisplay.FLOAT_TOP || dragElement.imgDisplay === ImageDisplay.FLOAT_BOTTOM)) { + draw.getPreviewer().clearResizer(); + draw.getImageParticle().dragFloatImage(evt.movementX, evt.movementY); + } + } + host.dragover(evt); + host.isAllowDrop = true; + return; + } + if (!host.isAllowSelection || !host.mouseDownStartPosition) + return; + const target = evt.target; + const pageIndex = target.dataset.index; + if (pageIndex) { + draw.setPageNo(Number(pageIndex)); + } + const position = draw.getPosition(); + const positionResult = position.getPositionByXY({ + x: evt.offsetX, + y: evt.offsetY + }); + if (!~positionResult.index) + return; + const { index: index2, isTable, tdValueIndex, tdIndex, trIndex, tableId } = positionResult; + const { index: startIndex, isTable: startIsTable, tdIndex: startTdIndex, trIndex: startTrIndex, tableId: startTableId } = host.mouseDownStartPosition; + const endIndex = isTable ? tdValueIndex : index2; + const rangeManager = draw.getRange(); + if (isTable && startIsTable && (tdIndex !== startTdIndex || trIndex !== startTrIndex)) { + rangeManager.setRange(endIndex, endIndex, tableId, startTdIndex, tdIndex, startTrIndex, trIndex); + } else { + let end = ~endIndex ? endIndex : 0; + if ((startIsTable || isTable) && startTableId !== tableId) + return; + let start = startIndex; + if (start > end) { + [start, end] = [end, start]; + } + if (start === end) + return; + const elementList = draw.getElementList(); + const startElement = elementList[start + 1]; + const endElement = elementList[end]; + if ((startElement == null ? void 0 : startElement.controlComponent) === ControlComponent.PLACEHOLDER && (endElement == null ? void 0 : endElement.controlComponent) === ControlComponent.PLACEHOLDER && startElement.controlId === endElement.controlId) { + return; + } + rangeManager.setRange(start, end); + } + draw.render({ + isSubmitHistory: false, + isSetCursor: false, + isCompute: false + }); +} +function backspace(evt, host) { + const draw = host.getDraw(); + if (draw.isReadonly()) + return; + const rangeManager = draw.getRange(); + if (!rangeManager.getIsCanInput()) + return; + const { startIndex, endIndex, isCrossRowCol } = rangeManager.getRange(); + const control = draw.getControl(); + let curIndex; + if (isCrossRowCol) { + const rowCol = draw.getTableParticle().getRangeRowCol(); + if (!rowCol) + return; + let isDeleted = false; + for (let r = 0; r < rowCol.length; r++) { + const row = rowCol[r]; + for (let c = 0; c < row.length; c++) { + const col = row[c]; + if (col.value.length > 1) { + draw.spliceElementList(col.value, 1, col.value.length - 1); + isDeleted = true; + } + } + } + curIndex = isDeleted ? 0 : null; + } else if (control.getActiveControl() && control.getIsRangeCanCaptureEvent()) { + curIndex = control.keydown(evt); + } else { + const position = draw.getPosition(); + const cursorPosition = position.getCursorPosition(); + if (!cursorPosition) + return; + const { index: index2 } = cursorPosition; + const isCollapsed = rangeManager.getIsCollapsed(); + const elementList = draw.getElementList(); + if (isCollapsed && index2 === 0) { + const firstElement = elementList[index2]; + if (firstElement.value === ZERO) { + if (firstElement.listId) { + draw.getListParticle().unsetList(); + } + evt.preventDefault(); + return; + } + } + const startElement = elementList[startIndex]; + if (isCollapsed && startElement.rowFlex && startElement.value === ZERO) { + const rowFlexElementList = rangeManager.getRangeRowElementList(); + if (rowFlexElementList) { + const preElement = elementList[startIndex - 1]; + rowFlexElementList.forEach((element) => { + element.rowFlex = preElement == null ? void 0 : preElement.rowFlex; + }); + } + } + if (!isCollapsed) { + draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex); + } else { + draw.spliceElementList(elementList, index2, 1); + } + curIndex = isCollapsed ? index2 - 1 : startIndex; + } + draw.getGlobalEvent().setCanvasEventAbility(); + if (curIndex === null) { + rangeManager.setRange(startIndex, startIndex); + draw.render({ + curIndex: startIndex, + isSubmitHistory: false + }); + } else { + rangeManager.setRange(curIndex, curIndex); + draw.render({ + curIndex + }); + } +} +function del(evt, host) { + var _a; + const draw = host.getDraw(); + if (draw.isReadonly()) + return; + const rangeManager = draw.getRange(); + if (!rangeManager.getIsCanInput()) + return; + const { startIndex, endIndex, isCrossRowCol } = rangeManager.getRange(); + const elementList = draw.getElementList(); + const control = draw.getControl(); + let curIndex; + if (isCrossRowCol) { + const rowCol = draw.getTableParticle().getRangeRowCol(); + if (!rowCol) + return; + let isDeleted = false; + for (let r = 0; r < rowCol.length; r++) { + const row = rowCol[r]; + for (let c = 0; c < row.length; c++) { + const col = row[c]; + if (col.value.length > 1) { + draw.spliceElementList(col.value, 1, col.value.length - 1); + isDeleted = true; + } + } + } + curIndex = isDeleted ? 0 : null; + } else if (control.getActiveControl() && control.getIsRangeWithinControl()) { + curIndex = control.keydown(evt); + } else if ((_a = elementList[endIndex + 1]) == null ? void 0 : _a.controlId) { + curIndex = control.removeControl(endIndex + 1); + } else { + const position = draw.getPosition(); + const cursorPosition = position.getCursorPosition(); + if (!cursorPosition) + return; + const { index: index2 } = cursorPosition; + const positionContext = position.getPositionContext(); + if (positionContext.isDirectHit && positionContext.isImage) { + draw.spliceElementList(elementList, index2, 1); + curIndex = index2 - 1; + } else { + const isCollapsed = rangeManager.getIsCollapsed(); + if (!isCollapsed) { + draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex); + } else { + if (!elementList[index2 + 1]) + return; + draw.spliceElementList(elementList, index2 + 1, 1); + } + curIndex = isCollapsed ? index2 : startIndex; + } + } + draw.getGlobalEvent().setCanvasEventAbility(); + if (curIndex === null) { + rangeManager.setRange(startIndex, startIndex); + draw.render({ + curIndex: startIndex, + isSubmitHistory: false + }); + } else { + rangeManager.setRange(curIndex, curIndex); + draw.render({ + curIndex + }); + } +} +function enter(evt, host) { + var _a, _b; + const draw = host.getDraw(); + if (draw.isReadonly()) + return; + const rangeManager = draw.getRange(); + if (!rangeManager.getIsCanInput()) + return; + const { startIndex, endIndex } = rangeManager.getRange(); + const isCollapsed = rangeManager.getIsCollapsed(); + const elementList = draw.getElementList(); + const startElement = elementList[startIndex]; + const endElement = elementList[endIndex]; + if (isCollapsed && endElement.listId && endElement.value === ZERO && ((_a = elementList[endIndex + 1]) == null ? void 0 : _a.listId) !== endElement.listId) { + draw.getListParticle().unsetList(); + return; + } + const enterText = { + value: ZERO + }; + if (evt.shiftKey && startElement.listId) { + enterText.listWrap = true; + } + formatElementContext(elementList, [enterText], startIndex, { + isBreakWhenWrap: true, + editorOptions: draw.getOptions() + }); + if (!(endElement.titleId && endElement.titleId !== ((_b = elementList[endIndex + 1]) == null ? void 0 : _b.titleId))) { + const copyElement = getAnchorElement(elementList, endIndex); + if (copyElement) { + const copyAttr = [...EDITOR_ROW_ATTR]; + if (copyElement.controlComponent !== ControlComponent.POSTFIX) { + copyAttr.push(...EDITOR_ELEMENT_STYLE_ATTR); + } + copyAttr.forEach((attr) => { + const value = copyElement[attr]; + if (value !== void 0) { + enterText[attr] = value; + } + }); + } + } + const control = draw.getControl(); + const activeControl = control.getActiveControl(); + let curIndex; + if (activeControl && control.getIsRangeWithinControl()) { + curIndex = control.setValue([enterText]); + } else { + const position = draw.getPosition(); + const cursorPosition = position.getCursorPosition(); + if (!cursorPosition) + return; + const { index: index2 } = cursorPosition; + if (isCollapsed) { + draw.spliceElementList(elementList, index2 + 1, 0, enterText); + } else { + draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex, enterText); + } + curIndex = index2 + 1; + } + if (~curIndex) { + rangeManager.setRange(curIndex, curIndex); + draw.render({ curIndex }); + } + evt.preventDefault(); +} +function left(evt, host) { + var _a, _b; + const draw = host.getDraw(); + const isReadonly = draw.isReadonly(); + if (isReadonly) + return; + const position = draw.getPosition(); + const cursorPosition = position.getCursorPosition(); + if (!cursorPosition) + return; + const positionContext = position.getPositionContext(); + const { index: index2 } = cursorPosition; + if (index2 <= 0 && !positionContext.isTable) + return; + const rangeManager = draw.getRange(); + const { startIndex, endIndex } = rangeManager.getRange(); + const isCollapsed = rangeManager.getIsCollapsed(); + const elementList = draw.getElementList(); + const control = draw.getControl(); + if (draw.getMode() === EditorMode.FORM && control.getActiveControl() && ((_a = elementList[index2]) == null ? void 0 : _a.controlComponent) === ControlComponent.PREFIX) { + control.initNextControl({ + direction: MoveDirection.UP + }); + return; + } + let moveCount = 1; + if (isMod(evt)) { + const LETTER_REG = draw.getLetterReg(); + const moveStartIndex = evt.shiftKey && !isCollapsed && startIndex === (cursorPosition == null ? void 0 : cursorPosition.index) ? endIndex : startIndex; + if (LETTER_REG.test((_b = elementList[moveStartIndex]) == null ? void 0 : _b.value)) { + let i = moveStartIndex - 1; + while (i > 0) { + const element = elementList[i]; + if (!LETTER_REG.test(element.value)) { + break; + } + moveCount++; + i--; + } + } + } + const curIndex = startIndex - moveCount; + let anchorStartIndex = curIndex; + let anchorEndIndex = curIndex; + if (evt.shiftKey && cursorPosition) { + if (startIndex !== endIndex) { + if (startIndex === cursorPosition.index) { + anchorStartIndex = startIndex; + anchorEndIndex = endIndex - moveCount; + } else { + anchorStartIndex = curIndex; + anchorEndIndex = endIndex; + } + } else { + anchorEndIndex = endIndex; + } + } + if (!evt.shiftKey) { + const element = elementList[startIndex]; + if (element.type === ElementType.TABLE) { + const trList = element.trList; + const lastTrIndex = trList.length - 1; + const lastTr = trList[lastTrIndex]; + const lastTdIndex = lastTr.tdList.length - 1; + const lastTd = lastTr.tdList[lastTdIndex]; + position.setPositionContext({ + isTable: true, + index: startIndex, + trIndex: lastTrIndex, + tdIndex: lastTdIndex, + tdId: lastTd.id, + trId: lastTr.id, + tableId: element.id + }); + anchorStartIndex = lastTd.value.length - 1; + anchorEndIndex = anchorStartIndex; + draw.getTableTool().render(); + } else if (element.tableId) { + if (startIndex === 0) { + const originalElementList = draw.getOriginalElementList(); + const trList = originalElementList[positionContext.index].trList; + outer: + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + if (tr.id !== element.trId) + continue; + const tdList = tr.tdList; + for (let d = 0; d < tdList.length; d++) { + const td = tdList[d]; + if (td.id !== element.tdId) + continue; + if (r === 0 && d === 0) { + position.setPositionContext({ + isTable: false + }); + anchorStartIndex = positionContext.index - 1; + anchorEndIndex = anchorStartIndex; + draw.getTableTool().dispose(); + } else { + let preTrIndex = r; + let preTdIndex = d - 1; + if (preTdIndex < 0) { + preTrIndex = r - 1; + preTdIndex = trList[preTrIndex].tdList.length - 1; + } + const preTr = trList[preTrIndex]; + const preTd = preTr.tdList[preTdIndex]; + position.setPositionContext({ + isTable: true, + index: positionContext.index, + trIndex: preTrIndex, + tdIndex: preTdIndex, + tdId: preTd.id, + trId: preTr.id, + tableId: element.id + }); + anchorStartIndex = preTd.value.length - 1; + anchorEndIndex = anchorStartIndex; + draw.getTableTool().render(); + } + break outer; + } + } + } + } + } + if (!~anchorStartIndex || !~anchorEndIndex) + return; + rangeManager.setRange(anchorStartIndex, anchorEndIndex); + const isAnchorCollapsed = anchorStartIndex === anchorEndIndex; + draw.render({ + curIndex: isAnchorCollapsed ? anchorStartIndex : void 0, + isSetCursor: isAnchorCollapsed, + isSubmitHistory: false, + isCompute: false + }); + evt.preventDefault(); +} +function right(evt, host) { + var _a, _b; + const draw = host.getDraw(); + const isReadonly = draw.isReadonly(); + if (isReadonly) + return; + const position = draw.getPosition(); + const cursorPosition = position.getCursorPosition(); + if (!cursorPosition) + return; + const { index: index2 } = cursorPosition; + const positionList = position.getPositionList(); + const positionContext = position.getPositionContext(); + if (index2 > positionList.length - 1 && !positionContext.isTable) + return; + const rangeManager = draw.getRange(); + const { startIndex, endIndex } = rangeManager.getRange(); + const isCollapsed = rangeManager.getIsCollapsed(); + let elementList = draw.getElementList(); + const control = draw.getControl(); + if (draw.getMode() === EditorMode.FORM && control.getActiveControl() && ((_a = elementList[index2 + 1]) == null ? void 0 : _a.controlComponent) === ControlComponent.POSTFIX) { + control.initNextControl({ + direction: MoveDirection.DOWN + }); + return; + } + let moveCount = 1; + if (isMod(evt)) { + const LETTER_REG = draw.getLetterReg(); + const moveStartIndex = evt.shiftKey && !isCollapsed && startIndex === (cursorPosition == null ? void 0 : cursorPosition.index) ? endIndex : startIndex; + if (LETTER_REG.test((_b = elementList[moveStartIndex + 1]) == null ? void 0 : _b.value)) { + let i = moveStartIndex + 2; + while (i < elementList.length) { + const element = elementList[i]; + if (!LETTER_REG.test(element.value)) { + break; + } + moveCount++; + i++; + } + } + } + const curIndex = endIndex + moveCount; + let anchorStartIndex = curIndex; + let anchorEndIndex = curIndex; + if (evt.shiftKey && cursorPosition) { + if (startIndex !== endIndex) { + if (startIndex === cursorPosition.index) { + anchorStartIndex = startIndex; + anchorEndIndex = curIndex; + } else { + anchorStartIndex = startIndex + moveCount; + anchorEndIndex = endIndex; + } + } else { + anchorStartIndex = startIndex; + } + } + if (!evt.shiftKey) { + const element = elementList[endIndex]; + const nextElement = elementList[endIndex + 1]; + if ((nextElement == null ? void 0 : nextElement.type) === ElementType.TABLE) { + const trList = nextElement.trList; + const nextTr = trList[0]; + const nextTd = nextTr.tdList[0]; + position.setPositionContext({ + isTable: true, + index: endIndex + 1, + trIndex: 0, + tdIndex: 0, + tdId: nextTd.id, + trId: nextTr.id, + tableId: nextElement.id + }); + anchorStartIndex = 0; + anchorEndIndex = 0; + draw.getTableTool().render(); + } else if (element.tableId) { + if (!nextElement) { + const originalElementList = draw.getOriginalElementList(); + const trList = originalElementList[positionContext.index].trList; + outer: + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + if (tr.id !== element.trId) + continue; + const tdList = tr.tdList; + for (let d = 0; d < tdList.length; d++) { + const td = tdList[d]; + if (td.id !== element.tdId) + continue; + if (r === trList.length - 1 && d === tdList.length - 1) { + position.setPositionContext({ + isTable: false + }); + anchorStartIndex = positionContext.index; + anchorEndIndex = anchorStartIndex; + elementList = draw.getElementList(); + draw.getTableTool().dispose(); + } else { + let nextTrIndex = r; + let nextTdIndex = d + 1; + if (nextTdIndex > tdList.length - 1) { + nextTrIndex = r + 1; + nextTdIndex = 0; + } + const preTr = trList[nextTrIndex]; + const preTd = preTr.tdList[nextTdIndex]; + position.setPositionContext({ + isTable: true, + index: positionContext.index, + trIndex: nextTrIndex, + tdIndex: nextTdIndex, + tdId: preTd.id, + trId: preTr.id, + tableId: element.id + }); + anchorStartIndex = 0; + anchorEndIndex = anchorStartIndex; + draw.getTableTool().render(); + } + break outer; + } + } + } + } + } + const maxElementListIndex = elementList.length - 1; + if (anchorStartIndex > maxElementListIndex || anchorEndIndex > maxElementListIndex) { + return; + } + rangeManager.setRange(anchorStartIndex, anchorEndIndex); + const isAnchorCollapsed = anchorStartIndex === anchorEndIndex; + draw.render({ + curIndex: isAnchorCollapsed ? anchorStartIndex : void 0, + isSetCursor: isAnchorCollapsed, + isSubmitHistory: false, + isCompute: false + }); + evt.preventDefault(); +} +function tab(evt, host) { + const draw = host.getDraw(); + const isReadonly = draw.isReadonly(); + if (isReadonly) + return; + evt.preventDefault(); + const control = draw.getControl(); + const activeControl = control.getActiveControl(); + if (activeControl && control.getIsRangeWithinControl()) { + control.initNextControl({ + direction: evt.shiftKey ? MoveDirection.UP : MoveDirection.DOWN + }); + } else { + const tabElement = { + type: ElementType.TAB, + value: "" + }; + const rangeManager = draw.getRange(); + const { startIndex } = rangeManager.getRange(); + const elementList = draw.getElementList(); + formatElementContext(elementList, [tabElement], startIndex, { + editorOptions: draw.getOptions() + }); + draw.insertElementList([tabElement]); + } +} +function getNextPositionIndex(payload) { + const { positionList, index: index2, isUp, rowNo, cursorX } = payload; + let nextIndex = -1; + const probablePosition = []; + if (isUp) { + let p = index2 - 1; + while (p >= 0) { + const position = positionList[p]; + p--; + if (position.rowNo === rowNo) + continue; + if (probablePosition[0] && probablePosition[0].rowNo !== position.rowNo) { + break; + } + probablePosition.unshift(position); + } + } else { + let p = index2 + 1; + while (p < positionList.length) { + const position = positionList[p]; + p++; + if (position.rowNo === rowNo) + continue; + if (probablePosition[0] && probablePosition[0].rowNo !== position.rowNo) { + break; + } + probablePosition.push(position); + } + } + for (let p = 0; p < probablePosition.length; p++) { + const nextPosition = probablePosition[p]; + const { coordinate: { leftTop: [nextLeftX], rightTop: [nextRightX] } } = nextPosition; + if (p === probablePosition.length - 1) { + nextIndex = nextPosition.index; + } + if (cursorX < nextLeftX || cursorX > nextRightX) + continue; + nextIndex = nextPosition.index; + break; + } + return nextIndex; +} +function updown(evt, host) { + const draw = host.getDraw(); + const isReadonly = draw.isReadonly(); + if (isReadonly) + return; + const position = draw.getPosition(); + const cursorPosition = position.getCursorPosition(); + if (!cursorPosition) + return; + const rangeManager = draw.getRange(); + const { startIndex, endIndex } = rangeManager.getRange(); + let positionList = position.getPositionList(); + const isUp = evt.key === KeyMap.Up; + let anchorStartIndex = -1; + let anchorEndIndex = -1; + const positionContext = position.getPositionContext(); + if (!evt.shiftKey && positionContext.isTable && (isUp && cursorPosition.rowIndex === 0 || !isUp && cursorPosition.rowIndex === draw.getRowCount() - 1)) { + const { index: index2, trIndex, tdIndex, tableId } = positionContext; + if (isUp) { + if (trIndex === 0) { + position.setPositionContext({ + isTable: false + }); + anchorStartIndex = index2 - 1; + anchorEndIndex = anchorStartIndex; + draw.getTableTool().dispose(); + } else { + let preTrIndex = -1; + let preTdIndex = -1; + const originalElementList = draw.getOriginalElementList(); + const trList = originalElementList[index2].trList; + const curTdColIndex = trList[trIndex].tdList[tdIndex].colIndex; + outer: + for (let r = trIndex - 1; r >= 0; r--) { + const tr = trList[r]; + const tdList = tr.tdList; + for (let d = 0; d < tdList.length; d++) { + const td = tdList[d]; + if (td.colIndex === curTdColIndex || td.colIndex + td.colspan - 1 >= curTdColIndex && td.colIndex <= curTdColIndex) { + preTrIndex = r; + preTdIndex = d; + break outer; + } + } + } + if (!~preTrIndex || !~preTdIndex) + return; + const preTr = trList[preTrIndex]; + const preTd = preTr.tdList[preTdIndex]; + position.setPositionContext({ + isTable: true, + index: index2, + trIndex: preTrIndex, + tdIndex: preTdIndex, + tdId: preTr.id, + trId: preTd.id, + tableId + }); + anchorStartIndex = preTd.value.length - 1; + anchorEndIndex = anchorStartIndex; + draw.getTableTool().render(); + } + } else { + const originalElementList = draw.getOriginalElementList(); + const trList = originalElementList[index2].trList; + if (trIndex === trList.length - 1) { + position.setPositionContext({ + isTable: false + }); + anchorStartIndex = index2; + anchorEndIndex = anchorStartIndex; + draw.getTableTool().dispose(); + } else { + let nexTrIndex = -1; + let nextTdIndex = -1; + const curTdColIndex = trList[trIndex].tdList[tdIndex].colIndex; + outer: + for (let r = trIndex + 1; r < trList.length; r++) { + const tr = trList[r]; + const tdList = tr.tdList; + for (let d = 0; d < tdList.length; d++) { + const td = tdList[d]; + if (td.colIndex === curTdColIndex || td.colIndex + td.colspan - 1 >= curTdColIndex && td.colIndex <= curTdColIndex) { + nexTrIndex = r; + nextTdIndex = d; + break outer; + } + } + } + if (!~nexTrIndex || !~nextTdIndex) + return; + const nextTr = trList[nexTrIndex]; + const nextTd = nextTr.tdList[nextTdIndex]; + position.setPositionContext({ + isTable: true, + index: index2, + trIndex: nexTrIndex, + tdIndex: nextTdIndex, + tdId: nextTr.id, + trId: nextTd.id, + tableId + }); + anchorStartIndex = nextTd.value.length - 1; + anchorEndIndex = anchorStartIndex; + draw.getTableTool().render(); + } + } + } else { + let anchorPosition = cursorPosition; + if (evt.shiftKey) { + if (startIndex === cursorPosition.index) { + anchorPosition = positionList[endIndex]; + } else { + anchorPosition = positionList[startIndex]; + } + } + const { index: index2, rowNo, rowIndex, coordinate: { rightTop: [curRightX] } } = anchorPosition; + if (isUp && rowIndex === 0 || !isUp && rowIndex === draw.getRowCount() - 1) { + return; + } + const nextIndex = getNextPositionIndex({ + positionList, + index: index2, + rowNo, + isUp, + cursorX: curRightX + }); + if (nextIndex < 0) + return; + anchorStartIndex = nextIndex; + anchorEndIndex = nextIndex; + if (evt.shiftKey) { + if (startIndex !== endIndex) { + if (startIndex === cursorPosition.index) { + anchorStartIndex = startIndex; + } else { + anchorEndIndex = endIndex; + } + } else { + if (isUp) { + anchorEndIndex = endIndex; + } else { + anchorStartIndex = startIndex; + } + } + } + const elementList = draw.getElementList(); + const nextElement = elementList[nextIndex]; + if (nextElement.type === ElementType.TABLE) { + const { scale } = draw.getOptions(); + const margins = draw.getMargins(); + const trList = nextElement.trList; + let trIndex = -1; + let tdIndex = -1; + let tdPositionIndex = -1; + if (isUp) { + outer: + for (let r = trList.length - 1; r >= 0; r--) { + const tr = trList[r]; + const tdList = tr.tdList; + for (let d = 0; d < tdList.length; d++) { + const td = tdList[d]; + const tdX = td.x * scale + margins[3]; + const tdWidth = td.width * scale; + if (curRightX >= tdX && curRightX <= tdX + tdWidth) { + const tdPositionList = td.positionList; + const lastPosition = tdPositionList[tdPositionList.length - 1]; + const nextPositionIndex = getNextPositionIndex({ + positionList: tdPositionList, + index: lastPosition.index + 1, + rowNo: lastPosition.rowNo - 1, + isUp, + cursorX: curRightX + }) || lastPosition.index; + trIndex = r; + tdIndex = d; + tdPositionIndex = nextPositionIndex; + break outer; + } + } + } + } else { + outer: + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + const tdList = tr.tdList; + for (let d = 0; d < tdList.length; d++) { + const td = tdList[d]; + const tdX = td.x * scale + margins[3]; + const tdWidth = td.width * scale; + if (curRightX >= tdX && curRightX <= tdX + tdWidth) { + const tdPositionList = td.positionList; + const nextPositionIndex = getNextPositionIndex({ + positionList: tdPositionList, + index: -1, + rowNo: -1, + isUp, + cursorX: curRightX + }) || 0; + trIndex = r; + tdIndex = d; + tdPositionIndex = nextPositionIndex; + break outer; + } + } + } + } + if (~trIndex && ~tdIndex && ~tdPositionIndex) { + const nextTr = trList[trIndex]; + const nextTd = nextTr.tdList[tdIndex]; + position.setPositionContext({ + isTable: true, + index: nextIndex, + trIndex, + tdIndex, + tdId: nextTd.id, + trId: nextTr.id, + tableId: nextElement.id + }); + anchorStartIndex = tdPositionIndex; + anchorEndIndex = anchorStartIndex; + positionList = position.getPositionList(); + draw.getTableTool().render(); + } + } + } + if (!~anchorStartIndex || !~anchorEndIndex) + return; + if (anchorStartIndex > anchorEndIndex) { + [anchorStartIndex, anchorEndIndex] = [anchorEndIndex, anchorStartIndex]; + } + rangeManager.setRange(anchorStartIndex, anchorEndIndex); + const isCollapsed = anchorStartIndex === anchorEndIndex; + draw.render({ + curIndex: isCollapsed ? anchorStartIndex : void 0, + isSetCursor: isCollapsed, + isSubmitHistory: false, + isCompute: false + }); + draw.getCursor().moveCursorToVisible({ + cursorPosition: positionList[isUp ? anchorStartIndex : anchorEndIndex], + direction: isUp ? MoveDirection.UP : MoveDirection.DOWN + }); +} +function keydown(evt, host) { + if (host.isComposing) + return; + const draw = host.getDraw(); + if (evt.key === KeyMap.Backspace) { + backspace(evt, host); + } else if (evt.key === KeyMap.Delete) { + del(evt, host); + } else if (evt.key === KeyMap.Enter) { + enter(evt, host); + } else if (evt.key === KeyMap.Left) { + left(evt, host); + } else if (evt.key === KeyMap.Right) { + right(evt, host); + } else if (evt.key === KeyMap.Up || evt.key === KeyMap.Down) { + updown(evt, host); + } else if (isMod(evt) && evt.key === KeyMap.Z) { + if (draw.isReadonly() && draw.getMode() !== EditorMode.FORM) + return; + draw.getHistoryManager().undo(); + evt.preventDefault(); + } else if (isMod(evt) && evt.key === KeyMap.Y) { + if (draw.isReadonly() && draw.getMode() !== EditorMode.FORM) + return; + draw.getHistoryManager().redo(); + evt.preventDefault(); + } else if (isMod(evt) && evt.key === KeyMap.C) { + host.copy(); + evt.preventDefault(); + } else if (isMod(evt) && evt.key === KeyMap.X) { + host.cut(); + evt.preventDefault(); + } else if (isMod(evt) && evt.key === KeyMap.A) { + host.selectAll(); + evt.preventDefault(); + } else if (isMod(evt) && evt.key === KeyMap.S) { + if (draw.isReadonly()) + return; + const listener = draw.getListener(); + if (listener.saved) { + listener.saved(draw.getValue()); + } + const eventBus = draw.getEventBus(); + if (eventBus.isSubscribe("saved")) { + eventBus.emit("saved", draw.getValue()); + } + evt.preventDefault(); + } else if (evt.key === KeyMap.ESC) { + host.clearPainterStyle(); + const zoneManager = draw.getZone(); + if (!zoneManager.isMainActive()) { + zoneManager.setZone(EditorZone.MAIN); + } + evt.preventDefault(); + } else if (evt.key === KeyMap.TAB) { + tab(evt, host); + } +} +function input(data2, host) { + var _a; + const draw = host.getDraw(); + if (draw.isReadonly() || draw.isDisabled()) + return; + const position = draw.getPosition(); + const cursorPosition = position.getCursorPosition(); + if (!data2 || !cursorPosition) + return; + const isComposing = host.isComposing; + if (isComposing && ((_a = host.compositionInfo) == null ? void 0 : _a.value) === data2) + return; + const rangeManager = draw.getRange(); + if (!rangeManager.getIsCanInput()) + return; + removeComposingInput(host); + if (!isComposing) { + const cursor = draw.getCursor(); + cursor.clearAgentDomValue(); + } + const { TEXT, HYPERLINK, SUBSCRIPT, SUPERSCRIPT, DATE } = ElementType; + const text = data2.replaceAll(` +`, ZERO); + const { startIndex, endIndex } = rangeManager.getRange(); + const elementList = draw.getElementList(); + const copyElement = getAnchorElement(elementList, endIndex); + if (!copyElement) + return; + const isDesignMode = draw.isDesignMode(); + const inputData = splitText(text).map((value) => { + var _a2, _b; + const newElement = { + value + }; + if (isDesignMode || !((_a2 = copyElement.title) == null ? void 0 : _a2.disabled) && !((_b = copyElement.control) == null ? void 0 : _b.disabled)) { + const nextElement = elementList[endIndex + 1]; + if (!copyElement.type || copyElement.type === TEXT || copyElement.type === HYPERLINK && (nextElement == null ? void 0 : nextElement.type) === HYPERLINK || copyElement.type === DATE && (nextElement == null ? void 0 : nextElement.type) === DATE || copyElement.type === SUBSCRIPT && (nextElement == null ? void 0 : nextElement.type) === SUBSCRIPT || copyElement.type === SUPERSCRIPT && (nextElement == null ? void 0 : nextElement.type) === SUPERSCRIPT) { + EDITOR_ELEMENT_COPY_ATTR.forEach((attr) => { + if (attr === "groupIds" && !(nextElement == null ? void 0 : nextElement.groupIds)) + return; + const value2 = copyElement[attr]; + if (value2 !== void 0) { + newElement[attr] = value2; + } + }); + } + if (isComposing) { + newElement.underline = true; + } + } + return newElement; + }); + const control = draw.getControl(); + let curIndex; + if (control.getActiveControl() && control.getIsRangeWithinControl()) { + curIndex = control.setValue(inputData); + } else { + const start = startIndex + 1; + if (startIndex !== endIndex) { + draw.spliceElementList(elementList, start, endIndex - startIndex); + } + formatElementContext(elementList, inputData, startIndex, { + editorOptions: draw.getOptions() + }); + draw.spliceElementList(elementList, start, 0, ...inputData); + curIndex = startIndex + inputData.length; + } + if (~curIndex) { + rangeManager.setRange(curIndex, curIndex); + draw.render({ + curIndex, + isSubmitHistory: !isComposing + }); + } + if (isComposing) { + host.compositionInfo = { + elementList, + value: text, + startIndex: curIndex - inputData.length, + endIndex: curIndex + }; + } +} +function removeComposingInput(host) { + if (!host.compositionInfo) + return; + const { elementList, startIndex, endIndex } = host.compositionInfo; + elementList.splice(startIndex + 1, endIndex - startIndex); + const rangeManager = host.getDraw().getRange(); + rangeManager.setRange(startIndex, startIndex); + host.compositionInfo = null; +} +function cut(host) { + const draw = host.getDraw(); + const rangeManager = draw.getRange(); + const { startIndex, endIndex } = rangeManager.getRange(); + if (!~startIndex && !~startIndex) + return; + if (draw.isReadonly() || !rangeManager.getIsCanInput()) + return; + const elementList = draw.getElementList(); + let start = startIndex; + let end = endIndex; + if (startIndex === endIndex) { + const position = draw.getPosition(); + const positionList = position.getPositionList(); + const startPosition = positionList[startIndex]; + const curRowNo = startPosition.rowNo; + const curPageNo = startPosition.pageNo; + const cutElementIndexList = []; + for (let p = 0; p < positionList.length; p++) { + const position2 = positionList[p]; + if (position2.pageNo > curPageNo) + break; + if (position2.pageNo === curPageNo && position2.rowNo === curRowNo) { + cutElementIndexList.push(p); + } + } + const firstElementIndex = cutElementIndexList[0] - 1; + start = firstElementIndex < 0 ? 0 : firstElementIndex; + end = cutElementIndexList[cutElementIndexList.length - 1]; + } + const options = draw.getOptions(); + writeElementList(elementList.slice(start + 1, end + 1), options); + const control = draw.getControl(); + let curIndex; + if (control.getActiveControl() && control.getIsRangeWithinControl()) { + curIndex = control.cut(); + } else { + draw.spliceElementList(elementList, start + 1, end - start); + curIndex = start; + } + rangeManager.setRange(curIndex, curIndex); + draw.render({ curIndex }); +} +function copy(host) { + const draw = host.getDraw(); + const { copy: copy2 } = draw.getOverride(); + if (copy2) { + const overrideResult = copy2(); + if ((overrideResult == null ? void 0 : overrideResult.preventDefault) !== false) + return; + } + const rangeManager = draw.getRange(); + let copyElementList = null; + const range = rangeManager.getRange(); + if (range.isCrossRowCol) { + const tableElement = rangeManager.getRangeTableElement(); + if (!tableElement) + return; + const rowCol = draw.getTableParticle().getRangeRowCol(); + if (!rowCol) + return; + const copyTableElement = { + type: ElementType.TABLE, + value: "", + colgroup: [], + trList: [] + }; + const firstRow = rowCol[0]; + const colStartIndex = firstRow[0].colIndex; + const lastCol = firstRow[firstRow.length - 1]; + const colEndIndex = lastCol.colIndex + lastCol.colspan - 1; + for (let c = colStartIndex; c <= colEndIndex; c++) { + copyTableElement.colgroup.push(tableElement.colgroup[c]); + } + for (let r = 0; r < rowCol.length; r++) { + const row = rowCol[r]; + const tr = tableElement.trList[row[0].rowIndex]; + const coptTr = { + tdList: [], + height: tr.height, + minHeight: tr.minHeight + }; + for (let c = 0; c < row.length; c++) { + coptTr.tdList.push(row[c]); + } + copyTableElement.trList.push(coptTr); + } + copyElementList = zipElementList([copyTableElement]); + } else { + copyElementList = rangeManager.getIsCollapsed() ? rangeManager.getRangeRowElementList() : rangeManager.getSelectionElementList(); + } + if (!(copyElementList == null ? void 0 : copyElementList.length)) + return; + writeElementList(copyElementList, draw.getOptions()); +} +function drop(evt, host) { + var _a, _b; + const draw = host.getDraw(); + const { drop: drop2 } = draw.getOverride(); + if (drop2) { + const overrideResult = drop2(evt); + if ((overrideResult == null ? void 0 : overrideResult.preventDefault) !== false) + return; + } + evt.preventDefault(); + const data2 = (_a = evt.dataTransfer) == null ? void 0 : _a.getData("text"); + if (data2) { + host.input(data2); + } else { + const files = (_b = evt.dataTransfer) == null ? void 0 : _b.files; + if (!files) + return; + for (let i = 0; i < files.length; i++) { + const file = files[i]; + if (file.type.startsWith("image")) { + pasteImage(host, file); + } + } + } +} +function getWordRangeBySegmenter(host) { + var _a; + if (!Intl.Segmenter) + return null; + const draw = host.getDraw(); + const cursorPosition = draw.getPosition().getCursorPosition(); + if (!cursorPosition) + return null; + const rangeManager = draw.getRange(); + const paragraphInfo = rangeManager.getRangeParagraphInfo(); + if (!paragraphInfo) + return null; + const paragraphText = ((_a = paragraphInfo == null ? void 0 : paragraphInfo.elementList) == null ? void 0 : _a.map((e) => !e.type || e.type !== ElementType.CONTROL && TEXTLIKE_ELEMENT_TYPE.includes(e.type) ? e.value : ZERO).join("")) || ""; + if (!paragraphText) + return null; + const cursorStartIndex = cursorPosition.index; + const offset = paragraphInfo.startIndex; + const segmenter = new Intl.Segmenter(void 0, { granularity: "word" }); + const segments = segmenter.segment(paragraphText); + let startIndex = -1; + let endIndex = -1; + for (const { segment, index: index2, isWordLike } of segments) { + const realSegmentStartIndex = index2 + offset; + if (isWordLike && cursorStartIndex >= realSegmentStartIndex && cursorStartIndex < realSegmentStartIndex + segment.length) { + startIndex = realSegmentStartIndex - 1; + endIndex = startIndex + segment.length; + break; + } + } + return ~startIndex && ~endIndex ? { startIndex, endIndex } : null; +} +function getWordRangeByCursor(host) { + const draw = host.getDraw(); + const cursorPosition = draw.getPosition().getCursorPosition(); + if (!cursorPosition) + return null; + const { value, index: index2 } = cursorPosition; + const LETTER_REG = draw.getLetterReg(); + let upCount = 0; + let downCount = 0; + const isNumber = NUMBER_LIKE_REG.test(value); + if (isNumber || LETTER_REG.test(value)) { + const elementList = draw.getElementList(); + let upStartIndex = index2 - 1; + while (upStartIndex > 0) { + const value2 = elementList[upStartIndex].value; + if (isNumber && NUMBER_LIKE_REG.test(value2) || !isNumber && LETTER_REG.test(value2)) { + upCount++; + upStartIndex--; + } else { + break; + } + } + let downStartIndex = index2 + 1; + while (downStartIndex < elementList.length) { + const value2 = elementList[downStartIndex].value; + if (isNumber && NUMBER_LIKE_REG.test(value2) || !isNumber && LETTER_REG.test(value2)) { + downCount++; + downStartIndex++; + } else { + break; + } + } + } + const startIndex = index2 - upCount - 1; + if (startIndex < 0) + return null; + return { + startIndex, + endIndex: index2 + downCount + }; +} +function dblclick(host, evt) { + const draw = host.getDraw(); + const position = draw.getPosition(); + const positionContext = position.getPositionByXY({ + x: evt.offsetX, + y: evt.offsetY + }); + if (positionContext.isImage && positionContext.isDirectHit) { + draw.getPreviewer().render(); + return; + } + if (draw.getIsPagingMode()) { + if (!~positionContext.index && positionContext.zone) { + draw.getZone().setZone(positionContext.zone); + draw.clearSideEffect(); + position.setPositionContext({ + isTable: false + }); + return; + } + } + if ((positionContext.isCheckbox || positionContext.isRadio) && positionContext.isDirectHit) { + return; + } + const rangeManager = draw.getRange(); + const segmenterRange = getWordRangeBySegmenter(host) || getWordRangeByCursor(host); + if (!segmenterRange) + return; + rangeManager.setRange(segmenterRange.startIndex, segmenterRange.endIndex); + draw.render({ + isSubmitHistory: false, + isSetCursor: false, + isCompute: false + }); + rangeManager.setRangeStyle(); +} +function threeClick(host) { + var _a, _b; + const draw = host.getDraw(); + const position = draw.getPosition(); + const cursorPosition = position.getCursorPosition(); + if (!cursorPosition) + return; + const { index: index2 } = cursorPosition; + const elementList = draw.getElementList(); + let upCount = 0; + let downCount = 0; + let upStartIndex = index2 - 1; + while (upStartIndex > 0) { + const element = elementList[upStartIndex]; + const preElement = elementList[upStartIndex - 1]; + if (element.value === ZERO && !element.listWrap || element.listId !== (preElement == null ? void 0 : preElement.listId) || element.titleId !== (preElement == null ? void 0 : preElement.titleId)) { + break; + } + upCount++; + upStartIndex--; + } + let downStartIndex = index2 + 1; + while (downStartIndex < elementList.length) { + const element = elementList[downStartIndex]; + const nextElement = elementList[downStartIndex + 1]; + if (element.value === ZERO && !element.listWrap || element.listId !== (nextElement == null ? void 0 : nextElement.listId) || element.titleId !== (nextElement == null ? void 0 : nextElement.titleId)) { + break; + } + downCount++; + downStartIndex++; + } + const rangeManager = draw.getRange(); + let newStartIndex = index2 - upCount - 1; + if (((_a = elementList[newStartIndex]) == null ? void 0 : _a.value) !== ZERO) { + newStartIndex -= 1; + } + if (newStartIndex < 0) + return; + let newEndIndex = index2 + downCount + 1; + if (((_b = elementList[newEndIndex]) == null ? void 0 : _b.value) === ZERO || newEndIndex > elementList.length - 1) { + newEndIndex -= 1; + } + rangeManager.setRange(newStartIndex, newEndIndex); + draw.render({ + isSubmitHistory: false, + isSetCursor: false, + isCompute: false + }); +} +var click = { + dblclick, + threeClick +}; +function compositionstart(host) { + host.isComposing = true; +} +function compositionend(host, evt) { + host.isComposing = false; + const draw = host.getDraw(); + if (!evt.data) { + removeComposingInput(host); + const rangeManager = draw.getRange(); + const { endIndex: curIndex } = rangeManager.getRange(); + draw.render({ + curIndex, + isSubmitHistory: false + }); + } else { + setTimeout(() => { + if (host.compositionInfo) { + input(evt.data, host); + } + }, 1); + } + const cursor = draw.getCursor(); + cursor.clearAgentDomValue(); +} +var composition = { + compositionstart, + compositionend +}; +function dragover(evt, host) { + const draw = host.getDraw(); + const isReadonly = draw.isReadonly(); + if (isReadonly) + return; + evt.preventDefault(); + const pageContainer = draw.getPageContainer(); + const editorRegion = findParent(evt.target, (node) => node === pageContainer, true); + if (!editorRegion) + return; + const target = evt.target; + const pageIndex = target.dataset.index; + if (pageIndex) { + draw.setPageNo(Number(pageIndex)); + } + const position = draw.getPosition(); + const positionContext = position.adjustPositionContext({ + x: evt.offsetX, + y: evt.offsetY + }); + if (!positionContext) + return; + const { isTable, tdValueIndex, index: index2 } = positionContext; + const positionList = position.getPositionList(); + const curIndex = isTable ? tdValueIndex : index2; + if (~index2) { + const rangeManager = draw.getRange(); + rangeManager.setRange(curIndex, curIndex); + position.setCursorPosition(positionList[curIndex]); + } + const cursor = draw.getCursor(); + const { cursor: { dragColor, dragWidth } } = draw.getOptions(); + cursor.drawCursor({ + width: dragWidth, + color: dragColor, + isBlink: false + }); +} +var drag = { + dragover +}; +class CanvasEvent { + constructor(draw) { + this.draw = draw; + this.pageContainer = draw.getPageContainer(); + this.pageList = draw.getPageList(); + this.range = this.draw.getRange(); + this.position = this.draw.getPosition(); + this.isAllowSelection = false; + this.isComposing = false; + this.compositionInfo = null; + this.isAllowDrag = false; + this.isAllowDrop = false; + this.cacheRange = null; + this.cacheElementList = null; + this.cachePositionList = null; + this.cachePositionContext = null; + this.mouseDownStartPosition = null; + } + getDraw() { + return this.draw; + } + register() { + this.pageContainer.addEventListener("click", this.click.bind(this)); + this.pageContainer.addEventListener("mousedown", this.mousedown.bind(this)); + this.pageContainer.addEventListener("mouseup", this.mouseup.bind(this)); + this.pageContainer.addEventListener("mouseleave", this.mouseleave.bind(this)); + this.pageContainer.addEventListener("mousemove", this.mousemove.bind(this)); + this.pageContainer.addEventListener("dblclick", this.dblclick.bind(this)); + this.pageContainer.addEventListener("dragover", this.dragover.bind(this)); + this.pageContainer.addEventListener("drop", this.drop.bind(this)); + threeClick$1(this.pageContainer, this.threeClick.bind(this)); + } + setIsAllowSelection(payload) { + this.isAllowSelection = payload; + if (!payload) { + this.applyPainterStyle(); + } + } + setIsAllowDrag(payload) { + this.isAllowDrag = payload; + this.isAllowDrop = payload; + } + clearPainterStyle() { + this.pageList.forEach((p) => { + p.style.cursor = "text"; + }); + this.draw.setPainterStyle(null); + } + applyPainterStyle() { + const painterStyle = this.draw.getPainterStyle(); + if (!painterStyle) + return; + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const selection = this.range.getSelection(); + if (!selection) + return; + const painterStyleKeys = Object.keys(painterStyle); + selection.forEach((s) => { + painterStyleKeys.forEach((pKey) => { + const key = pKey; + s[key] = painterStyle[key]; + }); + }); + this.draw.render({ isSetCursor: false }); + const painterOptions = this.draw.getPainterOptions(); + if (!painterOptions || !painterOptions.isDblclick) { + this.clearPainterStyle(); + } + } + selectAll() { + const position = this.position.getPositionList(); + this.range.setRange(0, position.length - 1); + this.draw.render({ + isSubmitHistory: false, + isSetCursor: false, + isCompute: false + }); + } + mousemove(evt) { + mousemove(evt, this); + } + mousedown(evt) { + mousedown(evt, this); + } + click() { + if (isIOS && !this.draw.isReadonly()) { + this.draw.getCursor().getAgentDom().focus(); + } + } + mouseup(evt) { + mouseup(evt, this); + } + mouseleave(evt) { + mouseleave(evt, this); + } + keydown(evt) { + keydown(evt, this); + } + dblclick(evt) { + click.dblclick(this, evt); + } + threeClick() { + click.threeClick(this); + } + input(data2) { + input(data2, this); + } + cut() { + cut(this); + } + copy() { + copy(this); + } + compositionstart() { + composition.compositionstart(this); + } + compositionend(evt) { + composition.compositionend(this, evt); + } + drop(evt) { + drop(evt, this); + } + dragover(evt) { + drag.dragover(evt, this); + } +} +class GlobalEvent { + constructor(draw, canvasEvent) { + this.clearSideEffect = (evt) => { + if (!this.cursor) + return; + const target = (evt == null ? void 0 : evt.composedPath()[0]) || evt.target; + const pageList = this.draw.getPageList(); + const innerEditorDom = findParent(target, (node) => pageList.includes(node), true); + if (innerEditorDom) { + this.setRangeStyle(); + return; + } + const outerEditorDom = findParent(target, (node) => !!node && node.nodeType === 1 && !!node.getAttribute(EDITOR_COMPONENT), true); + if (outerEditorDom) { + this.setRangeStyle(); + this.watchCursorActive(); + return; + } + this.cursor.recoveryCursor(); + this.range.recoveryRangeStyle(); + this.previewer.clearResizer(); + this.tableTool.dispose(); + this.hyperlinkParticle.clearHyperlinkPopup(); + this.control.destroyControl(); + this.dateParticle.clearDatePicker(); + this.imageParticle.destroyFloatImage(); + }; + this.setCanvasEventAbility = () => { + this.canvasEvent.setIsAllowDrag(false); + this.canvasEvent.setIsAllowSelection(false); + }; + this.setRangeStyle = () => { + this.range.setRangeStyle(); + }; + this.setPageScale = (evt) => { + if (!evt.ctrlKey) + return; + evt.preventDefault(); + const { scale } = this.options; + if (evt.deltaY < 0) { + const nextScale = scale * 10 + 1; + if (nextScale <= 30) { + this.draw.setPageScale(nextScale / 10); + } + } else { + const nextScale = scale * 10 - 1; + if (nextScale >= 5) { + this.draw.setPageScale(nextScale / 10); + } + } + }; + this._handleVisibilityChange = () => { + if (document.visibilityState === "visible") { + const range = this.range.getRange(); + const isSetCursor = !!~range.startIndex && !!~range.endIndex && range.startIndex === range.endIndex; + this.range.replaceRange(range); + this.draw.render({ + isSetCursor, + isCompute: false, + isSubmitHistory: false, + curIndex: range.startIndex + }); + } + }; + this._handleDprChange = () => { + this.draw.setPageDevicePixel(); + }; + this.draw = draw; + this.options = draw.getOptions(); + this.canvasEvent = canvasEvent; + this.cursor = null; + this.range = draw.getRange(); + this.previewer = draw.getPreviewer(); + this.tableTool = draw.getTableTool(); + this.hyperlinkParticle = draw.getHyperlinkParticle(); + this.dateParticle = draw.getDateParticle(); + this.imageParticle = draw.getImageParticle(); + this.control = draw.getControl(); + this.dprMediaQueryList = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`); + } + register() { + this.cursor = this.draw.getCursor(); + this.addEvent(); + } + addEvent() { + window.addEventListener("blur", this.clearSideEffect); + document.addEventListener("keyup", this.setRangeStyle); + document.addEventListener("click", this.clearSideEffect); + document.addEventListener("mouseup", this.setCanvasEventAbility); + document.addEventListener("wheel", this.setPageScale, { passive: false }); + document.addEventListener("visibilitychange", this._handleVisibilityChange); + this.dprMediaQueryList.addEventListener("change", this._handleDprChange); + } + removeEvent() { + window.removeEventListener("blur", this.clearSideEffect); + document.removeEventListener("keyup", this.setRangeStyle); + document.removeEventListener("click", this.clearSideEffect); + document.removeEventListener("mouseup", this.setCanvasEventAbility); + document.removeEventListener("wheel", this.setPageScale); + document.removeEventListener("visibilitychange", this._handleVisibilityChange); + this.dprMediaQueryList.removeEventListener("change", this._handleDprChange); + } + watchCursorActive() { + if (!this.range.getIsCollapsed()) + return; + setTimeout(() => { + var _a, _b; + if (!((_a = this.cursor) == null ? void 0 : _a.getAgentIsActive())) { + (_b = this.cursor) == null ? void 0 : _b.drawCursor({ + isFocus: false, + isBlink: false + }); + } + }); + } +} +class HistoryManager { + constructor(draw) { + this.undoStack = []; + this.redoStack = []; + this.maxRecordCount = draw.getOptions().historyMaxRecordCount + 1; + } + undo() { + if (this.undoStack.length > 1) { + const pop = this.undoStack.pop(); + this.redoStack.push(pop); + if (this.undoStack.length) { + this.undoStack[this.undoStack.length - 1](); + } + } + } + redo() { + if (this.redoStack.length) { + const pop = this.redoStack.pop(); + this.undoStack.push(pop); + pop(); + } + } + execute(fn) { + this.undoStack.push(fn); + if (this.redoStack.length) { + this.redoStack = []; + } + while (this.undoStack.length > this.maxRecordCount) { + this.undoStack.shift(); + } + } + isCanUndo() { + return this.undoStack.length > 1; + } + isCanRedo() { + return !!this.redoStack.length; + } + isStackEmpty() { + return !this.undoStack.length && !this.redoStack.length; + } + recovery() { + this.undoStack = []; + this.redoStack = []; + } + popUndo() { + return this.undoStack.pop(); + } +} +class Position { + constructor(draw) { + this.positionList = []; + this.floatPositionList = []; + this.cursorPosition = null; + this.positionContext = { + isTable: false, + isControl: false + }; + this.draw = draw; + this.eventBus = draw.getEventBus(); + this.options = draw.getOptions(); + } + getFloatPositionList() { + return this.floatPositionList; + } + getTablePositionList(sourceElementList) { + const { index: index2, trIndex, tdIndex } = this.positionContext; + return sourceElementList[index2].trList[trIndex].tdList[tdIndex].positionList || []; + } + getPositionList() { + return this.positionContext.isTable ? this.getTablePositionList(this.draw.getOriginalElementList()) : this.getOriginalPositionList(); + } + getMainPositionList() { + return this.positionContext.isTable ? this.getTablePositionList(this.draw.getOriginalMainElementList()) : this.positionList; + } + getOriginalPositionList() { + const zoneManager = this.draw.getZone(); + if (zoneManager.isHeaderActive()) { + const header = this.draw.getHeader(); + return header.getPositionList(); + } + if (zoneManager.isFooterActive()) { + const footer = this.draw.getFooter(); + return footer.getPositionList(); + } + return this.positionList; + } + getOriginalMainPositionList() { + return this.positionList; + } + getSelectionPositionList() { + const { startIndex, endIndex } = this.draw.getRange().getRange(); + if (startIndex === endIndex) + return null; + const positionList = this.getPositionList(); + return positionList.slice(startIndex + 1, endIndex + 1); + } + setPositionList(payload) { + this.positionList = payload; + } + setFloatPositionList(payload) { + this.floatPositionList = payload; + } + computePageRowPosition(payload) { + const { positionList, rowList, pageNo, startX, startY, startRowIndex, startIndex, innerWidth, zone: zone2 } = payload; + const { scale, table: { tdPadding } } = this.options; + let x = startX; + let y = startY; + let index2 = startIndex; + for (let i = 0; i < rowList.length; i++) { + const curRow = rowList[i]; + if (!curRow.isSurround) { + const curRowWidth = curRow.width + (curRow.offsetX || 0); + if (curRow.rowFlex === RowFlex.CENTER) { + x += (innerWidth - curRowWidth) / 2; + } else if (curRow.rowFlex === RowFlex.RIGHT) { + x += innerWidth - curRowWidth; + } + } + x += curRow.offsetX || 0; + const tablePreX = x; + const tablePreY = y; + for (let j = 0; j < curRow.elementList.length; j++) { + const element = curRow.elementList[j]; + const metrics = element.metrics; + const offsetY = element.imgDisplay !== ImageDisplay.INLINE && element.type === ElementType.IMAGE || element.type === ElementType.LATEX ? curRow.ascent - metrics.height : curRow.ascent; + if (element.left) { + x += element.left; + } + const positionItem = { + pageNo, + index: index2, + value: element.value, + rowIndex: startRowIndex + i, + rowNo: i, + metrics, + left: element.left || 0, + ascent: offsetY, + lineHeight: curRow.height, + isFirstLetter: j === 0, + isLastLetter: j === curRow.elementList.length - 1, + coordinate: { + leftTop: [x, y], + leftBottom: [x, y + curRow.height], + rightTop: [x + metrics.width, y], + rightBottom: [x + metrics.width, y + curRow.height] + } + }; + if (element.imgDisplay === ImageDisplay.SURROUND || element.imgDisplay === ImageDisplay.FLOAT_TOP || element.imgDisplay === ImageDisplay.FLOAT_BOTTOM) { + const prePosition = positionList[positionList.length - 1]; + if (prePosition) { + positionItem.metrics = prePosition.metrics; + positionItem.coordinate = prePosition.coordinate; + } + if (!element.imgFloatPosition) { + element.imgFloatPosition = { + x, + y, + pageNo + }; + } + this.floatPositionList.push({ + pageNo, + element, + position: positionItem, + isTable: payload.isTable, + index: payload.index, + tdIndex: payload.tdIndex, + trIndex: payload.trIndex, + tdValueIndex: index2, + zone: zone2 + }); + } + positionList.push(positionItem); + index2++; + x += metrics.width; + if (element.type === ElementType.TABLE) { + const tdPaddingWidth = tdPadding[1] + tdPadding[3]; + const tdPaddingHeight = tdPadding[0] + tdPadding[2]; + for (let t = 0; t < element.trList.length; t++) { + const tr = element.trList[t]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + td.positionList = []; + const rowList2 = td.rowList; + const drawRowResult = this.computePageRowPosition({ + positionList: td.positionList, + rowList: rowList2, + pageNo, + startRowIndex: 0, + startIndex: 0, + startX: (td.x + tdPadding[3]) * scale + tablePreX, + startY: (td.y + tdPadding[0]) * scale + tablePreY, + innerWidth: (td.width - tdPaddingWidth) * scale, + isTable: true, + index: index2 - 1, + tdIndex: d, + trIndex: t, + zone: zone2 + }); + if (td.verticalAlign === VerticalAlign.MIDDLE || td.verticalAlign === VerticalAlign.BOTTOM) { + const rowsHeight = rowList2.reduce((pre, cur) => pre + cur.height, 0); + const blankHeight = (td.height - tdPaddingHeight) * scale - rowsHeight; + const offsetHeight = td.verticalAlign === VerticalAlign.MIDDLE ? blankHeight / 2 : blankHeight; + if (Math.floor(offsetHeight) > 0) { + td.positionList.forEach((tdPosition) => { + const { coordinate: { leftTop, leftBottom, rightBottom, rightTop } } = tdPosition; + leftTop[1] += offsetHeight; + leftBottom[1] += offsetHeight; + rightBottom[1] += offsetHeight; + rightTop[1] += offsetHeight; + }); + } + } + x = drawRowResult.x; + y = drawRowResult.y; + } + } + x = tablePreX; + y = tablePreY; + } + } + x = startX; + y += curRow.height; + } + return { x, y, index: index2 }; + } + computePositionList() { + var _a; + this.positionList = []; + const innerWidth = this.draw.getInnerWidth(); + const pageRowList = this.draw.getPageRowList(); + const margins = this.draw.getMargins(); + const startX = margins[3]; + const header = this.draw.getHeader(); + const extraHeight = header.getExtraHeight(); + const startY = margins[0] + extraHeight; + let startRowIndex = 0; + for (let i = 0; i < pageRowList.length; i++) { + const rowList = pageRowList[i]; + const startIndex = (_a = rowList[0]) == null ? void 0 : _a.startIndex; + this.computePageRowPosition({ + positionList: this.positionList, + rowList, + pageNo: i, + startRowIndex, + startIndex, + startX, + startY, + innerWidth + }); + startRowIndex += rowList.length; + } + } + computeRowPosition(payload) { + const { row, innerWidth } = payload; + const positionList = []; + this.computePageRowPosition({ + positionList, + innerWidth, + rowList: [deepClone(row)], + pageNo: 0, + startX: 0, + startY: 0, + startIndex: 0, + startRowIndex: 0 + }); + return positionList; + } + setCursorPosition(position) { + this.cursorPosition = position; + } + getCursorPosition() { + return this.cursorPosition; + } + getPositionContext() { + return this.positionContext; + } + setPositionContext(payload) { + this.eventBus.emit("positionContextChange", { + value: payload, + oldValue: this.positionContext + }); + this.positionContext = payload; + } + getPositionByXY(payload) { + var _a, _b, _c, _d, _e; + const { x, y, isTable } = payload; + let { elementList, positionList } = payload; + if (!elementList) { + elementList = this.draw.getOriginalElementList(); + } + if (!positionList) { + positionList = this.getOriginalPositionList(); + } + const zoneManager = this.draw.getZone(); + const curPageNo = (_a = payload.pageNo) != null ? _a : this.draw.getPageNo(); + const isMainActive = zoneManager.isMainActive(); + const positionNo = isMainActive ? curPageNo : 0; + if (!isTable) { + const floatTopPosition = this.getFloatPositionByXY({ + ...payload, + imgDisplays: [ImageDisplay.FLOAT_TOP, ImageDisplay.SURROUND] + }); + if (floatTopPosition) + return floatTopPosition; + } + for (let j = 0; j < positionList.length; j++) { + const { index: index2, pageNo, left: left2, isFirstLetter, coordinate: { leftTop, rightTop, leftBottom } } = positionList[j]; + if (positionNo !== pageNo) + continue; + if (pageNo > positionNo) + break; + if (leftTop[0] - left2 <= x && rightTop[0] >= x && leftTop[1] <= y && leftBottom[1] >= y) { + let curPositionIndex2 = j; + const element = elementList[j]; + if (element.type === ElementType.TABLE) { + for (let t = 0; t < element.trList.length; t++) { + const tr = element.trList[t]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const tablePosition = this.getPositionByXY({ + x, + y, + td, + pageNo: curPageNo, + tablePosition: positionList[j], + isTable: true, + elementList: td.value, + positionList: td.positionList + }); + if (~tablePosition.index) { + const { index: tdValueIndex, hitLineStartIndex: hitLineStartIndex3 } = tablePosition; + const tdValueElement = td.value[tdValueIndex]; + return { + index: index2, + isCheckbox: tablePosition.isCheckbox || tdValueElement.type === ElementType.CHECKBOX || tdValueElement.controlComponent === ControlComponent.CHECKBOX, + isRadio: tdValueElement.type === ElementType.RADIO || tdValueElement.controlComponent === ControlComponent.RADIO, + isControl: !!tdValueElement.controlId, + isImage: tablePosition.isImage, + isDirectHit: tablePosition.isDirectHit, + isTable: true, + tdIndex: d, + trIndex: t, + tdValueIndex, + tdId: td.id, + trId: tr.id, + tableId: element.id, + hitLineStartIndex: hitLineStartIndex3 + }; + } + } + } + } + if (element.type === ElementType.IMAGE || element.type === ElementType.LATEX) { + return { + index: curPositionIndex2, + isDirectHit: true, + isImage: true + }; + } + if (element.type === ElementType.CHECKBOX || element.controlComponent === ControlComponent.CHECKBOX) { + return { + index: curPositionIndex2, + isDirectHit: true, + isCheckbox: true + }; + } + if (element.type === ElementType.RADIO || element.controlComponent === ControlComponent.RADIO) { + return { + index: curPositionIndex2, + isDirectHit: true, + isRadio: true + }; + } + let hitLineStartIndex2; + if (elementList[index2].value !== ZERO) { + const valueWidth = rightTop[0] - leftTop[0]; + if (x < leftTop[0] + valueWidth / 2) { + curPositionIndex2 = j - 1; + if (isFirstLetter) { + hitLineStartIndex2 = j; + } + } + } + return { + isDirectHit: true, + hitLineStartIndex: hitLineStartIndex2, + index: curPositionIndex2, + isControl: !!element.controlId + }; + } + } + if (!isTable) { + const floatBottomPosition = this.getFloatPositionByXY({ + ...payload, + imgDisplays: [ImageDisplay.FLOAT_BOTTOM] + }); + if (floatBottomPosition) + return floatBottomPosition; + } + let isLastArea = false; + let curPositionIndex = -1; + let hitLineStartIndex; + if (isTable) { + const { scale } = this.options; + const { td, tablePosition } = payload; + if (td && tablePosition) { + const { leftTop } = tablePosition.coordinate; + const tdX = td.x * scale + leftTop[0]; + const tdY = td.y * scale + leftTop[1]; + const tdWidth = td.width * scale; + const tdHeight = td.height * scale; + if (!(tdX < x && x < tdX + tdWidth && tdY < y && y < tdY + tdHeight)) { + return { + index: curPositionIndex + }; + } + } + } + const lastLetterList = positionList.filter((p) => p.isLastLetter && p.pageNo === positionNo); + for (let j = 0; j < lastLetterList.length; j++) { + const { index: index2, rowNo, coordinate: { leftTop, leftBottom } } = lastLetterList[j]; + if (y > leftTop[1] && y <= leftBottom[1]) { + const headIndex = positionList.findIndex((p) => p.pageNo === positionNo && p.rowNo === rowNo); + const headElement = elementList[headIndex]; + const headPosition = positionList[headIndex]; + const headStartX = headElement.listStyle === ListStyle.CHECKBOX ? this.options.margins[3] : headPosition.coordinate.leftTop[0]; + if (x < headStartX) { + if (~headIndex) { + if (headPosition.value === ZERO) { + curPositionIndex = headIndex; + } else { + curPositionIndex = headIndex - 1; + hitLineStartIndex = headIndex; + } + } else { + curPositionIndex = index2; + } + } else { + if (headElement.listStyle === ListStyle.CHECKBOX && x < leftTop[0]) { + return { + index: headIndex, + isDirectHit: true, + isCheckbox: true + }; + } + curPositionIndex = index2; + } + isLastArea = true; + break; + } + } + if (!isLastArea) { + const header = this.draw.getHeader(); + const headerHeight = header.getHeight(); + const headerBottomY = header.getHeaderTop() + headerHeight; + const footer = this.draw.getFooter(); + const pageHeight = this.draw.getHeight(); + const footerTopY = pageHeight - (footer.getFooterBottom() + footer.getHeight()); + if (isMainActive) { + if (y < headerBottomY) { + return { + index: -1, + zone: EditorZone.HEADER + }; + } + if (y > footerTopY) { + return { + index: -1, + zone: EditorZone.FOOTER + }; + } + } else { + if (y <= footerTopY && y >= headerBottomY) { + return { + index: -1, + zone: EditorZone.MAIN + }; + } + } + const margins = this.draw.getMargins(); + if (y <= margins[1]) { + for (let p = 0; p < positionList.length; p++) { + const position = positionList[p]; + if (position.pageNo !== positionNo || position.rowNo !== 0) + continue; + const { leftTop, rightTop } = position.coordinate; + if (x <= margins[3] || x >= leftTop[0] && x <= rightTop[0] || ((_b = positionList[p + 1]) == null ? void 0 : _b.rowNo) !== 0) { + return { + index: position.index + }; + } + } + } else { + const lastLetter = lastLetterList[lastLetterList.length - 1]; + if (lastLetter) { + const lastRowNo = lastLetter.rowNo; + for (let p = 0; p < positionList.length; p++) { + const position = positionList[p]; + if (position.pageNo !== positionNo || position.rowNo !== lastRowNo) { + continue; + } + const { leftTop, rightTop } = position.coordinate; + if (x <= margins[3] || x >= leftTop[0] && x <= rightTop[0] || ((_c = positionList[p + 1]) == null ? void 0 : _c.rowNo) !== lastRowNo) { + return { + index: position.index + }; + } + } + } + } + return { + index: ((_d = lastLetterList[lastLetterList.length - 1]) == null ? void 0 : _d.index) || positionList.length - 1 + }; + } + return { + hitLineStartIndex, + index: curPositionIndex, + isControl: !!((_e = elementList[curPositionIndex]) == null ? void 0 : _e.controlId) + }; + } + getFloatPositionByXY(payload) { + var _a; + const { x, y } = payload; + const currentPageNo = (_a = payload.pageNo) != null ? _a : this.draw.getPageNo(); + const currentZone = this.draw.getZone().getZone(); + for (let f = 0; f < this.floatPositionList.length; f++) { + const { position, element, isTable, index: index2, trIndex, tdIndex, tdValueIndex, zone: floatElementZone, pageNo } = this.floatPositionList[f]; + if (currentPageNo === pageNo && element.type === ElementType.IMAGE && element.imgDisplay && payload.imgDisplays.includes(element.imgDisplay) && (!floatElementZone || floatElementZone === currentZone)) { + const imgFloatPosition = element.imgFloatPosition; + if (x >= imgFloatPosition.x && x <= imgFloatPosition.x + element.width && y >= imgFloatPosition.y && y <= imgFloatPosition.y + element.height) { + if (isTable) { + return { + index: index2, + isDirectHit: true, + isImage: true, + isTable, + trIndex, + tdIndex, + tdValueIndex, + tdId: element.tdId, + trId: element.trId, + tableId: element.tableId + }; + } + return { + index: position.index, + isDirectHit: true, + isImage: true + }; + } + } + } + } + adjustPositionContext(payload) { + const positionResult = this.getPositionByXY(payload); + if (!~positionResult.index) + return null; + if (positionResult.isControl && this.draw.getMode() !== EditorMode.READONLY) { + const { index: index22, isTable: isTable2, trIndex: trIndex2, tdIndex: tdIndex2, tdValueIndex } = positionResult; + const control = this.draw.getControl(); + const { newIndex } = control.moveCursor({ + index: index22, + isTable: isTable2, + trIndex: trIndex2, + tdIndex: tdIndex2, + tdValueIndex + }); + if (isTable2) { + positionResult.tdValueIndex = newIndex; + } else { + positionResult.index = newIndex; + } + } + const { index: index2, isCheckbox, isRadio, isControl, isImage, isDirectHit, isTable, trIndex, tdIndex, tdId, trId, tableId } = positionResult; + this.setPositionContext({ + isTable: isTable || false, + isCheckbox: isCheckbox || false, + isRadio: isRadio || false, + isControl: isControl || false, + isImage: isImage || false, + isDirectHit: isDirectHit || false, + index: index2, + trIndex, + tdIndex, + tdId, + trId, + tableId + }); + return positionResult; + } + setSurroundPosition(payload) { + var _a; + const { pageNo, row, rowElement, rowElementRect, surroundElementList, availableWidth } = payload; + let x = rowElementRect.x; + let rowIncreaseWidth = 0; + if (surroundElementList.length && !getIsBlockElement(rowElement) && !((_a = rowElement.control) == null ? void 0 : _a.minWidth)) { + for (let s = 0; s < surroundElementList.length; s++) { + const surroundElement = surroundElementList[s]; + const floatPosition = surroundElement.imgFloatPosition; + if (floatPosition.pageNo !== pageNo) + continue; + const surroundRect = { + ...floatPosition, + width: surroundElement.width, + height: surroundElement.height + }; + if (isRectIntersect(rowElementRect, surroundRect)) { + row.isSurround = true; + const translateX = surroundRect.width + surroundRect.x - rowElementRect.x; + rowElement.left = translateX; + row.width += translateX; + rowIncreaseWidth += translateX; + x = surroundRect.x + surroundRect.width; + if (row.width + rowElement.metrics.width > availableWidth) { + rowElement.left = 0; + row.width -= rowIncreaseWidth; + break; + } + } + } + } + return { x, rowIncreaseWidth }; + } +} +class RangeManager { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + this.listener = draw.getListener(); + this.eventBus = draw.getEventBus(); + this.position = draw.getPosition(); + this.historyManager = draw.getHistoryManager(); + this.range = { + startIndex: -1, + endIndex: -1 + }; + } + getRange() { + return this.range; + } + clearRange() { + this.setRange(-1, -1); + } + getIsCollapsed() { + const { startIndex, endIndex } = this.range; + return startIndex === endIndex; + } + getSelection() { + const { startIndex, endIndex } = this.range; + if (startIndex === endIndex) + return null; + const elementList = this.draw.getElementList(); + return elementList.slice(startIndex + 1, endIndex + 1); + } + getSelectionElementList() { + if (this.range.isCrossRowCol) { + const rowCol = this.draw.getTableParticle().getRangeRowCol(); + if (!rowCol) + return null; + const elementList = []; + for (let r = 0; r < rowCol.length; r++) { + const row = rowCol[r]; + for (let c = 0; c < row.length; c++) { + const col = row[c]; + elementList.push(...col.value); + } + } + return elementList; + } + return this.getSelection(); + } + getTextLikeSelection() { + const selection = this.getSelection(); + if (!selection) + return null; + return selection.filter((s) => !s.type || TEXTLIKE_ELEMENT_TYPE.includes(s.type)); + } + getTextLikeSelectionElementList() { + const selection = this.getSelectionElementList(); + if (!selection) + return null; + return selection.filter((s) => !s.type || TEXTLIKE_ELEMENT_TYPE.includes(s.type)); + } + getRangeRow() { + const { startIndex, endIndex } = this.range; + if (!~startIndex && !~endIndex) + return null; + const positionList = this.position.getPositionList(); + const rangeRow = /* @__PURE__ */ new Map(); + for (let p = startIndex; p < endIndex + 1; p++) { + const { pageNo, rowNo } = positionList[p]; + const rowSet = rangeRow.get(pageNo); + if (!rowSet) { + rangeRow.set(pageNo, /* @__PURE__ */ new Set([rowNo])); + } else { + if (!rowSet.has(rowNo)) { + rowSet.add(rowNo); + } + } + } + return rangeRow; + } + getRangeRowElementList() { + const { startIndex, endIndex, isCrossRowCol } = this.range; + if (!~startIndex && !~endIndex) + return null; + if (isCrossRowCol) { + return this.getSelectionElementList(); + } + const rangeRow = this.getRangeRow(); + if (!rangeRow) + return null; + const positionList = this.position.getPositionList(); + const elementList = this.draw.getElementList(); + const rowElementList = []; + for (let p = 0; p < positionList.length; p++) { + const position = positionList[p]; + const rowSet = rangeRow.get(position.pageNo); + if (!rowSet) + continue; + if (rowSet.has(position.rowNo)) { + rowElementList.push(elementList[p]); + } + } + return rowElementList; + } + getRangeParagraph() { + const { startIndex, endIndex } = this.range; + if (!~startIndex && !~endIndex) + return null; + const positionList = this.position.getPositionList(); + const elementList = this.draw.getElementList(); + const rangeRow = /* @__PURE__ */ new Map(); + let start = startIndex; + while (start >= 0) { + const { pageNo, rowNo } = positionList[start]; + let rowArray = rangeRow.get(pageNo); + if (!rowArray) { + rowArray = []; + rangeRow.set(pageNo, rowArray); + } + if (!rowArray.includes(rowNo)) { + rowArray.unshift(rowNo); + } + const element = elementList[start]; + const preElement = elementList[start - 1]; + if (element.value === ZERO && !element.listWrap || element.listId !== (preElement == null ? void 0 : preElement.listId) || element.titleId !== (preElement == null ? void 0 : preElement.titleId)) { + break; + } + start--; + } + const isCollapsed = startIndex === endIndex; + if (!isCollapsed) { + let middle = startIndex + 1; + while (middle < endIndex) { + const { pageNo, rowNo } = positionList[middle]; + let rowArray = rangeRow.get(pageNo); + if (!rowArray) { + rowArray = []; + rangeRow.set(pageNo, rowArray); + } + if (!rowArray.includes(rowNo)) { + rowArray.push(rowNo); + } + middle++; + } + } + let end = endIndex; + if (isCollapsed && elementList[startIndex].value === ZERO) { + end += 1; + } + while (end < positionList.length) { + const element = elementList[end]; + const nextElement = elementList[end + 1]; + if (element.value === ZERO && !element.listWrap || element.listId !== (nextElement == null ? void 0 : nextElement.listId) || element.titleId !== (nextElement == null ? void 0 : nextElement.titleId)) { + break; + } + const { pageNo, rowNo } = positionList[end]; + let rowArray = rangeRow.get(pageNo); + if (!rowArray) { + rowArray = []; + rangeRow.set(pageNo, rowArray); + } + if (!rowArray.includes(rowNo)) { + rowArray.push(rowNo); + } + end++; + } + return rangeRow; + } + getRangeParagraphInfo() { + const { startIndex, endIndex } = this.range; + if (!~startIndex && !~endIndex) + return null; + let startPositionIndex = -1; + const rangeElementList = []; + const rangeRow = this.getRangeParagraph(); + if (!rangeRow) + return null; + const elementList = this.draw.getElementList(); + const positionList = this.position.getPositionList(); + for (let p = 0; p < positionList.length; p++) { + const position = positionList[p]; + const rowArray = rangeRow.get(position.pageNo); + if (!rowArray) + continue; + if (rowArray.includes(position.rowNo)) { + if (!~startPositionIndex) { + startPositionIndex = position.index; + } + rangeElementList.push(elementList[p]); + } + } + if (!rangeElementList.length) + return null; + return { + elementList: rangeElementList, + startIndex: startPositionIndex + }; + } + getRangeParagraphElementList() { + var _a; + return ((_a = this.getRangeParagraphInfo()) == null ? void 0 : _a.elementList) || null; + } + getRangeTableElement() { + const positionContext = this.position.getPositionContext(); + if (!positionContext.isTable) + return null; + const originalElementList = this.draw.getOriginalElementList(); + return originalElementList[positionContext.index]; + } + getIsSelectAll() { + const elementList = this.draw.getElementList(); + const { startIndex, endIndex } = this.range; + return startIndex === 0 && elementList.length - 1 === endIndex && !this.position.getPositionContext().isTable; + } + getIsPointInRange(x, y) { + const { startIndex, endIndex } = this.range; + const positionList = this.position.getPositionList(); + for (let p = startIndex + 1; p <= endIndex; p++) { + const position = positionList[p]; + if (!position) + break; + const { coordinate: { leftTop, rightBottom } } = positionList[p]; + if (x >= leftTop[0] && x <= rightBottom[0] && y >= leftTop[1] && y <= rightBottom[1]) { + return true; + } + } + return false; + } + getKeywordRangeList(payload) { + const searchMatchList = this.draw.getSearch().getMatchList(payload, this.draw.getOriginalElementList()); + const searchRangeMap = /* @__PURE__ */ new Map(); + for (const searchMatch of searchMatchList) { + const searchRange = searchRangeMap.get(searchMatch.groupId); + if (searchRange) { + searchRange.endIndex += 1; + } else { + const { type, groupId, tableId, index: index2, tdIndex, trIndex } = searchMatch; + const range = { + startIndex: index2 - 1, + endIndex: index2 + }; + if (type === EditorContext.TABLE) { + range.tableId = tableId; + range.startTdIndex = tdIndex; + range.endTdIndex = tdIndex; + range.startTrIndex = trIndex; + range.endTrIndex = trIndex; + } + searchRangeMap.set(groupId, range); + } + } + const rangeList = []; + searchRangeMap.forEach((searchRange) => { + rangeList.push(searchRange); + }); + return rangeList; + } + getIsCanInput() { + const { startIndex, endIndex } = this.getRange(); + if (!~startIndex && !~endIndex) + return false; + if (startIndex === endIndex) + return true; + const elementList = this.draw.getElementList(); + const startElement = elementList[startIndex]; + const endElement = elementList[endIndex]; + return !startElement.controlId && !endElement.controlId || (!startElement.controlId || startElement.controlComponent === ControlComponent.POSTFIX) && (!endElement.controlId || endElement.controlComponent === ControlComponent.POSTFIX) || !!startElement.controlId && endElement.controlId === startElement.controlId && endElement.controlComponent !== ControlComponent.POSTFIX; + } + setRange(startIndex, endIndex, tableId, startTdIndex, endTdIndex, startTrIndex, endTrIndex) { + this.range.startIndex = startIndex; + this.range.endIndex = endIndex; + this.range.tableId = tableId; + this.range.startTdIndex = startTdIndex; + this.range.endTdIndex = endTdIndex; + this.range.startTrIndex = startTrIndex; + this.range.endTrIndex = endTrIndex; + this.range.isCrossRowCol = !!(startTdIndex || endTdIndex || startTrIndex || endTrIndex); + this.range.zone = this.draw.getZone().getZone(); + const control = this.draw.getControl(); + if (~startIndex && ~endIndex) { + const elementList = this.draw.getElementList(); + const element = elementList[startIndex]; + if (element == null ? void 0 : element.controlId) { + control.initControl(); + return; + } + } + control.destroyControl(); + } + replaceRange(range) { + this.setRange(range.startIndex, range.endIndex, range.tableId, range.startTdIndex, range.endTdIndex, range.startTrIndex, range.endTrIndex); + } + setRangeStyle() { + var _a, _b; + const rangeStyleChangeListener = this.listener.rangeStyleChange; + const isSubscribeRangeStyleChange = this.eventBus.isSubscribe("rangeStyleChange"); + if (!rangeStyleChangeListener && !isSubscribeRangeStyleChange) + return; + const { startIndex, endIndex, isCrossRowCol } = this.range; + if (!~startIndex && !~endIndex) + return; + let curElement; + if (isCrossRowCol) { + const originalElementList = this.draw.getOriginalElementList(); + const positionContext = this.position.getPositionContext(); + curElement = originalElementList[positionContext.index]; + } else { + const index2 = ~endIndex ? endIndex : 0; + const elementList = this.draw.getElementList(); + curElement = getAnchorElement(elementList, index2); + } + if (!curElement) + return; + const curElementList = this.getSelection() || [curElement]; + const type = curElement.type || ElementType.TEXT; + const font = curElement.font || this.options.defaultFont; + const size = curElement.size || this.options.defaultSize; + const bold = !~curElementList.findIndex((el) => !el.bold); + const italic = !~curElementList.findIndex((el) => !el.italic); + const underline = !~curElementList.findIndex((el) => { + var _a2; + return !el.underline && !((_a2 = el.control) == null ? void 0 : _a2.underline); + }); + const strikeout = !~curElementList.findIndex((el) => !el.strikeout); + const color = curElement.color || null; + const highlight = curElement.highlight || null; + const rowFlex = curElement.rowFlex || null; + const rowMargin = (_a = curElement.rowMargin) != null ? _a : this.options.defaultRowMargin; + const dashArray = curElement.dashArray || []; + const level = curElement.level || null; + const listType = curElement.listType || null; + const listStyle = curElement.listStyle || null; + const textDecoration = underline ? curElement.textDecoration || null : null; + const painter = !!this.draw.getPainterStyle(); + const undo = this.historyManager.isCanUndo(); + const redo = this.historyManager.isCanRedo(); + const groupIds = curElement.groupIds || null; + const extension = (_b = curElement.extension) != null ? _b : null; + const rangeStyle = { + type, + undo, + redo, + painter, + font, + size, + bold, + italic, + underline, + strikeout, + color, + highlight, + rowFlex, + rowMargin, + dashArray, + level, + listType, + listStyle, + groupIds, + textDecoration, + extension + }; + if (rangeStyleChangeListener) { + rangeStyleChangeListener(rangeStyle); + } + if (isSubscribeRangeStyleChange) { + this.eventBus.emit("rangeStyleChange", rangeStyle); + } + } + recoveryRangeStyle() { + const rangeStyleChangeListener = this.listener.rangeStyleChange; + const isSubscribeRangeStyleChange = this.eventBus.isSubscribe("rangeStyleChange"); + if (!rangeStyleChangeListener && !isSubscribeRangeStyleChange) + return; + const font = this.options.defaultFont; + const size = this.options.defaultSize; + const rowMargin = this.options.defaultRowMargin; + const painter = !!this.draw.getPainterStyle(); + const undo = this.historyManager.isCanUndo(); + const redo = this.historyManager.isCanRedo(); + const rangeStyle = { + type: null, + undo, + redo, + painter, + font, + size, + bold: false, + italic: false, + underline: false, + strikeout: false, + color: null, + highlight: null, + rowFlex: null, + rowMargin, + dashArray: [], + level: null, + listType: null, + listStyle: null, + groupIds: null, + textDecoration: null, + extension: null + }; + if (rangeStyleChangeListener) { + rangeStyleChangeListener(rangeStyle); + } + if (isSubscribeRangeStyleChange) { + this.eventBus.emit("rangeStyleChange", rangeStyle); + } + } + shrinkBoundary(context = {}) { + const elementList = context.elementList || this.draw.getElementList(); + const range = context.range || this.getRange(); + const { startIndex, endIndex } = range; + if (!~startIndex && !~endIndex) + return; + const startElement = elementList[startIndex]; + const endElement = elementList[endIndex]; + if (startIndex === endIndex) { + if (startElement.controlComponent === ControlComponent.PLACEHOLDER) { + let index2 = startIndex - 1; + while (index2 > 0) { + const preElement = elementList[index2]; + if (preElement.controlId !== startElement.controlId || preElement.controlComponent === ControlComponent.PREFIX) { + range.startIndex = index2; + range.endIndex = index2; + break; + } + index2--; + } + } + } else { + if (startElement.controlComponent === ControlComponent.PLACEHOLDER || endElement.controlComponent === ControlComponent.PLACEHOLDER) { + let index2 = endIndex - 1; + while (index2 > 0) { + const preElement = elementList[index2]; + if (preElement.controlId !== endElement.controlId || preElement.controlComponent === ControlComponent.PREFIX) { + range.startIndex = index2; + range.endIndex = index2; + return; + } + index2--; + } + } + if (startElement.controlComponent === ControlComponent.PREFIX) { + let index2 = startIndex + 1; + while (index2 < elementList.length) { + const nextElement = elementList[index2]; + if (nextElement.controlId !== startElement.controlId || nextElement.controlComponent === ControlComponent.VALUE) { + range.startIndex = index2 - 1; + break; + } else if (nextElement.controlComponent === ControlComponent.PLACEHOLDER) { + range.startIndex = index2 - 1; + range.endIndex = index2 - 1; + return; + } + index2++; + } + } + if (endElement.controlComponent !== ControlComponent.VALUE) { + let index2 = startIndex - 1; + while (index2 > 0) { + const preElement = elementList[index2]; + if (preElement.controlId !== startElement.controlId || preElement.controlComponent === ControlComponent.VALUE) { + range.startIndex = index2; + break; + } else if (preElement.controlComponent === ControlComponent.PLACEHOLDER) { + range.startIndex = index2; + range.endIndex = index2; + return; + } + index2--; + } + } + } + } + render(ctx, x, y, width, height) { + ctx.save(); + ctx.globalAlpha = this.options.rangeAlpha; + ctx.fillStyle = this.options.rangeColor; + ctx.fillRect(x, y, width, height); + ctx.restore(); + } + toString() { + const selection = this.getTextLikeSelection(); + if (!selection) + return ""; + return selection.map((s) => s.value).join("").replace(new RegExp(ZERO, "g"), ""); + } +} +class Background { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + this.imageCache = /* @__PURE__ */ new Map(); + } + _renderBackgroundColor(ctx, color, width, height) { + ctx.save(); + ctx.fillStyle = color; + ctx.fillRect(0, 0, width, height); + ctx.restore(); + } + _drawImage(ctx, imageElement, width, height) { + const { background, scale } = this.options; + if (background.size === BackgroundSize.CONTAIN) { + const imageWidth = imageElement.width * scale; + const imageHeight = imageElement.height * scale; + if (!background.repeat || background.repeat === BackgroundRepeat.NO_REPEAT) { + ctx.drawImage(imageElement, 0, 0, imageWidth, imageHeight); + } else { + let startX = 0; + let startY = 0; + const repeatXCount = background.repeat === BackgroundRepeat.REPEAT || background.repeat === BackgroundRepeat.REPEAT_X ? Math.ceil(width * scale / imageWidth) : 1; + const repeatYCount = background.repeat === BackgroundRepeat.REPEAT || background.repeat === BackgroundRepeat.REPEAT_Y ? Math.ceil(height * scale / imageHeight) : 1; + for (let x = 0; x < repeatXCount; x++) { + for (let y = 0; y < repeatYCount; y++) { + ctx.drawImage(imageElement, startX, startY, imageWidth, imageHeight); + startY += imageHeight; + } + startY = 0; + startX += imageWidth; + } + } + } else { + ctx.drawImage(imageElement, 0, 0, width * scale, height * scale); + } + } + _renderBackgroundImage(ctx, width, height) { + const { background } = this.options; + const imageElementCache = this.imageCache.get(background.image); + if (imageElementCache) { + this._drawImage(ctx, imageElementCache, width, height); + } else { + const img = new Image(); + img.setAttribute("crossOrigin", "Anonymous"); + img.src = background.image; + img.onload = () => { + this.imageCache.set(background.image, img); + this._drawImage(ctx, img, width, height); + this.draw.render({ + isCompute: false, + isSubmitHistory: false + }); + }; + } + } + render(ctx, pageNo) { + const { background: { image, color, applyPageNumbers } } = this.options; + if (image && (!(applyPageNumbers == null ? void 0 : applyPageNumbers.length) || applyPageNumbers.includes(pageNo))) { + const { width, height } = this.options; + this._renderBackgroundImage(ctx, width, height); + } else { + const width = this.draw.getCanvasWidth(pageNo); + const height = this.draw.getCanvasHeight(pageNo); + this._renderBackgroundColor(ctx, color, width, height); + } + } +} +class AbstractRichText { + constructor() { + this.fillRect = this.clearFillInfo(); + } + clearFillInfo() { + this.fillColor = void 0; + this.fillDecorationStyle = void 0; + this.fillRect = { + x: 0, + y: 0, + width: 0, + height: 0 + }; + return this.fillRect; + } + recordFillInfo(ctx, x, y, width, height, color, decorationStyle) { + const isFirstRecord = !this.fillRect.width; + if (!isFirstRecord && (this.fillColor !== color || this.fillDecorationStyle !== decorationStyle)) { + this.render(ctx); + this.clearFillInfo(); + this.recordFillInfo(ctx, x, y, width, height, color, decorationStyle); + return; + } + if (isFirstRecord) { + this.fillRect.x = x; + this.fillRect.y = y; + } + if (height && this.fillRect.height < height) { + this.fillRect.height = height; + } + this.fillRect.width += width; + this.fillColor = color; + this.fillDecorationStyle = decorationStyle; + } +} +class Highlight extends AbstractRichText { + constructor(draw) { + super(); + this.options = draw.getOptions(); + } + render(ctx) { + if (!this.fillRect.width) + return; + const { highlightAlpha } = this.options; + const { x, y, width, height } = this.fillRect; + ctx.save(); + ctx.globalAlpha = highlightAlpha; + ctx.fillStyle = this.fillColor; + ctx.fillRect(x, y, width, height); + ctx.restore(); + this.clearFillInfo(); + } +} +class Margin { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + } + render(ctx, pageNo) { + const { marginIndicatorColor, pageMode } = this.options; + const width = this.draw.getWidth(); + const height = pageMode === PageMode.CONTINUITY ? this.draw.getCanvasHeight(pageNo) : this.draw.getHeight(); + const margins = this.draw.getMargins(); + const marginIndicatorSize = this.draw.getMarginIndicatorSize(); + ctx.save(); + ctx.translate(0.5, 0.5); + ctx.strokeStyle = marginIndicatorColor; + ctx.beginPath(); + const leftTopPoint = [margins[3], margins[0]]; + const rightTopPoint = [width - margins[1], margins[0]]; + const leftBottomPoint = [margins[3], height - margins[2]]; + const rightBottomPoint = [ + width - margins[1], + height - margins[2] + ]; + ctx.moveTo(leftTopPoint[0] - marginIndicatorSize, leftTopPoint[1]); + ctx.lineTo(...leftTopPoint); + ctx.lineTo(leftTopPoint[0], leftTopPoint[1] - marginIndicatorSize); + ctx.moveTo(rightTopPoint[0] + marginIndicatorSize, rightTopPoint[1]); + ctx.lineTo(...rightTopPoint); + ctx.lineTo(rightTopPoint[0], rightTopPoint[1] - marginIndicatorSize); + ctx.moveTo(leftBottomPoint[0] - marginIndicatorSize, leftBottomPoint[1]); + ctx.lineTo(...leftBottomPoint); + ctx.lineTo(leftBottomPoint[0], leftBottomPoint[1] + marginIndicatorSize); + ctx.moveTo(rightBottomPoint[0] + marginIndicatorSize, rightBottomPoint[1]); + ctx.lineTo(...rightBottomPoint); + ctx.lineTo(rightBottomPoint[0], rightBottomPoint[1] + marginIndicatorSize); + ctx.stroke(); + ctx.restore(); + } +} +class Search { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + this.position = draw.getPosition(); + this.searchNavigateIndex = null; + this.searchKeyword = null; + this.searchMatchList = []; + } + getSearchKeyword() { + return this.searchKeyword; + } + setSearchKeyword(payload) { + this.searchKeyword = payload; + this.searchNavigateIndex = null; + } + searchNavigatePre() { + if (!this.searchMatchList.length || !this.searchKeyword) + return null; + if (this.searchNavigateIndex === null) { + this.searchNavigateIndex = 0; + } else { + let index2 = this.searchNavigateIndex - 1; + let isExistPre = false; + const searchNavigateId = this.searchMatchList[this.searchNavigateIndex].groupId; + while (index2 >= 0) { + const match = this.searchMatchList[index2]; + if (searchNavigateId !== match.groupId) { + isExistPre = true; + this.searchNavigateIndex = index2 - (this.searchKeyword.length - 1); + break; + } + index2--; + } + if (!isExistPre) { + const lastSearchMatch = this.searchMatchList[this.searchMatchList.length - 1]; + if (lastSearchMatch.groupId === searchNavigateId) + return null; + this.searchNavigateIndex = this.searchMatchList.length - 1 - (this.searchKeyword.length - 1); + } + } + return this.searchNavigateIndex; + } + searchNavigateNext() { + if (!this.searchMatchList.length || !this.searchKeyword) + return null; + if (this.searchNavigateIndex === null) { + this.searchNavigateIndex = 0; + } else { + let index2 = this.searchNavigateIndex + 1; + let isExistNext = false; + const searchNavigateId = this.searchMatchList[this.searchNavigateIndex].groupId; + while (index2 < this.searchMatchList.length) { + const match = this.searchMatchList[index2]; + if (searchNavigateId !== match.groupId) { + isExistNext = true; + this.searchNavigateIndex = index2; + break; + } + index2++; + } + if (!isExistNext) { + const firstSearchMatch = this.searchMatchList[0]; + if (firstSearchMatch.groupId === searchNavigateId) + return null; + this.searchNavigateIndex = 0; + } + } + return this.searchNavigateIndex; + } + searchNavigateScrollIntoView(position) { + const { coordinate: { leftTop, leftBottom, rightTop }, pageNo } = position; + const height = this.draw.getHeight(); + const pageGap = this.draw.getPageGap(); + const preY = pageNo * (height + pageGap); + const anchor = document.createElement("div"); + anchor.style.position = "absolute"; + const ANCHOR_OVERFLOW_SIZE = 50; + anchor.style.width = `${rightTop[0] - leftTop[0] + ANCHOR_OVERFLOW_SIZE}px`; + anchor.style.height = `${leftBottom[1] - leftTop[1] + ANCHOR_OVERFLOW_SIZE}px`; + anchor.style.left = `${leftTop[0]}px`; + anchor.style.top = `${leftTop[1] + preY}px`; + this.draw.getContainer().append(anchor); + anchor.scrollIntoView(false); + anchor.remove(); + } + getSearchNavigateIndexList() { + if (this.searchNavigateIndex === null || !this.searchKeyword) + return []; + return new Array(this.searchKeyword.length).fill(this.searchNavigateIndex).map((navigate, index2) => navigate + index2); + } + getSearchMatchList() { + return this.searchMatchList; + } + getSearchNavigateInfo() { + if (!this.searchKeyword || !this.searchMatchList.length) + return null; + const index2 = this.searchNavigateIndex !== null ? this.searchNavigateIndex / this.searchKeyword.length + 1 : 0; + let count = 0; + let groupId = null; + for (let s = 0; s < this.searchMatchList.length; s++) { + const match = this.searchMatchList[s]; + if (groupId === match.groupId) + continue; + groupId = match.groupId; + count += 1; + } + return { + index: index2, + count + }; + } + getMatchList(payload, originalElementList) { + const keyword = payload.toLocaleLowerCase(); + const searchMatchList = []; + const elementListGroup = []; + const originalElementListLength = originalElementList.length; + const tableIndexList = []; + for (let e = 0; e < originalElementListLength; e++) { + const element = originalElementList[e]; + if (element.type === ElementType.TABLE) { + tableIndexList.push(e); + } + } + let i = 0; + let elementIndex = 0; + while (elementIndex < originalElementListLength - 1) { + const endIndex = tableIndexList.length ? tableIndexList[i] : originalElementListLength; + const pageElement = originalElementList.slice(elementIndex, endIndex); + if (pageElement.length) { + elementListGroup.push({ + index: elementIndex, + type: EditorContext.PAGE, + elementList: pageElement + }); + } + const tableElement = originalElementList[endIndex]; + if (tableElement) { + elementListGroup.push({ + index: endIndex, + type: EditorContext.TABLE, + elementList: [tableElement] + }); + } + elementIndex = endIndex + 1; + i++; + } + function searchClosure(payload2, type, elementList, restArgs) { + if (!payload2) + return; + const text = elementList.map((e) => !e.type || TEXTLIKE_ELEMENT_TYPE.includes(e.type) && e.controlComponent !== ControlComponent.CHECKBOX ? e.value : ZERO).filter(Boolean).join("").toLocaleLowerCase(); + const matchStartIndexList = []; + let index2 = text.indexOf(payload2); + while (index2 !== -1) { + matchStartIndexList.push(index2); + index2 = text.indexOf(payload2, index2 + payload2.length); + } + for (let m = 0; m < matchStartIndexList.length; m++) { + const startIndex = matchStartIndexList[m]; + const groupId = getUUID(); + for (let i2 = 0; i2 < payload2.length; i2++) { + const index22 = startIndex + i2 + ((restArgs == null ? void 0 : restArgs.startIndex) || 0); + searchMatchList.push({ + type, + index: index22, + groupId, + ...restArgs + }); + } + } + } + for (let e = 0; e < elementListGroup.length; e++) { + const group2 = elementListGroup[e]; + if (group2.type === EditorContext.TABLE) { + const tableElement = group2.elementList[0]; + for (let t = 0; t < tableElement.trList.length; t++) { + const tr = tableElement.trList[t]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const restArgs = { + tableId: tableElement.id, + tableIndex: group2.index, + trIndex: t, + tdIndex: d, + tdId: td.id + }; + searchClosure(keyword, group2.type, td.value, restArgs); + } + } + } else { + searchClosure(keyword, group2.type, group2.elementList, { + startIndex: group2.index + }); + } + } + return searchMatchList; + } + compute(payload) { + this.searchMatchList = this.getMatchList(payload, this.draw.getOriginalElementList()); + } + render(ctx, pageIndex) { + var _a, _b; + if (!this.searchMatchList || !this.searchMatchList.length || !this.searchKeyword) { + return; + } + const { searchMatchAlpha, searchMatchColor, searchNavigateMatchColor } = this.options; + const positionList = this.position.getOriginalPositionList(); + const elementList = this.draw.getOriginalElementList(); + ctx.save(); + ctx.globalAlpha = searchMatchAlpha; + for (let s = 0; s < this.searchMatchList.length; s++) { + const searchMatch = this.searchMatchList[s]; + let position = null; + if (searchMatch.type === EditorContext.TABLE) { + const { tableIndex, trIndex, tdIndex, index: index2 } = searchMatch; + position = (_b = (_a = elementList[tableIndex]) == null ? void 0 : _a.trList[trIndex].tdList[tdIndex]) == null ? void 0 : _b.positionList[index2]; + } else { + position = positionList[searchMatch.index]; + } + if (!position) + continue; + const { coordinate: { leftTop, leftBottom, rightTop }, pageNo } = position; + if (pageNo !== pageIndex) + continue; + const searchMatchIndexList = this.getSearchNavigateIndexList(); + if (searchMatchIndexList.includes(s)) { + ctx.fillStyle = searchNavigateMatchColor; + const preSearchMatch = this.searchMatchList[s - 1]; + if (!preSearchMatch || preSearchMatch.groupId !== searchMatch.groupId) { + this.searchNavigateScrollIntoView(position); + } + } else { + ctx.fillStyle = searchMatchColor; + } + const x = leftTop[0]; + const y = leftTop[1]; + const width = rightTop[0] - leftTop[0]; + const height = leftBottom[1] - leftTop[1]; + ctx.fillRect(x, y, width, height); + } + ctx.restore(); + } +} +class Strikeout extends AbstractRichText { + constructor(draw) { + super(); + this.options = draw.getOptions(); + } + render(ctx) { + if (!this.fillRect.width) + return; + const { scale, strikeoutColor } = this.options; + const { x, y, width } = this.fillRect; + ctx.save(); + ctx.lineWidth = scale; + ctx.strokeStyle = strikeoutColor; + const adjustY = y + 0.5; + ctx.beginPath(); + ctx.moveTo(x, adjustY); + ctx.lineTo(x + width, adjustY); + ctx.stroke(); + ctx.restore(); + this.clearFillInfo(); + } +} +var TextDecorationStyle; +(function(TextDecorationStyle2) { + TextDecorationStyle2["SOLID"] = "solid"; + TextDecorationStyle2["DOUBLE"] = "double"; + TextDecorationStyle2["DASHED"] = "dashed"; + TextDecorationStyle2["DOTTED"] = "dotted"; + TextDecorationStyle2["WAVY"] = "wavy"; +})(TextDecorationStyle || (TextDecorationStyle = {})); +var DashType; +(function(DashType2) { + DashType2["SOLID"] = "solid"; + DashType2["DASHED"] = "dashed"; + DashType2["DOTTED"] = "dotted"; +})(DashType || (DashType = {})); +class Underline extends AbstractRichText { + constructor(draw) { + super(); + this.options = draw.getOptions(); + } + _drawLine(ctx, startX, startY, width, dashType) { + const endX = startX + width; + ctx.beginPath(); + switch (dashType) { + case DashType.DASHED: + ctx.setLineDash([3, 1]); + break; + case DashType.DOTTED: + ctx.setLineDash([1, 1]); + break; + } + ctx.moveTo(startX, startY); + ctx.lineTo(endX, startY); + ctx.stroke(); + } + _drawDouble(ctx, startX, startY, width) { + const SPACING = 3; + const endX = startX + width; + const endY = startY + SPACING * this.options.scale; + ctx.beginPath(); + ctx.moveTo(startX, startY); + ctx.lineTo(endX, startY); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(startX, endY); + ctx.lineTo(endX, endY); + ctx.stroke(); + } + _drawWave(ctx, startX, startY, width) { + const { scale } = this.options; + const AMPLITUDE = 1.2 * scale; + const FREQUENCY = 1 / scale; + const adjustY = startY + 2 * AMPLITUDE; + ctx.beginPath(); + for (let x = 0; x < width; x++) { + const y = AMPLITUDE * Math.sin(FREQUENCY * x); + ctx.lineTo(startX + x, adjustY + y); + } + ctx.stroke(); + } + render(ctx) { + if (!this.fillRect.width) + return; + const { underlineColor, scale } = this.options; + const { x, y, width } = this.fillRect; + ctx.save(); + ctx.strokeStyle = this.fillColor || underlineColor; + ctx.lineWidth = scale; + const adjustY = Math.floor(y + 2 * ctx.lineWidth) + 0.5; + switch (this.fillDecorationStyle) { + case TextDecorationStyle.WAVY: + this._drawWave(ctx, x, adjustY, width); + break; + case TextDecorationStyle.DOUBLE: + this._drawDouble(ctx, x, adjustY, width); + break; + case TextDecorationStyle.DASHED: + this._drawLine(ctx, x, adjustY, width, DashType.DASHED); + break; + case TextDecorationStyle.DOTTED: + this._drawLine(ctx, x, adjustY, width, DashType.DOTTED); + break; + default: + this._drawLine(ctx, x, adjustY, width); + break; + } + ctx.restore(); + this.clearFillInfo(); + } +} +class TextParticle { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + this.ctx = draw.getCtx(); + this.curX = -1; + this.curY = -1; + this.text = ""; + this.curStyle = ""; + this.cacheMeasureText = /* @__PURE__ */ new Map(); + } + measureBasisWord(ctx, font) { + ctx.save(); + ctx.font = font; + const textMetrics = this.measureText(ctx, { + value: METRICS_BASIS_TEXT + }); + ctx.restore(); + return textMetrics; + } + measureWord(ctx, elementList, curIndex) { + const LETTER_REG = this.draw.getLetterReg(); + let width = 0; + let endElement = elementList[curIndex]; + let i = curIndex; + while (i < elementList.length) { + const element = elementList[i]; + if (element.type && element.type !== ElementType.TEXT || !LETTER_REG.test(element.value)) { + endElement = element; + break; + } + width += this.measureText(ctx, element).width; + i++; + } + return { + width, + endElement + }; + } + measurePunctuationWidth(ctx, element) { + if (!element || !PUNCTUATION_LIST.includes(element.value)) + return 0; + return this.measureText(ctx, element).width; + } + measureText(ctx, element) { + if (element.width) { + const textMetrics2 = ctx.measureText(element.value); + return { + width: element.width, + actualBoundingBoxAscent: textMetrics2.actualBoundingBoxAscent, + actualBoundingBoxDescent: textMetrics2.actualBoundingBoxDescent, + actualBoundingBoxLeft: textMetrics2.actualBoundingBoxLeft, + actualBoundingBoxRight: textMetrics2.actualBoundingBoxRight, + fontBoundingBoxAscent: textMetrics2.fontBoundingBoxAscent, + fontBoundingBoxDescent: textMetrics2.fontBoundingBoxDescent + }; + } + const id = `${element.value}${ctx.font}`; + const cacheTextMetrics = this.cacheMeasureText.get(id); + if (cacheTextMetrics) { + return cacheTextMetrics; + } + const textMetrics = ctx.measureText(element.value); + this.cacheMeasureText.set(id, textMetrics); + return textMetrics; + } + complete() { + this._render(); + this.text = ""; + } + record(ctx, element, x, y) { + this.ctx = ctx; + if (this.options.renderMode === RenderMode.COMPATIBILITY) { + this._setCurXY(x, y); + this.text = element.value; + this.curStyle = element.style; + this.curColor = element.color; + this.complete(); + return; + } + if (!this.text) { + this._setCurXY(x, y); + } + if (this.curStyle && element.style !== this.curStyle || element.color !== this.curColor) { + this.complete(); + this._setCurXY(x, y); + } + this.text += element.value; + this.curStyle = element.style; + this.curColor = element.color; + } + _setCurXY(x, y) { + this.curX = x; + this.curY = y; + } + _render() { + if (!this.text || !~this.curX || !~this.curX) + return; + this.ctx.save(); + this.ctx.font = this.curStyle; + this.ctx.fillStyle = this.curColor || this.options.defaultColor; + this.ctx.fillText(this.text, this.curX, this.curY); + this.ctx.restore(); + } +} +class PageNumber { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + } + render(ctx, pageNo) { + const { scale, pageMode, pageNumber: { size, font, color, rowFlex, numberType, format, startPageNo, fromPageNo } } = this.options; + if (pageNo < fromPageNo) + return; + let text = format; + const pageNoReg = new RegExp(FORMAT_PLACEHOLDER.PAGE_NO); + if (pageNoReg.test(text)) { + const realPageNo = pageNo + startPageNo - fromPageNo; + const pageNoText = numberType === NumberType.CHINESE ? convertNumberToChinese(realPageNo) : `${realPageNo}`; + text = text.replace(pageNoReg, pageNoText); + } + const pageCountReg = new RegExp(FORMAT_PLACEHOLDER.PAGE_COUNT); + if (pageCountReg.test(text)) { + const pageCount = this.draw.getPageCount() - fromPageNo; + const pageCountText = numberType === NumberType.CHINESE ? convertNumberToChinese(pageCount) : `${pageCount}`; + text = text.replace(pageCountReg, pageCountText); + } + const width = this.draw.getWidth(); + const height = pageMode === PageMode.CONTINUITY ? this.draw.getCanvasHeight(pageNo) : this.draw.getHeight(); + const pageNumberBottom = this.draw.getPageNumberBottom(); + const y = height - pageNumberBottom; + ctx.save(); + ctx.fillStyle = color; + ctx.font = `${size * scale}px ${font}`; + let x = 0; + const margins = this.draw.getMargins(); + const { width: textWidth } = ctx.measureText(text); + if (rowFlex === RowFlex.CENTER) { + x = (width - textWidth) / 2; + } else if (rowFlex === RowFlex.RIGHT) { + x = width - textWidth - margins[1]; + } else { + x = margins[3]; + } + ctx.fillText(text, x, y); + ctx.restore(); + } +} +class ScrollObserver { + constructor(draw) { + this._observer = debounce(() => { + const { intersectionPageNo, visiblePageNoList } = this.getPageVisibleInfo(); + this.draw.setIntersectionPageNo(intersectionPageNo); + this.draw.setVisiblePageNoList(visiblePageNoList); + }, 150); + this.draw = draw; + this.options = draw.getOptions(); + this.scrollContainer = this.getScrollContainer(); + setTimeout(() => { + if (!window.scrollY) { + this._observer(); + } + }); + this._addEvent(); + } + getScrollContainer() { + return this.options.scrollContainerSelector ? document.querySelector(this.options.scrollContainerSelector) || document : document; + } + _addEvent() { + this.scrollContainer.addEventListener("scroll", this._observer); + } + removeEvent() { + this.scrollContainer.removeEventListener("scroll", this._observer); + } + getElementVisibleInfo(element) { + const rect = element.getBoundingClientRect(); + const viewHeight = this.scrollContainer === document ? Math.max(document.documentElement.clientHeight, window.innerHeight) : this.scrollContainer.clientHeight; + const visibleHeight = Math.min(rect.bottom, viewHeight) - Math.max(rect.top, 0); + return { + intersectionHeight: visibleHeight > 0 ? visibleHeight : 0 + }; + } + getPageVisibleInfo() { + const pageList = this.draw.getPageList(); + const visiblePageNoList = []; + let intersectionPageNo = 0; + let intersectionMaxHeight = 0; + for (let i = 0; i < pageList.length; i++) { + const curPage = pageList[i]; + const { intersectionHeight } = this.getElementVisibleInfo(curPage); + if (intersectionMaxHeight && !intersectionHeight) + break; + if (intersectionHeight) { + visiblePageNoList.push(i); + } + if (intersectionHeight > intersectionMaxHeight) { + intersectionMaxHeight = intersectionHeight; + intersectionPageNo = i; + } + } + return { + intersectionPageNo, + visiblePageNoList + }; + } +} +class SelectionObserver { + constructor(draw) { + this.step = 5; + this.thresholdPoints = [70, 40, 10, 20]; + this._mousedown = () => { + this.isMousedown = true; + this.clientWidth = this.selectionContainer instanceof Document ? document.documentElement.clientWidth : this.selectionContainer.clientWidth; + this.clientHeight = this.selectionContainer instanceof Document ? document.documentElement.clientHeight : this.selectionContainer.clientHeight; + if (!(this.selectionContainer instanceof Document)) { + const rect = this.selectionContainer.getBoundingClientRect(); + this.containerRect = rect; + } + }; + this._mouseup = () => { + this.isMousedown = false; + this._stopMove(); + }; + this._mousemove = (evt) => { + if (!this.isMousedown || this.rangeManager.getIsCollapsed()) + return; + let { x, y } = evt; + if (this.containerRect) { + x = x - this.containerRect.x; + y = y - this.containerRect.y; + } + if (y < this.thresholdPoints[0]) { + this._startMove(MoveDirection.UP); + } else if (this.clientHeight - y <= this.thresholdPoints[1]) { + this._startMove(MoveDirection.DOWN); + } else if (x < this.thresholdPoints[2]) { + this._startMove(MoveDirection.LEFT); + } else if (this.clientWidth - x < this.thresholdPoints[3]) { + this._startMove(MoveDirection.RIGHT); + } else { + this._stopMove(); + } + }; + this.rangeManager = draw.getRange(); + const { scrollContainerSelector } = draw.getOptions(); + this.selectionContainer = scrollContainerSelector ? document.querySelector(scrollContainerSelector) || document : document; + this.requestAnimationFrameId = null; + this.isMousedown = false; + this.isMoving = false; + this.clientWidth = 0; + this.clientHeight = 0; + this.containerRect = null; + this._addEvent(); + } + _addEvent() { + const container = this.selectionContainer; + container.addEventListener("mousedown", this._mousedown); + container.addEventListener("mousemove", this._mousemove); + container.addEventListener("mouseup", this._mouseup); + document.addEventListener("mouseleave", this._mouseup); + } + removeEvent() { + const container = this.selectionContainer; + container.removeEventListener("mousedown", this._mousedown); + container.removeEventListener("mousemove", this._mousemove); + container.removeEventListener("mouseup", this._mouseup); + document.removeEventListener("mouseleave", this._mouseup); + } + _move(direction) { + const container = this.selectionContainer instanceof Document ? window : this.selectionContainer; + const x = this.selectionContainer instanceof Document ? window.scrollX : container.scrollLeft; + const y = this.selectionContainer instanceof Document ? window.scrollY : container.scrollTop; + if (direction === MoveDirection.DOWN) { + container.scrollTo(x, y + this.step); + } else if (direction === MoveDirection.UP) { + container.scrollTo(x, y - this.step); + } else if (direction === MoveDirection.LEFT) { + container.scrollTo(x - this.step, y); + } else { + container.scrollTo(x + this.step, y); + } + this.requestAnimationFrameId = window.requestAnimationFrame(this._move.bind(this, direction)); + } + _startMove(direction) { + if (this.isMoving) + return; + this.isMoving = true; + this._move(direction); + } + _stopMove() { + if (this.requestAnimationFrameId) { + window.cancelAnimationFrame(this.requestAnimationFrameId); + this.requestAnimationFrameId = null; + this.isMoving = false; + } + } +} +var TableBorder; +(function(TableBorder2) { + TableBorder2["ALL"] = "all"; + TableBorder2["EMPTY"] = "empty"; + TableBorder2["EXTERNAL"] = "external"; +})(TableBorder || (TableBorder = {})); +var TdBorder; +(function(TdBorder2) { + TdBorder2["TOP"] = "top"; + TdBorder2["RIGHT"] = "right"; + TdBorder2["BOTTOM"] = "bottom"; + TdBorder2["LEFT"] = "left"; +})(TdBorder || (TdBorder = {})); +var TdSlash; +(function(TdSlash2) { + TdSlash2["FORWARD"] = "forward"; + TdSlash2["BACK"] = "back"; +})(TdSlash || (TdSlash = {})); +class TableParticle { + constructor(draw) { + this.draw = draw; + this.range = draw.getRange(); + this.options = draw.getOptions(); + } + getTrListGroupByCol(payload) { + var _a; + const trList = deepClone(payload); + for (let t = 0; t < payload.length; t++) { + const tr = trList[t]; + for (let d = tr.tdList.length - 1; d >= 0; d--) { + const td = tr.tdList[d]; + const { rowspan, rowIndex, colIndex } = td; + const curRowIndex = rowIndex + rowspan - 1; + if (curRowIndex !== d) { + const changeTd = tr.tdList.splice(d, 1)[0]; + (_a = trList[curRowIndex]) == null ? void 0 : _a.tdList.splice(colIndex, 0, changeTd); + } + } + } + return trList; + } + getRangeRowCol() { + const { isTable, index: index2, trIndex, tdIndex } = this.draw.getPosition().getPositionContext(); + if (!isTable) + return null; + const { isCrossRowCol, startTdIndex, endTdIndex, startTrIndex, endTrIndex } = this.range.getRange(); + const originalElementList = this.draw.getOriginalElementList(); + const element = originalElementList[index2]; + const curTrList = element.trList; + if (!isCrossRowCol) { + return [[curTrList[trIndex].tdList[tdIndex]]]; + } + let startTd = curTrList[startTrIndex].tdList[startTdIndex]; + let endTd = curTrList[endTrIndex].tdList[endTdIndex]; + if (startTd.x > endTd.x || startTd.y > endTd.y) { + [startTd, endTd] = [endTd, startTd]; + } + const startColIndex = startTd.colIndex; + const endColIndex = endTd.colIndex + (endTd.colspan - 1); + const startRowIndex = startTd.rowIndex; + const endRowIndex = endTd.rowIndex + (endTd.rowspan - 1); + const rowCol = []; + for (let t = 0; t < curTrList.length; t++) { + const tr = curTrList[t]; + const tdList = []; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const tdColIndex = td.colIndex; + const tdRowIndex = td.rowIndex; + if (tdColIndex >= startColIndex && tdColIndex <= endColIndex && tdRowIndex >= startRowIndex && tdRowIndex <= endRowIndex) { + tdList.push(td); + } + } + if (tdList.length) { + rowCol.push(tdList); + } + } + return rowCol.length ? rowCol : null; + } + _drawOuterBorder(payload) { + const { ctx, startX, startY, width, height, isDrawFullBorder } = payload; + ctx.beginPath(); + const x = Math.round(startX); + const y = Math.round(startY); + ctx.translate(0.5, 0.5); + if (isDrawFullBorder) { + ctx.rect(x, y, width, height); + } else { + ctx.moveTo(x, y + height); + ctx.lineTo(x, y); + ctx.lineTo(x + width, y); + } + ctx.stroke(); + ctx.translate(-0.5, -0.5); + } + _drawSlash(ctx, td, startX, startY) { + var _a, _b; + const { scale } = this.options; + ctx.save(); + const width = td.width * scale; + const height = td.height * scale; + const x = Math.round(td.x * scale + startX); + const y = Math.round(td.y * scale + startY); + if ((_a = td.slashTypes) == null ? void 0 : _a.includes(TdSlash.FORWARD)) { + ctx.moveTo(x + width, y); + ctx.lineTo(x, y + height); + } + if ((_b = td.slashTypes) == null ? void 0 : _b.includes(TdSlash.BACK)) { + ctx.moveTo(x, y); + ctx.lineTo(x + width, y + height); + } + ctx.stroke(); + ctx.restore(); + } + _drawBorder(ctx, element, startX, startY) { + var _a, _b, _c, _d, _e, _f; + const { colgroup, trList, borderType } = element; + if (!colgroup || !trList) + return; + const { scale } = this.options; + const tableWidth = element.width * scale; + const tableHeight = element.height * scale; + const isEmptyBorderType = borderType === TableBorder.EMPTY; + const isExternalBorderType = borderType === TableBorder.EXTERNAL; + ctx.save(); + ctx.lineWidth = scale; + if (!isEmptyBorderType) { + this._drawOuterBorder({ + ctx, + startX, + startY, + width: tableWidth, + height: tableHeight, + isDrawFullBorder: isExternalBorderType + }); + } + for (let t = 0; t < trList.length; t++) { + const tr = trList[t]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + if ((_a = td.slashTypes) == null ? void 0 : _a.length) { + this._drawSlash(ctx, td, startX, startY); + } + if (!((_b = td.borderTypes) == null ? void 0 : _b.length) && (isEmptyBorderType || isExternalBorderType)) { + continue; + } + const width = td.width * scale; + const height = td.height * scale; + const x = Math.round(td.x * scale + startX + width); + const y = Math.round(td.y * scale + startY); + ctx.translate(0.5, 0.5); + ctx.beginPath(); + if ((_c = td.borderTypes) == null ? void 0 : _c.includes(TdBorder.TOP)) { + ctx.moveTo(x - width, y); + ctx.lineTo(x, y); + ctx.stroke(); + } + if ((_d = td.borderTypes) == null ? void 0 : _d.includes(TdBorder.RIGHT)) { + ctx.moveTo(x, y); + ctx.lineTo(x, y + height); + ctx.stroke(); + } + if ((_e = td.borderTypes) == null ? void 0 : _e.includes(TdBorder.BOTTOM)) { + ctx.moveTo(x, y + height); + ctx.lineTo(x - width, y + height); + ctx.stroke(); + } + if ((_f = td.borderTypes) == null ? void 0 : _f.includes(TdBorder.LEFT)) { + ctx.moveTo(x - width, y); + ctx.lineTo(x - width, y + height); + ctx.stroke(); + } + if (!isEmptyBorderType && !isExternalBorderType) { + ctx.moveTo(x, y); + ctx.lineTo(x, y + height); + ctx.lineTo(x - width, y + height); + ctx.stroke(); + } + ctx.translate(-0.5, -0.5); + } + } + ctx.restore(); + } + _drawBackgroundColor(ctx, element, startX, startY) { + const { trList } = element; + if (!trList) + return; + const { scale } = this.options; + for (let t = 0; t < trList.length; t++) { + const tr = trList[t]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + if (!td.backgroundColor) + continue; + ctx.save(); + const width = td.width * scale; + const height = td.height * scale; + const x = Math.round(td.x * scale + startX); + const y = Math.round(td.y * scale + startY); + ctx.fillStyle = td.backgroundColor; + ctx.fillRect(x, y, width, height); + ctx.restore(); + } + } + } + getTableWidth(element) { + return element.colgroup.reduce((pre, cur) => pre + cur.width, 0); + } + getTableHeight(element) { + const trList = element.trList; + if (!(trList == null ? void 0 : trList.length)) + return 0; + return this.getTdListByColIndex(trList, 0).reduce((pre, cur) => pre + cur.height, 0); + } + getRowCountByColIndex(trList, colIndex) { + return this.getTdListByColIndex(trList, colIndex).reduce((pre, cur) => pre + cur.rowspan, 0); + } + getTdListByColIndex(trList, colIndex) { + const data2 = []; + for (let r = 0; r < trList.length; r++) { + const tdList = trList[r].tdList; + for (let d = 0; d < tdList.length; d++) { + const td = tdList[d]; + const min = td.colIndex; + const max = min + td.colspan - 1; + if (colIndex >= min && colIndex <= max) { + data2.push(td); + } + } + } + return data2; + } + computeRowColInfo(element) { + const { colgroup, trList } = element; + if (!colgroup || !trList) + return; + let preX = 0; + for (let t = 0; t < trList.length; t++) { + const tr = trList[t]; + const isLastTr = trList.length - 1 === t; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + let colIndex = 0; + if (trList.length > 1 && t !== 0) { + const preTd = tr.tdList[d - 1]; + const start = preTd ? preTd.colIndex + preTd.colspan : d; + for (let c = start; c < colgroup.length; c++) { + const rowCount = this.getRowCountByColIndex(trList.slice(0, t), c); + if (rowCount === t) { + colIndex = c; + let preColWidth = 0; + for (let preC = 0; preC < c; preC++) { + preColWidth += colgroup[preC].width; + } + preX = preColWidth; + break; + } + } + } else { + const preTd = tr.tdList[d - 1]; + if (preTd) { + colIndex = preTd.colIndex + preTd.colspan; + } + } + let width = 0; + for (let col = 0; col < td.colspan; col++) { + width += colgroup[col + colIndex].width; + } + let height = 0; + for (let row = 0; row < td.rowspan; row++) { + const curTr = trList[row + t] || trList[t]; + height += curTr.height; + } + const isLastRowTd = tr.tdList.length - 1 === d; + let isLastColTd = isLastTr; + if (!isLastColTd) { + if (td.rowspan > 1) { + const nextTrLength = trList.length - 1 - t; + isLastColTd = td.rowspan - 1 === nextTrLength; + } + } + const isLastTd = isLastTr && isLastRowTd; + td.isLastRowTd = isLastRowTd; + td.isLastColTd = isLastColTd; + td.isLastTd = isLastTd; + td.x = preX; + let preY = 0; + for (let preR = 0; preR < t; preR++) { + const preTdList = trList[preR].tdList; + for (let preD = 0; preD < preTdList.length; preD++) { + const td2 = preTdList[preD]; + if (colIndex >= td2.colIndex && colIndex < td2.colIndex + td2.colspan) { + preY += td2.height; + break; + } + } + } + td.y = preY; + td.width = width; + td.height = height; + td.rowIndex = t; + td.colIndex = colIndex; + td.trIndex = t; + td.tdIndex = d; + preX += width; + if (isLastRowTd && !isLastTd) { + preX = 0; + } + } + } + } + drawRange(ctx, element, startX, startY) { + const { scale, rangeAlpha, rangeColor } = this.options; + const { type, trList } = element; + if (!trList || type !== ElementType.TABLE) + return; + const { isCrossRowCol, startTdIndex, endTdIndex, startTrIndex, endTrIndex } = this.range.getRange(); + if (!isCrossRowCol) + return; + let startTd = trList[startTrIndex].tdList[startTdIndex]; + let endTd = trList[endTrIndex].tdList[endTdIndex]; + if (startTd.x > endTd.x || startTd.y > endTd.y) { + [startTd, endTd] = [endTd, startTd]; + } + const startColIndex = startTd.colIndex; + const endColIndex = endTd.colIndex + (endTd.colspan - 1); + const startRowIndex = startTd.rowIndex; + const endRowIndex = endTd.rowIndex + (endTd.rowspan - 1); + ctx.save(); + for (let t = 0; t < trList.length; t++) { + const tr = trList[t]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const tdColIndex = td.colIndex; + const tdRowIndex = td.rowIndex; + if (tdColIndex >= startColIndex && tdColIndex <= endColIndex && tdRowIndex >= startRowIndex && tdRowIndex <= endRowIndex) { + const x = td.x * scale; + const y = td.y * scale; + const width = td.width * scale; + const height = td.height * scale; + ctx.globalAlpha = rangeAlpha; + ctx.fillStyle = rangeColor; + ctx.fillRect(x + startX, y + startY, width, height); + } + } + } + ctx.restore(); + } + render(ctx, element, startX, startY) { + this._drawBackgroundColor(ctx, element, startX, startY); + this._drawBorder(ctx, element, startX, startY); + } +} +var TableOrder; +(function(TableOrder2) { + TableOrder2["ROW"] = "row"; + TableOrder2["COL"] = "col"; +})(TableOrder || (TableOrder = {})); +class TableTool { + constructor(draw) { + this.MIN_TD_WIDTH = 20; + this.ROW_COL_OFFSET = 18; + this.ROW_COL_QUICK_WIDTH = 16; + this.ROW_COL_QUICK_OFFSET = 5; + this.ROW_COL_QUICK_POSITION = this.ROW_COL_OFFSET + (this.ROW_COL_OFFSET - this.ROW_COL_QUICK_WIDTH) / 2; + this.BORDER_VALUE = 4; + this.TABLE_SELECT_OFFSET = 20; + this.draw = draw; + this.canvas = draw.getPage(); + this.options = draw.getOptions(); + this.position = draw.getPosition(); + this.container = draw.getContainer(); + this.toolRowContainer = null; + this.toolRowAddBtn = null; + this.toolColAddBtn = null; + this.toolTableSelectBtn = null; + this.toolColContainer = null; + this.toolBorderContainer = null; + this.anchorLine = null; + this.mousedownX = 0; + this.mousedownY = 0; + } + dispose() { + var _a, _b, _c, _d, _e, _f; + (_a = this.toolRowContainer) == null ? void 0 : _a.remove(); + (_b = this.toolRowAddBtn) == null ? void 0 : _b.remove(); + (_c = this.toolColAddBtn) == null ? void 0 : _c.remove(); + (_d = this.toolTableSelectBtn) == null ? void 0 : _d.remove(); + (_e = this.toolColContainer) == null ? void 0 : _e.remove(); + (_f = this.toolBorderContainer) == null ? void 0 : _f.remove(); + this.toolRowContainer = null; + this.toolRowAddBtn = null; + this.toolColAddBtn = null; + this.toolTableSelectBtn = null; + this.toolColContainer = null; + this.toolBorderContainer = null; + } + render() { + const { isTable, index: index2, trIndex, tdIndex } = this.position.getPositionContext(); + if (!isTable) + return; + this.dispose(); + const { scale } = this.options; + const elementList = this.draw.getOriginalElementList(); + const positionList = this.position.getOriginalPositionList(); + const element = elementList[index2]; + const position = positionList[index2]; + const { colgroup, trList } = element; + const { coordinate: { leftTop } } = position; + const height = this.draw.getHeight(); + const pageGap = this.draw.getPageGap(); + const prePageHeight = this.draw.getPageNo() * (height + pageGap); + const tableX = leftTop[0]; + const tableY = leftTop[1] + prePageHeight; + const td = element.trList[trIndex].tdList[tdIndex]; + const rowIndex = td.rowIndex; + const colIndex = td.colIndex; + const tableHeight = element.height * scale; + const tableWidth = element.width * scale; + const tableSelectBtn = document.createElement("div"); + tableSelectBtn.classList.add(`${EDITOR_PREFIX}-table-tool__select`); + tableSelectBtn.style.height = `${tableHeight * scale}`; + tableSelectBtn.style.left = `${tableX}px`; + tableSelectBtn.style.top = `${tableY}px`; + tableSelectBtn.style.transform = `translate(-${this.TABLE_SELECT_OFFSET * scale}px, ${-this.TABLE_SELECT_OFFSET * scale}px)`; + tableSelectBtn.onclick = () => { + this.draw.getTableOperate().tableSelectAll(); + }; + this.container.append(tableSelectBtn); + this.toolTableSelectBtn = tableSelectBtn; + const rowHeightList = trList.map((tr) => tr.height); + const rowContainer = document.createElement("div"); + rowContainer.classList.add(`${EDITOR_PREFIX}-table-tool__row`); + rowContainer.style.transform = `translateX(-${this.ROW_COL_OFFSET * scale}px)`; + for (let r = 0; r < rowHeightList.length; r++) { + const rowHeight = rowHeightList[r] * scale; + const rowItem = document.createElement("div"); + rowItem.classList.add(`${EDITOR_PREFIX}-table-tool__row__item`); + if (r === rowIndex) { + rowItem.classList.add("active"); + } + const rowItemAnchor = document.createElement("div"); + rowItemAnchor.classList.add(`${EDITOR_PREFIX}-table-tool__anchor`); + rowItemAnchor.onmousedown = (evt) => { + this._mousedown({ + evt, + element, + index: r, + order: TableOrder.ROW + }); + }; + rowItem.append(rowItemAnchor); + rowItem.style.height = `${rowHeight}px`; + rowContainer.append(rowItem); + } + rowContainer.style.left = `${tableX}px`; + rowContainer.style.top = `${tableY}px`; + this.container.append(rowContainer); + this.toolRowContainer = rowContainer; + const rowAddBtn = document.createElement("div"); + rowAddBtn.classList.add(`${EDITOR_PREFIX}-table-tool__quick__add`); + rowAddBtn.style.height = `${tableHeight * scale}`; + rowAddBtn.style.left = `${tableX}px`; + rowAddBtn.style.top = `${tableY + tableHeight}px`; + rowAddBtn.style.transform = `translate(-${this.ROW_COL_QUICK_POSITION * scale}px, ${this.ROW_COL_QUICK_OFFSET * scale}px)`; + rowAddBtn.onclick = () => { + this.position.setPositionContext({ + index: index2, + isTable: true, + trIndex: trList.length - 1, + tdIndex: 0 + }); + this.draw.getTableOperate().insertTableBottomRow(); + }; + this.container.append(rowAddBtn); + this.toolRowAddBtn = rowAddBtn; + const colWidthList = colgroup.map((col) => col.width); + const colContainer = document.createElement("div"); + colContainer.classList.add(`${EDITOR_PREFIX}-table-tool__col`); + colContainer.style.transform = `translateY(-${this.ROW_COL_OFFSET * scale}px)`; + for (let c = 0; c < colWidthList.length; c++) { + const colWidth = colWidthList[c] * scale; + const colItem = document.createElement("div"); + colItem.classList.add(`${EDITOR_PREFIX}-table-tool__col__item`); + if (c === colIndex) { + colItem.classList.add("active"); + } + const colItemAnchor = document.createElement("div"); + colItemAnchor.classList.add(`${EDITOR_PREFIX}-table-tool__anchor`); + colItemAnchor.onmousedown = (evt) => { + this._mousedown({ + evt, + element, + index: c, + order: TableOrder.COL + }); + }; + colItem.append(colItemAnchor); + colItem.style.width = `${colWidth}px`; + colContainer.append(colItem); + } + colContainer.style.left = `${tableX}px`; + colContainer.style.top = `${tableY}px`; + this.container.append(colContainer); + this.toolColContainer = colContainer; + const colAddBtn = document.createElement("div"); + colAddBtn.classList.add(`${EDITOR_PREFIX}-table-tool__quick__add`); + colAddBtn.style.height = `${tableHeight * scale}`; + colAddBtn.style.left = `${tableX + tableWidth}px`; + colAddBtn.style.top = `${tableY}px`; + colAddBtn.style.transform = `translate(${this.ROW_COL_QUICK_OFFSET * scale}px, -${this.ROW_COL_QUICK_POSITION * scale}px)`; + colAddBtn.onclick = () => { + this.position.setPositionContext({ + index: index2, + isTable: true, + trIndex: 0, + tdIndex: trList[0].tdList.length - 1 || 0 + }); + this.draw.getTableOperate().insertTableRightCol(); + }; + this.container.append(colAddBtn); + this.toolColAddBtn = colAddBtn; + const borderContainer = document.createElement("div"); + borderContainer.classList.add(`${EDITOR_PREFIX}-table-tool__border`); + borderContainer.style.height = `${tableHeight}px`; + borderContainer.style.width = `${tableWidth}px`; + borderContainer.style.left = `${tableX}px`; + borderContainer.style.top = `${tableY}px`; + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + for (let d = 0; d < tr.tdList.length; d++) { + const td2 = tr.tdList[d]; + const rowBorder = document.createElement("div"); + rowBorder.classList.add(`${EDITOR_PREFIX}-table-tool__border__row`); + rowBorder.style.width = `${td2.width * scale}px`; + rowBorder.style.height = `${this.BORDER_VALUE}px`; + rowBorder.style.top = `${(td2.y + td2.height) * scale - this.BORDER_VALUE / 2}px`; + rowBorder.style.left = `${td2.x * scale}px`; + rowBorder.onmousedown = (evt) => { + this._mousedown({ + evt, + element, + index: td2.rowIndex + td2.rowspan - 1, + order: TableOrder.ROW + }); + }; + borderContainer.appendChild(rowBorder); + const colBorder = document.createElement("div"); + colBorder.classList.add(`${EDITOR_PREFIX}-table-tool__border__col`); + colBorder.style.width = `${this.BORDER_VALUE}px`; + colBorder.style.height = `${td2.height * scale}px`; + colBorder.style.top = `${td2.y * scale}px`; + colBorder.style.left = `${(td2.x + td2.width) * scale - this.BORDER_VALUE / 2}px`; + colBorder.onmousedown = (evt) => { + this._mousedown({ + evt, + element, + index: td2.colIndex + td2.colspan - 1, + order: TableOrder.COL + }); + }; + borderContainer.appendChild(colBorder); + } + } + this.container.append(borderContainer); + this.toolBorderContainer = borderContainer; + } + _mousedown(payload) { + const { evt, index: index2, order, element } = payload; + this.canvas = this.draw.getPage(); + const { scale } = this.options; + const width = this.draw.getWidth(); + const height = this.draw.getHeight(); + const pageGap = this.draw.getPageGap(); + const prePageHeight = this.draw.getPageNo() * (height + pageGap); + this.mousedownX = evt.x; + this.mousedownY = evt.y; + const target = evt.target; + const canvasRect = this.canvas.getBoundingClientRect(); + const cursor = window.getComputedStyle(target).cursor; + document.body.style.cursor = cursor; + this.canvas.style.cursor = cursor; + let startX = 0; + let startY = 0; + const anchorLine = document.createElement("div"); + anchorLine.classList.add(`${EDITOR_PREFIX}-table-anchor__line`); + if (order === TableOrder.ROW) { + anchorLine.classList.add(`${EDITOR_PREFIX}-table-anchor__line__row`); + anchorLine.style.width = `${width}px`; + startX = 0; + startY = prePageHeight + this.mousedownY - canvasRect.top; + } else { + anchorLine.classList.add(`${EDITOR_PREFIX}-table-anchor__line__col`); + anchorLine.style.height = `${height}px`; + startX = this.mousedownX - canvasRect.left; + startY = prePageHeight; + } + anchorLine.style.left = `${startX}px`; + anchorLine.style.top = `${startY}px`; + this.container.append(anchorLine); + this.anchorLine = anchorLine; + let dx = 0; + let dy = 0; + const mousemoveFn = (evt2) => { + const movePosition = this._mousemove(evt2, order, startX, startY); + if (movePosition) { + dx = movePosition.dx; + dy = movePosition.dy; + } + }; + document.addEventListener("mousemove", mousemoveFn); + document.addEventListener("mouseup", () => { + var _a; + let isChangeSize = false; + if (order === TableOrder.ROW) { + const trList = element.trList; + const tr = trList[index2] || trList[index2 - 1]; + const { defaultTrMinHeight } = this.options.table; + if (dy < 0 && tr.height + dy < defaultTrMinHeight) { + dy = defaultTrMinHeight - tr.height; + } + if (dy) { + tr.height += dy; + tr.minHeight = tr.height; + isChangeSize = true; + } + } else { + const { colgroup } = element; + if (colgroup && dx) { + const innerWidth = this.draw.getInnerWidth(); + const curColWidth = colgroup[index2].width; + if (dx < 0 && curColWidth + dx < this.MIN_TD_WIDTH) { + dx = this.MIN_TD_WIDTH - curColWidth; + } + const nextColWidth = (_a = colgroup[index2 + 1]) == null ? void 0 : _a.width; + if (dx > 0 && nextColWidth && nextColWidth - dx < this.MIN_TD_WIDTH) { + dx = nextColWidth - this.MIN_TD_WIDTH; + } + const moveColWidth = curColWidth + dx; + if (index2 === colgroup.length - 1) { + let moveTableWidth = 0; + for (let c = 0; c < colgroup.length; c++) { + const group2 = colgroup[c]; + if (c === index2 + 1) { + moveTableWidth -= dx; + } + if (c === index2) { + moveTableWidth += moveColWidth; + } + if (c !== index2) { + moveTableWidth += group2.width; + } + } + if (moveTableWidth > innerWidth) { + const tableWidth = element.width; + dx = innerWidth - tableWidth; + } + } + if (dx) { + if (colgroup.length - 1 !== index2) { + colgroup[index2 + 1].width -= dx / scale; + } + colgroup[index2].width += dx / scale; + isChangeSize = true; + } + } + } + if (isChangeSize) { + this.draw.render({ isSetCursor: false }); + } + anchorLine.remove(); + document.removeEventListener("mousemove", mousemoveFn); + document.body.style.cursor = ""; + this.canvas.style.cursor = "text"; + }, { + once: true + }); + evt.preventDefault(); + } + _mousemove(evt, tableOrder, startX, startY) { + if (!this.anchorLine) + return null; + const dx = evt.x - this.mousedownX; + const dy = evt.y - this.mousedownY; + if (tableOrder === TableOrder.ROW) { + this.anchorLine.style.top = `${startY + dy}px`; + } else { + this.anchorLine.style.left = `${startX + dx}px`; + } + evt.preventDefault(); + return { dx, dy }; + } +} +class HyperlinkParticle { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + this.container = draw.getContainer(); + const { hyperlinkPopupContainer, hyperlinkDom } = this._createHyperlinkPopupDom(); + this.hyperlinkDom = hyperlinkDom; + this.hyperlinkPopupContainer = hyperlinkPopupContainer; + } + _createHyperlinkPopupDom() { + const hyperlinkPopupContainer = document.createElement("div"); + hyperlinkPopupContainer.classList.add(`${EDITOR_PREFIX}-hyperlink-popup`); + const hyperlinkDom = document.createElement("a"); + hyperlinkDom.target = "_blank"; + hyperlinkDom.rel = "noopener"; + hyperlinkPopupContainer.append(hyperlinkDom); + this.container.append(hyperlinkPopupContainer); + return { hyperlinkPopupContainer, hyperlinkDom }; + } + drawHyperlinkPopup(element, position) { + const { coordinate: { leftTop: [left2, top] }, lineHeight } = position; + const height = this.draw.getHeight(); + const pageGap = this.draw.getPageGap(); + const preY = this.draw.getPageNo() * (height + pageGap); + this.hyperlinkPopupContainer.style.display = "block"; + this.hyperlinkPopupContainer.style.left = `${left2}px`; + this.hyperlinkPopupContainer.style.top = `${top + preY + lineHeight}px`; + const url = element.url || "#"; + this.hyperlinkDom.href = url; + this.hyperlinkDom.title = url; + this.hyperlinkDom.innerText = url; + } + clearHyperlinkPopup() { + this.hyperlinkPopupContainer.style.display = "none"; + } + openHyperlink(element) { + const newTab = window.open(element.url, "_blank"); + if (newTab) { + newTab.opener = null; + } + } + render(ctx, element, x, y) { + ctx.save(); + ctx.font = element.style; + if (!element.color) { + element.color = this.options.defaultHyperlinkColor; + } + ctx.fillStyle = element.color; + if (element.underline === void 0) { + element.underline = true; + } + ctx.fillText(element.value, x, y); + ctx.restore(); + } +} +class Header { + constructor(draw, data2) { + this.draw = draw; + this.position = draw.getPosition(); + this.options = draw.getOptions(); + this.elementList = data2 || []; + this.rowList = []; + this.positionList = []; + } + getRowList() { + return this.rowList; + } + setElementList(elementList) { + this.elementList = elementList; + } + getElementList() { + return this.elementList; + } + getPositionList() { + return this.positionList; + } + compute() { + this.recovery(); + this._computeRowList(); + this._computePositionList(); + } + recovery() { + this.rowList = []; + this.positionList = []; + } + _computeRowList() { + const innerWidth = this.draw.getInnerWidth(); + const margins = this.draw.getMargins(); + const surroundElementList = pickSurroundElementList(this.elementList); + this.rowList = this.draw.computeRowList({ + startX: margins[3], + startY: this.getHeaderTop(), + innerWidth, + elementList: this.elementList, + surroundElementList + }); + } + _computePositionList() { + const headerTop = this.getHeaderTop(); + const innerWidth = this.draw.getInnerWidth(); + const margins = this.draw.getMargins(); + const startX = margins[3]; + const startY = headerTop; + this.position.computePageRowPosition({ + positionList: this.positionList, + rowList: this.rowList, + pageNo: 0, + startRowIndex: 0, + startIndex: 0, + startX, + startY, + innerWidth, + zone: EditorZone.HEADER + }); + } + getHeaderTop() { + const { header: { top, disabled }, scale } = this.options; + if (disabled) + return 0; + return Math.floor(top * scale); + } + getMaxHeight() { + const { header: { maxHeightRadio } } = this.options; + const height = this.draw.getHeight(); + return Math.floor(height * maxHeightRadioMapping[maxHeightRadio]); + } + getHeight() { + const maxHeight = this.getMaxHeight(); + const rowHeight = this.getRowHeight(); + return rowHeight > maxHeight ? maxHeight : rowHeight; + } + getRowHeight() { + return this.rowList.reduce((pre, cur) => pre + cur.height, 0); + } + getExtraHeight() { + const margins = this.draw.getMargins(); + const headerHeight = this.getHeight(); + const headerTop = this.getHeaderTop(); + const extraHeight = headerTop + headerHeight - margins[0]; + return extraHeight <= 0 ? 0 : extraHeight; + } + render(ctx, pageNo) { + ctx.globalAlpha = 1; + const innerWidth = this.draw.getInnerWidth(); + const maxHeight = this.getMaxHeight(); + const rowList = []; + let curRowHeight = 0; + for (let r = 0; r < this.rowList.length; r++) { + const row = this.rowList[r]; + if (curRowHeight + row.height > maxHeight) { + break; + } + rowList.push(row); + curRowHeight += row.height; + } + this.draw.drawRow(ctx, { + elementList: this.elementList, + positionList: this.positionList, + rowList, + pageNo, + startIndex: 0, + innerWidth, + zone: EditorZone.HEADER + }); + } +} +class SuperscriptParticle { + getOffsetY(element) { + return -element.metrics.height / 2; + } + render(ctx, element, x, y) { + ctx.save(); + ctx.font = element.style; + if (element.color) { + ctx.fillStyle = element.color; + } + ctx.fillText(element.value, x, y + this.getOffsetY(element)); + ctx.restore(); + } +} +class SubscriptParticle { + getOffsetY(element) { + return element.metrics.height / 2; + } + render(ctx, element, x, y) { + ctx.save(); + ctx.font = element.style; + if (element.color) { + ctx.fillStyle = element.color; + } + ctx.fillText(element.value, x, y + this.getOffsetY(element)); + ctx.restore(); + } +} +class SeparatorParticle { + constructor(draw) { + this.options = draw.getOptions(); + } + render(ctx, element, x, y) { + var _a; + ctx.save(); + const { scale, separator: { lineWidth, strokeStyle } } = this.options; + ctx.lineWidth = (element.lineWidth || lineWidth) * scale; + ctx.strokeStyle = element.color || strokeStyle; + if ((_a = element.dashArray) == null ? void 0 : _a.length) { + ctx.setLineDash(element.dashArray); + } + const offsetY = Math.round(y); + ctx.translate(0, ctx.lineWidth / 2); + ctx.beginPath(); + ctx.moveTo(x, offsetY); + ctx.lineTo(x + element.width * scale, offsetY); + ctx.stroke(); + ctx.restore(); + } +} +class PageBreakParticle { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + this.i18n = draw.getI18n(); + } + render(ctx, element, x, y) { + const { pageBreak: { font, fontSize, lineDash } } = this.options; + const displayName = this.i18n.t("pageBreak.displayName"); + const { scale, defaultRowMargin } = this.options; + const size = fontSize * scale; + const elementWidth = element.width * scale; + const offsetY = this.draw.getDefaultBasicRowMarginHeight() * defaultRowMargin; + ctx.save(); + ctx.font = `${size}px ${font}`; + const textMeasure = ctx.measureText(displayName); + const halfX = (elementWidth - textMeasure.width) / 2; + ctx.setLineDash(lineDash); + ctx.translate(0, 0.5 + offsetY); + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x + halfX, y); + ctx.moveTo(x + halfX + textMeasure.width, y); + ctx.lineTo(x + elementWidth, y); + ctx.stroke(); + ctx.fillText(displayName, x + halfX, y + textMeasure.actualBoundingBoxAscent - size / 2); + ctx.restore(); + } +} +class Watermark { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + } + render(ctx) { + const { watermark: { data: data2, opacity, font, size, color, repeat, gap }, scale } = this.options; + const width = this.draw.getWidth(); + const height = this.draw.getHeight(); + ctx.save(); + ctx.globalAlpha = opacity; + ctx.font = `${size * scale}px ${font}`; + const measureText = ctx.measureText(data2); + if (repeat) { + const dpr = this.draw.getPagePixelRatio(); + const temporaryCanvas = document.createElement("canvas"); + const temporaryCtx = temporaryCanvas.getContext("2d"); + const textWidth = measureText.width; + const textHeight = measureText.actualBoundingBoxAscent + measureText.actualBoundingBoxDescent; + const diagonalLength = Math.sqrt(Math.pow(textWidth, 2) + Math.pow(textHeight, 2)); + const patternWidth = diagonalLength + 2 * gap[0] * scale; + const patternHeight = diagonalLength + 2 * gap[1] * scale; + temporaryCanvas.width = patternWidth; + temporaryCanvas.height = patternHeight; + temporaryCanvas.style.width = `${patternWidth * dpr}px`; + temporaryCanvas.style.height = `${patternHeight * dpr}px`; + temporaryCtx.translate(patternWidth / 2, patternHeight / 2); + temporaryCtx.rotate(-45 * Math.PI / 180); + temporaryCtx.translate(-patternWidth / 2, -patternHeight / 2); + temporaryCtx.font = `${size * scale}px ${font}`; + temporaryCtx.fillStyle = color; + temporaryCtx.fillText(data2, (patternWidth - textWidth) / 2, (patternHeight - textHeight) / 2 + measureText.actualBoundingBoxAscent); + const pattern = ctx.createPattern(temporaryCanvas, "repeat"); + if (pattern) { + ctx.fillStyle = pattern; + ctx.fillRect(0, 0, width, height); + } + } else { + const x = width / 2; + const y = height / 2; + ctx.fillStyle = color; + ctx.translate(x, y); + ctx.rotate(-45 * Math.PI / 180); + ctx.fillText(data2, -measureText.width / 2, measureText.actualBoundingBoxAscent - size / 2); + } + ctx.restore(); + } +} +class ControlSearch { + constructor(control) { + this.draw = control.getDraw(); + this.options = this.draw.getOptions(); + this.highlightList = []; + this.highlightMatchResult = []; + } + getHighlightMatchResult() { + return this.highlightMatchResult; + } + getHighlightList() { + return this.highlightList; + } + setHighlightList(payload) { + this.highlightList = payload; + } + computeHighlightList() { + const search = this.draw.getSearch(); + const computeHighlight = (elementList, restArgs) => { + let i = 0; + while (i < elementList.length) { + const element = elementList[i]; + i++; + if (element.type === ElementType.TABLE) { + const trList = element.trList; + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const restArgs2 = { + tableId: element.id, + tableIndex: i - 1, + trIndex: r, + tdIndex: d, + tdId: td.id + }; + computeHighlight(td.value, restArgs2); + } + } + } + const currentControl = element == null ? void 0 : element.control; + if (!currentControl) + continue; + const highlightIndex = this.highlightList.findIndex((highlight2) => highlight2.id === element.controlId || currentControl.conceptId && currentControl.conceptId === highlight2.conceptId); + if (!~highlightIndex) + continue; + const startIndex = i; + let newEndIndex = i; + while (newEndIndex < elementList.length) { + const nextElement = elementList[newEndIndex]; + if (nextElement.controlId !== element.controlId) + break; + newEndIndex++; + } + i = newEndIndex; + const controlElementList = elementList.slice(startIndex, newEndIndex).map((element2) => element2.controlComponent === ControlComponent.VALUE ? element2 : { value: ZERO }); + const highlight = this.highlightList[highlightIndex]; + const { ruleList } = highlight; + for (let r = 0; r < ruleList.length; r++) { + const rule = ruleList[r]; + const searchResult = search.getMatchList(rule.keyword, controlElementList); + this.highlightMatchResult.push(...searchResult.map((result) => ({ + ...result, + ...rule, + ...restArgs, + index: result.index + startIndex + }))); + } + } + }; + this.highlightMatchResult = []; + computeHighlight(this.draw.getOriginalMainElementList()); + } + renderHighlightList(ctx, pageIndex) { + var _a, _b, _c; + if (!((_a = this.highlightMatchResult) == null ? void 0 : _a.length)) + return; + const { searchMatchAlpha, searchMatchColor } = this.options; + const positionList = this.draw.getPosition().getOriginalPositionList(); + const elementList = this.draw.getOriginalElementList(); + ctx.save(); + for (let s = 0; s < this.highlightMatchResult.length; s++) { + const searchMatch = this.highlightMatchResult[s]; + let position = null; + if (searchMatch.tableId) { + const { tableIndex, trIndex, tdIndex, index: index2 } = searchMatch; + position = (_c = (_b = elementList[tableIndex]) == null ? void 0 : _b.trList[trIndex].tdList[tdIndex]) == null ? void 0 : _c.positionList[index2]; + } else { + position = positionList[searchMatch.index]; + } + if (!position) + continue; + const { coordinate: { leftTop, leftBottom, rightTop }, pageNo } = position; + if (pageNo !== pageIndex) + continue; + ctx.fillStyle = searchMatch.backgroundColor || searchMatchColor; + ctx.globalAlpha = searchMatch.alpha || searchMatchAlpha; + const x = leftTop[0]; + const y = leftTop[1]; + const width = rightTop[0] - leftTop[0]; + const height = leftBottom[1] - leftTop[1]; + ctx.fillRect(x, y, width, height); + } + ctx.restore(); + } +} +class ControlBorder { + constructor(draw) { + this.borderRect = this.clearBorderInfo(); + this.options = draw.getOptions(); + } + clearBorderInfo() { + this.borderRect = { + x: 0, + y: 0, + width: 0, + height: 0 + }; + return this.borderRect; + } + recordBorderInfo(x, y, width, height) { + const isFirstRecord = !this.borderRect.width; + if (isFirstRecord) { + this.borderRect.x = x; + this.borderRect.y = y; + this.borderRect.height = height; + } + this.borderRect.width += width; + } + render(ctx) { + if (!this.borderRect.width) + return; + const { scale, control: { borderWidth, borderColor } } = this.options; + const { x, y, width, height } = this.borderRect; + ctx.save(); + ctx.translate(0, 1 * scale); + ctx.lineWidth = borderWidth * scale; + ctx.strokeStyle = borderColor; + ctx.beginPath(); + ctx.rect(x, y, width, height); + ctx.stroke(); + ctx.restore(); + this.clearBorderInfo(); + } +} +class SelectControl { + constructor(element, control) { + this.options = control.getDraw().getOptions(); + this.element = element; + this.control = control; + this.isPopup = false; + this.selectDom = null; + } + setElement(element) { + this.element = element; + } + getElement() { + return this.element; + } + getIsPopup() { + return this.isPopup; + } + getCode() { + var _a; + return ((_a = this.element.control) == null ? void 0 : _a.code) || null; + } + getValue(context = {}) { + const elementList = context.elementList || this.control.getElementList(); + const { startIndex } = context.range || this.control.getRange(); + const startElement = elementList[startIndex]; + const data2 = []; + let preIndex = startIndex; + while (preIndex > 0) { + const preElement = elementList[preIndex]; + if (preElement.controlId !== startElement.controlId || preElement.controlComponent === ControlComponent.PREFIX) { + break; + } + if (preElement.controlComponent === ControlComponent.VALUE) { + data2.unshift(preElement); + } + preIndex--; + } + let nextIndex = startIndex + 1; + while (nextIndex < elementList.length) { + const nextElement = elementList[nextIndex]; + if (nextElement.controlId !== startElement.controlId || nextElement.controlComponent === ControlComponent.POSTFIX) { + break; + } + if (nextElement.controlComponent === ControlComponent.VALUE) { + data2.push(nextElement); + } + nextIndex++; + } + return data2; + } + setValue() { + return -1; + } + keydown(evt) { + if (this.control.getIsDisabledControl()) { + return null; + } + const elementList = this.control.getElementList(); + const range = this.control.getRange(); + this.control.shrinkBoundary(); + const { startIndex, endIndex } = range; + const startElement = elementList[startIndex]; + const endElement = elementList[endIndex]; + if (evt.key === KeyMap.Backspace) { + if (startIndex !== endIndex) { + return this.clearSelect(); + } else { + if (startElement.controlComponent === ControlComponent.PREFIX || endElement.controlComponent === ControlComponent.POSTFIX || startElement.controlComponent === ControlComponent.PLACEHOLDER) { + return this.control.removeControl(startIndex); + } else { + return this.clearSelect(); + } + } + } else if (evt.key === KeyMap.Delete) { + if (startIndex !== endIndex) { + return this.clearSelect(); + } else { + const endNextElement = elementList[endIndex + 1]; + if (startElement.controlComponent === ControlComponent.PREFIX && endNextElement.controlComponent === ControlComponent.PLACEHOLDER || endNextElement.controlComponent === ControlComponent.POSTFIX || startElement.controlComponent === ControlComponent.PLACEHOLDER) { + return this.control.removeControl(startIndex); + } else { + return this.clearSelect(); + } + } + } + return endIndex; + } + cut() { + if (this.control.getIsDisabledControl()) { + return -1; + } + this.control.shrinkBoundary(); + const { startIndex, endIndex } = this.control.getRange(); + if (startIndex === endIndex) { + return startIndex; + } + return this.clearSelect(); + } + clearSelect(context = {}, options = {}) { + const { isIgnoreDisabledRule = false, isAddPlaceholder = true } = options; + if (!isIgnoreDisabledRule && this.control.getIsDisabledControl(context)) { + return -1; + } + const elementList = context.elementList || this.control.getElementList(); + const { startIndex } = context.range || this.control.getRange(); + const startElement = elementList[startIndex]; + let leftIndex = -1; + let rightIndex = -1; + let preIndex = startIndex; + while (preIndex > 0) { + const preElement = elementList[preIndex]; + if (preElement.controlId !== startElement.controlId || preElement.controlComponent === ControlComponent.PREFIX) { + leftIndex = preIndex; + break; + } + preIndex--; + } + let nextIndex = startIndex + 1; + while (nextIndex < elementList.length) { + const nextElement = elementList[nextIndex]; + if (nextElement.controlId !== startElement.controlId || nextElement.controlComponent === ControlComponent.POSTFIX) { + rightIndex = nextIndex - 1; + break; + } + nextIndex++; + } + if (!~leftIndex || !~rightIndex) + return -1; + const draw = this.control.getDraw(); + draw.spliceElementList(elementList, leftIndex + 1, rightIndex - leftIndex); + if (isAddPlaceholder) { + this.control.addPlaceholder(preIndex, context); + } + this.element.control.code = null; + return preIndex; + } + setSelect(code, context = {}, options = {}) { + if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl(context)) { + return; + } + const elementList = context.elementList || this.control.getElementList(); + const range = context.range || this.control.getRange(); + const control = this.element.control; + const oldCode = control.code; + if (code === oldCode) { + this.control.repaintControl({ + curIndex: range.startIndex, + isCompute: false, + isSubmitHistory: false + }); + this.destroy(); + return; + } + const valueSets = control.valueSets; + if (!Array.isArray(valueSets) || !valueSets.length) + return; + const valueSet = valueSets.find((v) => v.code === code); + if (!valueSet) + return; + const valueElement = this.getValue(context)[0]; + const styleElement = valueElement ? pickObject(valueElement, EDITOR_ELEMENT_STYLE_ATTR) : pickObject(elementList[range.startIndex], CONTROL_STYLE_ATTR); + const prefixIndex = this.clearSelect(context, { + isAddPlaceholder: false + }); + if (!~prefixIndex) + return; + if (!oldCode) { + this.control.removePlaceholder(prefixIndex, context); + } + const propertyElement = omitObject(elementList[prefixIndex], EDITOR_ELEMENT_STYLE_ATTR); + const start = prefixIndex + 1; + const data2 = splitText(valueSet.value); + const draw = this.control.getDraw(); + for (let i = 0; i < data2.length; i++) { + const newElement = { + ...styleElement, + ...propertyElement, + type: ElementType.TEXT, + value: data2[i], + controlComponent: ControlComponent.VALUE + }; + formatElementContext(elementList, [newElement], prefixIndex, { + editorOptions: this.options + }); + draw.spliceElementList(elementList, start + i, 0, newElement); + } + control.code = code; + if (!context.range) { + const newIndex = start + data2.length - 1; + this.control.repaintControl({ + curIndex: newIndex + }); + this.destroy(); + } + } + _createSelectPopupDom() { + const control = this.element.control; + const valueSets = control.valueSets; + if (!Array.isArray(valueSets) || !valueSets.length) + return; + const position = this.control.getPosition(); + if (!position) + return; + const selectPopupContainer = document.createElement("div"); + selectPopupContainer.classList.add(`${EDITOR_PREFIX}-select-control-popup`); + selectPopupContainer.setAttribute(EDITOR_COMPONENT, EditorComponent.POPUP); + const ul = document.createElement("ul"); + for (let v = 0; v < valueSets.length; v++) { + const valueSet = valueSets[v]; + const li = document.createElement("li"); + const code = this.getCode(); + if (code === valueSet.code) { + li.classList.add("active"); + } + li.onclick = () => { + this.setSelect(valueSet.code); + }; + li.append(document.createTextNode(valueSet.value)); + ul.append(li); + } + selectPopupContainer.append(ul); + const { coordinate: { leftTop: [left2, top] }, lineHeight } = position; + const preY = this.control.getPreY(); + selectPopupContainer.style.left = `${left2}px`; + selectPopupContainer.style.top = `${top + preY + lineHeight}px`; + const container = this.control.getContainer(); + container.append(selectPopupContainer); + this.selectDom = selectPopupContainer; + } + awake() { + var _a; + if (this.isPopup || this.control.getIsDisabledControl()) + return; + const { startIndex } = this.control.getRange(); + const elementList = this.control.getElementList(); + if (((_a = elementList[startIndex + 1]) == null ? void 0 : _a.controlId) !== this.element.controlId) { + return; + } + this._createSelectPopupDom(); + this.isPopup = true; + } + destroy() { + var _a; + if (!this.isPopup) + return; + (_a = this.selectDom) == null ? void 0 : _a.remove(); + this.isPopup = false; + } +} +class TextControl { + constructor(element, control) { + this.options = control.getDraw().getOptions(); + this.element = element; + this.control = control; + } + setElement(element) { + this.element = element; + } + getElement() { + return this.element; + } + getValue(context = {}) { + const elementList = context.elementList || this.control.getElementList(); + const { startIndex } = context.range || this.control.getRange(); + const startElement = elementList[startIndex]; + const data2 = []; + let preIndex = startIndex; + while (preIndex > 0) { + const preElement = elementList[preIndex]; + if (preElement.controlId !== startElement.controlId || preElement.controlComponent === ControlComponent.PREFIX) { + break; + } + if (preElement.controlComponent === ControlComponent.VALUE) { + data2.unshift(preElement); + } + preIndex--; + } + let nextIndex = startIndex + 1; + while (nextIndex < elementList.length) { + const nextElement = elementList[nextIndex]; + if (nextElement.controlId !== startElement.controlId || nextElement.controlComponent === ControlComponent.POSTFIX) { + break; + } + if (nextElement.controlComponent === ControlComponent.VALUE) { + data2.push(nextElement); + } + nextIndex++; + } + return data2; + } + setValue(data2, context = {}, options = {}) { + if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl(context)) { + return -1; + } + const elementList = context.elementList || this.control.getElementList(); + const range = context.range || this.control.getRange(); + this.control.shrinkBoundary(context); + const { startIndex, endIndex } = range; + const draw = this.control.getDraw(); + if (startIndex !== endIndex) { + draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex); + } else { + this.control.removePlaceholder(startIndex, context); + } + const startElement = elementList[startIndex]; + const anchorElement = startElement.type && !TEXTLIKE_ELEMENT_TYPE.includes(startElement.type) || startElement.controlComponent === ControlComponent.PREFIX ? pickObject(startElement, [ + "control", + "controlId", + ...CONTROL_STYLE_ATTR + ]) : omitObject(startElement, ["type"]); + const start = range.startIndex + 1; + for (let i = 0; i < data2.length; i++) { + const newElement = { + ...anchorElement, + ...data2[i], + controlComponent: ControlComponent.VALUE + }; + formatElementContext(elementList, [newElement], startIndex, { + editorOptions: this.options + }); + draw.spliceElementList(elementList, start + i, 0, newElement); + } + return start + data2.length - 1; + } + clearValue(context = {}, options = {}) { + if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl(context)) { + return -1; + } + const elementList = context.elementList || this.control.getElementList(); + const range = context.range || this.control.getRange(); + const { startIndex, endIndex } = range; + this.control.getDraw().spliceElementList(elementList, startIndex + 1, endIndex - startIndex); + const value = this.getValue(context); + if (!value.length) { + this.control.addPlaceholder(startIndex, context); + } + return startIndex; + } + keydown(evt) { + if (this.control.getIsDisabledControl()) { + return null; + } + const elementList = this.control.getElementList(); + const range = this.control.getRange(); + this.control.shrinkBoundary(); + const { startIndex, endIndex } = range; + const startElement = elementList[startIndex]; + const endElement = elementList[endIndex]; + const draw = this.control.getDraw(); + if (evt.key === KeyMap.Backspace) { + if (startIndex !== endIndex) { + draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex); + const value = this.getValue(); + if (!value.length) { + this.control.addPlaceholder(startIndex); + } + return startIndex; + } else { + if (startElement.controlComponent === ControlComponent.PREFIX || endElement.controlComponent === ControlComponent.POSTFIX || startElement.controlComponent === ControlComponent.PLACEHOLDER) { + return this.control.removeControl(startIndex); + } else { + draw.spliceElementList(elementList, startIndex, 1); + const value = this.getValue(); + if (!value.length) { + this.control.addPlaceholder(startIndex - 1); + } + return startIndex - 1; + } + } + } else if (evt.key === KeyMap.Delete) { + if (startIndex !== endIndex) { + draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex); + const value = this.getValue(); + if (!value.length) { + this.control.addPlaceholder(startIndex); + } + return startIndex; + } else { + const endNextElement = elementList[endIndex + 1]; + if (startElement.controlComponent === ControlComponent.PREFIX && endNextElement.controlComponent === ControlComponent.PLACEHOLDER || endNextElement.controlComponent === ControlComponent.POSTFIX || startElement.controlComponent === ControlComponent.PLACEHOLDER) { + return this.control.removeControl(startIndex); + } else { + draw.spliceElementList(elementList, startIndex + 1, 1); + const value = this.getValue(); + if (!value.length) { + this.control.addPlaceholder(startIndex); + } + return startIndex; + } + } + } + return endIndex; + } + cut() { + if (this.control.getIsDisabledControl()) { + return -1; + } + this.control.shrinkBoundary(); + const { startIndex, endIndex } = this.control.getRange(); + if (startIndex === endIndex) { + return startIndex; + } + const draw = this.control.getDraw(); + const elementList = this.control.getElementList(); + draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex); + const value = this.getValue(); + if (!value.length) { + this.control.addPlaceholder(startIndex); + } + return startIndex; + } +} +class DatePicker { + constructor(draw, options = {}) { + this.draw = draw; + this.options = options; + this.lang = this._getLang(); + this.now = new Date(); + this.dom = this._createDom(); + this.renderOptions = null; + this.isDatePicker = true; + this.pickDate = null; + this._bindEvent(); + } + _createDom() { + const datePickerContainer = document.createElement("div"); + datePickerContainer.classList.add(`${EDITOR_PREFIX}-date-container`); + datePickerContainer.setAttribute(EDITOR_COMPONENT, EditorComponent.POPUP); + const dateWrap = document.createElement("div"); + dateWrap.classList.add(`${EDITOR_PREFIX}-date-wrap`); + const datePickerTitle = document.createElement("div"); + datePickerTitle.classList.add(`${EDITOR_PREFIX}-date-title`); + const preYearTitle = document.createElement("span"); + preYearTitle.classList.add(`${EDITOR_PREFIX}-date-title__pre-year`); + preYearTitle.innerText = `<<`; + const preMonthTitle = document.createElement("span"); + preMonthTitle.classList.add(`${EDITOR_PREFIX}-date-title__pre-month`); + preMonthTitle.innerText = `<`; + const nowTitle = document.createElement("span"); + nowTitle.classList.add(`${EDITOR_PREFIX}-date-title__now`); + const nextMonthTitle = document.createElement("span"); + nextMonthTitle.classList.add(`${EDITOR_PREFIX}-date-title__next-month`); + nextMonthTitle.innerText = `>`; + const nextYearTitle = document.createElement("span"); + nextYearTitle.classList.add(`${EDITOR_PREFIX}-date-title__next-year`); + nextYearTitle.innerText = `>>`; + datePickerTitle.append(preYearTitle); + datePickerTitle.append(preMonthTitle); + datePickerTitle.append(nowTitle); + datePickerTitle.append(nextMonthTitle); + datePickerTitle.append(nextYearTitle); + const datePickerWeek = document.createElement("div"); + datePickerWeek.classList.add(`${EDITOR_PREFIX}-date-week`); + const { weeks: { sun, mon, tue, wed, thu, fri, sat } } = this.lang; + const weekList = [sun, mon, tue, wed, thu, fri, sat]; + weekList.forEach((week) => { + const weekDom = document.createElement("span"); + weekDom.innerText = `${week}`; + datePickerWeek.append(weekDom); + }); + const datePickerDay = document.createElement("div"); + datePickerDay.classList.add(`${EDITOR_PREFIX}-date-day`); + dateWrap.append(datePickerTitle); + dateWrap.append(datePickerWeek); + dateWrap.append(datePickerDay); + const timeWrap = document.createElement("ul"); + timeWrap.classList.add(`${EDITOR_PREFIX}-time-wrap`); + let hourTime; + let minuteTime; + let secondTime; + const timeList = [this.lang.hour, this.lang.minute, this.lang.second]; + timeList.forEach((t, i) => { + const li = document.createElement("li"); + const timeText = document.createElement("span"); + timeText.innerText = t; + li.append(timeText); + const ol = document.createElement("ol"); + const isHour = i === 0; + const isMinute = i === 1; + const endIndex = isHour ? 24 : 60; + for (let i2 = 0; i2 < endIndex; i2++) { + const time = document.createElement("li"); + time.innerText = `${String(i2).padStart(2, "0")}`; + time.setAttribute("data-id", `${i2}`); + ol.append(time); + } + if (isHour) { + hourTime = ol; + } else if (isMinute) { + minuteTime = ol; + } else { + secondTime = ol; + } + li.append(ol); + timeWrap.append(li); + }); + const datePickerMenu = document.createElement("div"); + datePickerMenu.classList.add(`${EDITOR_PREFIX}-date-menu`); + const timeMenu = document.createElement("button"); + timeMenu.classList.add(`${EDITOR_PREFIX}-date-menu__time`); + timeMenu.innerText = this.lang.timeSelect; + const nowMenu = document.createElement("button"); + nowMenu.classList.add(`${EDITOR_PREFIX}-date-menu__now`); + nowMenu.innerText = this.lang.now; + const submitMenu = document.createElement("button"); + submitMenu.classList.add(`${EDITOR_PREFIX}-date-menu__submit`); + submitMenu.innerText = this.lang.confirm; + datePickerMenu.append(timeMenu); + datePickerMenu.append(nowMenu); + datePickerMenu.append(submitMenu); + datePickerContainer.append(dateWrap); + datePickerContainer.append(timeWrap); + datePickerContainer.append(datePickerMenu); + this.draw.getContainer().append(datePickerContainer); + return { + container: datePickerContainer, + dateWrap, + datePickerWeek, + timeWrap, + title: { + preYear: preYearTitle, + preMonth: preMonthTitle, + now: nowTitle, + nextMonth: nextMonthTitle, + nextYear: nextYearTitle + }, + day: datePickerDay, + time: { + hour: hourTime, + minute: minuteTime, + second: secondTime + }, + menu: { + time: timeMenu, + now: nowMenu, + submit: submitMenu + } + }; + } + _bindEvent() { + this.dom.title.preYear.onclick = () => { + this._preYear(); + }; + this.dom.title.preMonth.onclick = () => { + this._preMonth(); + }; + this.dom.title.nextMonth.onclick = () => { + this._nextMonth(); + }; + this.dom.title.nextYear.onclick = () => { + this._nextYear(); + }; + this.dom.menu.time.onclick = () => { + this.isDatePicker = !this.isDatePicker; + this._toggleDateTimePicker(); + }; + this.dom.menu.now.onclick = () => { + this._now(); + this._submit(); + }; + this.dom.menu.submit.onclick = () => { + this.dispose(); + this._submit(); + }; + this.dom.time.hour.onclick = (evt) => { + if (!this.pickDate) + return; + const li = evt.target; + const id = li.dataset.id; + if (!id) + return; + this.pickDate.setHours(Number(id)); + this._setTimePick(false); + }; + this.dom.time.minute.onclick = (evt) => { + if (!this.pickDate) + return; + const li = evt.target; + const id = li.dataset.id; + if (!id) + return; + this.pickDate.setMinutes(Number(id)); + this._setTimePick(false); + }; + this.dom.time.second.onclick = (evt) => { + if (!this.pickDate) + return; + const li = evt.target; + const id = li.dataset.id; + if (!id) + return; + this.pickDate.setSeconds(Number(id)); + this._setTimePick(false); + }; + } + _setPosition() { + if (!this.renderOptions) + return; + const { position: { coordinate: { leftTop: [left2, top] }, lineHeight, pageNo } } = this.renderOptions; + const height = this.draw.getHeight(); + const pageGap = this.draw.getPageGap(); + const currentPageNo = pageNo != null ? pageNo : this.draw.getPageNo(); + const preY = currentPageNo * (height + pageGap); + this.dom.container.style.left = `${left2}px`; + this.dom.container.style.top = `${top + preY + lineHeight}px`; + } + isInvalidDate(value) { + return value.toDateString() === "Invalid Date"; + } + _setValue() { + var _a; + const value = (_a = this.renderOptions) == null ? void 0 : _a.value; + if (value) { + const setDate = new Date(value); + this.now = this.isInvalidDate(setDate) ? new Date() : setDate; + } else { + this.now = new Date(); + } + this.pickDate = new Date(this.now); + } + _getLang() { + const i18n = this.draw.getI18n(); + const t = i18n.t.bind(i18n); + return { + now: t("datePicker.now"), + confirm: t("datePicker.confirm"), + return: t("datePicker.return"), + timeSelect: t("datePicker.timeSelect"), + weeks: { + sun: t("datePicker.weeks.sun"), + mon: t("datePicker.weeks.mon"), + tue: t("datePicker.weeks.tue"), + wed: t("datePicker.weeks.wed"), + thu: t("datePicker.weeks.thu"), + fri: t("datePicker.weeks.fri"), + sat: t("datePicker.weeks.sat") + }, + year: t("datePicker.year"), + month: t("datePicker.month"), + hour: t("datePicker.hour"), + minute: t("datePicker.minute"), + second: t("datePicker.second") + }; + } + _setLangChange() { + this.dom.menu.time.innerText = this.lang.timeSelect; + this.dom.menu.now.innerText = this.lang.now; + this.dom.menu.submit.innerText = this.lang.confirm; + const { weeks: { sun, mon, tue, wed, thu, fri, sat } } = this.lang; + const weekList = [sun, mon, tue, wed, thu, fri, sat]; + this.dom.datePickerWeek.childNodes.forEach((child, i) => { + const childElement = child; + childElement.innerText = weekList[i]; + }); + const hourTitle = this.dom.time.hour.previousElementSibling; + hourTitle.innerText = this.lang.hour; + const minuteTitle = this.dom.time.minute.previousElementSibling; + minuteTitle.innerText = this.lang.minute; + const secondTitle = this.dom.time.second.previousElementSibling; + secondTitle.innerText = this.lang.second; + } + _update() { + const localDate = new Date(); + const localYear = localDate.getFullYear(); + const localMonth = localDate.getMonth() + 1; + const localDay = localDate.getDate(); + let pickYear = null; + let pickMonth = null; + let pickDay = null; + if (this.pickDate) { + pickYear = this.pickDate.getFullYear(); + pickMonth = this.pickDate.getMonth() + 1; + pickDay = this.pickDate.getDate(); + } + const year = this.now.getFullYear(); + const month = this.now.getMonth() + 1; + this.dom.title.now.innerText = `${year}${this.lang.year} ${String(month).padStart(2, "0")}${this.lang.month}`; + const curDate = new Date(year, month, 0); + const curDay = curDate.getDate(); + let curWeek = new Date(year, month - 1, 1).getDay(); + if (curWeek === 0) { + curWeek = 7; + } + const preDay = new Date(year, month - 1, 0).getDate(); + this.dom.day.innerHTML = ""; + const preStartDay = preDay - curWeek + 1; + for (let i = preStartDay; i <= preDay; i++) { + const dayDom = document.createElement("div"); + dayDom.classList.add("disable"); + dayDom.innerText = `${i}`; + dayDom.onclick = () => { + const newMonth = month - 2; + this.now = new Date(year, newMonth, i); + this._setDatePick(year, newMonth, i); + }; + this.dom.day.append(dayDom); + } + for (let i = 1; i <= curDay; i++) { + const dayDom = document.createElement("div"); + if (localYear === year && localMonth === month && localDay === i) { + dayDom.classList.add("active"); + } + if (this.pickDate && pickYear === year && pickMonth === month && pickDay === i) { + dayDom.classList.add("select"); + } + dayDom.innerText = `${i}`; + dayDom.onclick = (evt) => { + const newMonth = month - 1; + this.now = new Date(year, newMonth, i); + this._setDatePick(year, newMonth, i); + evt.stopPropagation(); + }; + this.dom.day.append(dayDom); + } + const nextEndDay = 6 * 7 - curWeek - curDay; + for (let i = 1; i <= nextEndDay; i++) { + const dayDom = document.createElement("div"); + dayDom.classList.add("disable"); + dayDom.innerText = `${i}`; + dayDom.onclick = () => { + this.now = new Date(year, month, i); + this._setDatePick(year, month, i); + }; + this.dom.day.append(dayDom); + } + } + _toggleDateTimePicker() { + if (this.isDatePicker) { + this.dom.dateWrap.classList.add("active"); + this.dom.timeWrap.classList.remove("active"); + this.dom.menu.time.innerText = this.lang.timeSelect; + } else { + this.dom.dateWrap.classList.remove("active"); + this.dom.timeWrap.classList.add("active"); + this.dom.menu.time.innerText = this.lang.return; + this._setTimePick(); + } + } + _setDatePick(year, month, day) { + var _a, _b, _c; + this.now = new Date(year, month, day); + (_a = this.pickDate) == null ? void 0 : _a.setFullYear(year); + (_b = this.pickDate) == null ? void 0 : _b.setMonth(month); + (_c = this.pickDate) == null ? void 0 : _c.setDate(day); + this._update(); + } + _setTimePick(isIntoView = true) { + var _a, _b, _c; + const hour = ((_a = this.pickDate) == null ? void 0 : _a.getHours()) || 0; + const minute = ((_b = this.pickDate) == null ? void 0 : _b.getMinutes()) || 0; + const second = ((_c = this.pickDate) == null ? void 0 : _c.getSeconds()) || 0; + const { hour: hourDom, minute: minuteDom, second: secondDom } = this.dom.time; + const timeDomList = [hourDom, minuteDom, secondDom]; + timeDomList.forEach((timeDom) => { + timeDom.querySelectorAll("li").forEach((li) => li.classList.remove("active")); + }); + const pickList = [ + [hourDom, hour], + [minuteDom, minute], + [secondDom, second] + ]; + pickList.forEach(([dom, time]) => { + const pickDom = dom.querySelector(`[data-id='${time}']`); + pickDom.classList.add("active"); + if (isIntoView) { + this._scrollIntoView(dom, pickDom); + } + }); + } + _scrollIntoView(container, selected) { + if (!selected) { + container.scrollTop = 0; + return; + } + const offsetParents = []; + let pointer = selected.offsetParent; + while (pointer && container !== pointer && container.contains(pointer)) { + offsetParents.push(pointer); + pointer = pointer.offsetParent; + } + const top = selected.offsetTop + offsetParents.reduce((prev, curr) => prev + curr.offsetTop, 0); + const bottom = top + selected.offsetHeight; + const viewRectTop = container.scrollTop; + const viewRectBottom = viewRectTop + container.clientHeight; + if (top < viewRectTop) { + container.scrollTop = top; + } else if (bottom > viewRectBottom) { + container.scrollTop = bottom - container.clientHeight; + } + } + _preMonth() { + this.now.setMonth(this.now.getMonth() - 1); + this._update(); + } + _nextMonth() { + this.now.setMonth(this.now.getMonth() + 1); + this._update(); + } + _preYear() { + this.now.setFullYear(this.now.getFullYear() - 1); + this._update(); + } + _nextYear() { + this.now.setFullYear(this.now.getFullYear() + 1); + this._update(); + } + _now() { + this.pickDate = new Date(); + this.dispose(); + } + _toggleVisible(isVisible) { + if (isVisible) { + this.dom.container.classList.add("active"); + } else { + this.dom.container.classList.remove("active"); + } + } + _submit() { + var _a; + if (this.options.onSubmit && this.pickDate) { + const format = (_a = this.renderOptions) == null ? void 0 : _a.dateFormat; + const pickDateString = this.formatDate(this.pickDate, format); + this.options.onSubmit(pickDateString); + } + } + formatDate(date, format = "yyyy-MM-dd hh:mm:ss") { + let dateString = format; + const dateOption = { + "y+": date.getFullYear().toString(), + "M+": (date.getMonth() + 1).toString(), + "d+": date.getDate().toString(), + "h+": date.getHours().toString(), + "m+": date.getMinutes().toString(), + "s+": date.getSeconds().toString() + }; + for (const k in dateOption) { + const reg = new RegExp("(" + k + ")").exec(format); + const key = k; + if (reg) { + dateString = dateString.replace(reg[1], reg[1].length === 1 ? dateOption[key] : dateOption[key].padStart(reg[1].length, "0")); + } + } + return dateString; + } + render(option) { + this.renderOptions = option; + this.lang = this._getLang(); + this._setLangChange(); + this._setValue(); + this._update(); + this._setPosition(); + this.isDatePicker = true; + this._toggleDateTimePicker(); + this._toggleVisible(true); + } + dispose() { + this._toggleVisible(false); + } + destroy() { + this.dom.container.remove(); + } +} +class DateControl { + constructor(element, control) { + const draw = control.getDraw(); + this.draw = draw; + this.options = draw.getOptions(); + this.element = element; + this.control = control; + this.isPopup = false; + this.datePicker = null; + } + setElement(element) { + this.element = element; + } + getElement() { + return this.element; + } + getIsPopup() { + return this.isPopup; + } + getValueRange(context = {}) { + const elementList = context.elementList || this.control.getElementList(); + const { startIndex } = context.range || this.control.getRange(); + const startElement = elementList[startIndex]; + let preIndex = startIndex; + while (preIndex > 0) { + const preElement = elementList[preIndex]; + if (preElement.controlId !== startElement.controlId || preElement.controlComponent === ControlComponent.PREFIX) { + break; + } + preIndex--; + } + let nextIndex = startIndex + 1; + while (nextIndex < elementList.length) { + const nextElement = elementList[nextIndex]; + if (nextElement.controlId !== startElement.controlId || nextElement.controlComponent === ControlComponent.POSTFIX) { + break; + } + nextIndex++; + } + if (preIndex === nextIndex) + return null; + return [preIndex, nextIndex - 1]; + } + getValue(context = {}) { + const elementList = context.elementList || this.control.getElementList(); + const range = this.getValueRange(context); + if (!range) + return []; + const data2 = []; + const [startIndex, endIndex] = range; + for (let i = startIndex; i <= endIndex; i++) { + const element = elementList[i]; + if (element.controlComponent === ControlComponent.VALUE) { + data2.push(element); + } + } + return data2; + } + setValue(data2, context = {}, options = {}) { + if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl(context)) { + return -1; + } + const elementList = context.elementList || this.control.getElementList(); + const range = context.range || this.control.getRange(); + this.control.shrinkBoundary(context); + const { startIndex, endIndex } = range; + const draw = this.control.getDraw(); + if (startIndex !== endIndex) { + draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex); + } else { + this.control.removePlaceholder(startIndex, context); + } + const startElement = elementList[startIndex]; + const anchorElement = startElement.type && !TEXTLIKE_ELEMENT_TYPE.includes(startElement.type) || startElement.controlComponent === ControlComponent.PREFIX ? pickObject(startElement, [ + "control", + "controlId", + ...CONTROL_STYLE_ATTR + ]) : omitObject(startElement, ["type"]); + const start = range.startIndex + 1; + for (let i = 0; i < data2.length; i++) { + const newElement = { + ...anchorElement, + ...data2[i], + controlComponent: ControlComponent.VALUE + }; + formatElementContext(elementList, [newElement], startIndex, { + editorOptions: this.options + }); + draw.spliceElementList(elementList, start + i, 0, newElement); + } + return start + data2.length - 1; + } + clearSelect(context = {}, options = {}) { + const { isIgnoreDisabledRule = false, isAddPlaceholder = true } = options; + if (!isIgnoreDisabledRule && this.control.getIsDisabledControl(context)) { + return -1; + } + const range = this.getValueRange(context); + if (!range) + return -1; + const [leftIndex, rightIndex] = range; + if (!~leftIndex || !~rightIndex) + return -1; + const elementList = context.elementList || this.control.getElementList(); + const draw = this.control.getDraw(); + draw.spliceElementList(elementList, leftIndex + 1, rightIndex - leftIndex); + if (isAddPlaceholder) { + this.control.addPlaceholder(leftIndex, context); + } + return leftIndex; + } + setSelect(date, context = {}, options = {}) { + if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl(context)) { + return; + } + const elementList = context.elementList || this.control.getElementList(); + const range = context.range || this.control.getRange(); + const valueElement = this.getValue(context)[0]; + const styleElement = valueElement ? pickObject(valueElement, EDITOR_ELEMENT_STYLE_ATTR) : pickObject(elementList[range.startIndex], CONTROL_STYLE_ATTR); + const prefixIndex = this.clearSelect(context, { + isAddPlaceholder: false + }); + if (!~prefixIndex) + return; + const propertyElement = omitObject(elementList[prefixIndex], EDITOR_ELEMENT_STYLE_ATTR); + const start = prefixIndex + 1; + const draw = this.control.getDraw(); + for (let i = 0; i < date.length; i++) { + const newElement = { + ...styleElement, + ...propertyElement, + type: ElementType.TEXT, + value: date[i], + controlComponent: ControlComponent.VALUE + }; + formatElementContext(elementList, [newElement], prefixIndex, { + editorOptions: this.options + }); + draw.spliceElementList(elementList, start + i, 0, newElement); + } + if (!context.range) { + const newIndex = start + date.length - 1; + this.control.repaintControl({ + curIndex: newIndex + }); + this.destroy(); + } + } + keydown(evt) { + if (this.control.getIsDisabledControl()) { + return null; + } + const elementList = this.control.getElementList(); + const range = this.control.getRange(); + this.control.shrinkBoundary(); + const { startIndex, endIndex } = range; + const startElement = elementList[startIndex]; + const endElement = elementList[endIndex]; + const draw = this.control.getDraw(); + if (evt.key === KeyMap.Backspace) { + if (startIndex !== endIndex) { + draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex); + const value = this.getValue(); + if (!value.length) { + this.control.addPlaceholder(startIndex); + } + return startIndex; + } else { + if (startElement.controlComponent === ControlComponent.PREFIX || endElement.controlComponent === ControlComponent.POSTFIX || startElement.controlComponent === ControlComponent.PLACEHOLDER) { + return this.control.removeControl(startIndex); + } else { + draw.spliceElementList(elementList, startIndex, 1); + const value = this.getValue(); + if (!value.length) { + this.control.addPlaceholder(startIndex - 1); + } + return startIndex - 1; + } + } + } else if (evt.key === KeyMap.Delete) { + if (startIndex !== endIndex) { + draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex); + const value = this.getValue(); + if (!value.length) { + this.control.addPlaceholder(startIndex); + } + return startIndex; + } else { + const endNextElement = elementList[endIndex + 1]; + if (startElement.controlComponent === ControlComponent.PREFIX && endNextElement.controlComponent === ControlComponent.PLACEHOLDER || endNextElement.controlComponent === ControlComponent.POSTFIX || startElement.controlComponent === ControlComponent.PLACEHOLDER) { + return this.control.removeControl(startIndex); + } else { + draw.spliceElementList(elementList, startIndex + 1, 1); + const value = this.getValue(); + if (!value.length) { + this.control.addPlaceholder(startIndex); + } + return startIndex; + } + } + } + return endIndex; + } + cut() { + if (this.control.getIsDisabledControl()) { + return -1; + } + this.control.shrinkBoundary(); + const { startIndex, endIndex } = this.control.getRange(); + if (startIndex === endIndex) { + return startIndex; + } + const draw = this.control.getDraw(); + const elementList = this.control.getElementList(); + draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex); + const value = this.getValue(); + if (!value.length) { + this.control.addPlaceholder(startIndex); + } + return startIndex; + } + awake() { + var _a, _b; + if (this.isPopup || this.control.getIsDisabledControl()) + return; + const position = this.control.getPosition(); + if (!position) + return; + const elementList = this.draw.getElementList(); + const { startIndex } = this.control.getRange(); + if (((_a = elementList[startIndex + 1]) == null ? void 0 : _a.controlId) !== this.element.controlId) { + return; + } + this.datePicker = new DatePicker(this.draw, { + onSubmit: this._setDate.bind(this) + }); + const value = this.getValue().map((el) => el.value).join("") || ""; + const dateFormat = (_b = this.element.control) == null ? void 0 : _b.dateFormat; + this.datePicker.render({ + value, + position, + dateFormat + }); + this.isPopup = true; + } + destroy() { + var _a; + if (!this.isPopup) + return; + (_a = this.datePicker) == null ? void 0 : _a.destroy(); + this.isPopup = false; + } + _setDate(date) { + if (!date) { + this.clearSelect(); + } else { + this.setSelect(date); + } + this.destroy(); + } +} +class Control { + constructor(draw) { + this.controlBorder = new ControlBorder(draw); + this.draw = draw; + this.range = draw.getRange(); + this.listener = draw.getListener(); + this.eventBus = draw.getEventBus(); + this.controlSearch = new ControlSearch(this); + this.options = draw.getOptions(); + this.controlOptions = this.options.control; + this.activeControl = null; + } + setHighlightList(payload) { + this.controlSearch.setHighlightList(payload); + } + computeHighlightList() { + const highlightList = this.controlSearch.getHighlightList(); + if (highlightList.length) { + this.controlSearch.computeHighlightList(); + } + } + renderHighlightList(ctx, pageNo) { + const highlightMatchResult = this.controlSearch.getHighlightMatchResult(); + if (highlightMatchResult.length) { + this.controlSearch.renderHighlightList(ctx, pageNo); + } + } + getDraw() { + return this.draw; + } + filterAssistElement(elementList) { + return elementList.filter((element) => { + var _a; + if (element.type === ElementType.TABLE) { + const trList = element.trList; + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + td.value = this.filterAssistElement(td.value); + } + } + } + if (!element.controlId) + return true; + if ((_a = element.control) == null ? void 0 : _a.minWidth) { + if (element.controlComponent === ControlComponent.PREFIX || element.controlComponent === ControlComponent.POSTFIX) { + element.value = ""; + return true; + } + } + return element.controlComponent !== ControlComponent.PREFIX && element.controlComponent !== ControlComponent.POSTFIX && element.controlComponent !== ControlComponent.PLACEHOLDER; + }); + } + getIsRangeCanCaptureEvent() { + if (!this.activeControl) + return false; + const { startIndex, endIndex } = this.getRange(); + if (!~startIndex && !~endIndex) + return false; + const elementList = this.getElementList(); + const startElement = elementList[startIndex]; + if (startIndex === endIndex && startElement.controlComponent === ControlComponent.POSTFIX) { + return true; + } + const endElement = elementList[endIndex]; + if (startElement.controlId && startElement.controlId === endElement.controlId && endElement.controlComponent !== ControlComponent.POSTFIX) { + return true; + } + return false; + } + getIsRangeInPostfix() { + if (!this.activeControl) + return false; + const { startIndex, endIndex } = this.getRange(); + if (startIndex !== endIndex) + return false; + const elementList = this.getElementList(); + const element = elementList[startIndex]; + return element.controlComponent === ControlComponent.POSTFIX; + } + getIsRangeWithinControl() { + const { startIndex, endIndex } = this.getRange(); + if (!~startIndex && !~endIndex) + return false; + const elementList = this.getElementList(); + const startElement = elementList[startIndex]; + const endElement = elementList[endIndex]; + if (startElement.controlId && startElement.controlId === endElement.controlId && endElement.controlComponent !== ControlComponent.POSTFIX) { + return true; + } + return false; + } + getIsElementListContainFullControl(elementList) { + if (!elementList.some((element) => element.controlId)) + return false; + let prefixCount = 0; + let postfixCount = 0; + for (let e = 0; e < elementList.length; e++) { + const element = elementList[e]; + if (element.controlComponent === ControlComponent.PREFIX) { + prefixCount++; + } else if (element.controlComponent === ControlComponent.POSTFIX) { + postfixCount++; + } + } + if (!prefixCount || !postfixCount) + return false; + return prefixCount === postfixCount; + } + getIsDisabledControl(context = {}) { + var _a, _b; + if (this.draw.isDesignMode() || !this.activeControl) + return false; + const { startIndex, endIndex } = context.range || this.range.getRange(); + if (startIndex === endIndex && ~startIndex && ~endIndex) { + const elementList = context.elementList || this.getElementList(); + const startElement = elementList[startIndex]; + if (startElement.controlComponent === ControlComponent.POSTFIX) { + return false; + } + } + return !!((_b = (_a = this.activeControl.getElement()) == null ? void 0 : _a.control) == null ? void 0 : _b.disabled); + } + getContainer() { + return this.draw.getContainer(); + } + getElementList() { + return this.draw.getElementList(); + } + getPosition() { + const positionList = this.draw.getPosition().getPositionList(); + const { endIndex } = this.range.getRange(); + return positionList[endIndex] || null; + } + getPreY() { + var _a, _b; + const height = this.draw.getHeight(); + const pageGap = this.draw.getPageGap(); + const pageNo = (_b = (_a = this.getPosition()) == null ? void 0 : _a.pageNo) != null ? _b : this.draw.getPageNo(); + return pageNo * (height + pageGap); + } + getRange() { + return this.range.getRange(); + } + shrinkBoundary(context = {}) { + this.range.shrinkBoundary(context); + } + getActiveControl() { + return this.activeControl; + } + initControl() { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + const elementList = this.getElementList(); + const range = this.getRange(); + const element = elementList[range.startIndex]; + if (this.activeControl) { + if (this.activeControl instanceof SelectControl || this.activeControl instanceof DateControl) { + if (element.controlComponent === ControlComponent.POSTFIX) { + this.activeControl.destroy(); + } else { + this.activeControl.awake(); + } + } + const controlElement = this.activeControl.getElement(); + if (element.controlId === controlElement.controlId) + return; + } + this.destroyControl(); + const control = element.control; + if (control.type === ControlType.TEXT) { + this.activeControl = new TextControl(element, this); + } else if (control.type === ControlType.SELECT) { + const selectControl = new SelectControl(element, this); + this.activeControl = selectControl; + selectControl.awake(); + } else if (control.type === ControlType.CHECKBOX) { + this.activeControl = new CheckboxControl(element, this); + } else if (control.type === ControlType.RADIO) { + this.activeControl = new RadioControl(element, this); + } else if (control.type === ControlType.DATE) { + const dateControl = new DateControl(element, this); + this.activeControl = dateControl; + dateControl.awake(); + } + nextTick(() => { + var _a; + const controlChangeListener = this.listener.controlChange; + const isSubscribeControlChange = this.eventBus.isSubscribe("controlChange"); + if (!controlChangeListener && !isSubscribeControlChange) + return; + let payload; + const value = (_a = this.activeControl) == null ? void 0 : _a.getValue(); + if (value && value.length) { + payload = zipElementList(value)[0].control; + } else { + payload = pickElementAttr(deepClone(element)).control; + } + if (controlChangeListener) { + controlChangeListener(payload); + } + if (isSubscribeControlChange) { + this.eventBus.emit("controlChange", payload); + } + }); + } + destroyControl() { + if (this.activeControl) { + if (this.activeControl instanceof SelectControl || this.activeControl instanceof DateControl) { + this.activeControl.destroy(); + } + this.activeControl = null; + nextTick(() => { + const controlChangeListener = this.listener.controlChange; + const isSubscribeControlChange = this.eventBus.isSubscribe("controlChange"); + if (!controlChangeListener && !isSubscribeControlChange) + return; + if (controlChangeListener) { + controlChangeListener(null); + } + if (isSubscribeControlChange) { + this.eventBus.emit("controlChange", null); + } + }); + } + } + repaintControl(options = {}) { + const { curIndex, isCompute = true, isSubmitHistory = true, isSetCursor = true } = options; + if (curIndex === void 0) { + this.range.clearRange(); + this.draw.render({ + isCompute, + isSubmitHistory, + isSetCursor: false + }); + } else { + this.range.setRange(curIndex, curIndex); + this.draw.render({ + curIndex, + isCompute, + isSetCursor, + isSubmitHistory + }); + } + } + reAwakeControl() { + if (!this.activeControl) + return; + const elementList = this.getElementList(); + const range = this.getRange(); + const element = elementList[range.startIndex]; + this.activeControl.setElement(element); + if ((this.activeControl instanceof DateControl || this.activeControl instanceof SelectControl) && this.activeControl.getIsPopup()) { + this.activeControl.destroy(); + this.activeControl.awake(); + } + } + moveCursor(position) { + const { index: index2, trIndex, tdIndex, tdValueIndex } = position; + let elementList = this.draw.getOriginalElementList(); + let element; + const newIndex = position.isTable ? tdValueIndex : index2; + if (position.isTable) { + elementList = elementList[index2].trList[trIndex].tdList[tdIndex].value; + element = elementList[tdValueIndex]; + } else { + element = elementList[index2]; + } + if (element.controlComponent === ControlComponent.VALUE) { + return { + newIndex, + newElement: element + }; + } else if (element.controlComponent === ControlComponent.POSTFIX) { + let startIndex = newIndex + 1; + while (startIndex < elementList.length) { + const nextElement = elementList[startIndex]; + if (nextElement.controlId !== element.controlId) { + return { + newIndex: startIndex - 1, + newElement: elementList[startIndex - 1] + }; + } + startIndex++; + } + } else if (element.controlComponent === ControlComponent.PREFIX) { + let startIndex = newIndex + 1; + while (startIndex < elementList.length) { + const nextElement = elementList[startIndex]; + if (nextElement.controlId !== element.controlId || nextElement.controlComponent !== ControlComponent.PREFIX) { + return { + newIndex: startIndex - 1, + newElement: elementList[startIndex - 1] + }; + } + startIndex++; + } + } else if (element.controlComponent === ControlComponent.PLACEHOLDER) { + let startIndex = newIndex - 1; + while (startIndex > 0) { + const preElement = elementList[startIndex]; + if (preElement.controlId !== element.controlId || preElement.controlComponent === ControlComponent.PREFIX) { + return { + newIndex: startIndex, + newElement: elementList[startIndex] + }; + } + startIndex--; + } + } + return { + newIndex, + newElement: element + }; + } + removeControl(startIndex, context = {}) { + const elementList = context.elementList || this.getElementList(); + const startElement = elementList[startIndex]; + if (!this.draw.isDesignMode()) { + const { deletable = true } = startElement.control; + if (!deletable) + return null; + } + let leftIndex = -1; + let rightIndex = -1; + let preIndex = startIndex; + while (preIndex > 0) { + const preElement = elementList[preIndex]; + if (preElement.controlId !== startElement.controlId) { + leftIndex = preIndex; + break; + } + preIndex--; + } + let nextIndex = startIndex + 1; + while (nextIndex < elementList.length) { + const nextElement = elementList[nextIndex]; + if (nextElement.controlId !== startElement.controlId) { + rightIndex = nextIndex - 1; + break; + } + nextIndex++; + } + if (nextIndex === elementList.length) { + rightIndex = nextIndex - 1; + } + if (!~leftIndex && !~rightIndex) + return startIndex; + leftIndex = ~leftIndex ? leftIndex : 0; + this.draw.spliceElementList(elementList, leftIndex + 1, rightIndex - leftIndex); + return leftIndex; + } + removePlaceholder(startIndex, context = {}) { + const elementList = context.elementList || this.getElementList(); + const startElement = elementList[startIndex]; + const nextElement = elementList[startIndex + 1]; + if (startElement.controlComponent === ControlComponent.PLACEHOLDER || nextElement.controlComponent === ControlComponent.PLACEHOLDER) { + let isHasSubmitHistory = false; + let index2 = startIndex; + while (index2 < elementList.length) { + const curElement = elementList[index2]; + if (curElement.controlId !== startElement.controlId) + break; + if (curElement.controlComponent === ControlComponent.PLACEHOLDER) { + if (!isHasSubmitHistory) { + isHasSubmitHistory = true; + this.draw.getHistoryManager().popUndo(); + this.draw.submitHistory(startIndex); + } + elementList.splice(index2, 1); + } else { + index2++; + } + } + } + } + addPlaceholder(startIndex, context = {}) { + const elementList = context.elementList || this.getElementList(); + const startElement = elementList[startIndex]; + const control = startElement.control; + if (!control.placeholder) + return; + const placeholderStrList = splitText(control.placeholder); + const anchorElementStyleAttr = pickObject(startElement, CONTROL_STYLE_ATTR); + for (let p = 0; p < placeholderStrList.length; p++) { + const value = placeholderStrList[p]; + const newElement = { + ...anchorElementStyleAttr, + value, + controlId: startElement.controlId, + type: ElementType.CONTROL, + control: startElement.control, + controlComponent: ControlComponent.PLACEHOLDER, + color: this.controlOptions.placeholderColor + }; + formatElementContext(elementList, [newElement], startIndex, { + editorOptions: this.options + }); + this.draw.spliceElementList(elementList, startIndex + p + 1, 0, newElement); + } + } + setValue(data2) { + if (!this.activeControl) { + throw new Error("active control is null"); + } + return this.activeControl.setValue(data2); + } + keydown(evt) { + if (!this.activeControl) { + throw new Error("active control is null"); + } + return this.activeControl.keydown(evt); + } + cut() { + if (!this.activeControl) { + throw new Error("active control is null"); + } + return this.activeControl.cut(); + } + getValueById(payload) { + const { id, conceptId } = payload; + const result = []; + if (!id && !conceptId) + return result; + const getValue = (elementList, zone2) => { + let i = 0; + while (i < elementList.length) { + const element = elementList[i]; + i++; + if (element.type === ElementType.TABLE) { + const trList = element.trList; + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + getValue(td.value, zone2); + } + } + } + if (!element.control || id && element.controlId !== id || conceptId && element.control.conceptId !== conceptId) { + continue; + } + const { type, code, valueSets } = element.control; + let j = i; + let textControlValue = ""; + const textControlElementList = []; + while (j < elementList.length) { + const nextElement = elementList[j]; + if (nextElement.controlId !== element.controlId) + break; + if ((type === ControlType.TEXT || type === ControlType.DATE) && nextElement.controlComponent === ControlComponent.VALUE) { + textControlValue += nextElement.value; + textControlElementList.push(omitObject(nextElement, CONTROL_CONTEXT_ATTR)); + } + j++; + } + if (type === ControlType.TEXT || type === ControlType.DATE) { + result.push({ + ...element.control, + zone: zone2, + value: textControlValue || null, + innerText: textControlValue || null, + elementList: zipElementList(textControlElementList) + }); + } else if (type === ControlType.SELECT || type === ControlType.CHECKBOX || type === ControlType.RADIO) { + const innerText = code == null ? void 0 : code.split(",").map((selectCode) => { + var _a; + return (_a = valueSets == null ? void 0 : valueSets.find((valueSet) => valueSet.code === selectCode)) == null ? void 0 : _a.value; + }).filter(Boolean).join(""); + result.push({ + ...element.control, + zone: zone2, + value: code || null, + innerText: innerText || null + }); + } + i = j; + } + }; + const data2 = [ + { + zone: EditorZone.HEADER, + elementList: this.draw.getHeaderElementList() + }, + { + zone: EditorZone.MAIN, + elementList: this.draw.getOriginalMainElementList() + }, + { + zone: EditorZone.FOOTER, + elementList: this.draw.getFooterElementList() + } + ]; + for (const { zone: zone2, elementList } of data2) { + getValue(elementList, zone2); + } + return result; + } + setValueById(payload) { + let isExistSet = false; + const { id, conceptId, value } = payload; + if (!id && !conceptId) + return; + const setValue = (elementList) => { + let i = 0; + while (i < elementList.length) { + const element = elementList[i]; + i++; + if (element.type === ElementType.TABLE) { + const trList = element.trList; + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + setValue(td.value); + } + } + } + if (!element.control || id && element.controlId !== id || conceptId && element.control.conceptId !== conceptId) { + continue; + } + isExistSet = true; + const { type } = element.control; + let currentEndIndex = i; + while (currentEndIndex < elementList.length) { + const nextElement = elementList[currentEndIndex]; + if (nextElement.controlId !== element.controlId) + break; + currentEndIndex++; + } + const fakeRange = { + startIndex: i - 1, + endIndex: currentEndIndex - 2 + }; + const controlContext = { + range: fakeRange, + elementList + }; + const controlRule = { + isIgnoreDisabledRule: true + }; + if (type === ControlType.TEXT) { + const formatValue = Array.isArray(value) ? value : [{ value }]; + formatElementList(formatValue, { + isHandleFirstElement: false, + editorOptions: this.options + }); + const text = new TextControl(element, this); + this.activeControl = text; + if (value) { + text.setValue(formatValue, controlContext, controlRule); + } else { + text.clearValue(controlContext, controlRule); + } + } else if (type === ControlType.SELECT) { + if (Array.isArray(value)) + continue; + const select = new SelectControl(element, this); + this.activeControl = select; + if (value) { + select.setSelect(value, controlContext, controlRule); + } else { + select.clearSelect(controlContext, controlRule); + } + } else if (type === ControlType.CHECKBOX) { + if (Array.isArray(value)) + continue; + const checkbox = new CheckboxControl(element, this); + this.activeControl = checkbox; + const codes = value ? value.split(",") : []; + checkbox.setSelect(codes, controlContext, controlRule); + } else if (type === ControlType.RADIO) { + if (Array.isArray(value)) + continue; + const radio = new RadioControl(element, this); + this.activeControl = radio; + const codes = value ? [value] : []; + radio.setSelect(codes, controlContext, controlRule); + } else if (type === ControlType.DATE) { + if (Array.isArray(value)) + continue; + const date = new DateControl(element, this); + this.activeControl = date; + if (value) { + date.setSelect(value, controlContext, controlRule); + } else { + date.clearSelect(controlContext, controlRule); + } + } + this.activeControl = null; + let newEndIndex = i; + while (newEndIndex < elementList.length) { + const nextElement = elementList[newEndIndex]; + if (nextElement.controlId !== element.controlId) + break; + newEndIndex++; + } + i = newEndIndex; + } + }; + this.destroyControl(); + const data2 = [ + this.draw.getHeaderElementList(), + this.draw.getOriginalMainElementList(), + this.draw.getFooterElementList() + ]; + for (const elementList of data2) { + setValue(elementList); + } + if (isExistSet) { + this.draw.render({ + isSetCursor: false + }); + } + } + setExtensionById(payload) { + const { id, conceptId, extension } = payload; + if (!id && !conceptId) + return; + const setExtension = (elementList) => { + let i = 0; + while (i < elementList.length) { + const element = elementList[i]; + i++; + if (element.type === ElementType.TABLE) { + const trList = element.trList; + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + setExtension(td.value); + } + } + } + if (!element.control || id && element.controlId !== id || conceptId && element.control.conceptId !== conceptId) { + continue; + } + element.control.extension = extension; + let newEndIndex = i; + while (newEndIndex < elementList.length) { + const nextElement = elementList[newEndIndex]; + if (nextElement.controlId !== element.controlId) + break; + newEndIndex++; + } + i = newEndIndex; + } + }; + const data2 = [ + this.draw.getHeaderElementList(), + this.draw.getOriginalMainElementList(), + this.draw.getFooterElementList() + ]; + for (const elementList of data2) { + setExtension(elementList); + } + } + setPropertiesById(payload) { + const { id, conceptId, properties } = payload; + if (!id && !conceptId) + return; + let isExistUpdate = false; + function setProperties(elementList) { + let i = 0; + while (i < elementList.length) { + const element = elementList[i]; + i++; + if (element.type === ElementType.TABLE) { + const trList = element.trList; + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + setProperties(td.value); + } + } + } + if (!element.control || id && element.controlId !== id || conceptId && element.control.conceptId !== conceptId) { + continue; + } + isExistUpdate = true; + element.control = { + ...element.control, + ...properties, + value: element.control.value + }; + CONTROL_STYLE_ATTR.forEach((key) => { + const controlStyleProperty = properties[key]; + if (controlStyleProperty) { + Reflect.set(element, key, controlStyleProperty); + } + }); + let newEndIndex = i; + while (newEndIndex < elementList.length) { + const nextElement = elementList[newEndIndex]; + if (nextElement.controlId !== element.controlId) + break; + newEndIndex++; + } + i = newEndIndex; + } + } + const pageComponentData = { + header: this.draw.getHeaderElementList(), + main: this.draw.getOriginalMainElementList(), + footer: this.draw.getFooterElementList() + }; + for (const key in pageComponentData) { + const elementList = pageComponentData[key]; + setProperties(elementList); + } + if (!isExistUpdate) + return; + for (const key in pageComponentData) { + const pageComponentKey = key; + const elementList = zipElementList(pageComponentData[pageComponentKey]); + pageComponentData[pageComponentKey] = elementList; + formatElementList(elementList, { + editorOptions: this.options, + isForceCompensation: true + }); + } + this.draw.setEditorData(pageComponentData); + this.draw.render({ + isSetCursor: false + }); + } + getList() { + const controlElementList = []; + function getControlElementList(elementList) { + for (let e = 0; e < elementList.length; e++) { + const element = elementList[e]; + if (element.type === ElementType.TABLE) { + const trList = element.trList; + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const tdElement = td.value; + getControlElementList(tdElement); + } + } + } + if (element.controlId) { + const controlElement = omitObject(element, [ + ...TITLE_CONTEXT_ATTR, + ...LIST_CONTEXT_ATTR + ]); + controlElementList.push(controlElement); + } + } + } + const data2 = [ + this.draw.getHeader().getElementList(), + this.draw.getOriginalMainElementList(), + this.draw.getFooter().getElementList() + ]; + for (const elementList of data2) { + getControlElementList(elementList); + } + return zipElementList(controlElementList, { + extraPickAttrs: ["controlId"] + }); + } + recordBorderInfo(x, y, width, height) { + this.controlBorder.recordBorderInfo(x, y, width, height); + } + drawBorder(ctx) { + this.controlBorder.render(ctx); + } + getPreControlContext() { + if (!this.activeControl) + return null; + const position = this.draw.getPosition(); + const positionContext = position.getPositionContext(); + if (!positionContext) + return null; + const controlElement = this.activeControl.getElement(); + function getPreContext(elementList2, start) { + for (let e = start; e > 0; e--) { + const element = elementList2[e]; + if (element.type === ElementType.TABLE) { + const trList = element.trList || []; + for (let r = trList.length - 1; r >= 0; r--) { + const tr = trList[r]; + const tdList = tr.tdList; + for (let d = tdList.length - 1; d >= 0; d--) { + const td = tdList[d]; + const context2 = getPreContext(td.value, td.value.length - 1); + if (context2) { + return { + positionContext: { + isTable: true, + index: e, + trIndex: r, + tdIndex: d, + tdId: td.id, + trId: tr.id, + tableId: element.id + }, + nextIndex: context2.nextIndex + }; + } + } + } + } + if (!element.controlId || element.controlId === controlElement.controlId) { + continue; + } + let nextIndex = e; + while (nextIndex > 0) { + const nextElement = elementList2[nextIndex]; + if (nextElement.controlComponent === ControlComponent.VALUE || nextElement.controlComponent === ControlComponent.PREFIX) { + break; + } + nextIndex--; + } + return { + positionContext: { + isTable: false + }, + nextIndex + }; + } + return null; + } + const { startIndex } = this.range.getRange(); + const elementList = this.getElementList(); + const context = getPreContext(elementList, startIndex); + if (context) { + return { + positionContext: positionContext.isTable ? positionContext : context.positionContext, + nextIndex: context.nextIndex + }; + } + if (controlElement.tableId) { + const originalElementList = this.draw.getOriginalElementList(); + const { index: index2, trIndex, tdIndex } = positionContext; + const trList = originalElementList[index2].trList; + for (let r = trIndex; r >= 0; r--) { + const tr = trList[r]; + const tdList = tr.tdList; + for (let d = tdList.length - 1; d >= 0; d--) { + if (trIndex === r && d >= tdIndex) + continue; + const td = tdList[d]; + const context3 = getPreContext(td.value, td.value.length - 1); + if (context3) { + return { + positionContext: { + isTable: true, + index: positionContext.index, + trIndex: r, + tdIndex: d, + tdId: td.id, + trId: tr.id, + tableId: controlElement.tableId + }, + nextIndex: context3.nextIndex + }; + } + } + } + const context2 = getPreContext(originalElementList, index2 - 1); + if (context2) { + return { + positionContext: { + isTable: false + }, + nextIndex: context2.nextIndex + }; + } + } + return null; + } + getNextControlContext() { + if (!this.activeControl) + return null; + const position = this.draw.getPosition(); + const positionContext = position.getPositionContext(); + if (!positionContext) + return null; + const controlElement = this.activeControl.getElement(); + function getNextContext(elementList2, start) { + for (let e = start; e < elementList2.length; e++) { + const element = elementList2[e]; + if (element.type === ElementType.TABLE) { + const trList = element.trList || []; + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + const tdList = tr.tdList; + for (let d = 0; d < tdList.length; d++) { + const td = tdList[d]; + const context2 = getNextContext(td.value, 0); + if (context2) { + return { + positionContext: { + isTable: true, + index: e, + trIndex: r, + tdIndex: d, + tdId: td.id, + trId: tr.id, + tableId: element.id + }, + nextIndex: context2.nextIndex + }; + } + } + } + } + if (!element.controlId || element.controlId === controlElement.controlId) { + continue; + } + return { + positionContext: { + isTable: false + }, + nextIndex: e + }; + } + return null; + } + const { endIndex } = this.range.getRange(); + const elementList = this.getElementList(); + const context = getNextContext(elementList, endIndex); + if (context) { + return { + positionContext: positionContext.isTable ? positionContext : context.positionContext, + nextIndex: context.nextIndex + }; + } + if (controlElement.tableId) { + const originalElementList = this.draw.getOriginalElementList(); + const { index: index2, trIndex, tdIndex } = positionContext; + const trList = originalElementList[index2].trList; + for (let r = trIndex; r < trList.length; r++) { + const tr = trList[r]; + const tdList = tr.tdList; + for (let d = 0; d < tdList.length; d++) { + if (trIndex === r && d <= tdIndex) + continue; + const td = tdList[d]; + const context3 = getNextContext(td.value, 0); + if (context3) { + return { + positionContext: { + isTable: true, + index: positionContext.index, + trIndex: r, + tdIndex: d, + tdId: td.id, + trId: tr.id, + tableId: controlElement.tableId + }, + nextIndex: context3.nextIndex + }; + } + } + } + const context2 = getNextContext(originalElementList, index2 + 1); + if (context2) { + return { + positionContext: { + isTable: false + }, + nextIndex: context2.nextIndex + }; + } + } + return null; + } + initNextControl(option = {}) { + const { direction = MoveDirection.DOWN } = option; + let context = null; + if (direction === MoveDirection.UP) { + context = this.getPreControlContext(); + } else { + context = this.getNextControlContext(); + } + if (!context) + return; + const { nextIndex, positionContext } = context; + const position = this.draw.getPosition(); + position.setPositionContext(positionContext); + this.draw.getRange().replaceRange({ + startIndex: nextIndex, + endIndex: nextIndex + }); + this.draw.render({ + curIndex: nextIndex, + isCompute: false, + isSetCursor: true, + isSubmitHistory: false + }); + const positionList = position.getPositionList(); + this.draw.getCursor().moveCursorToVisible({ + cursorPosition: positionList[nextIndex], + direction + }); + } + setMinWidthControlInfo(option) { + var _a, _b, _c, _d, _e; + const { row, rowElement, controlRealWidth, availableWidth } = option; + if (!((_a = rowElement.control) == null ? void 0 : _a.minWidth)) + return; + const { scale } = this.options; + const controlMinWidth = rowElement.control.minWidth * scale; + let controlFirstElement = null; + if (((_b = rowElement.control) == null ? void 0 : _b.minWidth) && (((_c = rowElement.control) == null ? void 0 : _c.rowFlex) === RowFlex.CENTER || ((_d = rowElement.control) == null ? void 0 : _d.rowFlex) === RowFlex.RIGHT)) { + let controlContentWidth = rowElement.metrics.width; + let controlElementIndex = row.elementList.length - 1; + while (controlElementIndex >= 0) { + const controlRowElement = row.elementList[controlElementIndex]; + controlContentWidth += controlRowElement.metrics.width; + if (((_e = row.elementList[controlElementIndex - 1]) == null ? void 0 : _e.controlComponent) === ControlComponent.PREFIX) { + controlFirstElement = controlRowElement; + break; + } + controlElementIndex--; + } + if (controlFirstElement) { + if (controlContentWidth < controlMinWidth) { + if (rowElement.control.rowFlex === RowFlex.CENTER) { + controlFirstElement.left = (controlMinWidth - controlContentWidth) / 2; + } else if (rowElement.control.rowFlex === RowFlex.RIGHT) { + controlFirstElement.left = controlMinWidth - controlContentWidth - rowElement.metrics.width; + } + } + } + } + const extraWidth = controlMinWidth - controlRealWidth; + if (extraWidth > 0) { + const controlFirstElementLeft = (controlFirstElement == null ? void 0 : controlFirstElement.left) || 0; + const rowRemainingWidth = availableWidth - row.width - rowElement.metrics.width; + const left2 = Math.min(rowRemainingWidth, extraWidth); + rowElement.left = left2 - controlFirstElementLeft; + row.width += left2 - controlFirstElementLeft; + } + } +} +class CheckboxParticle { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + } + setSelect(element) { + const { checkbox } = element; + if (checkbox) { + checkbox.value = !checkbox.value; + } else { + element.checkbox = { + value: true + }; + } + this.draw.render({ + isCompute: false, + isSetCursor: false + }); + } + render(payload) { + const { ctx, x, index: index2, row } = payload; + let { y } = payload; + const { checkbox: { gap, lineWidth, fillStyle, strokeStyle, verticalAlign }, scale } = this.options; + const { metrics, checkbox } = row.elementList[index2]; + if (verticalAlign === VerticalAlign.TOP || verticalAlign === VerticalAlign.MIDDLE) { + let nextIndex = index2 + 1; + let nextElement = null; + while (nextIndex < row.elementList.length) { + nextElement = row.elementList[nextIndex]; + if (nextElement.value !== ZERO && nextElement.value !== NBSP) + break; + nextIndex++; + } + if (nextElement) { + const { metrics: { boundingBoxAscent, boundingBoxDescent } } = nextElement; + const textHeight = boundingBoxAscent + boundingBoxDescent; + if (textHeight > metrics.height) { + if (verticalAlign === VerticalAlign.TOP) { + y -= boundingBoxAscent - metrics.height; + } else if (verticalAlign === VerticalAlign.MIDDLE) { + y -= (textHeight - metrics.height) / 2; + } + } + } + } + const left2 = Math.round(x + gap * scale); + const top = Math.round(y - metrics.height + lineWidth); + const width = metrics.width - gap * 2 * scale; + const height = metrics.height; + ctx.save(); + ctx.beginPath(); + ctx.translate(0.5, 0.5); + if (checkbox == null ? void 0 : checkbox.value) { + ctx.lineWidth = lineWidth; + ctx.strokeStyle = fillStyle; + ctx.rect(left2, top, width, height); + ctx.stroke(); + ctx.beginPath(); + ctx.fillStyle = fillStyle; + ctx.fillRect(left2, top, width, height); + ctx.beginPath(); + ctx.strokeStyle = strokeStyle; + ctx.lineWidth = lineWidth * 2 * scale; + ctx.moveTo(left2 + 2 * scale, top + height / 2); + ctx.lineTo(left2 + width / 2, top + height - 3 * scale); + ctx.lineTo(left2 + width - 2 * scale, top + 3 * scale); + ctx.stroke(); + } else { + ctx.lineWidth = lineWidth; + ctx.rect(left2, top, width, height); + ctx.stroke(); + } + ctx.closePath(); + ctx.restore(); + } +} +class RadioParticle { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + } + setSelect(element) { + const { radio } = element; + if (radio) { + radio.value = !radio.value; + } else { + element.radio = { + value: true + }; + } + this.draw.render({ + isCompute: false, + isSetCursor: false + }); + } + render(payload) { + const { ctx, x, index: index2, row } = payload; + let { y } = payload; + const { radio: { gap, lineWidth, fillStyle, strokeStyle, verticalAlign }, scale } = this.options; + const { metrics, radio } = row.elementList[index2]; + if (verticalAlign === VerticalAlign.TOP || verticalAlign === VerticalAlign.MIDDLE) { + let nextIndex = index2 + 1; + let nextElement = null; + while (nextIndex < row.elementList.length) { + nextElement = row.elementList[nextIndex]; + if (nextElement.value !== ZERO && nextElement.value !== NBSP) + break; + nextIndex++; + } + if (nextElement) { + const { metrics: { boundingBoxAscent, boundingBoxDescent } } = nextElement; + const textHeight = boundingBoxAscent + boundingBoxDescent; + if (textHeight > metrics.height) { + if (verticalAlign === VerticalAlign.TOP) { + y -= boundingBoxAscent - metrics.height; + } else if (verticalAlign === VerticalAlign.MIDDLE) { + y -= (textHeight - metrics.height) / 2; + } + } + } + } + const left2 = Math.round(x + gap * scale); + const top = Math.round(y - metrics.height + lineWidth); + const width = metrics.width - gap * 2 * scale; + const height = metrics.height; + ctx.save(); + ctx.beginPath(); + ctx.translate(0.5, 0.5); + ctx.strokeStyle = (radio == null ? void 0 : radio.value) ? fillStyle : strokeStyle; + ctx.lineWidth = lineWidth; + ctx.arc(left2 + width / 2, top + height / 2, width / 2, 0, Math.PI * 2); + ctx.stroke(); + if (radio == null ? void 0 : radio.value) { + ctx.beginPath(); + ctx.fillStyle = fillStyle; + ctx.arc(left2 + width / 2, top + height / 2, width / 3, 0, Math.PI * 2); + ctx.fill(); + } + ctx.closePath(); + ctx.restore(); + } +} +const encodedJs$2 = "KGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2NvbnN0IGE9Ilx1MjAwQiIsdT1gCmA7ZnVuY3Rpb24gZihpKXtsZXQgcz0iIixuPTA7Zm9yKDtuPGkubGVuZ3RoOyl7Y29uc3QgZT1pW25dO2lmKGUudHlwZT09PSJ0YWJsZSIpe2lmKGUudHJMaXN0KWZvcihsZXQgbD0wO2w8ZS50ckxpc3QubGVuZ3RoO2wrKyl7Y29uc3Qgbz1lLnRyTGlzdFtsXTtmb3IobGV0IHQ9MDt0PG8udGRMaXN0Lmxlbmd0aDt0Kyspe2NvbnN0IHI9by50ZExpc3RbdF07cys9ZihyLnZhbHVlKX19fWVsc2UgaWYoZS50eXBlPT09Imh5cGVybGluayIpe2NvbnN0IGw9ZS5oeXBlcmxpbmtJZCxvPVtdO2Zvcig7bjxpLmxlbmd0aDspe2NvbnN0IHQ9aVtuXTtpZihsIT09dC5oeXBlcmxpbmtJZCl7bi0tO2JyZWFrfWRlbGV0ZSB0LnR5cGUsby5wdXNoKHQpLG4rK31zKz1mKG8pfWVsc2UgaWYoZS5jb250cm9sSWQpe2NvbnN0IGw9ZS5jb250cm9sSWQsbz1bXTtmb3IoO248aS5sZW5ndGg7KXtjb25zdCB0PWlbbl07aWYobCE9PXQuY29udHJvbElkKXtuLS07YnJlYWt9dC5jb250cm9sQ29tcG9uZW50PT09InZhbHVlIiYmKGRlbGV0ZSB0LmNvbnRyb2xJZCxvLnB1c2godCkpLG4rK31zKz1mKG8pfWVsc2UoIWUudHlwZXx8ZS50eXBlPT09InRleHQiKSYmKHMrPWUudmFsdWUpO24rK31yZXR1cm4gc31mdW5jdGlvbiBoKGkpe2NvbnN0IHM9W10sbj0vWzAtOV0vLGU9L1tBLVphLXpdLyxsPS9ccy87bGV0IG89ITEsdD0hMSxyPSIiO2Z1bmN0aW9uIHAoKXtyJiYocy5wdXNoKHIpLHI9IiIpfWZvcihjb25zdCBjIG9mIGkpZS50ZXN0KGMpPyhvfHxwKCkscis9YyxvPSEwLHQ9ITEpOm4udGVzdChjKT8odHx8cCgpLHIrPWMsbz0hMSx0PSEwKToocCgpLG89ITEsdD0hMSxsLnRlc3QoYyl8fHMucHVzaChjKSk7cmV0dXJuIHAoKSxzfW9ubWVzc2FnZT1pPT57Y29uc3Qgcz1pLmRhdGEsZT1mKHMpLnJlcGxhY2UobmV3IFJlZ0V4cChgXiR7YX1gKSwiIikucmVwbGFjZShuZXcgUmVnRXhwKGEsImciKSx1KSxsPWgoZSk7cG9zdE1lc3NhZ2UobC5sZW5ndGgpfX0pKCk7Cg=="; +const blob$2 = typeof window !== "undefined" && window.Blob && new Blob([atob(encodedJs$2)], { type: "text/javascript;charset=utf-8" }); +function WorkerWrapper$2() { + const objURL = blob$2 && (window.URL || window.webkitURL).createObjectURL(blob$2); + try { + return objURL ? new Worker(objURL, {}) : new Worker("data:application/javascript;base64," + encodedJs$2, { type: "module" }); + } finally { + objURL && (window.URL || window.webkitURL).revokeObjectURL(objURL); + } +} +const encodedJs$1 = "KGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2NvbnN0IHU9e2ZpcnN0OjEsc2Vjb25kOjIsdGhpcmQ6Myxmb3VydGg6NCxmaWZ0aDo1LHNpeHRoOjZ9LGg9WyJ0ZXh0IiwiaHlwZXJsaW5rIiwic3Vic2NyaXB0Iiwic3VwZXJzY3JpcHQiLCJjb250cm9sIiwiZGF0ZSJdLGQ9Ilx1MjAwQiI7ZnVuY3Rpb24gZihuKXtyZXR1cm4hbi50eXBlfHxoLmluY2x1ZGVzKG4udHlwZSl9ZnVuY3Rpb24gRShuKXtjb25zdHtlbGVtZW50TGlzdDpvLHBvc2l0aW9uTGlzdDpnfT1uLHA9W107bGV0IHM9MDtmb3IoO3M8by5sZW5ndGg7KXtjb25zdCBlPW9bc107aWYoZS50aXRsZUlkKXtjb25zdCB0PWUudGl0bGVJZCxsPWUubGV2ZWwsaT17dHlwZToidGl0bGUiLHZhbHVlOiIiLGxldmVsOmwsdGl0bGVJZDp0LHBhZ2VObzpnW3NdLnBhZ2VOb30sYT1bXTtmb3IoO3M8by5sZW5ndGg7KXtjb25zdCBjPW9bc107aWYodCE9PWMudGl0bGVJZCl7cy0tO2JyZWFrfWEucHVzaChjKSxzKyt9aS52YWx1ZT1hLmZpbHRlcihjPT5mKGMpKS5tYXAoYz0+Yy52YWx1ZSkuam9pbigiIikucmVwbGFjZShuZXcgUmVnRXhwKGQsImciKSwiIikscC5wdXNoKGkpfXMrK31pZighcC5sZW5ndGgpcmV0dXJuIG51bGw7Y29uc3Qgdj0oZSx0KT0+e2NvbnN0IGw9dC5zdWJDYXRhbG9nW3Quc3ViQ2F0YWxvZy5sZW5ndGgtMV0saT11W2w9PW51bGw/dm9pZCAwOmwubGV2ZWxdLGE9dVtlLmxldmVsXTtsJiZhPmk/dihlLGwpOnQuc3ViQ2F0YWxvZy5wdXNoKHtpZDplLnRpdGxlSWQsbmFtZTplLnZhbHVlLGxldmVsOmUubGV2ZWwscGFnZU5vOmUucGFnZU5vLHN1YkNhdGFsb2c6W119KX0scj1bXTtmb3IobGV0IGU9MDtlPHAubGVuZ3RoO2UrKyl7Y29uc3QgdD1wW2VdLGw9cltyLmxlbmd0aC0xXSxpPXVbbD09bnVsbD92b2lkIDA6bC5sZXZlbF0sYT11W3QubGV2ZWxdO2wmJmE+aT92KHQsbCk6ci5wdXNoKHtpZDp0LnRpdGxlSWQsbmFtZTp0LnZhbHVlLGxldmVsOnQubGV2ZWwscGFnZU5vOnQucGFnZU5vLHN1YkNhdGFsb2c6W119KX1yZXR1cm4gcn1vbm1lc3NhZ2U9bj0+e2NvbnN0IG89bi5kYXRhLGc9RShvKTtwb3N0TWVzc2FnZShnKX19KSgpOwo="; +const blob$1 = typeof window !== "undefined" && window.Blob && new Blob([atob(encodedJs$1)], { type: "text/javascript;charset=utf-8" }); +function WorkerWrapper$1() { + const objURL = blob$1 && (window.URL || window.webkitURL).createObjectURL(blob$1); + try { + return objURL ? new Worker(objURL, {}) : new Worker("data:application/javascript;base64," + encodedJs$1, { type: "module" }); + } finally { + objURL && (window.URL || window.webkitURL).revokeObjectURL(objURL); + } +} +const encodedJs = "KGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2Z1bmN0aW9uIHUoZSl7Y29uc3QgdD1bXTtmb3IoY29uc3QgcyBvZiBlKXtpZihzLnR5cGU9PT0idGFibGUiKXtjb25zdCBvPXMudHJMaXN0O2ZvcihsZXQgbj0wO248by5sZW5ndGg7bisrKXtjb25zdCBjPW9bbl07Zm9yKGxldCByPTA7cjxjLnRkTGlzdC5sZW5ndGg7cisrKXtjb25zdCBpPWMudGRMaXN0W3JdO3QucHVzaCguLi51KGkudmFsdWUpKX19fWlmKCEhcy5ncm91cElkcylmb3IoY29uc3QgbyBvZiBzLmdyb3VwSWRzKXQuaW5jbHVkZXMobyl8fHQucHVzaChvKX1yZXR1cm4gdH1vbm1lc3NhZ2U9ZT0+e2NvbnN0IHQ9ZS5kYXRhLHM9dSh0KTtwb3N0TWVzc2FnZShzKX19KSgpOwo="; +const blob = typeof window !== "undefined" && window.Blob && new Blob([atob(encodedJs)], { type: "text/javascript;charset=utf-8" }); +function WorkerWrapper() { + const objURL = blob && (window.URL || window.webkitURL).createObjectURL(blob); + try { + return objURL ? new Worker(objURL, {}) : new Worker("data:application/javascript;base64," + encodedJs, { type: "module" }); + } finally { + objURL && (window.URL || window.webkitURL).revokeObjectURL(objURL); + } +} +class WorkerManager { + constructor(draw) { + this.draw = draw; + this.wordCountWorker = new WorkerWrapper$2(); + this.catalogWorker = new WorkerWrapper$1(); + this.groupWorker = new WorkerWrapper(); + } + getWordCount() { + return new Promise((resolve, reject) => { + this.wordCountWorker.onmessage = (evt) => { + resolve(evt.data); + }; + this.wordCountWorker.onerror = (evt) => { + reject(evt); + }; + const elementList = this.draw.getOriginalMainElementList(); + this.wordCountWorker.postMessage(elementList); + }); + } + getCatalog() { + return new Promise((resolve, reject) => { + this.catalogWorker.onmessage = (evt) => { + resolve(evt.data); + }; + this.catalogWorker.onerror = (evt) => { + reject(evt); + }; + const elementList = this.draw.getOriginalMainElementList(); + const positionList = this.draw.getPosition().getOriginalMainPositionList(); + this.catalogWorker.postMessage({ + elementList, + positionList + }); + }); + } + getGroupIds() { + return new Promise((resolve, reject) => { + this.groupWorker.onmessage = (evt) => { + resolve(evt.data); + }; + this.groupWorker.onerror = (evt) => { + reject(evt); + }; + const elementList = this.draw.getOriginalMainElementList(); + this.groupWorker.postMessage(elementList); + }); + } +} +class Previewer { + constructor(draw) { + this._keydown = () => { + if (this.resizerSelection.style.display === "block") { + this.clearResizer(); + document.removeEventListener("keydown", this._keydown); + } + }; + this.container = draw.getContainer(); + this.canvas = draw.getPage(); + this.draw = draw; + this.options = draw.getOptions(); + this.curElement = null; + this.curElementSrc = ""; + this.previewerDrawOption = {}; + this.curPosition = null; + const { resizerSelection, resizerHandleList, resizerImageContainer, resizerImage, resizerSize } = this._createResizerDom(); + this.resizerSelection = resizerSelection; + this.resizerHandleList = resizerHandleList; + this.resizerImageContainer = resizerImageContainer; + this.resizerImage = resizerImage; + this.resizerSize = resizerSize; + this.width = 0; + this.height = 0; + this.mousedownX = 0; + this.mousedownY = 0; + this.curHandleIndex = 0; + this.previewerContainer = null; + this.previewerImage = null; + } + _getElementPosition(element, position = null) { + var _a; + let x = 0; + let y = 0; + const height = this.draw.getHeight(); + const pageGap = this.draw.getPageGap(); + const pageNo = (_a = position == null ? void 0 : position.pageNo) != null ? _a : this.draw.getPageNo(); + const preY = pageNo * (height + pageGap); + if (element.imgFloatPosition) { + x = element.imgFloatPosition.x; + y = element.imgFloatPosition.y + preY; + } else if (position) { + const { coordinate: { leftTop: [left2, top] }, ascent } = position; + x = left2; + y = top + preY + ascent; + } + return { x, y }; + } + _createResizerDom() { + const { scale } = this.options; + const resizerSelection = document.createElement("div"); + resizerSelection.classList.add(`${EDITOR_PREFIX}-resizer-selection`); + resizerSelection.style.display = "none"; + resizerSelection.style.borderColor = this.options.resizerColor; + resizerSelection.style.borderWidth = `${scale}px`; + const resizerHandleList = []; + for (let i = 0; i < 8; i++) { + const handleDom = document.createElement("div"); + handleDom.style.background = this.options.resizerColor; + handleDom.classList.add(`resizer-handle`); + handleDom.classList.add(`handle-${i}`); + handleDom.setAttribute("data-index", String(i)); + handleDom.onmousedown = this._mousedown.bind(this); + resizerSelection.append(handleDom); + resizerHandleList.push(handleDom); + } + this.container.append(resizerSelection); + const resizerSizeView = document.createElement("div"); + resizerSizeView.classList.add(`${EDITOR_PREFIX}-resizer-size-view`); + const resizerSize = document.createElement("span"); + resizerSizeView.append(resizerSize); + resizerSelection.append(resizerSizeView); + const resizerImageContainer = document.createElement("div"); + resizerImageContainer.classList.add(`${EDITOR_PREFIX}-resizer-image`); + resizerImageContainer.style.display = "none"; + const resizerImage = document.createElement("img"); + resizerImageContainer.append(resizerImage); + this.container.append(resizerImageContainer); + return { + resizerSelection, + resizerHandleList, + resizerImageContainer, + resizerImage, + resizerSize + }; + } + _mousedown(evt) { + this.canvas = this.draw.getPage(); + if (!this.curElement) + return; + const { scale } = this.options; + this.mousedownX = evt.x; + this.mousedownY = evt.y; + const target = evt.target; + this.curHandleIndex = Number(target.dataset.index); + const cursor = window.getComputedStyle(target).cursor; + document.body.style.cursor = cursor; + this.canvas.style.cursor = cursor; + this.resizerImage.src = this.curElementSrc; + this.resizerImageContainer.style.display = "block"; + const { x: resizerLeft, y: resizerTop } = this._getElementPosition(this.curElement, this.curPosition); + this.resizerImageContainer.style.left = `${resizerLeft}px`; + this.resizerImageContainer.style.top = `${resizerTop}px`; + this.resizerImage.style.width = `${this.curElement.width * scale}px`; + this.resizerImage.style.height = `${this.curElement.height * scale}px`; + const mousemoveFn = this._mousemove.bind(this); + document.addEventListener("mousemove", mousemoveFn); + document.addEventListener("mouseup", () => { + var _a; + if (this.curElement && !this.previewerDrawOption.dragDisable) { + this.curElement.width = this.width; + this.curElement.height = this.height; + this.draw.render({ + isSetCursor: true, + curIndex: (_a = this.curPosition) == null ? void 0 : _a.index + }); + } + this.resizerImageContainer.style.display = "none"; + document.removeEventListener("mousemove", mousemoveFn); + document.body.style.cursor = ""; + this.canvas.style.cursor = "text"; + }, { + once: true + }); + evt.preventDefault(); + } + _mousemove(evt) { + if (!this.curElement || this.previewerDrawOption.dragDisable) + return; + const { scale } = this.options; + let dx = 0; + let dy = 0; + switch (this.curHandleIndex) { + case 0: + { + const offsetX = this.mousedownX - evt.x; + const offsetY = this.mousedownY - evt.y; + dx = Math.cbrt(offsetX ** 3 + offsetY ** 3); + dy = this.curElement.height * dx / this.curElement.width; + } + break; + case 1: + dy = this.mousedownY - evt.y; + break; + case 2: + { + const offsetX = evt.x - this.mousedownX; + const offsetY = this.mousedownY - evt.y; + dx = Math.cbrt(offsetX ** 3 + offsetY ** 3); + dy = this.curElement.height * dx / this.curElement.width; + } + break; + case 4: + { + const offsetX = evt.x - this.mousedownX; + const offsetY = evt.y - this.mousedownY; + dx = Math.cbrt(offsetX ** 3 + offsetY ** 3); + dy = this.curElement.height * dx / this.curElement.width; + } + break; + case 3: + dx = evt.x - this.mousedownX; + break; + case 5: + dy = evt.y - this.mousedownY; + break; + case 6: + { + const offsetX = this.mousedownX - evt.x; + const offsetY = evt.y - this.mousedownY; + dx = Math.cbrt(offsetX ** 3 + offsetY ** 3); + dy = this.curElement.height * dx / this.curElement.width; + } + break; + case 7: + dx = this.mousedownX - evt.x; + break; + } + const dw = this.curElement.width + dx / scale; + const dh = this.curElement.height + dy / scale; + if (dw <= 0 || dh <= 0) + return; + this.width = dw; + this.height = dh; + const elementWidth = dw * scale; + const elementHeight = dh * scale; + this.resizerImage.style.width = `${elementWidth}px`; + this.resizerImage.style.height = `${elementHeight}px`; + this._updateResizerRect(elementWidth, elementHeight); + this._updateResizerSizeView(elementWidth, elementHeight); + evt.preventDefault(); + } + _drawPreviewer() { + const previewerContainer = document.createElement("div"); + previewerContainer.classList.add(`${EDITOR_PREFIX}-image-previewer`); + const closeBtn = document.createElement("i"); + closeBtn.classList.add("image-close"); + closeBtn.onclick = () => { + this._clearPreviewer(); + }; + previewerContainer.append(closeBtn); + const imgContainer = document.createElement("div"); + imgContainer.classList.add(`${EDITOR_PREFIX}-image-container`); + const img = document.createElement("img"); + img.src = this.curElementSrc; + img.draggable = false; + imgContainer.append(img); + this.previewerImage = img; + previewerContainer.append(imgContainer); + let x = 0; + let y = 0; + let scaleSize = 1; + let rotateSize = 0; + const menuContainer = document.createElement("div"); + menuContainer.classList.add(`${EDITOR_PREFIX}-image-menu`); + const zoomIn = document.createElement("i"); + zoomIn.classList.add("zoom-in"); + zoomIn.onclick = () => { + scaleSize += 0.1; + this._setPreviewerTransform(scaleSize, rotateSize, x, y); + }; + menuContainer.append(zoomIn); + const zoomOut = document.createElement("i"); + zoomOut.onclick = () => { + if (scaleSize - 0.1 <= 0.1) + return; + scaleSize -= 0.1; + this._setPreviewerTransform(scaleSize, rotateSize, x, y); + }; + zoomOut.classList.add("zoom-out"); + menuContainer.append(zoomOut); + const rotate = document.createElement("i"); + rotate.classList.add("rotate"); + rotate.onclick = () => { + rotateSize += 1; + this._setPreviewerTransform(scaleSize, rotateSize, x, y); + }; + menuContainer.append(rotate); + const originalSize = document.createElement("i"); + originalSize.classList.add("original-size"); + originalSize.onclick = () => { + x = 0; + y = 0; + scaleSize = 1; + rotateSize = 0; + this._setPreviewerTransform(scaleSize, rotateSize, x, y); + }; + menuContainer.append(originalSize); + const imageDownload = document.createElement("i"); + imageDownload.classList.add("image-download"); + imageDownload.onclick = () => { + var _a; + const { mime } = this.previewerDrawOption; + downloadFile(img.src, `${(_a = this.curElement) == null ? void 0 : _a.id}.${mime || "png"}`); + }; + menuContainer.append(imageDownload); + previewerContainer.append(menuContainer); + this.previewerContainer = previewerContainer; + document.body.append(previewerContainer); + let startX = 0; + let startY = 0; + let isAllowDrag = false; + img.onmousedown = (evt) => { + isAllowDrag = true; + startX = evt.x; + startY = evt.y; + previewerContainer.style.cursor = "move"; + }; + previewerContainer.onmousemove = (evt) => { + if (!isAllowDrag) + return; + x += evt.x - startX; + y += evt.y - startY; + startX = evt.x; + startY = evt.y; + this._setPreviewerTransform(scaleSize, rotateSize, x, y); + }; + previewerContainer.onmouseup = () => { + isAllowDrag = false; + previewerContainer.style.cursor = "auto"; + }; + previewerContainer.onwheel = (evt) => { + evt.preventDefault(); + evt.stopPropagation(); + if (evt.deltaY < 0) { + scaleSize += 0.1; + } else { + if (scaleSize - 0.1 <= 0.1) + return; + scaleSize -= 0.1; + } + this._setPreviewerTransform(scaleSize, rotateSize, x, y); + }; + } + _setPreviewerTransform(scale, rotate, x, y) { + if (!this.previewerImage) + return; + this.previewerImage.style.left = `${x}px`; + this.previewerImage.style.top = `${y}px`; + this.previewerImage.style.transform = `scale(${scale}) rotate(${rotate * 90}deg)`; + } + _clearPreviewer() { + var _a; + (_a = this.previewerContainer) == null ? void 0 : _a.remove(); + this.previewerContainer = null; + document.body.style.overflow = "auto"; + } + _updateResizerRect(width, height) { + const { resizerSize: handleSize, scale } = this.options; + this.resizerSelection.style.width = `${width}px`; + this.resizerSelection.style.height = `${height}px`; + for (let i = 0; i < 8; i++) { + const left2 = i === 0 || i === 6 || i === 7 ? -handleSize : i === 1 || i === 5 ? width / 2 : width - handleSize; + const top = i === 0 || i === 1 || i === 2 ? -handleSize : i === 3 || i === 7 ? height / 2 - handleSize : height - handleSize; + this.resizerHandleList[i].style.transform = `scale(${scale})`; + this.resizerHandleList[i].style.left = `${left2}px`; + this.resizerHandleList[i].style.top = `${top}px`; + } + } + _updateResizerSizeView(width, height) { + this.resizerSize.innerText = `${Math.round(width)} \xD7 ${Math.round(height)}`; + } + render() { + this._drawPreviewer(); + document.body.style.overflow = "hidden"; + } + drawResizer(element, position = null, options = {}) { + this.previewerDrawOption = options; + this.curElementSrc = element[options.srcKey || "value"] || ""; + this.updateResizer(element, position); + document.addEventListener("keydown", this._keydown); + } + updateResizer(element, position = null) { + const { scale } = this.options; + const elementWidth = element.width * scale; + const elementHeight = element.height * scale; + this._updateResizerSizeView(elementWidth, elementHeight); + const { x: resizerLeft, y: resizerTop } = this._getElementPosition(element, position); + this.resizerSelection.style.left = `${resizerLeft}px`; + this.resizerSelection.style.top = `${resizerTop}px`; + this.resizerSelection.style.borderWidth = `${scale}px`; + this._updateResizerRect(elementWidth, elementHeight); + this.resizerSelection.style.display = "block"; + this.curElement = element; + this.curPosition = position; + this.width = elementWidth; + this.height = elementHeight; + } + clearResizer() { + this.resizerSelection.style.display = "none"; + document.removeEventListener("keydown", this._keydown); + } +} +class DateParticle { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + this.range = draw.getRange(); + this.datePicker = new DatePicker(draw, { + onSubmit: this._setValue.bind(this) + }); + } + _setValue(date) { + if (!date) + return; + const range = this.getDateElementRange(); + if (!range) + return; + const [leftIndex, rightIndex] = range; + const elementList = this.draw.getElementList(); + const startElement = elementList[leftIndex + 1]; + this.draw.spliceElementList(elementList, leftIndex + 1, rightIndex - leftIndex); + this.range.setRange(leftIndex, leftIndex); + const dateElement = { + type: ElementType.DATE, + value: "", + dateFormat: startElement.dateFormat, + valueList: [ + { + value: date + } + ] + }; + formatElementContext(elementList, [dateElement], leftIndex, { + editorOptions: this.options + }); + this.draw.insertElementList([dateElement]); + } + getDateElementRange() { + let leftIndex = -1; + let rightIndex = -1; + const { startIndex, endIndex } = this.range.getRange(); + if (!~startIndex && !~endIndex) + return null; + const elementList = this.draw.getElementList(); + const startElement = elementList[startIndex]; + if (startElement.type !== ElementType.DATE) + return null; + let preIndex = startIndex; + while (preIndex >= 0) { + const preElement = elementList[preIndex]; + if (preElement.dateId !== startElement.dateId) { + leftIndex = preIndex; + break; + } + preIndex--; + } + let nextIndex = startIndex + 1; + while (nextIndex < elementList.length) { + const nextElement = elementList[nextIndex]; + if (nextElement.dateId !== startElement.dateId) { + rightIndex = nextIndex - 1; + break; + } + nextIndex++; + } + if (nextIndex === elementList.length) { + rightIndex = nextIndex - 1; + } + if (!~leftIndex || !~rightIndex) + return null; + return [leftIndex, rightIndex]; + } + clearDatePicker() { + this.datePicker.dispose(); + } + renderDatePicker(element, position) { + const elementList = this.draw.getElementList(); + const range = this.getDateElementRange(); + const value = range ? elementList.slice(range[0] + 1, range[1] + 1).map((el) => el.value).join("") : ""; + this.datePicker.render({ + value, + position, + dateFormat: element.dateFormat + }); + } +} +var BlockType; +(function(BlockType2) { + BlockType2["IFRAME"] = "iframe"; + BlockType2["VIDEO"] = "video"; +})(BlockType || (BlockType = {})); +const _IFrameBlock = class { + constructor(element) { + this.element = element; + } + _defineIframeProperties(iframeWindow) { + Object.defineProperties(iframeWindow, { + parent: { + get: () => null + }, + __POWERED_BY_CANVAS_EDITOR__: { + get: () => true + } + }); + } + render(blockItemContainer) { + var _a, _b; + const block = this.element.block; + const iframe = document.createElement("iframe"); + iframe.setAttribute("data-id", this.element.id); + iframe.sandbox.add(..._IFrameBlock.sandbox); + iframe.style.border = "none"; + iframe.style.width = "100%"; + iframe.style.height = "100%"; + if ((_a = block.iframeBlock) == null ? void 0 : _a.src) { + iframe.src = block.iframeBlock.src; + } else if ((_b = block.iframeBlock) == null ? void 0 : _b.srcdoc) { + iframe.srcdoc = block.iframeBlock.srcdoc; + } + blockItemContainer.append(iframe); + this._defineIframeProperties(iframe.contentWindow); + } +}; +let IFrameBlock = _IFrameBlock; +IFrameBlock.sandbox = ["allow-scripts", "allow-same-origin"]; +class VideoBlock { + constructor(element) { + this.element = element; + } + render(blockItemContainer) { + var _a; + const block = this.element.block; + const video = document.createElement("video"); + video.style.width = "100%"; + video.style.height = "100%"; + video.style.objectFit = "contain"; + video.src = ((_a = block.videoBlock) == null ? void 0 : _a.src) || ""; + video.controls = true; + video.crossOrigin = "anonymous"; + blockItemContainer.append(video); + } +} +class BaseBlock { + constructor(blockParticle, element) { + this.draw = blockParticle.getDraw(); + this.blockContainer = blockParticle.getBlockContainer(); + this.element = element; + this.block = null; + this.blockItem = this._createBlockItem(); + this.blockContainer.append(this.blockItem); + } + getBlockElement() { + return this.element; + } + _createBlockItem() { + const blockItem = document.createElement("div"); + blockItem.classList.add(`${EDITOR_PREFIX}-block-item`); + return blockItem; + } + render() { + const block = this.element.block; + if (block.type === BlockType.IFRAME) { + this.block = new IFrameBlock(this.element); + this.block.render(this.blockItem); + } else if (block.type === BlockType.VIDEO) { + this.block = new VideoBlock(this.element); + this.block.render(this.blockItem); + } + } + setClientRects(pageNo, x, y) { + const height = this.draw.getHeight(); + const pageGap = this.draw.getPageGap(); + const preY = pageNo * (height + pageGap); + const { metrics } = this.element; + this.blockItem.style.width = `${metrics.width}px`; + this.blockItem.style.height = `${metrics.height}px`; + this.blockItem.style.left = `${x}px`; + this.blockItem.style.top = `${preY + y}px`; + } + remove() { + this.blockItem.remove(); + } +} +class BlockParticle { + constructor(draw) { + this.draw = draw; + this.container = draw.getContainer(); + this.blockMap = /* @__PURE__ */ new Map(); + this.blockContainer = this._createBlockContainer(); + this.container.append(this.blockContainer); + } + _createBlockContainer() { + const blockContainer = document.createElement("div"); + blockContainer.classList.add(`${EDITOR_PREFIX}-block-container`); + return blockContainer; + } + getDraw() { + return this.draw; + } + getBlockContainer() { + return this.blockContainer; + } + render(pageNo, element, x, y) { + const id = element.id; + const cacheBlock = this.blockMap.get(id); + if (cacheBlock) { + cacheBlock.setClientRects(pageNo, x, y); + } else { + const newBlock = new BaseBlock(this, element); + newBlock.render(); + newBlock.setClientRects(pageNo, x, y); + this.blockMap.set(id, newBlock); + } + } + clear() { + if (!this.blockMap.size) + return; + const elementList = this.draw.getElementList(); + const blockElementIds = []; + for (let e = 0; e < elementList.length; e++) { + const element = elementList[e]; + if (element.type === ElementType.BLOCK) { + blockElementIds.push(element.id); + } + } + this.blockMap.forEach((block) => { + const id = block.getBlockElement().id; + if (!blockElementIds.includes(id)) { + block.remove(); + this.blockMap.delete(id); + } + }); + } +} +const contextmenu$1 = { + global: { + cut: "\u526A\u5207", + copy: "\u590D\u5236", + paste: "\u7C98\u8D34", + selectAll: "\u5168\u9009", + print: "\u6253\u5370" + }, + control: { + "delete": "\u5220\u9664\u63A7\u4EF6" + }, + hyperlink: { + "delete": "\u5220\u9664\u94FE\u63A5", + cancel: "\u53D6\u6D88\u94FE\u63A5", + edit: "\u7F16\u8F91\u94FE\u63A5" + }, + image: { + change: "\u66F4\u6539\u56FE\u7247", + saveAs: "\u53E6\u5B58\u4E3A\u56FE\u7247", + textWrap: "\u6587\u5B57\u73AF\u7ED5", + textWrapType: { + embed: "\u5D4C\u5165\u578B", + upDown: "\u4E0A\u4E0B\u578B\u73AF\u7ED5", + surround: "\u56DB\u5468\u578B\u73AF\u7ED5", + floatTop: "\u6D6E\u4E8E\u6587\u5B57\u4E0A\u65B9", + floatBottom: "\u886C\u4E8E\u6587\u5B57\u4E0B\u65B9" + } + }, + table: { + insertRowCol: "\u63D2\u5165\u884C\u5217", + insertTopRow: "\u4E0A\u65B9\u63D2\u51651\u884C", + insertBottomRow: "\u4E0B\u65B9\u63D2\u51651\u884C", + insertLeftCol: "\u5DE6\u4FA7\u63D2\u51651\u5217", + insertRightCol: "\u53F3\u4FA7\u63D2\u51651\u5217", + deleteRowCol: "\u5220\u9664\u884C\u5217", + deleteRow: "\u5220\u96641\u884C", + deleteCol: "\u5220\u96641\u5217", + deleteTable: "\u5220\u9664\u6574\u4E2A\u8868\u683C", + mergeCell: "\u5408\u5E76\u5355\u5143\u683C", + mergeCancelCell: "\u53D6\u6D88\u5408\u5E76", + verticalAlign: "\u5782\u76F4\u5BF9\u9F50", + verticalAlignTop: "\u9876\u7AEF\u5BF9\u9F50", + verticalAlignMiddle: "\u5782\u76F4\u5C45\u4E2D", + verticalAlignBottom: "\u5E95\u7AEF\u5BF9\u9F50", + border: "\u8868\u683C\u8FB9\u6846", + borderAll: "\u6240\u6709\u6846\u7EBF", + borderEmpty: "\u65E0\u6846\u7EBF", + borderExternal: "\u5916\u4FA7\u6846\u7EBF", + borderTd: "\u5355\u5143\u683C\u8FB9\u6846", + borderTdTop: "\u4E0A\u8FB9\u6846", + borderTdRight: "\u53F3\u8FB9\u6846", + borderTdBottom: "\u4E0B\u8FB9\u6846", + borderTdLeft: "\u5DE6\u8FB9\u6846", + borderTdForward: "\u6B63\u659C\u7EBF", + borderTdBack: "\u53CD\u659C\u7EBF" + } +}; +const datePicker$1 = { + now: "\u6B64\u523B", + confirm: "\u786E\u5B9A", + "return": "\u8FD4\u56DE\u65E5\u671F", + timeSelect: "\u65F6\u95F4\u9009\u62E9", + weeks: { + sun: "\u65E5", + mon: "\u4E00", + tue: "\u4E8C", + wed: "\u4E09", + thu: "\u56DB", + fri: "\u4E94", + sat: "\u516D" + }, + year: "\u5E74", + month: "\u6708", + hour: "\u65F6", + minute: "\u5206", + second: "\u79D2" +}; +const frame$1 = { + header: "\u9875\u7709", + footer: "\u9875\u811A" +}; +const pageBreak$1 = { + displayName: "\u5206\u9875\u7B26" +}; +const zone$1 = { + headerTip: "\u53CC\u51FB\u7F16\u8F91\u9875\u7709", + footerTip: "\u53CC\u51FB\u7F16\u8F91\u9875\u811A" +}; +var zhCN = { + contextmenu: contextmenu$1, + datePicker: datePicker$1, + frame: frame$1, + pageBreak: pageBreak$1, + zone: zone$1 +}; +const contextmenu = { + global: { + cut: "Cut", + copy: "Copy", + paste: "Paste", + selectAll: "Select all", + print: "Print" + }, + control: { + "delete": "Delete control" + }, + hyperlink: { + "delete": "Delete hyperlink", + cancel: "Cancel hyperlink", + edit: "Edit hyperlink" + }, + image: { + change: "Change image", + saveAs: "Save as image", + textWrap: "Text wrap", + textWrapType: { + embed: "Embed", + upDown: "Up down", + surround: "Surround", + floatTop: "Float above text", + floatBottom: "Float below text" + } + }, + table: { + insertRowCol: "Insert row col", + insertTopRow: "Insert top 1 row", + insertBottomRow: "Insert bottom 1 row", + insertLeftCol: "Insert left 1 col", + insertRightCol: "Insert right 1 col", + deleteRowCol: "Delete row col", + deleteRow: "Delete 1 row", + deleteCol: "Delete 1 col", + deleteTable: "Delete table", + mergeCell: "Merge cell", + mergeCancelCell: "Cancel merge cell", + verticalAlign: "Vertical align", + verticalAlignTop: "Top", + verticalAlignMiddle: "Middle", + verticalAlignBottom: "Bottom", + border: "Table border", + borderAll: "All", + borderEmpty: "Empty", + borderExternal: "External", + borderTd: "Table cell border", + borderTdTop: "Top", + borderTdRight: "Right", + borderTdBottom: "Bottom", + borderTdLeft: "Left", + borderTdForward: "Forward", + borderTdBack: "Back" + } +}; +const datePicker = { + now: "Now", + confirm: "Confirm", + "return": "Return", + timeSelect: "Time select", + weeks: { + sun: "Sun", + mon: "Mon", + tue: "Tue", + wed: "Wed", + thu: "Thu", + fri: "Fri", + sat: "Sat" + }, + year: " ", + month: " ", + hour: "Hour", + minute: "Minute", + second: "Second" +}; +const frame = { + header: "Header", + footer: "Footer" +}; +const pageBreak = { + displayName: "Page Break" +}; +const zone = { + headerTip: "Double click to edit header", + footerTip: "Double click to edit footer" +}; +var en = { + contextmenu, + datePicker, + frame, + pageBreak, + zone +}; +class I18n { + constructor() { + this.langMap = /* @__PURE__ */ new Map([ + ["zhCN", zhCN], + ["en", en] + ]); + this.currentLocale = "zhCN"; + } + registerLangMap(locale, lang) { + const sourceLang = this.langMap.get(locale); + this.langMap.set(locale, mergeObject(sourceLang || zhCN, lang)); + } + getLocale() { + return this.currentLocale; + } + setLocale(locale) { + this.currentLocale = locale; + } + getLang() { + return this.langMap.get(this.currentLocale) || zhCN; + } + t(path) { + const keyList = path.split("."); + let value = ""; + let item = this.getLang(); + for (let k = 0; k < keyList.length; k++) { + const key = keyList[k]; + const currentValue = Reflect.get(item, key); + if (currentValue) { + value = item = currentValue; + } else { + return ""; + } + } + return value; + } +} +class ImageObserver { + constructor() { + this.promiseList = []; + } + add(payload) { + this.promiseList.push(payload); + } + clearAll() { + this.promiseList = []; + } + allSettled() { + return Promise.allSettled(this.promiseList); + } +} +class ZoneTip { + constructor(draw, zone2) { + this.draw = draw; + this.zone = zone2; + this.i18n = draw.getI18n(); + this.container = draw.getContainer(); + this.pageContainer = draw.getPageContainer(); + const { tipContainer, tipContent } = this._drawZoneTip(); + this.tipContainer = tipContainer; + this.tipContent = tipContent; + this.isDisableMouseMove = true; + this.currentMoveZone = EditorZone.MAIN; + const watchZones = []; + const { header, footer } = draw.getOptions(); + if (!header.disabled) { + watchZones.push(EditorZone.HEADER); + } + if (!footer.disabled) { + watchZones.push(EditorZone.FOOTER); + } + if (watchZones.length) { + this._watchMouseMoveZoneChange(watchZones); + } + } + _watchMouseMoveZoneChange(watchZones) { + this.pageContainer.addEventListener("mousemove", throttle((evt) => { + if (this.isDisableMouseMove || !this.draw.getIsPagingMode()) + return; + if (!evt.offsetY) + return; + if (evt.target instanceof HTMLCanvasElement) { + const mousemoveZone = this.zone.getZoneByY(evt.offsetY); + if (!watchZones.includes(mousemoveZone)) { + this._updateZoneTip(false); + return; + } + this.currentMoveZone = mousemoveZone; + this._updateZoneTip(this.zone.getZone() === EditorZone.MAIN && (mousemoveZone === EditorZone.HEADER || mousemoveZone === EditorZone.FOOTER), evt.x, evt.y); + } else { + this._updateZoneTip(false); + } + }, 250)); + this.pageContainer.addEventListener("mouseenter", () => { + this.isDisableMouseMove = false; + }); + this.pageContainer.addEventListener("mouseleave", () => { + this.isDisableMouseMove = true; + this._updateZoneTip(false); + }); + } + _drawZoneTip() { + const tipContainer = document.createElement("div"); + tipContainer.classList.add(`${EDITOR_PREFIX}-zone-tip`); + const tipContent = document.createElement("span"); + tipContainer.append(tipContent); + this.container.append(tipContainer); + return { + tipContainer, + tipContent + }; + } + _updateZoneTip(visible, left2, top) { + if (visible) { + this.tipContainer.classList.add("show"); + this.tipContainer.style.left = `${left2}px`; + this.tipContainer.style.top = `${top}px`; + this.tipContent.innerText = this.i18n.t(`zone.${this.currentMoveZone === EditorZone.HEADER ? "headerTip" : "footerTip"}`); + } else { + this.tipContainer.classList.remove("show"); + } + } +} +class Zone { + constructor(draw) { + this.INDICATOR_PADDING = 2; + this.INDICATOR_TITLE_TRANSLATE = [20, 5]; + this.draw = draw; + this.i18n = draw.getI18n(); + this.options = draw.getOptions(); + this.container = draw.getContainer(); + this.currentZone = EditorZone.MAIN; + this.indicatorContainer = null; + if (!this.options.zone.tipDisabled) { + new ZoneTip(draw, this); + } + } + isHeaderActive() { + return this.getZone() === EditorZone.HEADER; + } + isMainActive() { + return this.getZone() === EditorZone.MAIN; + } + isFooterActive() { + return this.getZone() === EditorZone.FOOTER; + } + getZone() { + return this.currentZone; + } + setZone(payload) { + const { header, footer } = this.options; + if (!header.editable && payload === EditorZone.HEADER || !footer.editable && payload === EditorZone.FOOTER) { + return; + } + if (this.currentZone === payload) + return; + this.currentZone = payload; + this.draw.getRange().clearRange(); + this.draw.render({ + isSubmitHistory: false, + isSetCursor: false, + isCompute: false + }); + this.drawZoneIndicator(); + nextTick(() => { + const listener = this.draw.getListener(); + if (listener.zoneChange) { + listener.zoneChange(payload); + } + const eventBus = this.draw.getEventBus(); + if (eventBus.isSubscribe("zoneChange")) { + eventBus.emit("zoneChange", payload); + } + }); + } + getZoneByY(y) { + const header = this.draw.getHeader(); + const headerBottomY = header.getHeaderTop() + header.getHeight(); + const footer = this.draw.getFooter(); + const pageHeight = this.draw.getHeight(); + const footerTopY = pageHeight - (footer.getFooterBottom() + footer.getHeight()); + if (y < headerBottomY) { + return EditorZone.HEADER; + } + if (y > footerTopY) { + return EditorZone.FOOTER; + } + return EditorZone.MAIN; + } + drawZoneIndicator() { + this._clearZoneIndicator(); + if (!this.isHeaderActive() && !this.isFooterActive()) + return; + const { scale } = this.options; + const isHeaderActive = this.isHeaderActive(); + const [offsetX, offsetY] = this.INDICATOR_TITLE_TRANSLATE; + const pageList = this.draw.getPageList(); + const margins = this.draw.getMargins(); + const innerWidth = this.draw.getInnerWidth(); + const pageHeight = this.draw.getHeight(); + const pageGap = this.draw.getPageGap(); + const preY = pageHeight + pageGap; + this.indicatorContainer = document.createElement("div"); + this.indicatorContainer.classList.add(`${EDITOR_PREFIX}-zone-indicator`); + const header = this.draw.getHeader(); + const footer = this.draw.getFooter(); + const indicatorHeight = isHeaderActive ? header.getHeight() : footer.getHeight(); + const indicatorTop = isHeaderActive ? header.getHeaderTop() : pageHeight - footer.getFooterBottom() - indicatorHeight; + for (let p = 0; p < pageList.length; p++) { + const startY = preY * p + indicatorTop; + const indicatorLeftX = margins[3] - this.INDICATOR_PADDING; + const indicatorRightX = margins[3] + innerWidth + this.INDICATOR_PADDING; + const indicatorTopY = isHeaderActive ? startY - this.INDICATOR_PADDING : startY + indicatorHeight + this.INDICATOR_PADDING; + const indicatorBottomY = isHeaderActive ? startY + indicatorHeight + this.INDICATOR_PADDING : startY - this.INDICATOR_PADDING; + const indicatorTitle = document.createElement("div"); + indicatorTitle.innerText = this.i18n.t(`frame.${isHeaderActive ? "header" : "footer"}`); + indicatorTitle.style.top = `${indicatorBottomY}px`; + indicatorTitle.style.transform = `translate(${offsetX * scale}px, ${offsetY * scale}px) scale(${scale})`; + this.indicatorContainer.append(indicatorTitle); + const lineTop = document.createElement("span"); + lineTop.classList.add(`${EDITOR_PREFIX}-zone-indicator-border__top`); + lineTop.style.top = `${indicatorTopY}px`; + lineTop.style.width = `${innerWidth}px`; + lineTop.style.marginLeft = `${margins[3]}px`; + this.indicatorContainer.append(lineTop); + const lineLeft = document.createElement("span"); + lineLeft.classList.add(`${EDITOR_PREFIX}-zone-indicator-border__left`); + lineLeft.style.top = `${startY}px`; + lineLeft.style.height = `${indicatorHeight}px`; + lineLeft.style.left = `${indicatorLeftX}px`; + this.indicatorContainer.append(lineLeft); + const lineBottom = document.createElement("span"); + lineBottom.classList.add(`${EDITOR_PREFIX}-zone-indicator-border__bottom`); + lineBottom.style.top = `${indicatorBottomY}px`; + this.indicatorContainer.append(lineBottom); + const lineRight = document.createElement("span"); + lineRight.classList.add(`${EDITOR_PREFIX}-zone-indicator-border__right`); + lineRight.style.top = `${startY}px`; + lineRight.style.height = `${indicatorHeight}px`; + lineRight.style.left = `${indicatorRightX}px`; + this.indicatorContainer.append(lineRight); + } + this.container.append(this.indicatorContainer); + } + _clearZoneIndicator() { + var _a; + (_a = this.indicatorContainer) == null ? void 0 : _a.remove(); + this.indicatorContainer = null; + } +} +class Footer { + constructor(draw, data2) { + this.draw = draw; + this.position = draw.getPosition(); + this.options = draw.getOptions(); + this.elementList = data2 || []; + this.rowList = []; + this.positionList = []; + } + getRowList() { + return this.rowList; + } + setElementList(elementList) { + this.elementList = elementList; + } + getElementList() { + return this.elementList; + } + getPositionList() { + return this.positionList; + } + compute() { + this.recovery(); + this._computeRowList(); + this._computePositionList(); + } + recovery() { + this.rowList = []; + this.positionList = []; + } + _computeRowList() { + const innerWidth = this.draw.getInnerWidth(); + this.rowList = this.draw.computeRowList({ + innerWidth, + elementList: this.elementList + }); + } + _computePositionList() { + const footerBottom = this.getFooterBottom(); + const innerWidth = this.draw.getInnerWidth(); + const margins = this.draw.getMargins(); + const startX = margins[3]; + const pageHeight = this.draw.getHeight(); + const footerHeight = this.getHeight(); + const startY = pageHeight - footerBottom - footerHeight; + this.position.computePageRowPosition({ + positionList: this.positionList, + rowList: this.rowList, + pageNo: 0, + startRowIndex: 0, + startIndex: 0, + startX, + startY, + innerWidth, + zone: EditorZone.FOOTER + }); + } + getFooterBottom() { + const { footer: { bottom, disabled }, scale } = this.options; + if (disabled) + return 0; + return Math.floor(bottom * scale); + } + getMaxHeight() { + const { footer: { maxHeightRadio } } = this.options; + const height = this.draw.getHeight(); + return Math.floor(height * maxHeightRadioMapping[maxHeightRadio]); + } + getHeight() { + const maxHeight = this.getMaxHeight(); + const rowHeight = this.getRowHeight(); + return rowHeight > maxHeight ? maxHeight : rowHeight; + } + getRowHeight() { + return this.rowList.reduce((pre, cur) => pre + cur.height, 0); + } + getExtraHeight() { + const margins = this.draw.getMargins(); + const footerHeight = this.getHeight(); + const footerBottom = this.getFooterBottom(); + const extraHeight = footerBottom + footerHeight - margins[2]; + return extraHeight <= 0 ? 0 : extraHeight; + } + render(ctx, pageNo) { + ctx.globalAlpha = 1; + const innerWidth = this.draw.getInnerWidth(); + const maxHeight = this.getMaxHeight(); + const rowList = []; + let curRowHeight = 0; + for (let r = 0; r < this.rowList.length; r++) { + const row = this.rowList[r]; + if (curRowHeight + row.height > maxHeight) { + break; + } + rowList.push(row); + curRowHeight += row.height; + } + this.draw.drawRow(ctx, { + elementList: this.elementList, + positionList: this.positionList, + rowList, + pageNo, + startIndex: 0, + innerWidth, + zone: EditorZone.FOOTER + }); + } +} +class ListParticle { + constructor(draw) { + this.UN_COUNT_STYLE_WIDTH = 20; + this.MEASURE_BASE_TEXT = "0"; + this.LIST_GAP = 10; + this.draw = draw; + this.range = draw.getRange(); + this.options = draw.getOptions(); + } + setList(listType, listStyle) { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + const { startIndex, endIndex } = this.range.getRange(); + if (!~startIndex && !~endIndex) + return; + const changeElementList = this.range.getRangeParagraphElementList(); + if (!changeElementList || !changeElementList.length) + return; + const isUnsetList = changeElementList.find((el) => el.listType === listType && el.listStyle === listStyle); + if (isUnsetList || !listType) { + this.unsetList(); + return; + } + const listId = getUUID(); + changeElementList.forEach((el) => { + el.listId = listId; + el.listType = listType; + el.listStyle = listStyle; + }); + const isSetCursor = startIndex === endIndex; + const curIndex = isSetCursor ? endIndex : startIndex; + this.draw.render({ curIndex, isSetCursor }); + } + unsetList() { + var _a; + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + const { startIndex, endIndex } = this.range.getRange(); + if (!~startIndex && !~endIndex) + return; + const changeElementList = (_a = this.range.getRangeParagraphElementList()) == null ? void 0 : _a.filter((el) => el.listId); + if (!changeElementList || !changeElementList.length) + return; + const elementList = this.draw.getElementList(); + const endElement = elementList[endIndex]; + if (endElement.listId) { + let start = endIndex + 1; + while (start < elementList.length) { + const element = elementList[start]; + if (element.value === ZERO && !element.listWrap) + break; + if (element.listId !== endElement.listId) { + this.draw.spliceElementList(elementList, start, 0, { + value: ZERO + }); + break; + } + start++; + } + } + changeElementList.forEach((el) => { + delete el.listId; + delete el.listType; + delete el.listStyle; + delete el.listWrap; + }); + const isSetCursor = startIndex === endIndex; + const curIndex = isSetCursor ? endIndex : startIndex; + this.draw.render({ curIndex, isSetCursor }); + } + computeListStyle(ctx, elementList) { + const listStyleMap = /* @__PURE__ */ new Map(); + let start = 0; + let curListId = elementList[start].listId; + let curElementList = []; + const elementLength = elementList.length; + while (start < elementLength) { + const curElement = elementList[start]; + if (curListId && curListId === curElement.listId) { + curElementList.push(curElement); + } else { + if (curElement.listId && curElement.listId !== curListId) { + if (curElementList.length) { + const width = this.getListStyleWidth(ctx, curElementList); + listStyleMap.set(curListId, width); + } + curListId = curElement.listId; + curElementList = curListId ? [curElement] : []; + } + } + start++; + } + if (curElementList.length) { + const width = this.getListStyleWidth(ctx, curElementList); + listStyleMap.set(curListId, width); + } + return listStyleMap; + } + getListStyleWidth(ctx, listElementList) { + const { scale, checkbox } = this.options; + const startElement = listElementList[0]; + if (startElement.listStyle && startElement.listStyle !== ListStyle.DECIMAL) { + if (startElement.listStyle === ListStyle.CHECKBOX) { + return (checkbox.width + this.LIST_GAP) * scale; + } + return this.UN_COUNT_STYLE_WIDTH * scale; + } + const count = listElementList.reduce((pre, cur) => { + if (cur.value === ZERO) { + pre += 1; + } + return pre; + }, 0); + if (!count) + return 0; + const text = `${this.MEASURE_BASE_TEXT.repeat(String(count).length)}${KeyMap.PERIOD}`; + const textMetrics = ctx.measureText(text); + return Math.ceil((textMetrics.width + this.LIST_GAP) * scale); + } + drawListStyle(ctx, row, position) { + var _a; + const { elementList, offsetX, listIndex, ascent } = row; + const startElement = elementList[0]; + if (startElement.value !== ZERO || startElement.listWrap) + return; + let tabWidth = 0; + const { defaultTabWidth, scale, defaultFont, defaultSize } = this.options; + for (let i = 1; i < elementList.length; i++) { + const element = elementList[i]; + if ((element == null ? void 0 : element.type) !== ElementType.TAB) + break; + tabWidth += defaultTabWidth * scale; + } + const { coordinate: { leftTop: [startX, startY] } } = position; + const x = startX - offsetX + tabWidth; + const y = startY + ascent; + if (startElement.listStyle === ListStyle.CHECKBOX) { + const { width, height, gap } = this.options.checkbox; + const checkboxRowElement = { + ...startElement, + checkbox: { + value: !!((_a = startElement.checkbox) == null ? void 0 : _a.value) + }, + metrics: { + ...startElement.metrics, + width: (width + gap * 2) * scale, + height: height * scale + } + }; + this.draw.getCheckboxParticle().render({ + ctx, + x: x - gap * scale, + y, + index: 0, + row: { + ...row, + elementList: [checkboxRowElement, ...row.elementList] + } + }); + } else { + let text = ""; + if (startElement.listType === ListType.UL) { + text = ulStyleMapping[startElement.listStyle] || ulStyleMapping[UlStyle.DISC]; + } else { + text = `${listIndex + 1}${KeyMap.PERIOD}`; + } + if (!text) + return; + ctx.save(); + ctx.font = `${defaultSize * scale}px ${defaultFont}`; + ctx.fillText(text, x, y); + ctx.restore(); + } + } +} +const _LineBreakParticle = class { + constructor(draw) { + this.options = draw.getOptions(); + } + render(ctx, element, x, y) { + const { scale, lineBreak: { color, lineWidth } } = this.options; + ctx.save(); + ctx.beginPath(); + const top = y - _LineBreakParticle.HEIGHT * scale / 2; + const left2 = x + element.metrics.width; + ctx.translate(left2, top); + ctx.scale(scale, scale); + ctx.strokeStyle = color; + ctx.lineWidth = lineWidth; + ctx.lineCap = "round"; + ctx.lineJoin = "round"; + ctx.beginPath(); + ctx.moveTo(8, 0); + ctx.lineTo(12, 0); + ctx.lineTo(12, 6); + ctx.lineTo(3, 6); + ctx.moveTo(3, 6); + ctx.lineTo(6, 3); + ctx.moveTo(3, 6); + ctx.lineTo(6, 9); + ctx.stroke(); + ctx.closePath(); + ctx.restore(); + } +}; +let LineBreakParticle = _LineBreakParticle; +LineBreakParticle.WIDTH = 12; +LineBreakParticle.HEIGHT = 9; +LineBreakParticle.GAP = 3; +class Placeholder { + constructor(draw) { + this.draw = draw; + this.position = draw.getPosition(); + this.options = draw.getOptions(); + this.elementList = []; + this.rowList = []; + this.positionList = []; + } + _recovery() { + this.elementList = []; + this.rowList = []; + this.positionList = []; + } + _compute() { + this._computeRowList(); + this._computePositionList(); + } + _computeRowList() { + const innerWidth = this.draw.getInnerWidth(); + this.rowList = this.draw.computeRowList({ + innerWidth, + elementList: this.elementList + }); + } + _computePositionList() { + const { lineBreak, scale } = this.options; + const headerExtraHeight = this.draw.getHeader().getExtraHeight(); + const innerWidth = this.draw.getInnerWidth(); + const margins = this.draw.getMargins(); + let startX = margins[3]; + if (!lineBreak.disabled) { + startX += (LineBreakParticle.WIDTH + LineBreakParticle.GAP) * scale; + } + const startY = margins[0] + headerExtraHeight; + this.position.computePageRowPosition({ + positionList: this.positionList, + rowList: this.rowList, + pageNo: 0, + startRowIndex: 0, + startIndex: 0, + startX, + startY, + innerWidth + }); + } + render(ctx) { + const { placeholder: { data: data2, font, size, color, opacity } } = this.options; + if (!data2) + return; + this._recovery(); + this.elementList = [ + { + value: data2, + font, + size, + color + } + ]; + formatElementList(this.elementList, { + editorOptions: this.options, + isForceCompensation: true + }); + this._compute(); + const innerWidth = this.draw.getInnerWidth(); + ctx.save(); + ctx.globalAlpha = opacity; + this.draw.drawRow(ctx, { + elementList: this.elementList, + positionList: this.positionList, + rowList: this.rowList, + pageNo: 0, + startIndex: 0, + innerWidth, + isDrawLineBreak: false + }); + ctx.restore(); + } +} +class Group { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + this.range = draw.getRange(); + this.fillRectMap = /* @__PURE__ */ new Map(); + } + setGroup() { + if (this.draw.isReadonly() || this.draw.getZone().getZone() !== EditorZone.MAIN) { + return null; + } + const selection = this.range.getSelection(); + if (!selection) + return null; + const groupId = getUUID(); + selection.forEach((el) => { + if (!Array.isArray(el.groupIds)) { + el.groupIds = []; + } + el.groupIds.push(groupId); + }); + this.draw.render({ + isSetCursor: false, + isCompute: false + }); + return groupId; + } + getElementListByGroupId(elementList, groupId) { + var _a, _b; + const groupElementList = []; + for (let e = 0; e < elementList.length; e++) { + const element = elementList[e]; + if (element.type === ElementType.TABLE) { + const trList = element.trList; + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const tdGroupElementList = this.getElementListByGroupId(td.value, groupId); + if (tdGroupElementList.length) { + groupElementList.push(...tdGroupElementList); + return groupElementList; + } + } + } + } + if ((_a = element == null ? void 0 : element.groupIds) == null ? void 0 : _a.includes(groupId)) { + groupElementList.push(element); + const nextElement = elementList[e + 1]; + if (!((_b = nextElement == null ? void 0 : nextElement.groupIds) == null ? void 0 : _b.includes(groupId))) + break; + } + } + return groupElementList; + } + deleteGroup(groupId) { + if (this.draw.isReadonly()) + return; + const elementList = this.draw.getOriginalMainElementList(); + const groupElementList = this.getElementListByGroupId(elementList, groupId); + if (!groupElementList.length) + return; + for (let e = 0; e < groupElementList.length; e++) { + const element = groupElementList[e]; + const groupIds = element.groupIds; + const groupIndex = groupIds.findIndex((id) => id === groupId); + groupIds.splice(groupIndex, 1); + if (!groupIds.length) { + delete element.groupIds; + } + } + this.draw.render({ + isSetCursor: false, + isCompute: false + }); + } + getContextByGroupId(elementList, groupId) { + var _a, _b; + for (let e = 0; e < elementList.length; e++) { + const element = elementList[e]; + if (element.type === ElementType.TABLE) { + const trList = element.trList; + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const range = this.getContextByGroupId(td.value, groupId); + if (range) { + return { + ...range, + isTable: true, + index: e, + trIndex: r, + tdIndex: d, + tdId: td.id, + trId: tr.id, + tableId: element.tableId + }; + } + } + } + } + const nextElement = elementList[e + 1]; + if (((_a = element.groupIds) == null ? void 0 : _a.includes(groupId)) && !((_b = nextElement == null ? void 0 : nextElement.groupIds) == null ? void 0 : _b.includes(groupId))) { + return { + isTable: false, + startIndex: e, + endIndex: e + }; + } + } + return null; + } + clearFillInfo() { + this.fillRectMap.clear(); + } + recordFillInfo(element, x, y, width, height) { + const groupIds = element.groupIds; + if (!groupIds) + return; + for (const groupId of groupIds) { + const fillRect = this.fillRectMap.get(groupId); + if (!fillRect) { + this.fillRectMap.set(groupId, { + x, + y, + width, + height + }); + } else { + fillRect.width += width; + } + } + } + render(ctx) { + var _a; + if (!this.fillRectMap.size) + return; + const range = this.range.getRange(); + const elementList = this.draw.getElementList(); + const anchorGroupIds = (_a = elementList[range.endIndex]) == null ? void 0 : _a.groupIds; + const { group: { backgroundColor, opacity, activeOpacity, activeBackgroundColor } } = this.options; + ctx.save(); + this.fillRectMap.forEach((fillRect, groupId) => { + const { x, y, width, height } = fillRect; + if (anchorGroupIds == null ? void 0 : anchorGroupIds.includes(groupId)) { + ctx.globalAlpha = activeOpacity; + ctx.fillStyle = activeBackgroundColor; + } else { + ctx.globalAlpha = opacity; + ctx.fillStyle = backgroundColor; + } + ctx.fillRect(x, y, width, height); + }); + ctx.restore(); + this.clearFillInfo(); + } +} +class MouseObserver { + constructor(draw) { + this.draw = draw; + this.eventBus = this.draw.getEventBus(); + this.pageContainer = this.draw.getPageContainer(); + this.pageContainer.addEventListener("mousemove", this._mousemove.bind(this)); + this.pageContainer.addEventListener("mouseenter", this._mouseenter.bind(this)); + this.pageContainer.addEventListener("mouseleave", this._mouseleave.bind(this)); + } + _mousemove(evt) { + if (!this.eventBus.isSubscribe("mousemove")) + return; + this.eventBus.emit("mousemove", evt); + } + _mouseenter(evt) { + if (!this.eventBus.isSubscribe("mouseenter")) + return; + this.eventBus.emit("mouseenter", evt); + } + _mouseleave(evt) { + if (!this.eventBus.isSubscribe("mouseleave")) + return; + this.eventBus.emit("mouseleave", evt); + } +} +class LineNumber { + constructor(draw) { + this.draw = draw; + this.options = draw.getOptions(); + } + render(ctx, pageNo) { + const { scale, lineNumber: { color, size, font, right: right2, type } } = this.options; + const textParticle = this.draw.getTextParticle(); + const margins = this.draw.getMargins(); + const positionList = this.draw.getPosition().getOriginalMainPositionList(); + const pageRowList = this.draw.getPageRowList(); + const rowList = pageRowList[pageNo]; + ctx.save(); + ctx.fillStyle = color; + ctx.font = `${size * scale}px ${font}`; + for (let i = 0; i < rowList.length; i++) { + const row = rowList[i]; + const { coordinate: { leftBottom } } = positionList[row.startIndex]; + const seq = type === LineNumberType.PAGE ? i + 1 : row.rowIndex + 1; + const textMetrics = textParticle.measureText(ctx, { + value: `${seq}` + }); + const x = margins[3] - (textMetrics.width + right2) * scale; + const y = leftBottom[1] - textMetrics.actualBoundingBoxAscent * scale; + ctx.fillText(`${seq}`, x, y); + } + ctx.restore(); + } +} +class PageBorder { + constructor(draw) { + this.draw = draw; + this.header = draw.getHeader(); + this.footer = draw.getFooter(); + this.options = draw.getOptions(); + } + render(ctx) { + const { scale, pageBorder: { color, lineWidth, padding } } = this.options; + ctx.save(); + ctx.translate(0.5, 0.5); + ctx.strokeStyle = color; + ctx.lineWidth = lineWidth * scale; + const margins = this.draw.getMargins(); + const x = margins[3] - padding[3] * scale; + const y = margins[0] + this.header.getExtraHeight() - padding[0] * scale; + const width = this.draw.getInnerWidth() + (padding[1] + padding[3]) * scale; + const height = this.draw.getHeight() - y - this.footer.getExtraHeight() - margins[2] + padding[2] * scale; + ctx.rect(x, y, width, height); + ctx.stroke(); + ctx.restore(); + } +} +function positionContextChange(draw, payload) { + const { value, oldValue } = payload; + if (oldValue.isTable && !value.isTable) { + draw.getTableTool().dispose(); + } +} +class Actuator { + constructor(draw) { + this.draw = draw; + this.eventBus = draw.getEventBus(); + this.execute(); + } + execute() { + this.eventBus.on("positionContextChange", (payload) => { + positionContextChange(this.draw, payload); + }); + } +} +class TableOperate { + constructor(draw) { + this.draw = draw; + this.range = draw.getRange(); + this.position = draw.getPosition(); + this.tableTool = draw.getTableTool(); + this.tableParticle = draw.getTableParticle(); + this.options = draw.getOptions(); + } + insertTable(row, col) { + var _a; + const { startIndex, endIndex } = this.range.getRange(); + if (!~startIndex && !~endIndex) + return; + const { defaultTrMinHeight } = this.options.table; + const elementList = this.draw.getElementList(); + let offsetX = 0; + if ((_a = elementList[startIndex]) == null ? void 0 : _a.listId) { + const positionList = this.position.getPositionList(); + const { rowIndex } = positionList[startIndex]; + const rowList = this.draw.getRowList(); + const row2 = rowList[rowIndex]; + offsetX = (row2 == null ? void 0 : row2.offsetX) || 0; + } + const innerWidth = this.draw.getContextInnerWidth() - offsetX; + const colgroup = []; + const colWidth = innerWidth / col; + for (let c = 0; c < col; c++) { + colgroup.push({ + width: colWidth + }); + } + const trList = []; + for (let r = 0; r < row; r++) { + const tdList = []; + const tr = { + height: defaultTrMinHeight, + tdList + }; + for (let c = 0; c < col; c++) { + tdList.push({ + colspan: 1, + rowspan: 1, + value: [] + }); + } + trList.push(tr); + } + const element = { + type: ElementType.TABLE, + value: "", + colgroup, + trList + }; + formatElementList([element], { + editorOptions: this.options + }); + formatElementContext(elementList, [element], startIndex, { + editorOptions: this.options + }); + const curIndex = startIndex + 1; + this.draw.spliceElementList(elementList, curIndex, startIndex === endIndex ? 0 : endIndex - startIndex, element); + this.range.setRange(curIndex, curIndex); + this.draw.render({ curIndex, isSetCursor: false }); + } + insertTableTopRow() { + const positionContext = this.position.getPositionContext(); + if (!positionContext.isTable) + return; + const { index: index2, trIndex, tableId } = positionContext; + const originalElementList = this.draw.getOriginalElementList(); + const element = originalElementList[index2]; + const curTrList = element.trList; + const curTr = curTrList[trIndex]; + if (curTr.tdList.length < element.colgroup.length) { + const curTrNo = curTr.tdList[0].rowIndex; + for (let t = 0; t < trIndex; t++) { + const tr = curTrList[t]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + if (td.rowspan > 1 && td.rowIndex + td.rowspan >= curTrNo + 1) { + td.rowspan += 1; + } + } + } + } + const newTrId = getUUID(); + const newTr = { + height: curTr.height, + id: newTrId, + tdList: [] + }; + for (let t = 0; t < curTr.tdList.length; t++) { + const curTd = curTr.tdList[t]; + const newTdId = getUUID(); + newTr.tdList.push({ + id: newTdId, + rowspan: 1, + colspan: curTd.colspan, + value: [ + { + value: ZERO, + size: 16, + tableId, + trId: newTrId, + tdId: newTdId + } + ] + }); + } + curTrList.splice(trIndex, 0, newTr); + this.position.setPositionContext({ + isTable: true, + index: index2, + trIndex, + tdIndex: 0, + tdId: newTr.tdList[0].id, + trId: newTr.id, + tableId + }); + this.range.setRange(0, 0); + this.draw.render({ curIndex: 0 }); + this.tableTool.render(); + } + insertTableBottomRow() { + const positionContext = this.position.getPositionContext(); + if (!positionContext.isTable) + return; + const { index: index2, trIndex, tableId } = positionContext; + const originalElementList = this.draw.getOriginalElementList(); + const element = originalElementList[index2]; + const curTrList = element.trList; + const curTr = curTrList[trIndex]; + const anchorTr = curTrList.length - 1 === trIndex ? curTr : curTrList[trIndex + 1]; + if (anchorTr.tdList.length < element.colgroup.length) { + const curTrNo = anchorTr.tdList[0].rowIndex; + for (let t = 0; t < trIndex + 1; t++) { + const tr = curTrList[t]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + if (td.rowspan > 1 && td.rowIndex + td.rowspan >= curTrNo + 1) { + td.rowspan += 1; + } + } + } + } + const newTrId = getUUID(); + const newTr = { + height: anchorTr.height, + id: newTrId, + tdList: [] + }; + for (let t = 0; t < anchorTr.tdList.length; t++) { + const curTd = anchorTr.tdList[t]; + const newTdId = getUUID(); + newTr.tdList.push({ + id: newTdId, + rowspan: 1, + colspan: curTd.colspan, + value: [ + { + value: ZERO, + size: 16, + tableId, + trId: newTrId, + tdId: newTdId + } + ] + }); + } + curTrList.splice(trIndex + 1, 0, newTr); + this.position.setPositionContext({ + isTable: true, + index: index2, + trIndex: trIndex + 1, + tdIndex: 0, + tdId: newTr.tdList[0].id, + trId: newTr.id, + tableId: element.id + }); + this.range.setRange(0, 0); + this.draw.render({ curIndex: 0 }); + } + insertTableLeftCol() { + const positionContext = this.position.getPositionContext(); + if (!positionContext.isTable) + return; + const { index: index2, tdIndex, tableId } = positionContext; + const originalElementList = this.draw.getOriginalElementList(); + const element = originalElementList[index2]; + const curTrList = element.trList; + const curTdIndex = tdIndex; + for (let t = 0; t < curTrList.length; t++) { + const tr = curTrList[t]; + const tdId = getUUID(); + tr.tdList.splice(curTdIndex, 0, { + id: tdId, + rowspan: 1, + colspan: 1, + value: [ + { + value: ZERO, + size: 16, + tableId, + trId: tr.id, + tdId + } + ] + }); + } + const colgroup = element.colgroup; + colgroup.splice(curTdIndex, 0, { + width: this.options.table.defaultColMinWidth + }); + const colgroupWidth = colgroup.reduce((pre, cur) => pre + cur.width, 0); + const width = this.draw.getOriginalInnerWidth(); + if (colgroupWidth > width) { + const adjustWidth = (colgroupWidth - width) / colgroup.length; + for (let g = 0; g < colgroup.length; g++) { + const group2 = colgroup[g]; + group2.width -= adjustWidth; + } + } + this.position.setPositionContext({ + isTable: true, + index: index2, + trIndex: 0, + tdIndex: curTdIndex, + tdId: curTrList[0].tdList[curTdIndex].id, + trId: curTrList[0].id, + tableId + }); + this.range.setRange(0, 0); + this.draw.render({ curIndex: 0 }); + this.tableTool.render(); + } + insertTableRightCol() { + const positionContext = this.position.getPositionContext(); + if (!positionContext.isTable) + return; + const { index: index2, tdIndex, tableId } = positionContext; + const originalElementList = this.draw.getOriginalElementList(); + const element = originalElementList[index2]; + const curTrList = element.trList; + const curTdIndex = tdIndex + 1; + for (let t = 0; t < curTrList.length; t++) { + const tr = curTrList[t]; + const tdId = getUUID(); + tr.tdList.splice(curTdIndex, 0, { + id: tdId, + rowspan: 1, + colspan: 1, + value: [ + { + value: ZERO, + size: 16, + tableId, + trId: tr.id, + tdId + } + ] + }); + } + const colgroup = element.colgroup; + colgroup.splice(curTdIndex, 0, { + width: this.options.table.defaultColMinWidth + }); + const colgroupWidth = colgroup.reduce((pre, cur) => pre + cur.width, 0); + const width = this.draw.getOriginalInnerWidth(); + if (colgroupWidth > width) { + const adjustWidth = (colgroupWidth - width) / colgroup.length; + for (let g = 0; g < colgroup.length; g++) { + const group2 = colgroup[g]; + group2.width -= adjustWidth; + } + } + this.position.setPositionContext({ + isTable: true, + index: index2, + trIndex: 0, + tdIndex: curTdIndex, + tdId: curTrList[0].tdList[curTdIndex].id, + trId: curTrList[0].id, + tableId: element.id + }); + this.range.setRange(0, 0); + this.draw.render({ curIndex: 0 }); + } + deleteTableRow() { + const positionContext = this.position.getPositionContext(); + if (!positionContext.isTable) + return; + const { index: index2, trIndex, tdIndex } = positionContext; + const originalElementList = this.draw.getOriginalElementList(); + const element = originalElementList[index2]; + const trList = element.trList; + const curTr = trList[trIndex]; + const curTdRowIndex = curTr.tdList[tdIndex].rowIndex; + if (trList.length <= 1) { + this.deleteTable(); + return; + } + for (let r = 0; r < curTdRowIndex; r++) { + const tr = trList[r]; + const tdList = tr.tdList; + for (let d = 0; d < tdList.length; d++) { + const td = tdList[d]; + if (td.rowIndex + td.rowspan > curTdRowIndex) { + td.rowspan--; + } + } + } + for (let d = 0; d < curTr.tdList.length; d++) { + const td = curTr.tdList[d]; + if (td.rowspan > 1) { + const tdId = getUUID(); + const nextTr = trList[trIndex + 1]; + nextTr.tdList.splice(d, 0, { + id: tdId, + rowspan: td.rowspan - 1, + colspan: td.colspan, + value: [ + { + value: ZERO, + size: 16, + tableId: element.id, + trId: nextTr.id, + tdId + } + ] + }); + } + } + trList.splice(trIndex, 1); + this.position.setPositionContext({ + isTable: false + }); + this.range.clearRange(); + this.draw.render({ + curIndex: positionContext.index + }); + this.tableTool.dispose(); + } + deleteTableCol() { + var _a; + const positionContext = this.position.getPositionContext(); + if (!positionContext.isTable) + return; + const { index: index2, tdIndex, trIndex } = positionContext; + const originalElementList = this.draw.getOriginalElementList(); + const element = originalElementList[index2]; + const curTrList = element.trList; + const curTd = curTrList[trIndex].tdList[tdIndex]; + const curColIndex = curTd.colIndex; + const moreTdTr = curTrList.find((tr) => tr.tdList.length > 1); + if (!moreTdTr) { + this.deleteTable(); + return; + } + for (let t = 0; t < curTrList.length; t++) { + const tr = curTrList[t]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + if (td.colIndex <= curColIndex && td.colIndex + td.colspan > curColIndex) { + if (td.colspan > 1) { + td.colspan--; + } else { + tr.tdList.splice(d, 1); + } + } + } + } + (_a = element.colgroup) == null ? void 0 : _a.splice(curColIndex, 1); + this.position.setPositionContext({ + isTable: false + }); + this.range.setRange(0, 0); + this.draw.render({ + curIndex: positionContext.index + }); + this.tableTool.dispose(); + } + deleteTable() { + const positionContext = this.position.getPositionContext(); + if (!positionContext.isTable) + return; + const originalElementList = this.draw.getOriginalElementList(); + originalElementList.splice(positionContext.index, 1); + const curIndex = positionContext.index - 1; + this.position.setPositionContext({ + isTable: false, + index: curIndex + }); + this.range.setRange(curIndex, curIndex); + this.draw.render({ curIndex }); + this.tableTool.dispose(); + } + mergeTableCell() { + const positionContext = this.position.getPositionContext(); + if (!positionContext.isTable) + return; + const { isCrossRowCol, startTdIndex, endTdIndex, startTrIndex, endTrIndex } = this.range.getRange(); + if (!isCrossRowCol) + return; + const { index: index2 } = positionContext; + const originalElementList = this.draw.getOriginalElementList(); + const element = originalElementList[index2]; + const curTrList = element.trList; + let startTd = curTrList[startTrIndex].tdList[startTdIndex]; + let endTd = curTrList[endTrIndex].tdList[endTdIndex]; + if (startTd.x > endTd.x || startTd.y > endTd.y) { + [startTd, endTd] = [endTd, startTd]; + } + const startColIndex = startTd.colIndex; + const endColIndex = endTd.colIndex + (endTd.colspan - 1); + const startRowIndex = startTd.rowIndex; + const endRowIndex = endTd.rowIndex + (endTd.rowspan - 1); + const rowCol = []; + for (let t = 0; t < curTrList.length; t++) { + const tr = curTrList[t]; + const tdList = []; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const tdColIndex = td.colIndex; + const tdRowIndex = td.rowIndex; + if (tdColIndex >= startColIndex && tdColIndex <= endColIndex && tdRowIndex >= startRowIndex && tdRowIndex <= endRowIndex) { + tdList.push(td); + } + } + if (tdList.length) { + rowCol.push(tdList); + } + } + if (!rowCol.length) + return; + const lastRow = rowCol[rowCol.length - 1]; + const leftTop = rowCol[0][0]; + const rightBottom = lastRow[lastRow.length - 1]; + const startX = leftTop.x; + const startY = leftTop.y; + const endX = rightBottom.x + rightBottom.width; + const endY = rightBottom.y + rightBottom.height; + for (let t = 0; t < rowCol.length; t++) { + const tr = rowCol[t]; + for (let d = 0; d < tr.length; d++) { + const td = tr[d]; + const tdStartX = td.x; + const tdStartY = td.y; + const tdEndX = tdStartX + td.width; + const tdEndY = tdStartY + td.height; + if (startX > tdStartX || startY > tdStartY || endX < tdEndX || endY < tdEndY) { + return; + } + } + } + const mergeTdIdList = []; + const anchorTd = rowCol[0][0]; + for (let t = 0; t < rowCol.length; t++) { + const tr = rowCol[t]; + for (let d = 0; d < tr.length; d++) { + const td = tr[d]; + const isAnchorTd = t === 0 && d === 0; + if (!isAnchorTd) { + mergeTdIdList.push(td.id); + } + if (t === 0 && d !== 0) { + anchorTd.colspan += td.colspan; + } + if (t !== 0) { + if (anchorTd.colIndex === td.colIndex) { + anchorTd.rowspan += td.rowspan; + } + } + } + } + for (let t = 0; t < curTrList.length; t++) { + const tr = curTrList[t]; + let d = 0; + while (d < tr.tdList.length) { + const td = tr.tdList[d]; + if (mergeTdIdList.includes(td.id)) { + tr.tdList.splice(d, 1); + d--; + } + d++; + } + } + this.position.setPositionContext({ + ...positionContext, + trIndex: anchorTd.trIndex, + tdIndex: anchorTd.tdIndex + }); + const curIndex = anchorTd.value.length - 1; + this.range.setRange(curIndex, curIndex); + this.draw.render(); + this.tableTool.render(); + } + cancelMergeTableCell() { + const positionContext = this.position.getPositionContext(); + if (!positionContext.isTable) + return; + const { index: index2, tdIndex, trIndex } = positionContext; + const originalElementList = this.draw.getOriginalElementList(); + const element = originalElementList[index2]; + const curTrList = element.trList; + const curTr = curTrList[trIndex]; + const curTd = curTr.tdList[tdIndex]; + if (curTd.rowspan === 1 && curTd.colspan === 1) + return; + const colspan = curTd.colspan; + if (curTd.colspan > 1) { + for (let c = 1; c < curTd.colspan; c++) { + const tdId = getUUID(); + curTr.tdList.splice(tdIndex + c, 0, { + id: tdId, + rowspan: 1, + colspan: 1, + value: [ + { + value: ZERO, + size: 16, + tableId: element.id, + trId: curTr.id, + tdId + } + ] + }); + } + curTd.colspan = 1; + } + if (curTd.rowspan > 1) { + for (let r = 1; r < curTd.rowspan; r++) { + const tr = curTrList[trIndex + r]; + for (let c = 0; c < colspan; c++) { + const tdId = getUUID(); + tr.tdList.splice(curTd.colIndex, 0, { + id: tdId, + rowspan: 1, + colspan: 1, + value: [ + { + value: ZERO, + size: 16, + tableId: element.id, + trId: tr.id, + tdId + } + ] + }); + } + } + curTd.rowspan = 1; + } + const curIndex = curTd.value.length - 1; + this.range.setRange(curIndex, curIndex); + this.draw.render(); + this.tableTool.render(); + } + tableTdVerticalAlign(payload) { + const rowCol = this.tableParticle.getRangeRowCol(); + if (!rowCol) + return; + for (let r = 0; r < rowCol.length; r++) { + const row = rowCol[r]; + for (let c = 0; c < row.length; c++) { + const td = row[c]; + if (!td || td.verticalAlign === payload || !td.verticalAlign && payload === VerticalAlign.TOP) { + continue; + } + td.verticalAlign = payload; + } + } + const { endIndex } = this.range.getRange(); + this.draw.render({ + curIndex: endIndex + }); + } + tableBorderType(payload) { + const positionContext = this.position.getPositionContext(); + if (!positionContext.isTable) + return; + const { index: index2 } = positionContext; + const originalElementList = this.draw.getOriginalElementList(); + const element = originalElementList[index2]; + if (!element.borderType && payload === TableBorder.ALL || element.borderType === payload) { + return; + } + element.borderType = payload; + const { endIndex } = this.range.getRange(); + this.draw.render({ + curIndex: endIndex + }); + } + tableTdBorderType(payload) { + const rowCol = this.tableParticle.getRangeRowCol(); + if (!rowCol) + return; + const tdList = rowCol.flat(); + const isSetBorderType = tdList.some((td) => { + var _a; + return !((_a = td.borderTypes) == null ? void 0 : _a.includes(payload)); + }); + tdList.forEach((td) => { + if (!td.borderTypes) { + td.borderTypes = []; + } + const borderTypeIndex = td.borderTypes.findIndex((type) => type === payload); + if (isSetBorderType) { + if (!~borderTypeIndex) { + td.borderTypes.push(payload); + } + } else { + if (~borderTypeIndex) { + td.borderTypes.splice(borderTypeIndex, 1); + } + } + if (!td.borderTypes.length) { + delete td.borderTypes; + } + }); + const { endIndex } = this.range.getRange(); + this.draw.render({ + curIndex: endIndex + }); + } + tableTdSlashType(payload) { + const rowCol = this.tableParticle.getRangeRowCol(); + if (!rowCol) + return; + const tdList = rowCol.flat(); + const isSetTdSlashType = tdList.some((td) => { + var _a; + return !((_a = td.slashTypes) == null ? void 0 : _a.includes(payload)); + }); + tdList.forEach((td) => { + if (!td.slashTypes) { + td.slashTypes = []; + } + const slashTypeIndex = td.slashTypes.findIndex((type) => type === payload); + if (isSetTdSlashType) { + if (!~slashTypeIndex) { + td.slashTypes.push(payload); + } + } else { + if (~slashTypeIndex) { + td.slashTypes.splice(slashTypeIndex, 1); + } + } + if (!td.slashTypes.length) { + delete td.slashTypes; + } + }); + const { endIndex } = this.range.getRange(); + this.draw.render({ + curIndex: endIndex + }); + } + tableTdBackgroundColor(payload) { + const rowCol = this.tableParticle.getRangeRowCol(); + if (!rowCol) + return; + for (let r = 0; r < rowCol.length; r++) { + const row = rowCol[r]; + for (let c = 0; c < row.length; c++) { + const col = row[c]; + col.backgroundColor = payload; + } + } + this.draw.render({ + isCompute: false + }); + } + tableSelectAll() { + const positionContext = this.position.getPositionContext(); + const { index: index2, tableId, isTable } = positionContext; + if (!isTable || !tableId) + return; + const { startIndex, endIndex } = this.range.getRange(); + const originalElementList = this.draw.getOriginalElementList(); + const trList = originalElementList[index2].trList; + const endTrIndex = trList.length - 1; + const endTdIndex = trList[endTrIndex].tdList.length - 1; + this.range.replaceRange({ + startIndex, + endIndex, + tableId, + startTdIndex: 0, + endTdIndex, + startTrIndex: 0, + endTrIndex + }); + this.draw.render({ + isCompute: false, + isSubmitHistory: false + }); + } +} +class Draw { + constructor(rootContainer, options, data2, listener, eventBus, override) { + this.container = this._wrapContainer(rootContainer); + this.pageList = []; + this.ctxList = []; + this.pageNo = 0; + this.pagePixelRatio = null; + this.mode = options.mode; + this.options = options; + this.elementList = data2.main; + this.listener = listener; + this.eventBus = eventBus; + this.override = override; + this._formatContainer(); + this.pageContainer = this._createPageContainer(); + this._createPage(0); + this.i18n = new I18n(); + this.historyManager = new HistoryManager(this); + this.position = new Position(this); + this.zone = new Zone(this); + this.range = new RangeManager(this); + this.margin = new Margin(this); + this.background = new Background(this); + this.search = new Search(this); + this.group = new Group(this); + this.underline = new Underline(this); + this.strikeout = new Strikeout(this); + this.highlight = new Highlight(this); + this.previewer = new Previewer(this); + this.imageParticle = new ImageParticle(this); + this.laTexParticle = new LaTexParticle(this); + this.textParticle = new TextParticle(this); + this.tableParticle = new TableParticle(this); + this.tableTool = new TableTool(this); + this.tableOperate = new TableOperate(this); + this.pageNumber = new PageNumber(this); + this.lineNumber = new LineNumber(this); + this.waterMark = new Watermark(this); + this.placeholder = new Placeholder(this); + this.header = new Header(this, data2.header); + this.footer = new Footer(this, data2.footer); + this.hyperlinkParticle = new HyperlinkParticle(this); + this.dateParticle = new DateParticle(this); + this.separatorParticle = new SeparatorParticle(this); + this.pageBreakParticle = new PageBreakParticle(this); + this.superscriptParticle = new SuperscriptParticle(); + this.subscriptParticle = new SubscriptParticle(); + this.checkboxParticle = new CheckboxParticle(this); + this.radioParticle = new RadioParticle(this); + this.blockParticle = new BlockParticle(this); + this.listParticle = new ListParticle(this); + this.lineBreakParticle = new LineBreakParticle(this); + this.control = new Control(this); + this.pageBorder = new PageBorder(this); + this.scrollObserver = new ScrollObserver(this); + this.selectionObserver = new SelectionObserver(this); + this.imageObserver = new ImageObserver(); + new MouseObserver(this); + this.canvasEvent = new CanvasEvent(this); + this.cursor = new Cursor(this, this.canvasEvent); + this.canvasEvent.register(); + this.globalEvent = new GlobalEvent(this, this.canvasEvent); + this.globalEvent.register(); + this.workerManager = new WorkerManager(this); + new Actuator(this); + const { letterClass } = options; + this.LETTER_REG = new RegExp(`[${letterClass.join("")}]`); + this.WORD_LIKE_REG = new RegExp(`${letterClass.map((letter) => `[^${letter}][${letter}]`).join("|")}`); + this.rowList = []; + this.pageRowList = []; + this.painterStyle = null; + this.painterOptions = null; + this.visiblePageNoList = []; + this.intersectionPageNo = 0; + this.lazyRenderIntersectionObserver = null; + this.printModeData = null; + this.render({ + isInit: true, + isSetCursor: false, + isFirstRender: true + }); + } + getLetterReg() { + return this.LETTER_REG; + } + getMode() { + return this.mode; + } + setMode(payload) { + if (this.mode === payload) + return; + if (payload === EditorMode.PRINT) { + this.printModeData = { + header: this.header.getElementList(), + main: this.elementList, + footer: this.footer.getElementList() + }; + const clonePrintModeData = deepClone(this.printModeData); + const editorDataKeys = ["header", "main", "footer"]; + editorDataKeys.forEach((key) => { + clonePrintModeData[key] = this.control.filterAssistElement(clonePrintModeData[key]); + }); + this.setEditorData(clonePrintModeData); + } + if (this.mode === EditorMode.PRINT && this.printModeData) { + this.setEditorData(this.printModeData); + this.printModeData = null; + } + this.clearSideEffect(); + this.range.clearRange(); + this.mode = payload; + this.options.mode = payload; + this.render({ + isSetCursor: false, + isSubmitHistory: false + }); + } + isReadonly() { + switch (this.mode) { + case EditorMode.DESIGN: + return false; + case EditorMode.READONLY: + case EditorMode.PRINT: + return true; + case EditorMode.FORM: + return !this.control.getIsRangeWithinControl(); + default: + return false; + } + } + isDisabled() { + var _a, _b, _c, _d, _e; + if (this.mode === EditorMode.DESIGN) + return false; + const { startIndex, endIndex } = this.range.getRange(); + const elementList = this.getElementList(); + if ((_a = this.getTd()) == null ? void 0 : _a.disabled) + return true; + if (startIndex === endIndex) { + const startElement = elementList[startIndex]; + const nextElement = elementList[startIndex + 1]; + return !!(((_b = startElement == null ? void 0 : startElement.title) == null ? void 0 : _b.disabled) && ((_c = nextElement == null ? void 0 : nextElement.title) == null ? void 0 : _c.disabled) || ((_d = startElement == null ? void 0 : startElement.control) == null ? void 0 : _d.disabled) && ((_e = nextElement == null ? void 0 : nextElement.control) == null ? void 0 : _e.disabled)); + } + const selectionElementList = elementList.slice(startIndex + 1, endIndex + 1); + return selectionElementList.some((element) => { + var _a2, _b2; + return ((_a2 = element.title) == null ? void 0 : _a2.disabled) || ((_b2 = element.control) == null ? void 0 : _b2.disabled); + }); + } + isDesignMode() { + return this.mode === EditorMode.DESIGN; + } + getOriginalWidth() { + const { paperDirection, width, height } = this.options; + return paperDirection === PaperDirection.VERTICAL ? width : height; + } + getOriginalHeight() { + const { paperDirection, width, height } = this.options; + return paperDirection === PaperDirection.VERTICAL ? height : width; + } + getWidth() { + return Math.floor(this.getOriginalWidth() * this.options.scale); + } + getHeight() { + return Math.floor(this.getOriginalHeight() * this.options.scale); + } + getMainHeight() { + const pageHeight = this.getHeight(); + return pageHeight - this.getMainOuterHeight(); + } + getMainOuterHeight() { + const margins = this.getMargins(); + const headerExtraHeight = this.header.getExtraHeight(); + const footerExtraHeight = this.footer.getExtraHeight(); + return margins[0] + margins[2] + headerExtraHeight + footerExtraHeight; + } + getCanvasWidth(pageNo = -1) { + const page = this.getPage(pageNo); + return page.width; + } + getCanvasHeight(pageNo = -1) { + const page = this.getPage(pageNo); + return page.height; + } + getInnerWidth() { + const width = this.getWidth(); + const margins = this.getMargins(); + return width - margins[1] - margins[3]; + } + getOriginalInnerWidth() { + const width = this.getOriginalWidth(); + const margins = this.getOriginalMargins(); + return width - margins[1] - margins[3]; + } + getContextInnerWidth() { + const positionContext = this.position.getPositionContext(); + if (positionContext.isTable) { + const { index: index2, trIndex, tdIndex } = positionContext; + const elementList = this.getOriginalElementList(); + const td = elementList[index2].trList[trIndex].tdList[tdIndex]; + const tdPadding = this.getTdPadding(); + return td.width - tdPadding[1] - tdPadding[3]; + } + return this.getOriginalInnerWidth(); + } + getMargins() { + return this.getOriginalMargins().map((m) => m * this.options.scale); + } + getOriginalMargins() { + const { margins, paperDirection } = this.options; + return paperDirection === PaperDirection.VERTICAL ? margins : [margins[1], margins[2], margins[3], margins[0]]; + } + getPageGap() { + return this.options.pageGap * this.options.scale; + } + getOriginalPageGap() { + return this.options.pageGap; + } + getPageNumberBottom() { + const { pageNumber: { bottom }, scale } = this.options; + return bottom * scale; + } + getMarginIndicatorSize() { + return this.options.marginIndicatorSize * this.options.scale; + } + getDefaultBasicRowMarginHeight() { + return this.options.defaultBasicRowMarginHeight * this.options.scale; + } + getTdPadding() { + const { table: { tdPadding }, scale } = this.options; + return tdPadding.map((m) => m * scale); + } + getContainer() { + return this.container; + } + getPageContainer() { + return this.pageContainer; + } + getVisiblePageNoList() { + return this.visiblePageNoList; + } + setVisiblePageNoList(payload) { + this.visiblePageNoList = payload; + if (this.listener.visiblePageNoListChange) { + this.listener.visiblePageNoListChange(this.visiblePageNoList); + } + if (this.eventBus.isSubscribe("visiblePageNoListChange")) { + this.eventBus.emit("visiblePageNoListChange", this.visiblePageNoList); + } + } + getIntersectionPageNo() { + return this.intersectionPageNo; + } + setIntersectionPageNo(payload) { + this.intersectionPageNo = payload; + if (this.listener.intersectionPageNoChange) { + this.listener.intersectionPageNoChange(this.intersectionPageNo); + } + if (this.eventBus.isSubscribe("intersectionPageNoChange")) { + this.eventBus.emit("intersectionPageNoChange", this.intersectionPageNo); + } + } + getPageNo() { + return this.pageNo; + } + setPageNo(payload) { + this.pageNo = payload; + } + getPage(pageNo = -1) { + return this.pageList[~pageNo ? pageNo : this.pageNo]; + } + getPageList() { + return this.pageList; + } + getPageCount() { + return this.pageList.length; + } + getTableRowList(sourceElementList) { + const positionContext = this.position.getPositionContext(); + const { index: index2, trIndex, tdIndex } = positionContext; + return sourceElementList[index2].trList[trIndex].tdList[tdIndex].rowList; + } + getOriginalRowList() { + const zoneManager = this.getZone(); + if (zoneManager.isHeaderActive()) { + return this.header.getRowList(); + } + if (zoneManager.isFooterActive()) { + return this.footer.getRowList(); + } + return this.rowList; + } + getRowList() { + const positionContext = this.position.getPositionContext(); + return positionContext.isTable ? this.getTableRowList(this.getOriginalElementList()) : this.getOriginalRowList(); + } + getPageRowList() { + return this.pageRowList; + } + getCtx() { + return this.ctxList[this.pageNo]; + } + getOptions() { + return this.options; + } + getSearch() { + return this.search; + } + getGroup() { + return this.group; + } + getHistoryManager() { + return this.historyManager; + } + getPosition() { + return this.position; + } + getZone() { + return this.zone; + } + getRange() { + return this.range; + } + getLineBreakParticle() { + return this.lineBreakParticle; + } + getTextParticle() { + return this.textParticle; + } + getHeaderElementList() { + return this.header.getElementList(); + } + getTableElementList(sourceElementList) { + var _a; + const positionContext = this.position.getPositionContext(); + const { index: index2, trIndex, tdIndex } = positionContext; + return ((_a = sourceElementList[index2].trList) == null ? void 0 : _a[trIndex].tdList[tdIndex].value) || []; + } + getElementList() { + const positionContext = this.position.getPositionContext(); + const elementList = this.getOriginalElementList(); + return positionContext.isTable ? this.getTableElementList(elementList) : elementList; + } + getMainElementList() { + const positionContext = this.position.getPositionContext(); + return positionContext.isTable ? this.getTableElementList(this.elementList) : this.elementList; + } + getOriginalElementList() { + const zoneManager = this.getZone(); + if (zoneManager.isHeaderActive()) { + return this.getHeaderElementList(); + } + if (zoneManager.isFooterActive()) { + return this.getFooterElementList(); + } + return this.elementList; + } + getOriginalMainElementList() { + return this.elementList; + } + getFooterElementList() { + return this.footer.getElementList(); + } + getTd() { + const positionContext = this.position.getPositionContext(); + const { index: index2, trIndex, tdIndex, isTable } = positionContext; + if (isTable) { + const elementList = this.getOriginalElementList(); + return elementList[index2].trList[trIndex].tdList[tdIndex]; + } + return null; + } + insertElementList(payload) { + if (!payload.length || !this.range.getIsCanInput()) + return; + const { startIndex, endIndex } = this.range.getRange(); + if (!~startIndex && !~endIndex) + return; + formatElementList(payload, { + isHandleFirstElement: false, + editorOptions: this.options + }); + let curIndex = -1; + let activeControl = this.control.getActiveControl(); + if (!activeControl && this.control.getIsRangeWithinControl()) { + this.control.initControl(); + activeControl = this.control.getActiveControl(); + } + if (activeControl && this.control.getIsRangeWithinControl()) { + curIndex = activeControl.setValue(payload, void 0, { + isIgnoreDisabledRule: true + }); + } else { + const elementList = this.getElementList(); + const isCollapsed = startIndex === endIndex; + const start = startIndex + 1; + if (!isCollapsed) { + this.spliceElementList(elementList, start, endIndex - startIndex); + } + this.spliceElementList(elementList, start, 0, ...payload); + curIndex = startIndex + payload.length; + const preElement = elementList[start - 1]; + if (payload[0].listId && preElement && !preElement.listId && (preElement == null ? void 0 : preElement.value) === ZERO && (!preElement.type || preElement.type === ElementType.TEXT)) { + elementList.splice(startIndex, 1); + curIndex -= 1; + } + } + if (~curIndex) { + this.range.setRange(curIndex, curIndex); + this.render({ + curIndex + }); + } + } + appendElementList(elementList, options = {}) { + if (!elementList.length) + return; + formatElementList(elementList, { + isHandleFirstElement: false, + editorOptions: this.options + }); + let curIndex; + const { isPrepend } = options; + if (isPrepend) { + this.elementList.splice(1, 0, ...elementList); + curIndex = elementList.length; + } else { + this.elementList.push(...elementList); + curIndex = this.elementList.length - 1; + } + this.range.setRange(curIndex, curIndex); + this.render({ + curIndex + }); + } + spliceElementList(elementList, start, deleteCount, ...items) { + var _a, _b, _c, _d; + const isDesignMode = this.isDesignMode(); + if (deleteCount > 0) { + const endIndex = start + deleteCount; + const endElement = elementList[endIndex]; + const endElementListId = endElement == null ? void 0 : endElement.listId; + if (endElementListId && ((_a = elementList[start - 1]) == null ? void 0 : _a.listId) !== endElementListId) { + let startIndex = endIndex; + while (startIndex < elementList.length) { + const curElement = elementList[startIndex]; + if (curElement.listId !== endElementListId || curElement.value === ZERO) { + break; + } + delete curElement.listId; + delete curElement.listType; + delete curElement.listStyle; + startIndex++; + } + } + if (!this.control.getActiveControl()) { + const tdDeletable = (_b = this.getTd()) == null ? void 0 : _b.deletable; + let deleteIndex = endIndex - 1; + while (deleteIndex >= start) { + const deleteElement = elementList[deleteIndex]; + if (isDesignMode || tdDeletable !== false && ((_c = deleteElement == null ? void 0 : deleteElement.control) == null ? void 0 : _c.deletable) !== false && ((_d = deleteElement == null ? void 0 : deleteElement.title) == null ? void 0 : _d.deletable) !== false) { + elementList.splice(deleteIndex, 1); + } + deleteIndex--; + } + } else { + elementList.splice(start, deleteCount); + } + } + for (let i = 0; i < items.length; i++) { + elementList.splice(start + i, 0, items[i]); + } + } + getCanvasEvent() { + return this.canvasEvent; + } + getGlobalEvent() { + return this.globalEvent; + } + getListener() { + return this.listener; + } + getEventBus() { + return this.eventBus; + } + getOverride() { + return this.override; + } + getCursor() { + return this.cursor; + } + getPreviewer() { + return this.previewer; + } + getImageParticle() { + return this.imageParticle; + } + getTableTool() { + return this.tableTool; + } + getTableOperate() { + return this.tableOperate; + } + getTableParticle() { + return this.tableParticle; + } + getHeader() { + return this.header; + } + getFooter() { + return this.footer; + } + getHyperlinkParticle() { + return this.hyperlinkParticle; + } + getDateParticle() { + return this.dateParticle; + } + getListParticle() { + return this.listParticle; + } + getCheckboxParticle() { + return this.checkboxParticle; + } + getRadioParticle() { + return this.radioParticle; + } + getControl() { + return this.control; + } + getWorkerManager() { + return this.workerManager; + } + getImageObserver() { + return this.imageObserver; + } + getI18n() { + return this.i18n; + } + getRowCount() { + return this.getRowList().length; + } + async getDataURL(payload = {}) { + const { pixelRatio, mode } = payload; + if (pixelRatio) { + this.setPagePixelRatio(pixelRatio); + } + const currentMode = this.mode; + const isSwitchMode = !!mode && currentMode !== mode; + if (isSwitchMode) { + this.setMode(mode); + } + this.render({ + isLazy: false, + isCompute: false, + isSetCursor: false, + isSubmitHistory: false + }); + await this.imageObserver.allSettled(); + const dataUrlList = this.pageList.map((c) => c.toDataURL()); + if (pixelRatio) { + this.setPagePixelRatio(null); + } + if (isSwitchMode) { + this.setMode(currentMode); + } + return dataUrlList; + } + getPainterStyle() { + return this.painterStyle && Object.keys(this.painterStyle).length ? this.painterStyle : null; + } + getPainterOptions() { + return this.painterOptions; + } + setPainterStyle(payload, options) { + this.painterStyle = payload; + this.painterOptions = options || null; + if (this.getPainterStyle()) { + this.pageList.forEach((c) => c.style.cursor = "copy"); + } + } + setDefaultRange() { + if (!this.elementList.length) + return; + setTimeout(() => { + const curIndex = this.elementList.length - 1; + this.range.setRange(curIndex, curIndex); + this.range.setRangeStyle(); + }); + } + getIsPagingMode() { + return this.options.pageMode === PageMode.PAGING; + } + setPageMode(payload) { + if (!payload || this.options.pageMode === payload) + return; + this.options.pageMode = payload; + if (payload === PageMode.PAGING) { + const { height } = this.options; + const dpr = this.getPagePixelRatio(); + const canvas = this.pageList[0]; + canvas.style.height = `${height}px`; + canvas.height = height * dpr; + this._initPageContext(this.ctxList[0]); + } else { + this._disconnectLazyRender(); + this.header.recovery(); + this.footer.recovery(); + this.zone.setZone(EditorZone.MAIN); + } + const { startIndex } = this.range.getRange(); + const isCollapsed = this.range.getIsCollapsed(); + this.render({ + isSetCursor: true, + curIndex: startIndex, + isSubmitHistory: false + }); + if (!isCollapsed) { + this.cursor.drawCursor({ + isShow: false + }); + } + setTimeout(() => { + if (this.listener.pageModeChange) { + this.listener.pageModeChange(payload); + } + if (this.eventBus.isSubscribe("pageModeChange")) { + this.eventBus.emit("pageModeChange", payload); + } + }); + } + setPageScale(payload) { + const dpr = this.getPagePixelRatio(); + this.options.scale = payload; + const width = this.getWidth(); + const height = this.getHeight(); + this.container.style.width = `${width}px`; + this.pageList.forEach((p, i) => { + p.width = width * dpr; + p.height = height * dpr; + p.style.width = `${width}px`; + p.style.height = `${height}px`; + p.style.marginBottom = `${this.getPageGap()}px`; + this._initPageContext(this.ctxList[i]); + }); + const cursorPosition = this.position.getCursorPosition(); + this.render({ + isSubmitHistory: false, + isSetCursor: !!cursorPosition, + curIndex: cursorPosition == null ? void 0 : cursorPosition.index + }); + if (this.listener.pageScaleChange) { + this.listener.pageScaleChange(payload); + } + if (this.eventBus.isSubscribe("pageScaleChange")) { + this.eventBus.emit("pageScaleChange", payload); + } + } + getPagePixelRatio() { + return this.pagePixelRatio || window.devicePixelRatio; + } + setPagePixelRatio(payload) { + if (!this.pagePixelRatio && payload === window.devicePixelRatio || payload === this.pagePixelRatio) { + return; + } + this.pagePixelRatio = payload; + this.setPageDevicePixel(); + } + setPageDevicePixel() { + const dpr = this.getPagePixelRatio(); + const width = this.getWidth(); + const height = this.getHeight(); + this.pageList.forEach((p, i) => { + p.width = width * dpr; + p.height = height * dpr; + this._initPageContext(this.ctxList[i]); + }); + this.render({ + isSubmitHistory: false, + isSetCursor: false + }); + } + setPaperSize(width, height) { + this.options.width = width; + this.options.height = height; + const dpr = this.getPagePixelRatio(); + const realWidth = this.getWidth(); + const realHeight = this.getHeight(); + this.container.style.width = `${realWidth}px`; + this.pageList.forEach((p, i) => { + p.width = realWidth * dpr; + p.height = realHeight * dpr; + p.style.width = `${realWidth}px`; + p.style.height = `${realHeight}px`; + this._initPageContext(this.ctxList[i]); + }); + this.render({ + isSubmitHistory: false, + isSetCursor: false + }); + } + setPaperDirection(payload) { + const dpr = this.getPagePixelRatio(); + this.options.paperDirection = payload; + const width = this.getWidth(); + const height = this.getHeight(); + this.container.style.width = `${width}px`; + this.pageList.forEach((p, i) => { + p.width = width * dpr; + p.height = height * dpr; + p.style.width = `${width}px`; + p.style.height = `${height}px`; + this._initPageContext(this.ctxList[i]); + }); + this.render({ + isSubmitHistory: false, + isSetCursor: false + }); + } + setPaperMargin(payload) { + this.options.margins = payload; + this.render({ + isSubmitHistory: false, + isSetCursor: false + }); + } + getValue(options = {}) { + const { pageNo, extraPickAttrs } = options; + let mainElementList = this.elementList; + if (Number.isInteger(pageNo) && pageNo >= 0 && pageNo < this.pageRowList.length) { + mainElementList = this.pageRowList[pageNo].flatMap((row) => row.elementList); + } + const data2 = { + header: zipElementList(this.getHeaderElementList(), { + extraPickAttrs + }), + main: zipElementList(mainElementList, { + extraPickAttrs + }), + footer: zipElementList(this.getFooterElementList(), { + extraPickAttrs + }) + }; + return { + version, + data: data2, + options: deepClone(this.options) + }; + } + setValue(payload, options) { + const { header, main, footer } = deepClone(payload); + if (!header && !main && !footer) + return; + const { isSetCursor = false } = options || {}; + const pageComponentData = [header, main, footer]; + pageComponentData.forEach((data2) => { + if (!data2) + return; + formatElementList(data2, { + editorOptions: this.options, + isForceCompensation: true + }); + }); + this.setEditorData({ + header, + main, + footer + }); + this.historyManager.recovery(); + const curIndex = isSetCursor ? (main == null ? void 0 : main.length) ? main.length - 1 : 0 : void 0; + if (curIndex !== void 0) { + this.range.setRange(curIndex, curIndex); + } + this.render({ + curIndex, + isSetCursor, + isFirstRender: true + }); + } + setEditorData(payload) { + const { header, main, footer } = payload; + if (header) { + this.header.setElementList(header); + } + if (main) { + this.elementList = main; + } + if (footer) { + this.footer.setElementList(footer); + } + } + _wrapContainer(rootContainer) { + const container = document.createElement("div"); + rootContainer.append(container); + return container; + } + _formatContainer() { + this.container.style.position = "relative"; + this.container.style.width = `${this.getWidth()}px`; + this.container.setAttribute(EDITOR_COMPONENT, EditorComponent.MAIN); + } + _createPageContainer() { + const pageContainer = document.createElement("div"); + pageContainer.classList.add(`${EDITOR_PREFIX}-page-container`); + this.container.append(pageContainer); + return pageContainer; + } + _createPage(pageNo) { + const width = this.getWidth(); + const height = this.getHeight(); + const canvas = document.createElement("canvas"); + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + canvas.style.display = "block"; + canvas.style.backgroundColor = "#ffffff"; + canvas.style.marginBottom = `${this.getPageGap()}px`; + canvas.setAttribute("data-index", String(pageNo)); + this.pageContainer.append(canvas); + const dpr = this.getPagePixelRatio(); + canvas.width = width * dpr; + canvas.height = height * dpr; + canvas.style.cursor = "text"; + const ctx = canvas.getContext("2d"); + this._initPageContext(ctx); + this.pageList.push(canvas); + this.ctxList.push(ctx); + } + _initPageContext(ctx) { + const dpr = this.getPagePixelRatio(); + ctx.scale(dpr, dpr); + ctx.letterSpacing = "0px"; + ctx.wordSpacing = "0px"; + ctx.direction = "ltr"; + } + getElementFont(el, scale = 1) { + const { defaultSize, defaultFont } = this.options; + const font = el.font || defaultFont; + const size = el.actualSize || el.size || defaultSize; + return `${el.italic ? "italic " : ""}${el.bold ? "bold " : ""}${size * scale}px ${font}`; + } + getElementSize(el) { + return el.actualSize || el.size || this.options.defaultSize; + } + getElementRowMargin(el) { + var _a; + const { defaultBasicRowMarginHeight, defaultRowMargin, scale } = this.options; + return defaultBasicRowMarginHeight * ((_a = el.rowMargin) != null ? _a : defaultRowMargin) * scale; + } + computeRowList(payload) { + var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k; + const { innerWidth, elementList, isPagingMode = false, isFromTable = false, startX = 0, startY = 0, pageHeight = 0, mainOuterHeight = 0, surroundElementList = [] } = payload; + const { defaultSize, defaultRowMargin, scale, table: { tdPadding }, defaultTabWidth } = this.options; + const defaultBasicRowMarginHeight = this.getDefaultBasicRowMarginHeight(); + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + const listStyleMap = this.listParticle.computeListStyle(ctx, elementList); + const rowList = []; + if (elementList.length) { + rowList.push({ + width: 0, + height: 0, + ascent: 0, + elementList: [], + startIndex: 0, + rowIndex: 0, + rowFlex: ((_a = elementList == null ? void 0 : elementList[0]) == null ? void 0 : _a.rowFlex) || ((_b = elementList == null ? void 0 : elementList[1]) == null ? void 0 : _b.rowFlex) + }); + } + let x = startX; + let y = startY; + let pageNo = 0; + let listId; + let listIndex = 0; + let controlRealWidth = 0; + for (let i = 0; i < elementList.length; i++) { + const curRow = rowList[rowList.length - 1]; + const element = elementList[i]; + const rowMargin = defaultBasicRowMarginHeight * ((_c = element.rowMargin) != null ? _c : defaultRowMargin); + const metrics = { + width: 0, + height: 0, + boundingBoxAscent: 0, + boundingBoxDescent: 0 + }; + const offsetX = curRow.offsetX || element.listId && listStyleMap.get(element.listId) || 0; + const availableWidth = innerWidth - offsetX; + x += curRow.elementList.length === 1 ? offsetX : 0; + if (element.type === ElementType.IMAGE || element.type === ElementType.LATEX) { + if (element.imgDisplay === ImageDisplay.SURROUND || element.imgDisplay === ImageDisplay.FLOAT_TOP || element.imgDisplay === ImageDisplay.FLOAT_BOTTOM) { + metrics.width = 0; + metrics.height = 0; + metrics.boundingBoxDescent = 0; + } else { + const elementWidth = element.width * scale; + const elementHeight = element.height * scale; + if (elementWidth > availableWidth) { + const adaptiveHeight = elementHeight * availableWidth / elementWidth; + element.width = availableWidth / scale; + element.height = adaptiveHeight / scale; + metrics.width = availableWidth; + metrics.height = adaptiveHeight; + metrics.boundingBoxDescent = adaptiveHeight; + } else { + metrics.width = elementWidth; + metrics.height = elementHeight; + metrics.boundingBoxDescent = elementHeight; + } + } + metrics.boundingBoxAscent = 0; + } else if (element.type === ElementType.TABLE) { + const tdPaddingWidth = tdPadding[1] + tdPadding[3]; + const tdPaddingHeight = tdPadding[0] + tdPadding[2]; + if (element.pagingId) { + let tableIndex = i + 1; + let combineCount = 0; + while (tableIndex < elementList.length) { + const nextElement2 = elementList[tableIndex]; + if (nextElement2.pagingId === element.pagingId) { + const nexTrList = nextElement2.trList.filter((tr) => !tr.pagingRepeat); + element.trList.push(...nexTrList); + element.height += nextElement2.height; + tableIndex++; + combineCount++; + } else { + break; + } + } + if (combineCount) { + elementList.splice(i + 1, combineCount); + } + } + element.pagingIndex = (_d = element.pagingIndex) != null ? _d : 0; + this.tableParticle.computeRowColInfo(element); + const trList = element.trList; + for (let t = 0; t < trList.length; t++) { + const tr = trList[t]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const rowList2 = this.computeRowList({ + innerWidth: (td.width - tdPaddingWidth) * scale, + elementList: td.value, + isFromTable: true, + isPagingMode + }); + const rowHeight = rowList2.reduce((pre, cur) => pre + cur.height, 0); + td.rowList = rowList2; + const curTdHeight = rowHeight / scale + tdPaddingHeight; + if (td.height < curTdHeight) { + const extraHeight = curTdHeight - td.height; + const changeTr = trList[t + td.rowspan - 1]; + changeTr.height += extraHeight; + changeTr.tdList.forEach((changeTd) => { + changeTd.height += extraHeight; + }); + } + let curTdMinHeight = 0; + let curTdRealHeight = 0; + let i2 = 0; + while (i2 < td.rowspan) { + const curTr = trList[i2 + t] || trList[t]; + curTdMinHeight += curTr.minHeight; + curTdRealHeight += curTr.height; + i2++; + } + td.realMinHeight = curTdMinHeight; + td.realHeight = curTdRealHeight; + td.mainHeight = curTdHeight; + } + } + const reduceTrList = this.tableParticle.getTrListGroupByCol(trList); + for (let t = 0; t < reduceTrList.length; t++) { + const tr = reduceTrList[t]; + let reduceHeight = -1; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const curTdRealHeight = td.realHeight; + const curTdHeight = td.mainHeight; + const curTdMinHeight = td.realMinHeight; + const curReduceHeight = curTdHeight < curTdMinHeight ? curTdRealHeight - curTdMinHeight : curTdRealHeight - curTdHeight; + if (!~reduceHeight || curReduceHeight < reduceHeight) { + reduceHeight = curReduceHeight; + } + } + if (reduceHeight > 0) { + const changeTr = trList[t]; + changeTr.height -= reduceHeight; + changeTr.tdList.forEach((changeTd) => { + changeTd.height -= reduceHeight; + }); + } + } + this.tableParticle.computeRowColInfo(element); + const tableHeight = this.tableParticle.getTableHeight(element); + const tableWidth = this.tableParticle.getTableWidth(element); + element.width = tableWidth; + element.height = tableHeight; + const elementWidth = tableWidth * scale; + const elementHeight = tableHeight * scale; + metrics.width = elementWidth; + metrics.height = elementHeight; + metrics.boundingBoxDescent = elementHeight; + metrics.boundingBoxAscent = -rowMargin; + if (isPagingMode) { + const height2 = this.getHeight(); + const marginHeight = this.getMainOuterHeight(); + let curPagePreHeight = marginHeight; + for (let r = 0; r < rowList.length; r++) { + const row = rowList[r]; + if (row.height + curPagePreHeight > height2 || ((_e = rowList[r - 1]) == null ? void 0 : _e.isPageBreak)) { + curPagePreHeight = marginHeight + row.height; + } else { + curPagePreHeight += row.height; + } + } + const rowMarginHeight = rowMargin * 2 * scale; + if (curPagePreHeight + element.trList[0].height + rowMarginHeight > height2 || element.pagingIndex !== 0 && element.trList[0].pagingRepeat) { + curPagePreHeight = marginHeight; + } + if (curPagePreHeight + rowMarginHeight + elementHeight > height2) { + const trList2 = element.trList; + let deleteStart = 0; + let deleteCount = 0; + let preTrHeight = 0; + if (trList2.length > 1) { + for (let r = 0; r < trList2.length; r++) { + const tr = trList2[r]; + const trHeight = tr.height * scale; + if (curPagePreHeight + rowMarginHeight + preTrHeight + trHeight > height2) { + const rowColCount = tr.tdList.reduce((pre, cur) => pre + cur.colspan, 0); + if (((_f = element.colgroup) == null ? void 0 : _f.length) !== rowColCount) { + deleteCount = 0; + } + break; + } else { + deleteStart = r + 1; + deleteCount = trList2.length - deleteStart; + preTrHeight += trHeight; + } + } + } + if (deleteCount) { + const cloneTrList = trList2.splice(deleteStart, deleteCount); + const cloneTrHeight = cloneTrList.reduce((pre, cur) => pre + cur.height, 0); + const pagingId = element.pagingId || getUUID(); + element.pagingId = pagingId; + element.height -= cloneTrHeight; + metrics.height -= cloneTrHeight; + metrics.boundingBoxDescent -= cloneTrHeight; + const cloneElement = deepClone(element); + cloneElement.pagingId = pagingId; + cloneElement.pagingIndex = element.pagingIndex + 1; + const repeatTrList = trList2.filter((tr) => tr.pagingRepeat); + if (repeatTrList.length) { + const cloneRepeatTrList = deepClone(repeatTrList); + cloneRepeatTrList.forEach((tr) => tr.id = getUUID()); + cloneTrList.unshift(...cloneRepeatTrList); + } + cloneElement.trList = cloneTrList; + cloneElement.id = getUUID(); + this.spliceElementList(elementList, i + 1, 0, cloneElement); + } + } + if (element.pagingId) { + const positionContext = this.position.getPositionContext(); + if (positionContext.isTable) { + let newPositionContextIndex = -1; + let newPositionContextTrIndex = -1; + let tableIndex = i; + while (tableIndex < elementList.length) { + const curElement = elementList[tableIndex]; + if (curElement.pagingId !== element.pagingId) + break; + const trIndex = curElement.trList.findIndex((r) => r.id === positionContext.trId); + if (~trIndex) { + newPositionContextIndex = tableIndex; + newPositionContextTrIndex = trIndex; + break; + } + tableIndex++; + } + if (~newPositionContextIndex) { + positionContext.index = newPositionContextIndex; + positionContext.trIndex = newPositionContextTrIndex; + this.position.setPositionContext(positionContext); + } + } + } + } + } else if (element.type === ElementType.SEPARATOR) { + const { separator: { lineWidth } } = this.options; + element.width = availableWidth / scale; + metrics.width = availableWidth; + metrics.height = lineWidth * scale; + metrics.boundingBoxAscent = -rowMargin; + metrics.boundingBoxDescent = -rowMargin + metrics.height; + } else if (element.type === ElementType.PAGE_BREAK) { + element.width = availableWidth / scale; + metrics.width = availableWidth; + metrics.height = defaultSize; + } else if (element.type === ElementType.RADIO || element.controlComponent === ControlComponent.RADIO) { + const { width, height: height2, gap } = this.options.radio; + const elementWidth = width + gap * 2; + element.width = elementWidth; + metrics.width = elementWidth * scale; + metrics.height = height2 * scale; + } else if (element.type === ElementType.CHECKBOX || element.controlComponent === ControlComponent.CHECKBOX) { + const { width, height: height2, gap } = this.options.checkbox; + const elementWidth = width + gap * 2; + element.width = elementWidth; + metrics.width = elementWidth * scale; + metrics.height = height2 * scale; + } else if (element.type === ElementType.TAB) { + metrics.width = defaultTabWidth * scale; + metrics.height = defaultSize * scale; + metrics.boundingBoxDescent = 0; + metrics.boundingBoxAscent = metrics.height; + } else if (element.type === ElementType.BLOCK) { + if (!element.width) { + metrics.width = availableWidth; + } else { + const elementWidth = element.width * scale; + metrics.width = Math.min(elementWidth, availableWidth); + } + metrics.height = element.height * scale; + metrics.boundingBoxDescent = metrics.height; + metrics.boundingBoxAscent = 0; + } else { + const size = element.size || defaultSize; + if (element.type === ElementType.SUPERSCRIPT || element.type === ElementType.SUBSCRIPT) { + element.actualSize = Math.ceil(size * 0.6); + } + metrics.height = (element.actualSize || size) * scale; + ctx.font = this.getElementFont(element); + const fontMetrics = this.textParticle.measureText(ctx, element); + metrics.width = fontMetrics.width * scale; + if (element.letterSpacing) { + metrics.width += element.letterSpacing * scale; + } + metrics.boundingBoxAscent = (element.value === ZERO ? element.size || defaultSize : fontMetrics.actualBoundingBoxAscent) * scale; + metrics.boundingBoxDescent = fontMetrics.actualBoundingBoxDescent * scale; + if (element.type === ElementType.SUPERSCRIPT) { + metrics.boundingBoxAscent += metrics.height / 2; + } else if (element.type === ElementType.SUBSCRIPT) { + metrics.boundingBoxDescent += metrics.height / 2; + } + } + const ascent = element.imgDisplay !== ImageDisplay.INLINE && element.type === ElementType.IMAGE || element.type === ElementType.LATEX ? metrics.height + rowMargin : metrics.boundingBoxAscent + rowMargin; + const height = rowMargin + metrics.boundingBoxAscent + metrics.boundingBoxDescent + rowMargin; + const rowElement = Object.assign(element, { + metrics, + left: 0, + style: this.getElementFont(element, scale) + }); + if ((_g = rowElement.control) == null ? void 0 : _g.minWidth) { + if (rowElement.controlComponent) { + controlRealWidth += metrics.width; + } + if (rowElement.controlComponent === ControlComponent.POSTFIX) { + this.control.setMinWidthControlInfo({ + row: curRow, + rowElement, + availableWidth, + controlRealWidth + }); + controlRealWidth = 0; + } + } + const preElement = elementList[i - 1]; + let nextElement = elementList[i + 1]; + let curRowWidth = curRow.width + metrics.width; + if (this.options.wordBreak === WordBreak.BREAK_WORD) { + if ((!(preElement == null ? void 0 : preElement.type) || (preElement == null ? void 0 : preElement.type) === ElementType.TEXT) && (!element.type || element.type === ElementType.TEXT)) { + const word = `${(preElement == null ? void 0 : preElement.value) || ""}${element.value}`; + if (this.WORD_LIKE_REG.test(word)) { + const { width, endElement } = this.textParticle.measureWord(ctx, elementList, i); + const wordWidth = width * scale; + if (wordWidth <= availableWidth) { + curRowWidth += wordWidth; + nextElement = endElement; + } + } + const punctuationWidth = this.textParticle.measurePunctuationWidth(ctx, nextElement); + curRowWidth += punctuationWidth * scale; + } + } + if (element.listId) { + if (element.listId !== listId) { + listIndex = 0; + } else if (element.value === ZERO && !element.listWrap) { + listIndex++; + } + } + listId = element.listId; + const surroundPosition = this.position.setSurroundPosition({ + pageNo, + rowElement, + row: curRow, + rowElementRect: { + x, + y, + height, + width: metrics.width + }, + availableWidth, + surroundElementList + }); + x = surroundPosition.x; + curRowWidth += surroundPosition.rowIncreaseWidth; + x += metrics.width; + const isForceBreak = element.type === ElementType.SEPARATOR || element.type === ElementType.TABLE || (preElement == null ? void 0 : preElement.type) === ElementType.TABLE || (preElement == null ? void 0 : preElement.type) === ElementType.BLOCK || element.type === ElementType.BLOCK || (preElement == null ? void 0 : preElement.imgDisplay) === ImageDisplay.INLINE || element.imgDisplay === ImageDisplay.INLINE || (preElement == null ? void 0 : preElement.listId) !== element.listId || i !== 0 && element.value === ZERO; + const isWidthNotEnough = curRowWidth > availableWidth; + const isWrap = isForceBreak || isWidthNotEnough; + if (isWrap) { + const row = { + width: metrics.width, + height, + startIndex: i, + elementList: [rowElement], + ascent, + rowIndex: curRow.rowIndex + 1, + rowFlex: ((_h = elementList[i]) == null ? void 0 : _h.rowFlex) || ((_i = elementList[i + 1]) == null ? void 0 : _i.rowFlex), + isPageBreak: element.type === ElementType.PAGE_BREAK + }; + if (rowElement.controlComponent !== ControlComponent.PREFIX && ((_j = rowElement.control) == null ? void 0 : _j.indentation) === ControlIndentation.VALUE_START) { + const preStartIndex = curRow.elementList.findIndex((el) => el.controlId === rowElement.controlId && el.controlComponent !== ControlComponent.PREFIX); + if (~preStartIndex) { + const preRowPositionList = this.position.computeRowPosition({ + row: curRow, + innerWidth: this.getInnerWidth() + }); + const valueStartPosition = preRowPositionList[preStartIndex]; + if (valueStartPosition) { + row.offsetX = valueStartPosition.coordinate.leftTop[0]; + } + } + } + if (element.listId) { + row.isList = true; + row.offsetX = listStyleMap.get(element.listId); + row.listIndex = listIndex; + } + rowList.push(row); + } else { + curRow.width += metrics.width; + if (i === 0 && getIsBlockElement(elementList[1])) { + curRow.height = defaultBasicRowMarginHeight; + curRow.ascent = defaultBasicRowMarginHeight; + } else if (curRow.height < height) { + curRow.height = height; + curRow.ascent = ascent; + } + curRow.elementList.push(rowElement); + } + if (isWrap || i === elementList.length - 1) { + curRow.isWidthNotEnough = isWidthNotEnough && !isForceBreak; + if (!curRow.isSurround && ((preElement == null ? void 0 : preElement.rowFlex) === RowFlex.JUSTIFY || (preElement == null ? void 0 : preElement.rowFlex) === RowFlex.ALIGNMENT && isWidthNotEnough)) { + const rowElementList = ((_k = curRow.elementList[0]) == null ? void 0 : _k.value) === ZERO ? curRow.elementList.slice(1) : curRow.elementList; + const gap = (availableWidth - curRow.width) / (rowElementList.length - 1); + for (let e = 0; e < rowElementList.length - 1; e++) { + const el = rowElementList[e]; + el.metrics.width += gap; + } + curRow.width = availableWidth; + } + } + if (isWrap) { + x = startX; + y += curRow.height; + if (isPagingMode && !isFromTable && pageHeight && (y - startY + mainOuterHeight + height > pageHeight || element.type === ElementType.PAGE_BREAK)) { + y = startY; + deleteSurroundElementList(surroundElementList, pageNo); + pageNo += 1; + } + rowElement.left = 0; + const nextRow = rowList[rowList.length - 1]; + const surroundPosition2 = this.position.setSurroundPosition({ + pageNo, + rowElement, + row: nextRow, + rowElementRect: { + x, + y, + height, + width: metrics.width + }, + availableWidth, + surroundElementList + }); + x = surroundPosition2.x; + x += metrics.width; + } + } + return rowList; + } + _computePageList() { + var _a; + const pageRowList = [[]]; + const { pageMode, pageNumber: { maxPageNo } } = this.options; + const height = this.getHeight(); + const marginHeight = this.getMainOuterHeight(); + let pageHeight = marginHeight; + let pageNo = 0; + if (pageMode === PageMode.CONTINUITY) { + pageRowList[0] = this.rowList; + pageHeight += this.rowList.reduce((pre, cur) => pre + cur.height, 0); + const dpr = this.getPagePixelRatio(); + const pageDom = this.pageList[0]; + const pageDomHeight = Number(pageDom.style.height.replace("px", "")); + if (pageHeight > pageDomHeight) { + pageDom.style.height = `${pageHeight}px`; + pageDom.height = pageHeight * dpr; + } else { + const reduceHeight = pageHeight < height ? height : pageHeight; + pageDom.style.height = `${reduceHeight}px`; + pageDom.height = reduceHeight * dpr; + } + this._initPageContext(this.ctxList[0]); + } else { + for (let i = 0; i < this.rowList.length; i++) { + const row = this.rowList[i]; + if (row.height + pageHeight > height || ((_a = this.rowList[i - 1]) == null ? void 0 : _a.isPageBreak)) { + if (Number.isInteger(maxPageNo) && pageNo >= maxPageNo) { + this.elementList = this.elementList.slice(0, row.startIndex); + break; + } + pageHeight = marginHeight + row.height; + pageRowList.push([row]); + pageNo++; + } else { + pageHeight += row.height; + pageRowList[pageNo].push(row); + } + } + } + return pageRowList; + } + _drawHighlight(ctx, payload) { + var _a; + const { control: { activeBackgroundColor } } = this.options; + const { rowList, positionList } = payload; + const activeControlElement = (_a = this.control.getActiveControl()) == null ? void 0 : _a.getElement(); + for (let i = 0; i < rowList.length; i++) { + const curRow = rowList[i]; + for (let j = 0; j < curRow.elementList.length; j++) { + const element = curRow.elementList[j]; + const preElement = curRow.elementList[j - 1]; + if (element.highlight || activeBackgroundColor && activeControlElement && element.controlId === activeControlElement.controlId && !this.control.getIsRangeInPostfix()) { + if (preElement && preElement.highlight && preElement.highlight !== element.highlight) { + this.highlight.render(ctx); + } + const { coordinate: { leftTop: [x, y] } } = positionList[curRow.startIndex + j]; + const offsetX = element.left || 0; + this.highlight.recordFillInfo(ctx, x - offsetX, y, element.metrics.width + offsetX, curRow.height, element.highlight || activeBackgroundColor); + } else if (preElement == null ? void 0 : preElement.highlight) { + this.highlight.render(ctx); + } + } + this.highlight.render(ctx); + } + } + drawRow(ctx, payload) { + var _a, _b, _c, _d, _e, _f, _g; + this._drawHighlight(ctx, payload); + const { scale, table: { tdPadding }, group: group2, lineBreak } = this.options; + const { rowList, pageNo, elementList, positionList, startIndex, zone: zone2, isDrawLineBreak = !lineBreak.disabled } = payload; + const isPrintMode = this.mode === EditorMode.PRINT; + const { isCrossRowCol, tableId } = this.range.getRange(); + let index2 = startIndex; + for (let i = 0; i < rowList.length; i++) { + const curRow = rowList[i]; + const rangeRecord = { + x: 0, + y: 0, + width: 0, + height: 0 + }; + let tableRangeElement = null; + for (let j = 0; j < curRow.elementList.length; j++) { + const element = curRow.elementList[j]; + const metrics = element.metrics; + const { ascent: offsetY, coordinate: { leftTop: [x, y] } } = positionList[curRow.startIndex + j]; + const preElement = curRow.elementList[j - 1]; + if (element.type === ElementType.IMAGE) { + this.textParticle.complete(); + if (element.imgDisplay !== ImageDisplay.SURROUND && element.imgDisplay !== ImageDisplay.FLOAT_TOP && element.imgDisplay !== ImageDisplay.FLOAT_BOTTOM) { + this.imageParticle.render(ctx, element, x, y + offsetY); + } + } else if (element.type === ElementType.LATEX) { + this.textParticle.complete(); + this.laTexParticle.render(ctx, element, x, y + offsetY); + } else if (element.type === ElementType.TABLE) { + if (isCrossRowCol) { + rangeRecord.x = x; + rangeRecord.y = y; + tableRangeElement = element; + } + this.tableParticle.render(ctx, element, x, y); + } else if (element.type === ElementType.HYPERLINK) { + this.textParticle.complete(); + this.hyperlinkParticle.render(ctx, element, x, y + offsetY); + } else if (element.type === ElementType.DATE) { + const nextElement = curRow.elementList[j + 1]; + if (!preElement || preElement.dateId !== element.dateId) { + this.textParticle.complete(); + } + this.textParticle.record(ctx, element, x, y + offsetY); + if (!nextElement || nextElement.dateId !== element.dateId) { + this.textParticle.complete(); + } + } else if (element.type === ElementType.SUPERSCRIPT) { + this.textParticle.complete(); + this.superscriptParticle.render(ctx, element, x, y + offsetY); + } else if (element.type === ElementType.SUBSCRIPT) { + this.underline.render(ctx); + this.textParticle.complete(); + this.subscriptParticle.render(ctx, element, x, y + offsetY); + } else if (element.type === ElementType.SEPARATOR) { + this.separatorParticle.render(ctx, element, x, y); + } else if (element.type === ElementType.PAGE_BREAK) { + if (this.mode !== EditorMode.CLEAN && !isPrintMode) { + this.pageBreakParticle.render(ctx, element, x, y); + } + } else if (element.type === ElementType.CHECKBOX || element.controlComponent === ControlComponent.CHECKBOX) { + this.textParticle.complete(); + this.checkboxParticle.render({ + ctx, + x, + y: y + offsetY, + index: j, + row: curRow + }); + } else if (element.type === ElementType.RADIO || element.controlComponent === ControlComponent.RADIO) { + this.textParticle.complete(); + this.radioParticle.render({ + ctx, + x, + y: y + offsetY, + index: j, + row: curRow + }); + } else if (element.type === ElementType.TAB) { + this.textParticle.complete(); + } else if (element.rowFlex === RowFlex.ALIGNMENT || element.rowFlex === RowFlex.JUSTIFY) { + this.textParticle.record(ctx, element, x, y + offsetY); + this.textParticle.complete(); + } else if (element.type === ElementType.BLOCK) { + this.textParticle.complete(); + this.blockParticle.render(pageNo, element, x, y); + } else { + if (element.left) { + this.textParticle.complete(); + } + this.textParticle.record(ctx, element, x, y + offsetY); + if (element.width || element.letterSpacing || PUNCTUATION_REG.test(element.value)) { + this.textParticle.complete(); + } + } + if (isDrawLineBreak && !isPrintMode && this.mode !== EditorMode.CLEAN && !curRow.isWidthNotEnough && j === curRow.elementList.length - 1) { + this.lineBreakParticle.render(ctx, element, x, y + curRow.height / 2); + } + if ((_a = element.control) == null ? void 0 : _a.border) { + if (((_b = preElement == null ? void 0 : preElement.control) == null ? void 0 : _b.border) && preElement.controlId !== element.controlId) { + this.control.drawBorder(ctx); + } + const rowMargin = this.getElementRowMargin(element); + this.control.recordBorderInfo(x, y + rowMargin, element.metrics.width, curRow.height - 2 * rowMargin); + } else if ((_c = preElement == null ? void 0 : preElement.control) == null ? void 0 : _c.border) { + this.control.drawBorder(ctx); + } + if (element.underline || ((_d = element.control) == null ? void 0 : _d.underline)) { + if ((preElement == null ? void 0 : preElement.type) === ElementType.SUBSCRIPT && element.type !== ElementType.SUBSCRIPT) { + this.underline.render(ctx); + } + const rowMargin = this.getElementRowMargin(element); + const offsetX = element.left || 0; + let offsetY2 = 0; + if (element.type === ElementType.SUBSCRIPT) { + offsetY2 = this.subscriptParticle.getOffsetY(element); + } + const color = ((_e = element.control) == null ? void 0 : _e.underline) ? this.options.underlineColor : element.color; + this.underline.recordFillInfo(ctx, x - offsetX, y + curRow.height - rowMargin + offsetY2, metrics.width + offsetX, 0, color, (_f = element.textDecoration) == null ? void 0 : _f.style); + } else if ((preElement == null ? void 0 : preElement.underline) || ((_g = preElement == null ? void 0 : preElement.control) == null ? void 0 : _g.underline)) { + this.underline.render(ctx); + } + if (element.strikeout) { + if (!element.type || TEXTLIKE_ELEMENT_TYPE.includes(element.type)) { + if (preElement && (preElement.type === ElementType.SUBSCRIPT && element.type !== ElementType.SUBSCRIPT || preElement.type === ElementType.SUPERSCRIPT && element.type !== ElementType.SUPERSCRIPT || this.getElementSize(preElement) !== this.getElementSize(element))) { + this.strikeout.render(ctx); + } + const standardMetrics = this.textParticle.measureBasisWord(ctx, this.getElementFont(element)); + let adjustY = y + offsetY + standardMetrics.actualBoundingBoxDescent * scale - metrics.height / 2; + if (element.type === ElementType.SUBSCRIPT) { + adjustY += this.subscriptParticle.getOffsetY(element); + } else if (element.type === ElementType.SUPERSCRIPT) { + adjustY += this.superscriptParticle.getOffsetY(element); + } + this.strikeout.recordFillInfo(ctx, x, adjustY, metrics.width); + } + } else if (preElement == null ? void 0 : preElement.strikeout) { + this.strikeout.render(ctx); + } + const { zone: currentZone, startIndex: startIndex2, endIndex } = this.range.getRange(); + if (currentZone === zone2 && startIndex2 !== endIndex && startIndex2 <= index2 && index2 <= endIndex) { + const positionContext = this.position.getPositionContext(); + if (!positionContext.isTable && !element.tdId || positionContext.tdId === element.tdId) { + if (startIndex2 === index2) { + const nextElement = elementList[startIndex2 + 1]; + if (nextElement && nextElement.value === ZERO) { + rangeRecord.x = x + metrics.width; + rangeRecord.y = y; + rangeRecord.height = curRow.height; + rangeRecord.width += this.options.rangeMinWidth; + } + } else { + let rangeWidth = metrics.width; + if (rangeWidth === 0 && curRow.elementList.length === 1) { + rangeWidth = this.options.rangeMinWidth; + } + if (!rangeRecord.width) { + rangeRecord.x = x; + rangeRecord.y = y; + rangeRecord.height = curRow.height; + } + rangeRecord.width += rangeWidth; + } + } + } + if (!group2.disabled && element.groupIds) { + this.group.recordFillInfo(element, x, y, metrics.width, curRow.height); + } + index2++; + if (element.type === ElementType.TABLE) { + const tdPaddingWidth = tdPadding[1] + tdPadding[3]; + for (let t = 0; t < element.trList.length; t++) { + const tr = element.trList[t]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + this.drawRow(ctx, { + elementList: td.value, + positionList: td.positionList, + rowList: td.rowList, + pageNo, + startIndex: 0, + innerWidth: (td.width - tdPaddingWidth) * scale, + zone: zone2, + isDrawLineBreak + }); + } + } + } + } + if (curRow.isList) { + this.listParticle.drawListStyle(ctx, curRow, positionList[curRow.startIndex]); + } + this.textParticle.complete(); + this.control.drawBorder(ctx); + this.underline.render(ctx); + this.strikeout.render(ctx); + this.group.render(ctx); + if (!isPrintMode) { + if (rangeRecord.width && rangeRecord.height) { + const { x, y, width, height } = rangeRecord; + this.range.render(ctx, x, y, width, height); + } + if (isCrossRowCol && tableRangeElement && tableRangeElement.id === tableId) { + const { coordinate: { leftTop: [x, y] } } = positionList[curRow.startIndex]; + this.tableParticle.drawRange(ctx, tableRangeElement, x, y); + } + } + } + } + _drawFloat(ctx, payload) { + const { scale } = this.options; + const floatPositionList = this.position.getFloatPositionList(); + const { imgDisplays, pageNo } = payload; + for (let e = 0; e < floatPositionList.length; e++) { + const floatPosition = floatPositionList[e]; + const element = floatPosition.element; + if ((pageNo === floatPosition.pageNo || floatPosition.zone === EditorZone.HEADER || floatPosition.zone == EditorZone.FOOTER) && element.imgDisplay && imgDisplays.includes(element.imgDisplay) && element.type === ElementType.IMAGE) { + const imgFloatPosition = element.imgFloatPosition; + this.imageParticle.render(ctx, element, imgFloatPosition.x * scale, imgFloatPosition.y * scale); + } + } + } + _clearPage(pageNo) { + const ctx = this.ctxList[pageNo]; + const pageDom = this.pageList[pageNo]; + ctx.clearRect(0, 0, Math.max(pageDom.width, this.getWidth()), Math.max(pageDom.height, this.getHeight())); + this.blockParticle.clear(); + } + _drawPage(payload) { + var _a, _b; + const { elementList, positionList, rowList, pageNo } = payload; + const { inactiveAlpha, pageMode, header, footer, pageNumber, lineNumber, pageBorder } = this.options; + const innerWidth = this.getInnerWidth(); + const ctx = this.ctxList[pageNo]; + ctx.globalAlpha = !this.zone.isMainActive() ? inactiveAlpha : 1; + this._clearPage(pageNo); + this.background.render(ctx, pageNo); + if (this.mode !== EditorMode.PRINT) { + this.margin.render(ctx, pageNo); + } + this._drawFloat(ctx, { + pageNo, + imgDisplays: [ImageDisplay.FLOAT_BOTTOM] + }); + this.control.renderHighlightList(ctx, pageNo); + const index2 = (_a = rowList[0]) == null ? void 0 : _a.startIndex; + this.drawRow(ctx, { + elementList, + positionList, + rowList, + pageNo, + startIndex: index2, + innerWidth, + zone: EditorZone.MAIN + }); + if (this.getIsPagingMode()) { + if (!header.disabled) { + this.header.render(ctx, pageNo); + } + if (!pageNumber.disabled) { + this.pageNumber.render(ctx, pageNo); + } + if (!footer.disabled) { + this.footer.render(ctx, pageNo); + } + } + this._drawFloat(ctx, { + pageNo, + imgDisplays: [ImageDisplay.FLOAT_TOP, ImageDisplay.SURROUND] + }); + if (this.search.getSearchKeyword()) { + this.search.render(ctx, pageNo); + } + if (pageMode !== PageMode.CONTINUITY && this.options.watermark.data) { + this.waterMark.render(ctx); + } + if (this.elementList.length <= 1 && !((_b = this.elementList[0]) == null ? void 0 : _b.listId)) { + this.placeholder.render(ctx); + } + if (!lineNumber.disabled) { + this.lineNumber.render(ctx, pageNo); + } + if (!pageBorder.disabled) { + this.pageBorder.render(ctx); + } + } + _disconnectLazyRender() { + var _a; + (_a = this.lazyRenderIntersectionObserver) == null ? void 0 : _a.disconnect(); + } + _lazyRender() { + const positionList = this.position.getOriginalMainPositionList(); + const elementList = this.getOriginalMainElementList(); + this._disconnectLazyRender(); + this.lazyRenderIntersectionObserver = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const index2 = Number(entry.target.dataset.index); + this._drawPage({ + elementList, + positionList, + rowList: this.pageRowList[index2], + pageNo: index2 + }); + } + }); + }); + this.pageList.forEach((el) => { + this.lazyRenderIntersectionObserver.observe(el); + }); + } + _immediateRender() { + const positionList = this.position.getOriginalMainPositionList(); + const elementList = this.getOriginalMainElementList(); + for (let i = 0; i < this.pageRowList.length; i++) { + this._drawPage({ + elementList, + positionList, + rowList: this.pageRowList[i], + pageNo: i + }); + } + } + render(payload) { + const { header, footer } = this.options; + const { isSubmitHistory = true, isSetCursor = true, isCompute = true, isLazy = true, isInit = false, isSourceHistory = false, isFirstRender = false } = payload || {}; + let { curIndex } = payload || {}; + const innerWidth = this.getInnerWidth(); + const isPagingMode = this.getIsPagingMode(); + const oldPageSize = this.pageRowList.length; + if (isCompute) { + this.position.setFloatPositionList([]); + if (isPagingMode) { + if (!header.disabled) { + this.header.compute(); + } + if (!footer.disabled) { + this.footer.compute(); + } + } + const margins = this.getMargins(); + const pageHeight = this.getHeight(); + const extraHeight = this.header.getExtraHeight(); + const mainOuterHeight = this.getMainOuterHeight(); + const startX = margins[3]; + const startY = margins[0] + extraHeight; + const surroundElementList = pickSurroundElementList(this.elementList); + this.rowList = this.computeRowList({ + startX, + startY, + pageHeight, + mainOuterHeight, + isPagingMode, + innerWidth, + surroundElementList, + elementList: this.elementList + }); + this.pageRowList = this._computePageList(); + this.position.computePositionList(); + const searchKeyword = this.search.getSearchKeyword(); + if (searchKeyword) { + this.search.compute(searchKeyword); + } + this.control.computeHighlightList(); + } + this.imageObserver.clearAll(); + this.cursor.recoveryCursor(); + for (let i = 0; i < this.pageRowList.length; i++) { + if (!this.pageList[i]) { + this._createPage(i); + } + } + const curPageCount = this.pageRowList.length; + const prePageCount = this.pageList.length; + if (prePageCount > curPageCount) { + const deleteCount = prePageCount - curPageCount; + this.ctxList.splice(curPageCount, deleteCount); + this.pageList.splice(curPageCount, deleteCount).forEach((page) => page.remove()); + } + if (isLazy && isPagingMode) { + this._lazyRender(); + } else { + this._immediateRender(); + } + if (isSetCursor) { + curIndex = this.setCursor(curIndex); + } + if (isSubmitHistory && !isFirstRender || curIndex !== void 0 && this.historyManager.isStackEmpty()) { + this.submitHistory(curIndex); + } + nextTick(() => { + if (isCompute && this.control.getActiveControl()) { + this.control.reAwakeControl(); + } + if (isCompute && !this.isReadonly() && this.position.getPositionContext().isTable) { + this.tableTool.render(); + } + if (isCompute && !this.zone.isMainActive()) { + this.zone.drawZoneIndicator(); + } + if (oldPageSize !== this.pageRowList.length) { + if (this.listener.pageSizeChange) { + this.listener.pageSizeChange(this.pageRowList.length); + } + if (this.eventBus.isSubscribe("pageSizeChange")) { + this.eventBus.emit("pageSizeChange", this.pageRowList.length); + } + } + if ((isSubmitHistory || isSourceHistory) && !isInit) { + if (this.listener.contentChange) { + this.listener.contentChange(); + } + if (this.eventBus.isSubscribe("contentChange")) { + this.eventBus.emit("contentChange"); + } + } + }); + } + setCursor(curIndex) { + var _a; + const positionContext = this.position.getPositionContext(); + const positionList = this.position.getPositionList(); + if (positionContext.isTable) { + const { index: index2, trIndex, tdIndex } = positionContext; + const elementList = this.getOriginalElementList(); + const tablePositionList = (_a = elementList[index2].trList) == null ? void 0 : _a[trIndex].tdList[tdIndex].positionList; + if (curIndex === void 0 && tablePositionList) { + curIndex = tablePositionList.length - 1; + } + const tablePosition = tablePositionList == null ? void 0 : tablePositionList[curIndex]; + console.log("tablePosition:::", tablePosition); + this.position.setCursorPosition(tablePosition || null); + } else { + this.position.setCursorPosition(curIndex !== void 0 ? positionList[curIndex] : null); + } + let isShowCursor = true; + if (curIndex !== void 0 && positionContext.isImage && positionContext.isDirectHit) { + const elementList = this.getElementList(); + const element = elementList[curIndex]; + if (IMAGE_ELEMENT_TYPE.includes(element.type)) { + isShowCursor = false; + const position = this.position.getCursorPosition(); + this.previewer.updateResizer(element, position); + } + } + this.cursor.drawCursor({ + isShow: isShowCursor + }); + return curIndex; + } + submitHistory(curIndex) { + const positionContext = this.position.getPositionContext(); + const oldElementList = getSlimCloneElementList(this.elementList); + const oldHeaderElementList = getSlimCloneElementList(this.header.getElementList()); + const oldFooterElementList = getSlimCloneElementList(this.footer.getElementList()); + const oldRange = deepClone(this.range.getRange()); + const pageNo = this.pageNo; + const oldPositionContext = deepClone(positionContext); + const zone2 = this.zone.getZone(); + this.historyManager.execute(() => { + this.zone.setZone(zone2); + this.setPageNo(pageNo); + this.position.setPositionContext(deepClone(oldPositionContext)); + this.header.setElementList(deepClone(oldHeaderElementList)); + this.footer.setElementList(deepClone(oldFooterElementList)); + this.elementList = deepClone(oldElementList); + this.range.replaceRange(deepClone(oldRange)); + this.render({ + curIndex, + isSubmitHistory: false, + isSourceHistory: true + }); + }); + } + destroy() { + this.container.remove(); + this.globalEvent.removeEvent(); + this.scrollObserver.removeEvent(); + this.selectionObserver.removeEvent(); + } + clearSideEffect() { + this.getPreviewer().clearResizer(); + this.getTableTool().dispose(); + this.getHyperlinkParticle().clearHyperlinkPopup(); + this.getDateParticle().clearDatePicker(); + } +} +class Command { + constructor(adapt) { + this.executeMode = adapt.mode.bind(adapt); + this.executeCut = adapt.cut.bind(adapt); + this.executeCopy = adapt.copy.bind(adapt); + this.executePaste = adapt.paste.bind(adapt); + this.executeSelectAll = adapt.selectAll.bind(adapt); + this.executeBackspace = adapt.backspace.bind(adapt); + this.executeSetRange = adapt.setRange.bind(adapt); + this.executeReplaceRange = adapt.replaceRange.bind(adapt); + this.executeSetPositionContext = adapt.setPositionContext.bind(adapt); + this.executeForceUpdate = adapt.forceUpdate.bind(adapt); + this.executeBlur = adapt.blur.bind(adapt); + this.executeUndo = adapt.undo.bind(adapt); + this.executeRedo = adapt.redo.bind(adapt); + this.executePainter = adapt.painter.bind(adapt); + this.executeApplyPainterStyle = adapt.applyPainterStyle.bind(adapt); + this.executeFormat = adapt.format.bind(adapt); + this.executeFont = adapt.font.bind(adapt); + this.executeSize = adapt.size.bind(adapt); + this.executeSizeAdd = adapt.sizeAdd.bind(adapt); + this.executeSizeMinus = adapt.sizeMinus.bind(adapt); + this.executeBold = adapt.bold.bind(adapt); + this.executeItalic = adapt.italic.bind(adapt); + this.executeUnderline = adapt.underline.bind(adapt); + this.executeStrikeout = adapt.strikeout.bind(adapt); + this.executeSuperscript = adapt.superscript.bind(adapt); + this.executeSubscript = adapt.subscript.bind(adapt); + this.executeColor = adapt.color.bind(adapt); + this.executeHighlight = adapt.highlight.bind(adapt); + this.executeTitle = adapt.title.bind(adapt); + this.executeList = adapt.list.bind(adapt); + this.executeRowFlex = adapt.rowFlex.bind(adapt); + this.executeRowMargin = adapt.rowMargin.bind(adapt); + this.executeInsertTable = adapt.insertTable.bind(adapt); + this.executeInsertTableTopRow = adapt.insertTableTopRow.bind(adapt); + this.executeInsertTableBottomRow = adapt.insertTableBottomRow.bind(adapt); + this.executeInsertTableLeftCol = adapt.insertTableLeftCol.bind(adapt); + this.executeInsertTableRightCol = adapt.insertTableRightCol.bind(adapt); + this.executeDeleteTableRow = adapt.deleteTableRow.bind(adapt); + this.executeDeleteTableCol = adapt.deleteTableCol.bind(adapt); + this.executeDeleteTable = adapt.deleteTable.bind(adapt); + this.executeMergeTableCell = adapt.mergeTableCell.bind(adapt); + this.executeCancelMergeTableCell = adapt.cancelMergeTableCell.bind(adapt); + this.executeTableTdVerticalAlign = adapt.tableTdVerticalAlign.bind(adapt); + this.executeTableBorderType = adapt.tableBorderType.bind(adapt); + this.executeTableTdBorderType = adapt.tableTdBorderType.bind(adapt); + this.executeTableTdSlashType = adapt.tableTdSlashType.bind(adapt); + this.executeTableTdBackgroundColor = adapt.tableTdBackgroundColor.bind(adapt); + this.executeTableSelectAll = adapt.tableSelectAll.bind(adapt); + this.executeImage = adapt.image.bind(adapt); + this.executeHyperlink = adapt.hyperlink.bind(adapt); + this.executeDeleteHyperlink = adapt.deleteHyperlink.bind(adapt); + this.executeCancelHyperlink = adapt.cancelHyperlink.bind(adapt); + this.executeEditHyperlink = adapt.editHyperlink.bind(adapt); + this.executeSeparator = adapt.separator.bind(adapt); + this.executePageBreak = adapt.pageBreak.bind(adapt); + this.executeAddWatermark = adapt.addWatermark.bind(adapt); + this.executeDeleteWatermark = adapt.deleteWatermark.bind(adapt); + this.executeSearch = adapt.search.bind(adapt); + this.executeSearchNavigatePre = adapt.searchNavigatePre.bind(adapt); + this.executeSearchNavigateNext = adapt.searchNavigateNext.bind(adapt); + this.executeReplace = adapt.replace.bind(adapt); + this.executePrint = adapt.print.bind(adapt); + this.executeReplaceImageElement = adapt.replaceImageElement.bind(adapt); + this.executeSaveAsImageElement = adapt.saveAsImageElement.bind(adapt); + this.executeChangeImageDisplay = adapt.changeImageDisplay.bind(adapt); + this.executePageMode = adapt.pageMode.bind(adapt); + this.executePageScaleRecovery = adapt.pageScaleRecovery.bind(adapt); + this.executePageScaleMinus = adapt.pageScaleMinus.bind(adapt); + this.executePageScaleAdd = adapt.pageScaleAdd.bind(adapt); + this.executePaperSize = adapt.paperSize.bind(adapt); + this.executePaperDirection = adapt.paperDirection.bind(adapt); + this.executeSetPaperMargin = adapt.setPaperMargin.bind(adapt); + this.executeInsertElementList = adapt.insertElementList.bind(adapt); + this.executeAppendElementList = adapt.appendElementList.bind(adapt); + this.executeUpdateElementById = adapt.updateElementById.bind(adapt); + this.executeSetValue = adapt.setValue.bind(adapt); + this.executeRemoveControl = adapt.removeControl.bind(adapt); + this.executeSetLocale = adapt.setLocale.bind(adapt); + this.executeLocationCatalog = adapt.locationCatalog.bind(adapt); + this.executeWordTool = adapt.wordTool.bind(adapt); + this.executeSetHTML = adapt.setHTML.bind(adapt); + this.executeSetGroup = adapt.setGroup.bind(adapt); + this.executeDeleteGroup = adapt.deleteGroup.bind(adapt); + this.executeLocationGroup = adapt.locationGroup.bind(adapt); + this.executeSetZone = adapt.setZone.bind(adapt); + this.executeUpdateOptions = adapt.updateOptions.bind(adapt); + this.executeInsertTitle = adapt.insertTitle.bind(adapt); + this.executeFocus = adapt.focus.bind(adapt); + this.getImage = adapt.getImage.bind(adapt); + this.getOptions = adapt.getOptions.bind(adapt); + this.getValue = adapt.getValue.bind(adapt); + this.getHTML = adapt.getHTML.bind(adapt); + this.getText = adapt.getText.bind(adapt); + this.getWordCount = adapt.getWordCount.bind(adapt); + this.getCursorPosition = adapt.getCursorPosition.bind(adapt); + this.getRange = adapt.getRange.bind(adapt); + this.getRangeText = adapt.getRangeText.bind(adapt); + this.getRangeContext = adapt.getRangeContext.bind(adapt); + this.getRangeRow = adapt.getRangeRow.bind(adapt); + this.getRangeParagraph = adapt.getRangeParagraph.bind(adapt); + this.getKeywordRangeList = adapt.getKeywordRangeList.bind(adapt); + this.getCatalog = adapt.getCatalog.bind(adapt); + this.getPaperMargin = adapt.getPaperMargin.bind(adapt); + this.getSearchNavigateInfo = adapt.getSearchNavigateInfo.bind(adapt); + this.getLocale = adapt.getLocale.bind(adapt); + this.getGroupIds = adapt.getGroupIds.bind(adapt); + this.getContainer = adapt.getContainer.bind(adapt); + this.getTitleValue = adapt.getTitleValue.bind(adapt); + this.getPositionContextByEvent = adapt.getPositionContextByEvent.bind(adapt); + this.executeSetControlValue = adapt.setControlValue.bind(adapt); + this.executeSetControlExtension = adapt.setControlExtension.bind(adapt); + this.executeSetControlProperties = adapt.setControlProperties.bind(adapt); + this.executeSetControlHighlight = adapt.setControlHighlight.bind(adapt); + this.getControlValue = adapt.getControlValue.bind(adapt); + this.getControlList = adapt.getControlList.bind(adapt); + this.executeLocationControl = adapt.locationControl.bind(adapt); + this.executeInsertControl = adapt.insertControl.bind(adapt); + } +} +function convertPxToPaperSize(width, height) { + if (width === 1125 && height === 1593) { + return { + size: "a3", + width: "297mm", + height: "420mm" + }; + } + if (width === 794 && height === 1123) { + return { + size: "a4", + width: "210mm", + height: "297mm" + }; + } + if (width === 565 && height === 796) { + return { + size: "a5", + width: "148mm", + height: "210mm" + }; + } + return { + size: "", + width: `${width}px`, + height: `${height}px` + }; +} +function printImageBase64(base64List, options) { + const { width, height, direction = PaperDirection.VERTICAL } = options; + const iframe = document.createElement("iframe"); + iframe.style.visibility = "hidden"; + iframe.style.position = "absolute"; + iframe.style.left = "0"; + iframe.style.top = "0"; + iframe.style.width = "0"; + iframe.style.height = "0"; + iframe.style.border = "none"; + document.body.append(iframe); + const contentWindow = iframe.contentWindow; + const doc = contentWindow.document; + doc.open(); + const container = document.createElement("div"); + const paperSize = convertPxToPaperSize(width, height); + base64List.forEach((base64) => { + const image = document.createElement("img"); + image.style.width = direction === PaperDirection.HORIZONTAL ? paperSize.height : paperSize.width; + image.style.height = direction === PaperDirection.HORIZONTAL ? paperSize.width : paperSize.height; + image.src = base64; + container.append(image); + }); + const style = document.createElement("style"); + const stylesheet = ` + * { + margin: 0; + padding: 0; + } + @page { + margin: 0; + size: ${paperSize.size} ${direction === PaperDirection.HORIZONTAL ? `landscape` : `portrait`}; + }`; + style.append(document.createTextNode(stylesheet)); + setTimeout(() => { + doc.write(`${style.outerHTML}${container.innerHTML}`); + contentWindow.print(); + doc.close(); + window.addEventListener("mouseover", () => { + iframe == null ? void 0 : iframe.remove(); + }, { + once: true + }); + }); +} +class CommandAdapt { + constructor(draw) { + this.draw = draw; + this.range = draw.getRange(); + this.position = draw.getPosition(); + this.historyManager = draw.getHistoryManager(); + this.canvasEvent = draw.getCanvasEvent(); + this.options = draw.getOptions(); + this.control = draw.getControl(); + this.workerManager = draw.getWorkerManager(); + this.searchManager = draw.getSearch(); + this.i18n = draw.getI18n(); + this.zone = draw.getZone(); + this.tableOperate = draw.getTableOperate(); + } + mode(payload) { + this.draw.setMode(payload); + } + cut() { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + this.canvasEvent.cut(); + } + copy() { + this.canvasEvent.copy(); + } + paste(payload) { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + pasteByApi(this.canvasEvent, payload); + } + selectAll() { + this.canvasEvent.selectAll(); + } + backspace() { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const elementList = this.draw.getElementList(); + const { startIndex, endIndex } = this.range.getRange(); + const isCollapsed = startIndex === endIndex; + if (isCollapsed && elementList[startIndex].value === ZERO && startIndex === 0) { + return; + } + if (!isCollapsed) { + this.draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex); + } else { + this.draw.spliceElementList(elementList, startIndex, 1); + } + const curIndex = isCollapsed ? startIndex - 1 : startIndex; + this.range.setRange(curIndex, curIndex); + this.draw.render({ curIndex }); + } + setRange(startIndex, endIndex, tableId, startTdIndex, endTdIndex, startTrIndex, endTrIndex) { + if (startIndex < 0 || endIndex < 0 || endIndex < startIndex) + return; + this.range.setRange(startIndex, endIndex, tableId, startTdIndex, endTdIndex, startTrIndex, endTrIndex); + const isCollapsed = startIndex === endIndex; + this.draw.render({ + curIndex: isCollapsed ? startIndex : void 0, + isCompute: false, + isSubmitHistory: false, + isSetCursor: isCollapsed + }); + } + replaceRange(range) { + this.setRange(range.startIndex, range.endIndex, range.tableId, range.startTdIndex, range.endTdIndex, range.startTrIndex, range.endTrIndex); + } + setPositionContext(range) { + const { tableId, startTrIndex, startTdIndex } = range; + const elementList = this.draw.getOriginalElementList(); + if (tableId) { + const tableElementIndex = elementList.findIndex((el) => el.id === tableId); + if (!~tableElementIndex) + return; + const tableElement = elementList[tableElementIndex]; + const tr = tableElement.trList[startTrIndex]; + const td = tr.tdList[startTdIndex]; + this.position.setPositionContext({ + isTable: true, + index: tableElementIndex, + trIndex: startTrIndex, + tdIndex: startTdIndex, + tdId: td.id, + trId: tr.id, + tableId + }); + } else { + this.position.setPositionContext({ + isTable: false + }); + } + } + forceUpdate(options) { + const { isSubmitHistory = false } = options || {}; + this.range.clearRange(); + this.draw.render({ + isSubmitHistory, + isSetCursor: false + }); + } + blur() { + this.range.clearRange(); + this.draw.getCursor().recoveryCursor(); + } + undo() { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.historyManager.undo(); + } + redo() { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.historyManager.redo(); + } + painter(options) { + if (!options.isDblclick && this.draw.getPainterStyle()) { + this.canvasEvent.clearPainterStyle(); + return; + } + const selection = this.range.getSelection(); + if (!selection) + return; + const painterStyle = {}; + selection.forEach((s) => { + const painterStyleKeys = EDITOR_ELEMENT_STYLE_ATTR; + painterStyleKeys.forEach((p) => { + const key = p; + if (painterStyle[key] === void 0) { + painterStyle[key] = s[key]; + } + }); + }); + this.draw.setPainterStyle(painterStyle, options); + } + applyPainterStyle() { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + this.canvasEvent.applyPainterStyle(); + } + format() { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const selection = this.range.getSelectionElementList(); + let renderOption = {}; + let changeElementList = []; + if (selection == null ? void 0 : selection.length) { + changeElementList = selection; + renderOption = { isSetCursor: false }; + } else { + const { endIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const enterElement = elementList[endIndex]; + if ((enterElement == null ? void 0 : enterElement.value) === ZERO) { + changeElementList.push(enterElement); + renderOption = { curIndex: endIndex }; + } + } + if (!changeElementList.length) + return; + changeElementList.forEach((el) => { + EDITOR_ELEMENT_STYLE_ATTR.forEach((attr) => { + delete el[attr]; + }); + }); + this.draw.render(renderOption); + } + font(payload) { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const selection = this.range.getSelectionElementList(); + if (selection == null ? void 0 : selection.length) { + selection.forEach((el) => { + el.font = payload; + }); + this.draw.render({ isSetCursor: false }); + } else { + const { endIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const enterElement = elementList[endIndex]; + if ((enterElement == null ? void 0 : enterElement.value) === ZERO) { + enterElement.font = payload; + this.draw.render({ curIndex: endIndex, isCompute: false }); + } + } + } + size(payload) { + const { minSize, maxSize, defaultSize } = this.options; + if (payload < minSize || payload > maxSize) + return; + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + let renderOption = {}; + let changeElementList = []; + const selection = this.range.getTextLikeSelectionElementList(); + if (selection == null ? void 0 : selection.length) { + changeElementList = selection; + renderOption = { isSetCursor: false }; + } else { + const { endIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const enterElement = elementList[endIndex]; + if ((enterElement == null ? void 0 : enterElement.value) === ZERO) { + changeElementList.push(enterElement); + renderOption = { curIndex: endIndex }; + } + } + if (!changeElementList.length) + return; + let isExistUpdate = false; + changeElementList.forEach((el) => { + if (!el.size && payload === defaultSize || el.size && el.size === payload) { + return; + } + el.size = payload; + isExistUpdate = true; + }); + if (isExistUpdate) { + this.draw.render(renderOption); + } + } + sizeAdd() { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const selection = this.range.getTextLikeSelectionElementList(); + let renderOption = {}; + let changeElementList = []; + if (selection == null ? void 0 : selection.length) { + changeElementList = selection; + renderOption = { isSetCursor: false }; + } else { + const { endIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const enterElement = elementList[endIndex]; + if ((enterElement == null ? void 0 : enterElement.value) === ZERO) { + changeElementList.push(enterElement); + renderOption = { curIndex: endIndex }; + } + } + if (!changeElementList.length) + return; + const { defaultSize, maxSize } = this.options; + let isExistUpdate = false; + changeElementList.forEach((el) => { + if (!el.size) { + el.size = defaultSize; + } + if (el.size >= maxSize) + return; + if (el.size + 2 > maxSize) { + el.size = maxSize; + } else { + el.size += 2; + } + isExistUpdate = true; + }); + if (isExistUpdate) { + this.draw.render(renderOption); + } + } + sizeMinus() { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const selection = this.range.getTextLikeSelectionElementList(); + let renderOption = {}; + let changeElementList = []; + if (selection == null ? void 0 : selection.length) { + changeElementList = selection; + renderOption = { isSetCursor: false }; + } else { + const { endIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const enterElement = elementList[endIndex]; + if ((enterElement == null ? void 0 : enterElement.value) === ZERO) { + changeElementList.push(enterElement); + renderOption = { curIndex: endIndex }; + } + } + if (!changeElementList.length) + return; + const { defaultSize, minSize } = this.options; + let isExistUpdate = false; + changeElementList.forEach((el) => { + if (!el.size) { + el.size = defaultSize; + } + if (el.size <= minSize) + return; + if (el.size - 2 < minSize) { + el.size = minSize; + } else { + el.size -= 2; + } + isExistUpdate = true; + }); + if (isExistUpdate) { + this.draw.render(renderOption); + } + } + bold() { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const selection = this.range.getSelectionElementList(); + if (selection == null ? void 0 : selection.length) { + const noBoldIndex = selection.findIndex((s) => !s.bold); + selection.forEach((el) => { + el.bold = !!~noBoldIndex; + }); + this.draw.render({ isSetCursor: false }); + } else { + const { endIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const enterElement = elementList[endIndex]; + if ((enterElement == null ? void 0 : enterElement.value) === ZERO) { + enterElement.bold = !enterElement.bold; + this.draw.render({ curIndex: endIndex, isCompute: false }); + } + } + } + italic() { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const selection = this.range.getSelectionElementList(); + if (selection == null ? void 0 : selection.length) { + const noItalicIndex = selection.findIndex((s) => !s.italic); + selection.forEach((el) => { + el.italic = !!~noItalicIndex; + }); + this.draw.render({ isSetCursor: false }); + } else { + const { endIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const enterElement = elementList[endIndex]; + if ((enterElement == null ? void 0 : enterElement.value) === ZERO) { + enterElement.italic = !enterElement.italic; + this.draw.render({ curIndex: endIndex, isCompute: false }); + } + } + } + underline(textDecoration) { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const selection = this.range.getSelectionElementList(); + if (selection == null ? void 0 : selection.length) { + const isSetUnderline = selection.some((s) => !s.underline || !textDecoration && s.textDecoration || textDecoration && !s.textDecoration || textDecoration && s.textDecoration && !isObjectEqual(s.textDecoration, textDecoration)); + selection.forEach((el) => { + el.underline = isSetUnderline; + if (isSetUnderline && textDecoration) { + el.textDecoration = textDecoration; + } else { + delete el.textDecoration; + } + }); + this.draw.render({ + isSetCursor: false, + isCompute: false + }); + } else { + const { endIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const enterElement = elementList[endIndex]; + if ((enterElement == null ? void 0 : enterElement.value) === ZERO) { + enterElement.underline = !enterElement.underline; + this.draw.render({ curIndex: endIndex, isCompute: false }); + } + } + } + strikeout() { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const selection = this.range.getSelectionElementList(); + if (selection == null ? void 0 : selection.length) { + const noStrikeoutIndex = selection.findIndex((s) => !s.strikeout); + selection.forEach((el) => { + el.strikeout = !!~noStrikeoutIndex; + }); + this.draw.render({ + isSetCursor: false, + isCompute: false + }); + } else { + const { endIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const enterElement = elementList[endIndex]; + if ((enterElement == null ? void 0 : enterElement.value) === ZERO) { + enterElement.strikeout = !enterElement.strikeout; + this.draw.render({ curIndex: endIndex, isCompute: false }); + } + } + } + superscript() { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const selection = this.range.getSelectionElementList(); + if (!selection) + return; + const superscriptIndex = selection.findIndex((s) => s.type === ElementType.SUPERSCRIPT); + selection.forEach((el) => { + if (~superscriptIndex) { + if (el.type === ElementType.SUPERSCRIPT) { + el.type = ElementType.TEXT; + delete el.actualSize; + } + } else { + if (!el.type || el.type === ElementType.TEXT || el.type === ElementType.SUBSCRIPT) { + el.type = ElementType.SUPERSCRIPT; + } + } + }); + this.draw.render({ isSetCursor: false }); + } + subscript() { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const selection = this.range.getSelectionElementList(); + if (!selection) + return; + const subscriptIndex = selection.findIndex((s) => s.type === ElementType.SUBSCRIPT); + selection.forEach((el) => { + if (~subscriptIndex) { + if (el.type === ElementType.SUBSCRIPT) { + el.type = ElementType.TEXT; + delete el.actualSize; + } + } else { + if (!el.type || el.type === ElementType.TEXT || el.type === ElementType.SUPERSCRIPT) { + el.type = ElementType.SUBSCRIPT; + } + } + }); + this.draw.render({ isSetCursor: false }); + } + color(payload) { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const selection = this.range.getSelectionElementList(); + if (selection == null ? void 0 : selection.length) { + selection.forEach((el) => { + if (payload) { + el.color = payload; + } else { + delete el.color; + } + }); + this.draw.render({ + isSetCursor: false, + isCompute: false + }); + } else { + const { endIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const enterElement = elementList[endIndex]; + if ((enterElement == null ? void 0 : enterElement.value) === ZERO) { + if (payload) { + enterElement.color = payload; + } else { + delete enterElement.color; + } + this.draw.render({ curIndex: endIndex, isCompute: false }); + } + } + } + highlight(payload) { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const selection = this.range.getSelectionElementList(); + if (selection == null ? void 0 : selection.length) { + selection.forEach((el) => { + if (payload) { + el.highlight = payload; + } else { + delete el.highlight; + } + }); + this.draw.render({ + isSetCursor: false, + isCompute: false + }); + } else { + const { endIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const enterElement = elementList[endIndex]; + if ((enterElement == null ? void 0 : enterElement.value) === ZERO) { + if (payload) { + enterElement.highlight = payload; + } else { + delete enterElement.highlight; + } + this.draw.render({ curIndex: endIndex, isCompute: false }); + } + } + } + title(payload) { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const { startIndex, endIndex } = this.range.getRange(); + if (!~startIndex && !~endIndex) + return; + const elementList = this.draw.getElementList(); + const changeElementList = startIndex === endIndex ? this.range.getRangeParagraphElementList() : elementList.slice(startIndex + 1, endIndex + 1); + if (!changeElementList || !changeElementList.length) + return; + const titleId = getUUID(); + const titleOptions = this.draw.getOptions().title; + changeElementList.forEach((el) => { + if (!el.type && el.value === ZERO) + return; + if (payload) { + el.level = payload; + el.titleId = titleId; + if (isTextLikeElement(el)) { + el.size = titleOptions[titleSizeMapping[payload]]; + el.bold = true; + } + } else { + if (el.titleId) { + delete el.titleId; + delete el.title; + delete el.level; + delete el.size; + delete el.bold; + } + } + }); + const isSetCursor = startIndex === endIndex; + const curIndex = isSetCursor ? endIndex : startIndex; + this.draw.render({ curIndex, isSetCursor }); + } + list(listType, listStyle) { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.draw.getListParticle().setList(listType, listStyle); + } + rowFlex(payload) { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + const { startIndex, endIndex } = this.range.getRange(); + if (!~startIndex && !~endIndex) + return; + const rowElementList = this.range.getRangeRowElementList(); + if (!rowElementList) + return; + rowElementList.forEach((element) => { + element.rowFlex = payload; + }); + const isSetCursor = startIndex === endIndex; + const curIndex = isSetCursor ? endIndex : startIndex; + this.draw.render({ curIndex, isSetCursor }); + } + rowMargin(payload) { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + const { startIndex, endIndex } = this.range.getRange(); + if (!~startIndex && !~endIndex) + return; + const rowElementList = this.range.getRangeRowElementList(); + if (!rowElementList) + return; + rowElementList.forEach((element) => { + element.rowMargin = payload; + }); + const isSetCursor = startIndex === endIndex; + const curIndex = isSetCursor ? endIndex : startIndex; + this.draw.render({ curIndex, isSetCursor }); + } + insertTable(row, col) { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const activeControl = this.control.getActiveControl(); + if (activeControl) + return; + this.tableOperate.insertTable(row, col); + } + insertTableTopRow() { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.tableOperate.insertTableTopRow(); + } + insertTableBottomRow() { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.tableOperate.insertTableBottomRow(); + } + insertTableLeftCol() { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.tableOperate.insertTableLeftCol(); + } + insertTableRightCol() { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.tableOperate.insertTableRightCol(); + } + deleteTableRow() { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.tableOperate.deleteTableRow(); + } + deleteTableCol() { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.tableOperate.deleteTableCol(); + } + deleteTable() { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.tableOperate.deleteTable(); + } + mergeTableCell() { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.tableOperate.mergeTableCell(); + } + cancelMergeTableCell() { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.tableOperate.cancelMergeTableCell(); + } + tableTdVerticalAlign(payload) { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.tableOperate.tableTdVerticalAlign(payload); + } + tableBorderType(payload) { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.tableOperate.tableBorderType(payload); + } + tableTdBorderType(payload) { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.tableOperate.tableTdBorderType(payload); + } + tableTdSlashType(payload) { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.tableOperate.tableTdSlashType(payload); + } + tableTdBackgroundColor(payload) { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.tableOperate.tableTdBackgroundColor(payload); + } + tableSelectAll() { + this.tableOperate.tableSelectAll(); + } + hyperlink(payload) { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const activeControl = this.control.getActiveControl(); + if (activeControl) + return; + const { startIndex, endIndex } = this.range.getRange(); + if (!~startIndex && !~endIndex) + return; + const elementList = this.draw.getElementList(); + const { valueList, url } = payload; + const hyperlinkId = getUUID(); + const newElementList = valueList == null ? void 0 : valueList.map((v) => ({ + url, + hyperlinkId, + value: v.value, + type: ElementType.HYPERLINK + })); + if (!newElementList) + return; + const start = startIndex + 1; + formatElementContext(elementList, newElementList, startIndex, { + editorOptions: this.options + }); + this.draw.spliceElementList(elementList, start, startIndex === endIndex ? 0 : endIndex - startIndex, ...newElementList); + const curIndex = start + newElementList.length - 1; + this.range.setRange(curIndex, curIndex); + this.draw.render({ curIndex }); + } + getHyperlinkRange() { + let leftIndex = -1; + let rightIndex = -1; + const { startIndex, endIndex } = this.range.getRange(); + if (!~startIndex && !~endIndex) + return null; + const elementList = this.draw.getElementList(); + const startElement = elementList[startIndex]; + if (startElement.type !== ElementType.HYPERLINK) + return null; + let preIndex = startIndex; + while (preIndex > 0) { + const preElement = elementList[preIndex]; + if (preElement.hyperlinkId !== startElement.hyperlinkId) { + leftIndex = preIndex + 1; + break; + } + preIndex--; + } + let nextIndex = startIndex + 1; + while (nextIndex < elementList.length) { + const nextElement = elementList[nextIndex]; + if (nextElement.hyperlinkId !== startElement.hyperlinkId) { + rightIndex = nextIndex - 1; + break; + } + nextIndex++; + } + if (nextIndex === elementList.length) { + rightIndex = nextIndex - 1; + } + if (!~leftIndex || !~rightIndex) + return null; + return [leftIndex, rightIndex]; + } + deleteHyperlink() { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const hyperRange = this.getHyperlinkRange(); + if (!hyperRange) + return; + const elementList = this.draw.getElementList(); + const [leftIndex, rightIndex] = hyperRange; + this.draw.spliceElementList(elementList, leftIndex, rightIndex - leftIndex + 1); + this.draw.getHyperlinkParticle().clearHyperlinkPopup(); + const newIndex = leftIndex - 1; + this.range.setRange(newIndex, newIndex); + this.draw.render({ + curIndex: newIndex + }); + } + cancelHyperlink() { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const hyperRange = this.getHyperlinkRange(); + if (!hyperRange) + return; + const elementList = this.draw.getElementList(); + const [leftIndex, rightIndex] = hyperRange; + for (let i = leftIndex; i <= rightIndex; i++) { + const element = elementList[i]; + delete element.type; + delete element.url; + delete element.hyperlinkId; + delete element.underline; + } + this.draw.getHyperlinkParticle().clearHyperlinkPopup(); + const { endIndex } = this.range.getRange(); + this.draw.render({ + curIndex: endIndex, + isCompute: false + }); + } + editHyperlink(payload) { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const hyperRange = this.getHyperlinkRange(); + if (!hyperRange) + return; + const elementList = this.draw.getElementList(); + const [leftIndex, rightIndex] = hyperRange; + for (let i = leftIndex; i <= rightIndex; i++) { + const element = elementList[i]; + element.url = payload; + } + this.draw.getHyperlinkParticle().clearHyperlinkPopup(); + const { endIndex } = this.range.getRange(); + this.draw.render({ + curIndex: endIndex, + isCompute: false + }); + } + separator(payload, lineWidth, color) { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const activeControl = this.control.getActiveControl(); + if (activeControl) + return; + const { startIndex, endIndex } = this.range.getRange(); + if (!~startIndex && !~endIndex) + return; + const elementList = this.draw.getElementList(); + let curIndex = -1; + const endElement = elementList[endIndex + 1]; + if (endElement && endElement.type === ElementType.SEPARATOR) { + if (endElement.dashArray && endElement.dashArray.join() === payload.join() && endElement.color === color && endElement.lineWidth === lineWidth) { + return; + } + curIndex = endIndex; + endElement.dashArray = payload; + endElement.color = color; + endElement.lineWidth = lineWidth; + } else { + const newElement = { + value: WRAP, + type: ElementType.SEPARATOR, + dashArray: payload, + lineWidth, + color + }; + formatElementContext(elementList, [newElement], startIndex, { + editorOptions: this.options + }); + if (startIndex !== 0 && elementList[startIndex].value === ZERO) { + this.draw.spliceElementList(elementList, startIndex, 1, newElement); + curIndex = startIndex - 1; + } else { + this.draw.spliceElementList(elementList, startIndex + 1, 0, newElement); + curIndex = startIndex; + } + } + this.range.setRange(curIndex, curIndex); + this.draw.render({ curIndex }); + } + pageBreak() { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const activeControl = this.control.getActiveControl(); + if (activeControl) + return; + this.insertElementList([ + { + type: ElementType.PAGE_BREAK, + value: WRAP + } + ]); + } + addWatermark(payload) { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + const options = this.draw.getOptions(); + const { color, size, opacity, font } = defaultWatermarkOption; + options.watermark.data = payload.data; + options.watermark.color = payload.color || color; + options.watermark.size = payload.size || size; + options.watermark.opacity = payload.opacity || opacity; + options.watermark.font = payload.font || font; + this.draw.render({ + isSetCursor: false, + isSubmitHistory: false, + isCompute: false + }); + } + deleteWatermark() { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + const options = this.draw.getOptions(); + if (options.watermark && options.watermark.data) { + options.watermark = { ...defaultWatermarkOption }; + this.draw.render({ + isSetCursor: false, + isSubmitHistory: false, + isCompute: false + }); + } + } + image(payload) { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const { startIndex, endIndex } = this.range.getRange(); + if (!~startIndex && !~endIndex) + return; + const { value, width, height, imgDisplay } = payload; + this.insertElementList([ + { + value, + width, + height, + id: getUUID(), + type: ElementType.IMAGE, + imgDisplay + } + ]); + } + search(payload) { + this.searchManager.setSearchKeyword(payload); + this.draw.render({ + isSetCursor: false, + isSubmitHistory: false + }); + } + searchNavigatePre() { + const index2 = this.searchManager.searchNavigatePre(); + if (index2 === null) + return; + this.draw.render({ + isSetCursor: false, + isSubmitHistory: false, + isCompute: false, + isLazy: false + }); + } + searchNavigateNext() { + const index2 = this.searchManager.searchNavigateNext(); + if (index2 === null) + return; + this.draw.render({ + isSetCursor: false, + isSubmitHistory: false, + isCompute: false, + isLazy: false + }); + } + getSearchNavigateInfo() { + return this.searchManager.getSearchNavigateInfo(); + } + replace(payload) { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + if (!payload || new RegExp(`${ZERO}`, "g").test(payload)) + return; + const matchList = this.draw.getSearch().getSearchMatchList(); + if (!matchList.length) + return; + let pageDiffCount = 0; + let tableDiffCount = 0; + let curGroupId = ""; + let curTdId = ""; + let firstMatchIndex = -1; + const elementList = this.draw.getOriginalElementList(); + for (let m = 0; m < matchList.length; m++) { + const match = matchList[m]; + if (match.type === EditorContext.TABLE) { + const { tableIndex, trIndex, tdIndex, index: index2, tdId } = match; + if (curTdId && tdId !== curTdId) { + tableDiffCount = 0; + } + curTdId = tdId; + const curTableIndex = tableIndex + pageDiffCount; + const tableElementList = elementList[curTableIndex].trList[trIndex].tdList[tdIndex].value; + const curIndex = index2 + tableDiffCount; + const tableElement = tableElementList[curIndex]; + if (curGroupId === match.groupId) { + this.draw.spliceElementList(tableElementList, curIndex, 1); + tableDiffCount--; + continue; + } + for (let p = 0; p < payload.length; p++) { + const value = payload[p]; + if (p === 0) { + tableElement.value = value; + } else { + this.draw.spliceElementList(tableElementList, curIndex + p, 0, { + ...tableElement, + value + }); + tableDiffCount++; + } + } + } else { + const curIndex = match.index + pageDiffCount; + const element = elementList[curIndex]; + if (element.type === ElementType.CONTROL && element.controlComponent !== ControlComponent.VALUE) { + continue; + } + if (!~firstMatchIndex) { + firstMatchIndex = m; + } + if (curGroupId === match.groupId) { + this.draw.spliceElementList(elementList, curIndex, 1); + pageDiffCount--; + continue; + } + for (let p = 0; p < payload.length; p++) { + const value = payload[p]; + if (p === 0) { + element.value = value; + } else { + this.draw.spliceElementList(elementList, curIndex + p, 0, { + ...element, + value + }); + pageDiffCount++; + } + } + } + curGroupId = match.groupId; + } + if (!~firstMatchIndex) + return; + const firstMatch = matchList[firstMatchIndex]; + const firstIndex = firstMatch.index + (payload.length - 1); + if (firstMatch.type === EditorContext.TABLE) { + const { tableIndex, trIndex, tdIndex, index: index2 } = firstMatch; + const element = elementList[tableIndex].trList[trIndex].tdList[tdIndex].value[index2]; + this.position.setPositionContext({ + isTable: true, + index: tableIndex, + trIndex, + tdIndex, + tdId: element.tdId, + trId: element.trId, + tableId: element.tableId + }); + } else { + this.position.setPositionContext({ + isTable: false + }); + } + this.range.setRange(firstIndex, firstIndex); + this.draw.render({ + curIndex: firstIndex + }); + } + async print() { + const { scale, printPixelRatio, paperDirection, width, height } = this.options; + if (scale !== 1) { + this.draw.setPageScale(1); + } + const base64List = await this.draw.getDataURL({ + pixelRatio: printPixelRatio, + mode: EditorMode.PRINT + }); + printImageBase64(base64List, { + width, + height, + direction: paperDirection + }); + if (scale !== 1) { + this.draw.setPageScale(scale); + } + } + replaceImageElement(payload) { + const { startIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const element = elementList[startIndex]; + if (!element || element.type !== ElementType.IMAGE) + return; + element.id = getUUID(); + element.value = payload; + this.draw.render({ + isSetCursor: false + }); + } + saveAsImageElement() { + const { startIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const element = elementList[startIndex]; + if (!element || element.type !== ElementType.IMAGE) + return; + downloadFile(element.value, `${element.id}.png`); + } + changeImageDisplay(element, display) { + if (element.imgDisplay === display) + return; + element.imgDisplay = display; + const { startIndex, endIndex } = this.range.getRange(); + if (display === ImageDisplay.SURROUND || display === ImageDisplay.FLOAT_TOP || display === ImageDisplay.FLOAT_BOTTOM) { + const positionList = this.position.getPositionList(); + const { pageNo, coordinate: { leftTop } } = positionList[startIndex]; + element.imgFloatPosition = { + pageNo, + x: leftTop[0], + y: leftTop[1] + }; + } else { + delete element.imgFloatPosition; + } + this.draw.getPreviewer().clearResizer(); + this.draw.render({ + isSetCursor: true, + curIndex: endIndex + }); + } + getImage(payload) { + return this.draw.getDataURL(payload); + } + getOptions() { + return this.options; + } + getValue(options) { + return this.draw.getValue(options); + } + getHTML() { + const options = this.options; + const headerElementList = this.draw.getHeaderElementList(); + const mainElementList = this.draw.getOriginalMainElementList(); + const footerElementList = this.draw.getFooterElementList(); + return { + header: createDomFromElementList(headerElementList, options).innerHTML, + main: createDomFromElementList(mainElementList, options).innerHTML, + footer: createDomFromElementList(footerElementList, options).innerHTML + }; + } + getText() { + const headerElementList = this.draw.getHeaderElementList(); + const mainElementList = this.draw.getOriginalMainElementList(); + const footerElementList = this.draw.getFooterElementList(); + return { + header: getTextFromElementList(headerElementList), + main: getTextFromElementList(mainElementList), + footer: getTextFromElementList(footerElementList) + }; + } + getWordCount() { + return this.workerManager.getWordCount(); + } + getCursorPosition() { + return this.position.getCursorPosition(); + } + getRange() { + return deepClone(this.range.getRange()); + } + getRangeText() { + return this.range.toString(); + } + getRangeContext() { + const range = this.range.getRange(); + const { startIndex, endIndex } = range; + if (!~startIndex && !~endIndex) + return null; + const isCollapsed = startIndex === endIndex; + const selectionText = this.range.toString(); + const selectionElementList = zipElementList(this.range.getSelectionElementList() || []); + const elementList = this.draw.getElementList(); + const startElement = pickElementAttr(elementList[isCollapsed ? startIndex : startIndex + 1], { + extraPickAttrs: ["id"] + }); + const endElement = pickElementAttr(elementList[endIndex], { + extraPickAttrs: ["id"] + }); + const positionList = this.position.getPositionList(); + const startPageNo = positionList[startIndex].pageNo; + const endPageNo = positionList[endIndex].pageNo; + const rangeRects = []; + const height = this.draw.getOriginalHeight(); + const pageGap = this.draw.getOriginalPageGap(); + const selectionPositionList = this.position.getSelectionPositionList(); + if (selectionPositionList) { + let currentRowNo = null; + let currentX = 0; + let rangeRect = null; + for (let p = 0; p < selectionPositionList.length; p++) { + const { rowNo, pageNo, coordinate: { leftTop, rightTop }, lineHeight } = selectionPositionList[p]; + if (currentRowNo === null || currentRowNo !== rowNo) { + if (rangeRect) { + rangeRects.push(rangeRect); + } + rangeRect = { + x: leftTop[0], + y: leftTop[1] + pageNo * (height + pageGap), + width: rightTop[0] - leftTop[0], + height: lineHeight + }; + currentRowNo = rowNo; + currentX = leftTop[0]; + } else { + rangeRect.width = rightTop[0] - currentX; + } + if (p === selectionPositionList.length - 1 && rangeRect) { + rangeRects.push(rangeRect); + } + } + } else { + const positionList2 = this.position.getPositionList(); + const position = positionList2[endIndex]; + const { coordinate: { rightTop }, pageNo, lineHeight } = position; + rangeRects.push({ + x: rightTop[0], + y: rightTop[1] + pageNo * (height + pageGap), + width: 0, + height: lineHeight + }); + } + const zone2 = this.draw.getZone().getZone(); + const { isTable, trIndex, tdIndex, index: index2 } = this.position.getPositionContext(); + let tableElement = null; + if (isTable) { + const originalElementList = this.draw.getOriginalElementList(); + const originTableElement = originalElementList[index2] || null; + if (originTableElement) { + tableElement = zipElementList([originTableElement])[0]; + } + } + let titleId = null; + let titleStartPageNo = null; + let start = startIndex - 1; + while (start > 0) { + const curElement = elementList[start]; + const preElement = elementList[start - 1]; + if (curElement.titleId && curElement.titleId !== (preElement == null ? void 0 : preElement.titleId)) { + titleId = curElement.titleId; + titleStartPageNo = positionList[start].pageNo; + break; + } + start--; + } + return deepClone({ + isCollapsed, + startElement, + endElement, + startPageNo, + endPageNo, + rangeRects, + zone: zone2, + isTable, + trIndex: trIndex != null ? trIndex : null, + tdIndex: tdIndex != null ? tdIndex : null, + tableElement, + selectionText, + selectionElementList, + titleId, + titleStartPageNo + }); + } + getRangeRow() { + const rowElementList = this.range.getRangeRowElementList(); + return rowElementList ? zipElementList(rowElementList) : null; + } + getRangeParagraph() { + const paragraphElementList = this.range.getRangeParagraphElementList(); + return paragraphElementList ? zipElementList(paragraphElementList) : null; + } + getKeywordRangeList(payload) { + return this.range.getKeywordRangeList(payload); + } + pageMode(payload) { + this.draw.setPageMode(payload); + } + pageScaleRecovery() { + const { scale } = this.options; + if (scale !== 1) { + this.draw.setPageScale(1); + } + } + pageScaleMinus() { + const { scale } = this.options; + const nextScale = scale * 10 - 1; + if (nextScale >= 5) { + this.draw.setPageScale(nextScale / 10); + } + } + pageScaleAdd() { + const { scale } = this.options; + const nextScale = scale * 10 + 1; + if (nextScale <= 30) { + this.draw.setPageScale(nextScale / 10); + } + } + paperSize(width, height) { + this.draw.setPaperSize(width, height); + } + paperDirection(payload) { + this.draw.setPaperDirection(payload); + } + getPaperMargin() { + return this.options.margins; + } + setPaperMargin(payload) { + return this.draw.setPaperMargin(payload); + } + insertElementList(payload) { + if (!payload.length) + return; + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const cloneElementList = deepClone(payload); + const { startIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + formatElementContext(elementList, cloneElementList, startIndex, { + isBreakWhenWrap: true, + editorOptions: this.options + }); + this.draw.insertElementList(cloneElementList); + } + appendElementList(elementList, options) { + if (!elementList.length) + return; + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.draw.appendElementList(deepClone(elementList), options); + } + updateElementById(payload) { + function getElementIndexById(elementList) { + for (let e = 0; e < elementList.length; e++) { + const element = elementList[e]; + if (element.id === payload.id) { + return e; + } + } + return -1; + } + const getElementListFnList = [ + this.draw.getOriginalMainElementList, + this.draw.getHeaderElementList, + this.draw.getFooterElementList + ]; + for (const getElementList of getElementListFnList) { + const elementList = getElementList.call(this.draw); + const elementIndex = getElementIndexById(elementList); + if (~elementIndex) { + elementList[elementIndex] = { + ...elementList[elementIndex], + ...payload.properties + }; + formatElementList(zipElementList([elementList[elementIndex]]), { + isHandleFirstElement: false, + editorOptions: this.options + }); + this.draw.render({ + isSetCursor: false + }); + break; + } + } + } + setValue(payload, options) { + this.draw.setValue(payload, options); + } + removeControl() { + const { startIndex, endIndex } = this.range.getRange(); + if (startIndex !== endIndex) + return; + const elementList = this.draw.getElementList(); + const element = elementList[startIndex]; + if (!element.controlId) + return; + const control = this.draw.getControl(); + const newIndex = control.removeControl(startIndex); + if (newIndex === null) + return; + this.range.setRange(newIndex, newIndex); + this.draw.render({ + curIndex: newIndex + }); + } + setLocale(payload) { + this.i18n.setLocale(payload); + } + getLocale() { + return this.i18n.getLocale(); + } + getCatalog() { + return this.workerManager.getCatalog(); + } + locationCatalog(titleId) { + var _a; + const elementList = this.draw.getMainElementList(); + let newIndex = -1; + for (let e = 0; e < elementList.length; e++) { + const element = elementList[e]; + if (element.titleId === titleId && ((_a = elementList[e + 1]) == null ? void 0 : _a.titleId) !== titleId) { + newIndex = e; + break; + } + } + if (!~newIndex) + return; + this.range.setRange(newIndex, newIndex); + this.draw.render({ + curIndex: newIndex, + isCompute: false, + isSubmitHistory: false + }); + } + wordTool() { + const elementList = this.draw.getMainElementList(); + let isApply = false; + for (let i = 0; i < elementList.length; i++) { + const element = elementList[i]; + if (element.value === ZERO) { + while (i + 1 < elementList.length) { + const nextElement = elementList[i + 1]; + if (nextElement.value !== ZERO && nextElement.value !== NBSP) + break; + elementList.splice(i + 1, 1); + isApply = true; + } + } + } + if (!isApply) { + const isCollapsed = this.range.getIsCollapsed(); + this.draw.getCursor().drawCursor({ + isShow: isCollapsed + }); + } else { + this.draw.render({ + isSetCursor: false + }); + } + } + setHTML(payload) { + const { header, main, footer } = payload; + const innerWidth = this.draw.getOriginalInnerWidth(); + const getElementList = (htmlText) => htmlText !== void 0 ? getElementListByHTML(htmlText, { + innerWidth + }) : void 0; + this.setValue({ + header: getElementList(header), + main: getElementList(main), + footer: getElementList(footer) + }); + } + setGroup() { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return null; + return this.draw.getGroup().setGroup(); + } + deleteGroup(groupId) { + const isReadonly = this.draw.isReadonly(); + if (isReadonly) + return; + this.draw.getGroup().deleteGroup(groupId); + } + getGroupIds() { + return this.draw.getWorkerManager().getGroupIds(); + } + locationGroup(groupId) { + const elementList = this.draw.getOriginalMainElementList(); + const context = this.draw.getGroup().getContextByGroupId(elementList, groupId); + if (!context) + return; + const { isTable, index: index2, trIndex, tdIndex, tdId, trId, tableId, endIndex } = context; + this.position.setPositionContext({ + isTable, + index: index2, + trIndex, + tdIndex, + tdId, + trId, + tableId + }); + this.range.setRange(endIndex, endIndex); + this.draw.render({ + curIndex: endIndex, + isCompute: false, + isSubmitHistory: false + }); + } + setZone(zone2) { + this.draw.getZone().setZone(zone2); + } + getControlValue(payload) { + return this.draw.getControl().getValueById(payload); + } + setControlValue(payload) { + this.draw.getControl().setValueById(payload); + } + setControlExtension(payload) { + this.draw.getControl().setExtensionById(payload); + } + setControlProperties(payload) { + this.draw.getControl().setPropertiesById(payload); + } + setControlHighlight(payload) { + this.draw.getControl().setHighlightList(payload); + this.draw.render({ + isSubmitHistory: false + }); + } + updateOptions(payload) { + const newOption = mergeOption(payload); + Object.entries(newOption).forEach(([key, value]) => { + Reflect.set(this.options, key, value); + }); + this.forceUpdate(); + } + getControlList() { + return this.draw.getControl().getList(); + } + locationControl(controlId, options) { + const isLocationAfter = (options == null ? void 0 : options.position) === LocationPosition.AFTER; + function location(elementList, zone2) { + let i = 0; + while (i < elementList.length) { + const element = elementList[i]; + i++; + if (element.type === ElementType.TABLE) { + const trList = element.trList; + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + const locationContext = location(td.value, zone2); + if (locationContext) { + return { + ...locationContext, + positionContext: { + isTable: true, + index: i - 1, + trIndex: r, + tdIndex: d, + tdId: element.tdId, + trId: element.trId, + tableId: element.tableId + } + }; + } + } + } + } + if ((element == null ? void 0 : element.controlId) !== controlId) + continue; + let curIndex = i - 1; + if (isLocationAfter) { + curIndex -= 1; + if (element.controlComponent !== ControlComponent.PLACEHOLDER && element.controlComponent !== ControlComponent.POSTFIX) { + continue; + } + } + return { + zone: zone2, + range: { + startIndex: curIndex, + endIndex: curIndex + }, + positionContext: { + isTable: false + } + }; + } + return null; + } + const data2 = [ + { + zone: EditorZone.HEADER, + elementList: this.draw.getHeaderElementList() + }, + { + zone: EditorZone.MAIN, + elementList: this.draw.getOriginalMainElementList() + }, + { + zone: EditorZone.FOOTER, + elementList: this.draw.getFooterElementList() + } + ]; + for (const context of data2) { + const locationContext = location(context.elementList, context.zone); + if (locationContext) { + this.setZone(locationContext.zone); + this.position.setPositionContext(locationContext.positionContext); + this.range.replaceRange(locationContext.range); + this.draw.render({ + curIndex: locationContext.range.startIndex, + isCompute: false, + isSubmitHistory: false + }); + break; + } + } + } + insertControl(payload) { + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const cloneElement = deepClone(payload); + const { startIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const copyElement = getAnchorElement(elementList, startIndex); + if (!copyElement) + return; + const cloneAttr = [ + ...TABLE_CONTEXT_ATTR, + ...EDITOR_ROW_ATTR, + ...LIST_CONTEXT_ATTR + ]; + cloneProperty(cloneAttr, copyElement, cloneElement); + this.draw.insertElementList([cloneElement]); + } + getContainer() { + return this.draw.getContainer(); + } + getTitleValue(payload) { + const { conceptId } = payload; + const result = []; + const getValue = (elementList, zone2) => { + var _a; + let i = 0; + while (i < elementList.length) { + const element = elementList[i]; + i++; + if (element.type === ElementType.TABLE) { + const trList = element.trList; + for (let r = 0; r < trList.length; r++) { + const tr = trList[r]; + for (let d = 0; d < tr.tdList.length; d++) { + const td = tr.tdList[d]; + getValue(td.value, zone2); + } + } + } + if (((_a = element == null ? void 0 : element.title) == null ? void 0 : _a.conceptId) !== conceptId) + continue; + const valueList = []; + let j = i; + while (j < elementList.length) { + const nextElement = elementList[j]; + j++; + if (element.titleId === nextElement.titleId) + continue; + if (nextElement.level && titleOrderNumberMapping[nextElement.level] <= titleOrderNumberMapping[element.level]) { + break; + } + valueList.push(nextElement); + } + result.push({ + ...element.title, + value: getTextFromElementList(valueList), + elementList: zipElementList(valueList), + zone: zone2 + }); + i = j; + } + }; + const data2 = [ + { + zone: EditorZone.HEADER, + elementList: this.draw.getHeaderElementList() + }, + { + zone: EditorZone.MAIN, + elementList: this.draw.getOriginalMainElementList() + }, + { + zone: EditorZone.FOOTER, + elementList: this.draw.getFooterElementList() + } + ]; + for (const { zone: zone2, elementList } of data2) { + getValue(elementList, zone2); + } + return result; + } + getPositionContextByEvent(evt) { + var _a, _b, _c; + const pageIndex = (_a = evt.target) == null ? void 0 : _a.dataset.index; + if (!pageIndex) + return null; + const pageNo = Number(pageIndex); + const positionContext = this.position.getPositionByXY({ + x: evt.offsetX, + y: evt.offsetY, + pageNo + }); + const { isDirectHit, isTable, index: index2, trIndex, tdIndex, tdValueIndex, zone: zone2 } = positionContext; + if (!isDirectHit || zone2 && zone2 !== this.zone.getZone()) + return null; + let element = null; + const elementList = this.draw.getOriginalElementList(); + let position = null; + const positionList = this.position.getOriginalPositionList(); + if (isTable) { + const td = (_b = elementList[index2].trList) == null ? void 0 : _b[trIndex].tdList[tdIndex]; + element = (td == null ? void 0 : td.value[tdValueIndex]) || null; + position = ((_c = td == null ? void 0 : td.positionList) == null ? void 0 : _c[tdValueIndex]) || null; + } else { + element = elementList[index2] || null; + position = positionList[index2] || null; + } + let rangeRect = null; + if (position) { + const { pageNo: pageNo2, coordinate: { leftTop, rightTop }, lineHeight } = position; + const height = this.draw.getOriginalHeight(); + const pageGap = this.draw.getOriginalPageGap(); + rangeRect = { + x: leftTop[0], + y: leftTop[1] + pageNo2 * (height + pageGap), + width: rightTop[0] - leftTop[0], + height: lineHeight + }; + } + return { + pageNo, + element, + rangeRect + }; + } + insertTitle(payload) { + var _a; + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled(); + if (isDisabled) + return; + const cloneElement = deepClone(payload); + const { startIndex } = this.range.getRange(); + const elementList = this.draw.getElementList(); + const copyElement = getAnchorElement(elementList, startIndex); + if (!copyElement) + return; + const cloneAttr = [ + ...TABLE_CONTEXT_ATTR, + ...EDITOR_ROW_ATTR, + ...LIST_CONTEXT_ATTR + ]; + (_a = cloneElement.valueList) == null ? void 0 : _a.forEach((valueItem) => { + cloneProperty(cloneAttr, copyElement, valueItem); + }); + this.draw.insertElementList([cloneElement]); + } + focus(payload) { + const { position = LocationPosition.AFTER } = payload || {}; + const curIndex = position === LocationPosition.BEFORE ? 0 : this.draw.getOriginalMainElementList().length - 1; + this.range.setRange(curIndex, curIndex); + this.draw.render({ + curIndex, + isCompute: false, + isSubmitHistory: false + }); + const positionList = this.draw.getPosition().getPositionList(); + this.draw.getCursor().moveCursorToVisible({ + cursorPosition: positionList[curIndex], + direction: MoveDirection.DOWN + }); + } +} +class Listener { + constructor() { + this.rangeStyleChange = null; + this.visiblePageNoListChange = null; + this.intersectionPageNoChange = null; + this.pageSizeChange = null; + this.pageScaleChange = null; + this.saved = null; + this.contentChange = null; + this.controlChange = null; + this.pageModeChange = null; + this.zoneChange = null; + } +} +class Register { + constructor(payload) { + const { contextMenu, shortcut, i18n } = payload; + this.contextMenuList = contextMenu.registerContextMenuList.bind(contextMenu); + this.getContextMenuList = contextMenu.getContextMenuList.bind(contextMenu); + this.shortcutList = shortcut.registerShortcutList.bind(shortcut); + this.langMap = i18n.registerLangMap.bind(i18n); + } +} +const NAME_PLACEHOLDER = { + SELECTED_TEXT: "%s" +}; +const INTERNAL_CONTEXT_MENU_KEY = { + GLOBAL: { + CUT: "globalCut", + COPY: "globalCopy", + PASTE: "globalPaste", + SELECT_ALL: "globalSelectAll", + PRINT: "globalPrint" + }, + CONTROL: { + DELETE: "controlDelete" + }, + HYPERLINK: { + DELETE: "hyperlinkDelete", + CANCEL: "hyperlinkCancel", + EDIT: "hyperlinkEdit" + }, + IMAGE: { + CHANGE: "imageChange", + SAVE_AS: "imageSaveAs", + TEXT_WRAP: "imageTextWrap", + TEXT_WRAP_EMBED: "imageTextWrapEmbed", + TEXT_WRAP_UP_DOWN: "imageTextWrapUpDown", + TEXT_WRAP_SURROUND: "imageTextWrapSurround", + TEXT_WRAP_FLOAT_TOP: "imageTextWrapFloatTop", + TEXT_WRAP_FLOAT_BOTTOM: "imageTextWrapFloatBottom" + }, + TABLE: { + BORDER: "border", + BORDER_ALL: "tableBorderAll", + BORDER_EMPTY: "tableBorderEmpty", + BORDER_EXTERNAL: "tableBorderExternal", + BORDER_TD: "tableBorderTd", + BORDER_TD_TOP: "tableBorderTdTop", + BORDER_TD_RIGHT: "tableBorderTdRight", + BORDER_TD_BOTTOM: "tableBorderTdBottom", + BORDER_TD_LEFT: "tableBorderTdLeft", + BORDER_TD_FORWARD: "tableBorderTdForward", + BORDER_TD_BACK: "tableBorderTdBack", + VERTICAL_ALIGN: "tableVerticalAlign", + VERTICAL_ALIGN_TOP: "tableVerticalAlignTop", + VERTICAL_ALIGN_MIDDLE: "tableVerticalAlignMiddle", + VERTICAL_ALIGN_BOTTOM: "tableVerticalAlignBottom", + INSERT_ROW_COL: "tableInsertRowCol", + INSERT_TOP_ROW: "tableInsertTopRow", + INSERT_BOTTOM_ROW: "tableInsertBottomRow", + INSERT_LEFT_COL: "tableInsertLeftCol", + INSERT_RIGHT_COL: "tableInsertRightCol", + DELETE_ROW_COL: "tableDeleteRowCol", + DELETE_ROW: "tableDeleteRow", + DELETE_COL: "tableDeleteCol", + DELETE_TABLE: "tableDeleteTable", + MERGE_CELL: "tableMergeCell", + CANCEL_MERGE_CELL: "tableCancelMergeCell" + } +}; +const { CONTROL: { DELETE: DELETE$1 } } = INTERNAL_CONTEXT_MENU_KEY; +const controlMenus = [ + { + key: DELETE$1, + i18nPath: "contextmenu.control.delete", + when: (payload) => { + var _a; + return !payload.isReadonly && !payload.editorHasSelection && !!((_a = payload.startElement) == null ? void 0 : _a.controlId) && payload.options.mode !== EditorMode.FORM; + }, + callback: (command) => { + command.executeRemoveControl(); + } + } +]; +const { GLOBAL: { CUT, COPY, PASTE, SELECT_ALL, PRINT } } = INTERNAL_CONTEXT_MENU_KEY; +const globalMenus = [ + { + key: CUT, + i18nPath: "contextmenu.global.cut", + shortCut: `${isApple ? "\u2318" : "Ctrl"} + X`, + when: (payload) => { + return !payload.isReadonly; + }, + callback: (command) => { + command.executeCut(); + } + }, + { + key: COPY, + i18nPath: "contextmenu.global.copy", + shortCut: `${isApple ? "\u2318" : "Ctrl"} + C`, + when: (payload) => { + return payload.editorHasSelection || payload.isCrossRowCol; + }, + callback: (command) => { + command.executeCopy(); + } + }, + { + key: PASTE, + i18nPath: "contextmenu.global.paste", + shortCut: `${isApple ? "\u2318" : "Ctrl"} + V`, + when: (payload) => { + return !payload.isReadonly && payload.editorTextFocus; + }, + callback: (command) => { + command.executePaste(); + } + }, + { + key: SELECT_ALL, + i18nPath: "contextmenu.global.selectAll", + shortCut: `${isApple ? "\u2318" : "Ctrl"} + A`, + when: (payload) => { + return payload.editorTextFocus; + }, + callback: (command) => { + command.executeSelectAll(); + } + }, + { + isDivider: true + }, + { + key: PRINT, + i18nPath: "contextmenu.global.print", + icon: "print", + when: () => true, + callback: (command) => { + command.executePrint(); + } + } +]; +const { HYPERLINK: { DELETE, CANCEL, EDIT } } = INTERNAL_CONTEXT_MENU_KEY; +const hyperlinkMenus = [ + { + key: DELETE, + i18nPath: "contextmenu.hyperlink.delete", + when: (payload) => { + var _a; + return !payload.isReadonly && ((_a = payload.startElement) == null ? void 0 : _a.type) === ElementType.HYPERLINK; + }, + callback: (command) => { + command.executeDeleteHyperlink(); + } + }, + { + key: CANCEL, + i18nPath: "contextmenu.hyperlink.cancel", + when: (payload) => { + var _a; + return !payload.isReadonly && ((_a = payload.startElement) == null ? void 0 : _a.type) === ElementType.HYPERLINK; + }, + callback: (command) => { + command.executeCancelHyperlink(); + } + }, + { + key: EDIT, + i18nPath: "contextmenu.hyperlink.edit", + when: (payload) => { + var _a; + return !payload.isReadonly && ((_a = payload.startElement) == null ? void 0 : _a.type) === ElementType.HYPERLINK; + }, + callback: (command, context) => { + var _a; + const url = window.prompt("\u7F16\u8F91\u94FE\u63A5", (_a = context.startElement) == null ? void 0 : _a.url); + if (url) { + command.executeEditHyperlink(url); + } + } + } +]; +const { IMAGE: { CHANGE, SAVE_AS, TEXT_WRAP, TEXT_WRAP_EMBED, TEXT_WRAP_UP_DOWN, TEXT_WRAP_SURROUND, TEXT_WRAP_FLOAT_TOP, TEXT_WRAP_FLOAT_BOTTOM } } = INTERNAL_CONTEXT_MENU_KEY; +const imageMenus = [ + { + key: CHANGE, + i18nPath: "contextmenu.image.change", + icon: "image-change", + when: (payload) => { + var _a; + return !payload.isReadonly && !payload.editorHasSelection && ((_a = payload.startElement) == null ? void 0 : _a.type) === ElementType.IMAGE; + }, + callback: (command) => { + const proxyInputFile = document.createElement("input"); + proxyInputFile.type = "file"; + proxyInputFile.accept = ".png, .jpg, .jpeg"; + proxyInputFile.onchange = () => { + const file = proxyInputFile.files[0]; + const fileReader = new FileReader(); + fileReader.readAsDataURL(file); + fileReader.onload = () => { + const value = fileReader.result; + command.executeReplaceImageElement(value); + }; + }; + proxyInputFile.click(); + } + }, + { + key: SAVE_AS, + i18nPath: "contextmenu.image.saveAs", + icon: "image", + when: (payload) => { + var _a; + return !payload.editorHasSelection && ((_a = payload.startElement) == null ? void 0 : _a.type) === ElementType.IMAGE; + }, + callback: (command) => { + command.executeSaveAsImageElement(); + } + }, + { + key: TEXT_WRAP, + i18nPath: "contextmenu.image.textWrap", + when: (payload) => { + var _a; + return !payload.isReadonly && !payload.editorHasSelection && ((_a = payload.startElement) == null ? void 0 : _a.type) === ElementType.IMAGE; + }, + childMenus: [ + { + key: TEXT_WRAP_EMBED, + i18nPath: "contextmenu.image.textWrapType.embed", + when: () => true, + callback: (command, context) => { + command.executeChangeImageDisplay(context.startElement, ImageDisplay.BLOCK); + } + }, + { + key: TEXT_WRAP_UP_DOWN, + i18nPath: "contextmenu.image.textWrapType.upDown", + when: () => true, + callback: (command, context) => { + command.executeChangeImageDisplay(context.startElement, ImageDisplay.INLINE); + } + }, + { + key: TEXT_WRAP_SURROUND, + i18nPath: "contextmenu.image.textWrapType.surround", + when: () => true, + callback: (command, context) => { + command.executeChangeImageDisplay(context.startElement, ImageDisplay.SURROUND); + } + }, + { + key: TEXT_WRAP_FLOAT_TOP, + i18nPath: "contextmenu.image.textWrapType.floatTop", + when: () => true, + callback: (command, context) => { + command.executeChangeImageDisplay(context.startElement, ImageDisplay.FLOAT_TOP); + } + }, + { + key: TEXT_WRAP_FLOAT_BOTTOM, + i18nPath: "contextmenu.image.textWrapType.floatBottom", + when: () => true, + callback: (command, context) => { + command.executeChangeImageDisplay(context.startElement, ImageDisplay.FLOAT_BOTTOM); + } + } + ] + } +]; +const { TABLE: { BORDER, BORDER_ALL, BORDER_EMPTY, BORDER_EXTERNAL, BORDER_TD, BORDER_TD_TOP, BORDER_TD_LEFT, BORDER_TD_BOTTOM, BORDER_TD_RIGHT, BORDER_TD_BACK, BORDER_TD_FORWARD, VERTICAL_ALIGN, VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_MIDDLE, VERTICAL_ALIGN_BOTTOM, INSERT_ROW_COL, INSERT_TOP_ROW, INSERT_BOTTOM_ROW, INSERT_LEFT_COL, INSERT_RIGHT_COL, DELETE_ROW_COL, DELETE_ROW, DELETE_COL, DELETE_TABLE, MERGE_CELL, CANCEL_MERGE_CELL } } = INTERNAL_CONTEXT_MENU_KEY; +const tableMenus = [ + { + isDivider: true + }, + { + key: BORDER, + i18nPath: "contextmenu.table.border", + icon: "border-all", + when: (payload) => { + return !payload.isReadonly && payload.isInTable && payload.options.mode !== EditorMode.FORM; + }, + childMenus: [ + { + key: BORDER_ALL, + i18nPath: "contextmenu.table.borderAll", + icon: "border-all", + when: () => true, + callback: (command) => { + command.executeTableBorderType(TableBorder.ALL); + } + }, + { + key: BORDER_EMPTY, + i18nPath: "contextmenu.table.borderEmpty", + icon: "border-empty", + when: () => true, + callback: (command) => { + command.executeTableBorderType(TableBorder.EMPTY); + } + }, + { + key: BORDER_EXTERNAL, + i18nPath: "contextmenu.table.borderExternal", + icon: "border-external", + when: () => true, + callback: (command) => { + command.executeTableBorderType(TableBorder.EXTERNAL); + } + }, + { + key: BORDER_TD, + i18nPath: "contextmenu.table.borderTd", + icon: "border-td", + when: () => true, + childMenus: [ + { + key: BORDER_TD_TOP, + i18nPath: "contextmenu.table.borderTdTop", + icon: "border-td-top", + when: () => true, + callback: (command) => { + command.executeTableTdBorderType(TdBorder.TOP); + } + }, + { + key: BORDER_TD_RIGHT, + i18nPath: "contextmenu.table.borderTdRight", + icon: "border-td-right", + when: () => true, + callback: (command) => { + command.executeTableTdBorderType(TdBorder.RIGHT); + } + }, + { + key: BORDER_TD_BOTTOM, + i18nPath: "contextmenu.table.borderTdBottom", + icon: "border-td-bottom", + when: () => true, + callback: (command) => { + command.executeTableTdBorderType(TdBorder.BOTTOM); + } + }, + { + key: BORDER_TD_LEFT, + i18nPath: "contextmenu.table.borderTdLeft", + icon: "border-td-left", + when: () => true, + callback: (command) => { + command.executeTableTdBorderType(TdBorder.LEFT); + } + }, + { + key: BORDER_TD_FORWARD, + i18nPath: "contextmenu.table.borderTdForward", + icon: "border-td-forward", + when: () => true, + callback: (command) => { + command.executeTableTdSlashType(TdSlash.FORWARD); + } + }, + { + key: BORDER_TD_BACK, + i18nPath: "contextmenu.table.borderTdBack", + icon: "border-td-back", + when: () => true, + callback: (command) => { + command.executeTableTdSlashType(TdSlash.BACK); + } + } + ] + } + ] + }, + { + key: VERTICAL_ALIGN, + i18nPath: "contextmenu.table.verticalAlign", + icon: "vertical-align", + when: (payload) => { + return !payload.isReadonly && payload.isInTable && payload.options.mode !== EditorMode.FORM; + }, + childMenus: [ + { + key: VERTICAL_ALIGN_TOP, + i18nPath: "contextmenu.table.verticalAlignTop", + icon: "vertical-align-top", + when: () => true, + callback: (command) => { + command.executeTableTdVerticalAlign(VerticalAlign.TOP); + } + }, + { + key: VERTICAL_ALIGN_MIDDLE, + i18nPath: "contextmenu.table.verticalAlignMiddle", + icon: "vertical-align-middle", + when: () => true, + callback: (command) => { + command.executeTableTdVerticalAlign(VerticalAlign.MIDDLE); + } + }, + { + key: VERTICAL_ALIGN_BOTTOM, + i18nPath: "contextmenu.table.verticalAlignBottom", + icon: "vertical-align-bottom", + when: () => true, + callback: (command) => { + command.executeTableTdVerticalAlign(VerticalAlign.BOTTOM); + } + } + ] + }, + { + key: INSERT_ROW_COL, + i18nPath: "contextmenu.table.insertRowCol", + icon: "insert-row-col", + when: (payload) => { + return !payload.isReadonly && payload.isInTable && payload.options.mode !== EditorMode.FORM; + }, + childMenus: [ + { + key: INSERT_TOP_ROW, + i18nPath: "contextmenu.table.insertTopRow", + icon: "insert-top-row", + when: () => true, + callback: (command) => { + command.executeInsertTableTopRow(); + } + }, + { + key: INSERT_BOTTOM_ROW, + i18nPath: "contextmenu.table.insertBottomRow", + icon: "insert-bottom-row", + when: () => true, + callback: (command) => { + command.executeInsertTableBottomRow(); + } + }, + { + key: INSERT_LEFT_COL, + i18nPath: "contextmenu.table.insertLeftCol", + icon: "insert-left-col", + when: () => true, + callback: (command) => { + command.executeInsertTableLeftCol(); + } + }, + { + key: INSERT_RIGHT_COL, + i18nPath: "contextmenu.table.insertRightCol", + icon: "insert-right-col", + when: () => true, + callback: (command) => { + command.executeInsertTableRightCol(); + } + } + ] + }, + { + key: DELETE_ROW_COL, + i18nPath: "contextmenu.table.deleteRowCol", + icon: "delete-row-col", + when: (payload) => { + return !payload.isReadonly && payload.isInTable && payload.options.mode !== EditorMode.FORM; + }, + childMenus: [ + { + key: DELETE_ROW, + i18nPath: "contextmenu.table.deleteRow", + icon: "delete-row", + when: () => true, + callback: (command) => { + command.executeDeleteTableRow(); + } + }, + { + key: DELETE_COL, + i18nPath: "contextmenu.table.deleteCol", + icon: "delete-col", + when: () => true, + callback: (command) => { + command.executeDeleteTableCol(); + } + }, + { + key: DELETE_TABLE, + i18nPath: "contextmenu.table.deleteTable", + icon: "delete-table", + when: () => true, + callback: (command) => { + command.executeDeleteTable(); + } + } + ] + }, + { + key: MERGE_CELL, + i18nPath: "contextmenu.table.mergeCell", + icon: "merge-cell", + when: (payload) => { + return !payload.isReadonly && payload.isCrossRowCol && payload.options.mode !== EditorMode.FORM; + }, + callback: (command) => { + command.executeMergeTableCell(); + } + }, + { + key: CANCEL_MERGE_CELL, + i18nPath: "contextmenu.table.mergeCancelCell", + icon: "merge-cancel-cell", + when: (payload) => { + return !payload.isReadonly && payload.isInTable && payload.options.mode !== EditorMode.FORM; + }, + callback: (command) => { + command.executeCancelMergeTableCell(); + } + } +]; +class ContextMenu { + constructor(draw, command) { + this._proxyContextMenuEvent = (evt) => { + this.context = this._getContext(); + const renderList = this._filterMenuList(this.contextMenuList); + const isRegisterContextMenu = renderList.some((menu) => !menu.isDivider); + if (isRegisterContextMenu) { + this.dispose(); + this._render({ + contextMenuList: renderList, + left: evt.x, + top: evt.y + }); + } + evt.preventDefault(); + }; + this._handleSideEffect = (evt) => { + if (this.contextMenuContainerList.length) { + const target = (evt == null ? void 0 : evt.composedPath()[0]) || evt.target; + const contextMenuDom = findParent(target, (node) => !!node && node.nodeType === 1 && node.getAttribute(EDITOR_COMPONENT) === EditorComponent.CONTEXTMENU, true); + if (!contextMenuDom) { + this.dispose(); + } + } + }; + this.options = draw.getOptions(); + this.draw = draw; + this.command = command; + this.range = draw.getRange(); + this.position = draw.getPosition(); + this.i18n = draw.getI18n(); + this.container = draw.getContainer(); + this.context = null; + this.contextMenuList = [ + ...globalMenus, + ...tableMenus, + ...imageMenus, + ...controlMenus, + ...hyperlinkMenus + ]; + this.contextMenuContainerList = []; + this.contextMenuRelationShip = /* @__PURE__ */ new Map(); + this._addEvent(); + } + getContextMenuList() { + return this.contextMenuList; + } + _addEvent() { + this.container.addEventListener("contextmenu", this._proxyContextMenuEvent); + document.addEventListener("mousedown", this._handleSideEffect); + } + removeEvent() { + this.container.removeEventListener("contextmenu", this._proxyContextMenuEvent); + document.removeEventListener("mousedown", this._handleSideEffect); + } + _filterMenuList(menuList) { + var _a; + const { contextMenuDisableKeys } = this.options; + const renderList = []; + for (let m = 0; m < menuList.length; m++) { + const menu = menuList[m]; + if (menu.disable || menu.key && contextMenuDisableKeys.includes(menu.key)) { + continue; + } + if (menu.isDivider) { + renderList.push(menu); + } else { + if ((_a = menu.when) == null ? void 0 : _a.call(menu, this.context)) { + renderList.push(menu); + } + } + } + return renderList; + } + _getContext() { + const isReadonly = this.draw.isReadonly(); + const { isCrossRowCol: crossRowCol, startIndex, endIndex } = this.range.getRange(); + const editorTextFocus = !!(~startIndex || ~endIndex); + const editorHasSelection = editorTextFocus && startIndex !== endIndex; + const { isTable, trIndex, tdIndex, index: index2 } = this.position.getPositionContext(); + let tableElement = null; + if (isTable) { + const originalElementList = this.draw.getOriginalElementList(); + const originTableElement = originalElementList[index2] || null; + if (originTableElement) { + tableElement = zipElementList([originTableElement], { + extraPickAttrs: ["id"] + })[0]; + } + } + const isCrossRowCol = isTable && !!crossRowCol; + const elementList = this.draw.getElementList(); + const startElement = elementList[startIndex] || null; + const endElement = elementList[endIndex] || null; + const zone2 = this.draw.getZone().getZone(); + return { + startElement, + endElement, + isReadonly, + editorHasSelection, + editorTextFocus, + isCrossRowCol, + zone: zone2, + isInTable: isTable, + trIndex: trIndex != null ? trIndex : null, + tdIndex: tdIndex != null ? tdIndex : null, + tableElement, + options: this.options + }; + } + _createContextMenuContainer() { + const contextMenuContainer = document.createElement("div"); + contextMenuContainer.classList.add(`${EDITOR_PREFIX}-contextmenu-container`); + contextMenuContainer.setAttribute(EDITOR_COMPONENT, EditorComponent.CONTEXTMENU); + this.container.append(contextMenuContainer); + return contextMenuContainer; + } + _render(payload) { + var _a; + const { contextMenuList, left: left2, top, parentMenuContainer } = payload; + const contextMenuContainer = this._createContextMenuContainer(); + const contextMenuContent = document.createElement("div"); + contextMenuContent.classList.add(`${EDITOR_PREFIX}-contextmenu-content`); + let childMenuContainer = null; + if (parentMenuContainer) { + this.contextMenuRelationShip.set(parentMenuContainer, contextMenuContainer); + } + for (let c = 0; c < contextMenuList.length; c++) { + const menu = contextMenuList[c]; + if (menu.isDivider) { + if (c !== 0 && c !== contextMenuList.length - 1 && !((_a = contextMenuList[c - 1]) == null ? void 0 : _a.isDivider)) { + const divider = document.createElement("div"); + divider.classList.add(`${EDITOR_PREFIX}-contextmenu-divider`); + contextMenuContent.append(divider); + } + } else { + const menuItem = document.createElement("div"); + menuItem.classList.add(`${EDITOR_PREFIX}-contextmenu-item`); + if (menu.childMenus) { + const childMenus = this._filterMenuList(menu.childMenus); + const isRegisterContextMenu = childMenus.some((menu2) => !menu2.isDivider); + if (isRegisterContextMenu) { + menuItem.classList.add(`${EDITOR_PREFIX}-contextmenu-sub-item`); + menuItem.onmouseenter = () => { + this._setHoverStatus(menuItem, true); + this._removeSubMenu(contextMenuContainer); + const subMenuRect = menuItem.getBoundingClientRect(); + const left22 = subMenuRect.left + subMenuRect.width; + const top2 = subMenuRect.top; + childMenuContainer = this._render({ + contextMenuList: childMenus, + left: left22, + top: top2, + parentMenuContainer: contextMenuContainer + }); + }; + menuItem.onmouseleave = (evt) => { + if (!childMenuContainer || !childMenuContainer.contains(evt.relatedTarget)) { + this._setHoverStatus(menuItem, false); + } + }; + } + } else { + menuItem.onmouseenter = () => { + this._setHoverStatus(menuItem, true); + this._removeSubMenu(contextMenuContainer); + }; + menuItem.onmouseleave = () => { + this._setHoverStatus(menuItem, false); + }; + menuItem.onclick = () => { + if (menu.callback && this.context) { + menu.callback(this.command, this.context); + } + this.dispose(); + }; + } + const icon = document.createElement("i"); + menuItem.append(icon); + if (menu.icon) { + icon.classList.add(`${EDITOR_PREFIX}-contextmenu-${menu.icon}`); + } + const span = document.createElement("span"); + const name = menu.i18nPath ? this._formatName(this.i18n.t(menu.i18nPath)) : this._formatName(menu.name || ""); + span.append(document.createTextNode(name)); + menuItem.append(span); + if (menu.shortCut) { + const span2 = document.createElement("span"); + span2.classList.add(`${EDITOR_PREFIX}-shortcut`); + span2.append(document.createTextNode(menu.shortCut)); + menuItem.append(span2); + } + contextMenuContent.append(menuItem); + } + } + contextMenuContainer.append(contextMenuContent); + contextMenuContainer.style.display = "block"; + const innerWidth = window.innerWidth; + const contextmenuRect = contextMenuContainer.getBoundingClientRect(); + const contextMenuWidth = contextmenuRect.width; + const adjustLeft = left2 + contextMenuWidth > innerWidth ? left2 - contextMenuWidth : left2; + contextMenuContainer.style.left = `${adjustLeft}px`; + const innerHeight = window.innerHeight; + const contextMenuHeight = contextmenuRect.height; + const adjustTop = top + contextMenuHeight > innerHeight ? top - contextMenuHeight : top; + contextMenuContainer.style.top = `${adjustTop}px`; + this.contextMenuContainerList.push(contextMenuContainer); + return contextMenuContainer; + } + _removeSubMenu(payload) { + const childMenu = this.contextMenuRelationShip.get(payload); + if (childMenu) { + this._removeSubMenu(childMenu); + childMenu.remove(); + this.contextMenuRelationShip.delete(payload); + } + } + _setHoverStatus(payload, status) { + var _a; + if (status) { + (_a = payload.parentNode) == null ? void 0 : _a.querySelectorAll(`${EDITOR_PREFIX}-contextmenu-item`).forEach((child) => child.classList.remove("hover")); + payload.classList.add("hover"); + } else { + payload.classList.remove("hover"); + } + } + _formatName(name) { + const placeholderValues = Object.values(NAME_PLACEHOLDER); + const placeholderReg = new RegExp(`${placeholderValues.join("|")}`); + let formatName = name; + if (placeholderReg.test(formatName)) { + const selectedReg = new RegExp(NAME_PLACEHOLDER.SELECTED_TEXT, "g"); + if (selectedReg.test(formatName)) { + const selectedText = this.range.toString(); + formatName = formatName.replace(selectedReg, selectedText); + } + } + return formatName; + } + registerContextMenuList(payload) { + this.contextMenuList.push(...payload); + } + dispose() { + this.contextMenuContainerList.forEach((child) => child.remove()); + this.contextMenuContainerList = []; + this.contextMenuRelationShip.clear(); + } +} +const richtextKeys = [ + { + key: KeyMap.X, + ctrl: true, + shift: true, + callback: (command) => { + command.executeStrikeout(); + } + }, + { + key: KeyMap.LEFT_BRACKET, + mod: true, + callback: (command) => { + command.executeSizeAdd(); + } + }, + { + key: KeyMap.RIGHT_BRACKET, + mod: true, + callback: (command) => { + command.executeSizeMinus(); + } + }, + { + key: KeyMap.B, + mod: true, + callback: (command) => { + command.executeBold(); + } + }, + { + key: KeyMap.I, + mod: true, + callback: (command) => { + command.executeItalic(); + } + }, + { + key: KeyMap.U, + mod: true, + callback: (command) => { + command.executeUnderline(); + } + }, + { + key: isApple ? KeyMap.COMMA : KeyMap.RIGHT_ANGLE_BRACKET, + mod: true, + shift: true, + callback: (command) => { + command.executeSuperscript(); + } + }, + { + key: isApple ? KeyMap.PERIOD : KeyMap.LEFT_ANGLE_BRACKET, + mod: true, + shift: true, + callback: (command) => { + command.executeSubscript(); + } + }, + { + key: KeyMap.L, + mod: true, + callback: (command) => { + command.executeRowFlex(RowFlex.LEFT); + } + }, + { + key: KeyMap.E, + mod: true, + callback: (command) => { + command.executeRowFlex(RowFlex.CENTER); + } + }, + { + key: KeyMap.R, + mod: true, + callback: (command) => { + command.executeRowFlex(RowFlex.RIGHT); + } + }, + { + key: KeyMap.J, + mod: true, + callback: (command) => { + command.executeRowFlex(RowFlex.ALIGNMENT); + } + }, + { + key: KeyMap.J, + mod: true, + shift: true, + callback: (command) => { + command.executeRowFlex(RowFlex.JUSTIFY); + } + } +]; +const titleKeys = [ + { + key: KeyMap.ZERO, + alt: true, + ctrl: true, + callback: (command) => { + command.executeTitle(null); + } + }, + { + key: KeyMap.ONE, + alt: true, + ctrl: true, + callback: (command) => { + command.executeTitle(TitleLevel.FIRST); + } + }, + { + key: KeyMap.TWO, + alt: true, + ctrl: true, + callback: (command) => { + command.executeTitle(TitleLevel.SECOND); + } + }, + { + key: KeyMap.THREE, + alt: true, + ctrl: true, + callback: (command) => { + command.executeTitle(TitleLevel.THIRD); + } + }, + { + key: KeyMap.FOUR, + alt: true, + ctrl: true, + callback: (command) => { + command.executeTitle(TitleLevel.FOURTH); + } + }, + { + key: KeyMap.FIVE, + alt: true, + ctrl: true, + callback: (command) => { + command.executeTitle(TitleLevel.FIFTH); + } + }, + { + key: KeyMap.SIX, + alt: true, + ctrl: true, + callback: (command) => { + command.executeTitle(TitleLevel.SIXTH); + } + } +]; +const listKeys = [ + { + key: KeyMap.I, + shift: true, + mod: true, + callback: (command) => { + command.executeList(ListType.UL, ListStyle.DISC); + } + }, + { + key: KeyMap.U, + shift: true, + mod: true, + callback: (command) => { + command.executeList(ListType.OL); + } + } +]; +class Shortcut { + constructor(draw, command) { + this._globalKeydown = (evt) => { + if (!this.globalShortcutList.length) + return; + this._execute(evt, this.globalShortcutList); + }; + this.command = command; + this.globalShortcutList = []; + this.agentShortcutList = []; + this._addShortcutList([...richtextKeys, ...titleKeys, ...listKeys]); + this._addEvent(); + const agentDom = draw.getCursor().getAgentDom(); + agentDom.addEventListener("keydown", this._agentKeydown.bind(this)); + } + _addEvent() { + document.addEventListener("keydown", this._globalKeydown); + } + removeEvent() { + document.removeEventListener("keydown", this._globalKeydown); + } + _addShortcutList(payload) { + for (let s = payload.length - 1; s >= 0; s--) { + const shortCut = payload[s]; + if (shortCut.isGlobal) { + this.globalShortcutList.unshift(shortCut); + } else { + this.agentShortcutList.unshift(shortCut); + } + } + } + registerShortcutList(payload) { + this._addShortcutList(payload); + } + _agentKeydown(evt) { + if (!this.agentShortcutList.length) + return; + this._execute(evt, this.agentShortcutList); + } + _execute(evt, shortCutList) { + var _a; + for (let s = 0; s < shortCutList.length; s++) { + const shortCut = shortCutList[s]; + if ((shortCut.mod ? isMod(evt) === !!shortCut.mod : evt.ctrlKey === !!shortCut.ctrl && evt.metaKey === !!shortCut.meta) && evt.shiftKey === !!shortCut.shift && evt.altKey === !!shortCut.alt && evt.key.toLowerCase() === shortCut.key.toLowerCase()) { + if (!shortCut.disable) { + (_a = shortCut == null ? void 0 : shortCut.callback) == null ? void 0 : _a.call(shortCut, this.command); + evt.preventDefault(); + } + break; + } + } + } +} +class Plugin { + constructor(editor) { + this.editor = editor; + } + use(pluginFunction, options) { + pluginFunction(this.editor, options); + } +} +class EventBus { + constructor() { + this.eventHub = /* @__PURE__ */ new Map(); + } + on(eventName, callback) { + if (!eventName || typeof callback !== "function") + return; + const eventSet = this.eventHub.get(eventName) || /* @__PURE__ */ new Set(); + eventSet.add(callback); + this.eventHub.set(eventName, eventSet); + } + emit(eventName, payload) { + if (!eventName) + return; + const callBackSet = this.eventHub.get(eventName); + if (!callBackSet) + return; + if (callBackSet.size === 1) { + const callBack = [...callBackSet]; + return callBack[0](payload); + } + callBackSet.forEach((callBack) => callBack(payload)); + } + off(eventName, callback) { + if (!eventName || typeof callback !== "function") + return; + const callBackSet = this.eventHub.get(eventName); + if (!callBackSet) + return; + callBackSet.delete(callback); + } + isSubscribe(eventName) { + const eventSet = this.eventHub.get(eventName); + return !!eventSet && eventSet.size > 0; + } +} +class Override { +} +class Editor { + constructor(container, data2, options = {}) { + const editorOptions = mergeOption(options); + data2 = deepClone(data2); + let headerElementList = []; + let mainElementList = []; + let footerElementList = []; + if (Array.isArray(data2)) { + mainElementList = data2; + } else { + headerElementList = data2.header || []; + mainElementList = data2.main; + footerElementList = data2.footer || []; + } + const pageComponentData = [ + headerElementList, + mainElementList, + footerElementList + ]; + pageComponentData.forEach((elementList) => { + formatElementList(elementList, { + editorOptions, + isForceCompensation: true + }); + }); + this.listener = new Listener(); + this.eventBus = new EventBus(); + this.override = new Override(); + const draw = new Draw(container, editorOptions, { + header: headerElementList, + main: mainElementList, + footer: footerElementList + }, this.listener, this.eventBus, this.override); + this.command = new Command(new CommandAdapt(draw)); + const contextMenu = new ContextMenu(draw, this.command); + const shortcut = new Shortcut(draw, this.command); + this.register = new Register({ + contextMenu, + shortcut, + i18n: draw.getI18n() + }); + this.destroy = () => { + draw.destroy(); + shortcut.removeEvent(); + contextMenu.removeEvent(); + }; + const plugin = new Plugin(this); + this.use = plugin.use.bind(plugin); + } +} +export { BackgroundRepeat, BackgroundSize, BlockType, Command, ControlIndentation, ControlType, EDITOR_COMPONENT, Editor, EditorComponent, EditorMode, EditorZone, ElementType, INTERNAL_CONTEXT_MENU_KEY, ImageDisplay, KeyMap, LETTER_CLASS, LineNumberType, ListStyle, ListType, LocationPosition, MaxHeightRatio, NumberType, PageMode, PaperDirection, RenderMode, RowFlex, TableBorder, TdBorder, TdSlash, TextDecorationStyle, TitleLevel, VerticalAlign, WordBreak, createDomFromElementList, Editor as default, getElementListByHTML, getTextFromElementList, splitText }; diff --git a/src/components/wordtpl/components/HighlightColorModal.vue b/src/components/wordtpl/components/HighlightColorModal.vue new file mode 100644 index 0000000..98a8c8b --- /dev/null +++ b/src/components/wordtpl/components/HighlightColorModal.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/components/wordtpl/components/HyperlinkDrawer.vue b/src/components/wordtpl/components/HyperlinkDrawer.vue new file mode 100644 index 0000000..9238aa0 --- /dev/null +++ b/src/components/wordtpl/components/HyperlinkDrawer.vue @@ -0,0 +1,62 @@ + + + + + + diff --git a/src/components/wordtpl/components/PagerMarginDrawer.vue b/src/components/wordtpl/components/PagerMarginDrawer.vue new file mode 100644 index 0000000..1337194 --- /dev/null +++ b/src/components/wordtpl/components/PagerMarginDrawer.vue @@ -0,0 +1,82 @@ + + + + + + diff --git a/src/components/wordtpl/components/WatermarkDrawer.vue b/src/components/wordtpl/components/WatermarkDrawer.vue new file mode 100644 index 0000000..fe082e9 --- /dev/null +++ b/src/components/wordtpl/components/WatermarkDrawer.vue @@ -0,0 +1,108 @@ + + + + + + diff --git a/src/components/wordtpl/style/DocDesign.css b/src/components/wordtpl/style/DocDesign.css new file mode 100644 index 0000000..d4ac4e1 --- /dev/null +++ b/src/components/wordtpl/style/DocDesign.css @@ -0,0 +1,1069 @@ +.content { + display: flex; +} + +.space-between { + display: flex; + align-items: center; + justify-content: space-between; +} + +.align-center { + display: flex; + align-items: center; +} + +.ellipsis { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.header { + height: 64px; + padding: 0; + background-color: #fff; + .header-title { + width: 200px; + } + .headerRight { + padding-right: 24px; + font-size: 14px; + color: rgba(0, 0, 0, 0.9); + .role-name { + margin-right: 8px; + } + } + .header-name { + width: 100%; + padding: 0 16px; + background-color: rgba(208, 208, 208, 0); + font-size: 18px; + line-height: 30px; + color: #1a1a1a; + font-weight: bold; + margin: 5px 0; + } +} + +.center { + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + position: relative; + margin: 0 10px; + width: 100%; + .left-action { + left: -10px; + border-radius: 0 3px 3px 0; + } + + .right-action { + border-radius: 3px 0 0 3px; + right: -13px; + } + + .action-icon { + cursor: pointer; + transition: all 0.3s; + position: absolute; + top: 50%; + + transform: translateY(-50%); + z-index: 999; + background-color: #ffffff; + width: 10px; + height: 48px; + + &:hover { + opacity: 0.7; + } + } +} +.menu { + width: 100%; + height: 60px; + margin: 0 auto; + top: 0; + z-index: 9; + overflow-x: auto; + display: flex; + align-items: center; + justify-content: center; + background: #f2f4f7; + box-shadow: 0 2px 4px 0 transparent; + .menu-item { + height: 24px; + display: flex; + align-items: center; + position: relative; + } + .menu-item > div { + width: 24px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + margin: 0 2px; + } + + .menu-item > div:hover { + background: rgba(25, 55, 88, 0.04); + } + + .menu-item > div.active { + background: rgba(25, 55, 88, 0.08); + } + + i { + width: 18px; + height: 18px; + display: inline-block; + background-repeat: no-repeat; + background-size: 100% 100%; + } +} + +.menu-item>div>span { + width: 16px; + height: 3px; + display: inline-block; + border: 1px solid #e2e6ed; +} + +.jeecg-menu-item-save i { + background-image: url('/@/assets/images/wordtpl/images/save.svg'); +} +.jeecg-menu-item-upload i { + background-image: url('/@/assets/images/wordtpl/images/upload.svg'); +} +.menu-item-preview i { + background-image: url('/@/assets/images/wordtpl/images/preview.svg'); +} +.jeecg-menu-item-undo i { + background-image: url('/@/assets/images/wordtpl/images/undo.svg'); +} +.jeecg-menu-item-redo i { + background-image: url('/@/assets/images/wordtpl/images/redo.svg'); +} +.jeecg-menu-item-painter i { + background-image: url('/@/assets/images/wordtpl/images/painter.svg'); +} +.jeecg-menu-item-format i { + background-image: url('/@/assets/images/wordtpl/images/format.svg'); +} +.jeecg-menu-item-size-add i { + background-image: url('/@/assets/images/wordtpl/images/size-add.svg'); +} +.jeecg-menu-item-size-minus i { + background-image: url('/@/assets/images/wordtpl/images/size-minus.svg'); +} +.jeecg-menu-item-bold i { + background-image: url('/@/assets/images/wordtpl/images/bold.svg'); +} +.jeecg-menu-item-italic i { + background-image: url('/@/assets/images/wordtpl/images/italic.svg'); +} + +.jeecg-menu-item-underline>i{ + flex-shrink: 0; + background-image: url('/@/assets/images/wordtpl/images/underline.svg'); +} +.jeecg-menu-item-underline li[data-decoration-style="solid"]{ + background-image: url(/@/assets/images/wordtpl/images/line-single.svg); + background-repeat: no-repeat; +} +.jeecg-menu-item-underline li[data-decoration-style="double"]{ + background-image: url(/@/assets/images/wordtpl/images/line-double.svg); + background-repeat: no-repeat; +} +.jeecg-menu-item-underline li[data-decoration-style="dashed"]{ + background-image: url(/@/assets/images/wordtpl/images/line-dash-small-gap.svg); + background-repeat: no-repeat; +} +.jeecg-menu-item-underline li[data-decoration-style="dotted"]{ + background-image: url(/@/assets/images/wordtpl/images/line-dot.svg); + background-repeat: no-repeat; +} +.jeecg-menu-item-underline li[data-decoration-style="wavy"] { + background-image: url(/@/assets/images/wordtpl/images/line-wavy.svg); + background-repeat: no-repeat; +} +.jeecg-menu-item-underline li i{ + pointer-events: none; + padding: 1px 5px !important; +} + +.jeecg-menu-item-underline .select { + width: 100%; + height: 100%; +} +.jeecg-menu-item-strikeout i{ + background-image: url('/@/assets/images/wordtpl/images/strikeout.svg'); +} + +.jeecg-menu-item-superscript i{ + background-image: url('/@/assets/images/wordtpl/images/superscript.svg'); +} + +.jeecg-menu-item-subscript i{ + background-image: url('/@/assets/images/wordtpl/images/subscript.svg'); +} + +.jeecg-menu-item-color i{ + background-image: url('/@/assets/images/wordtpl/images/color.svg'); +} + +.jeecg-menu-item-highlight i{ + background-image: url('/@/assets/images/wordtpl/images/highlight.svg'); +} + +.jeecg-menu-item-cellcolor i{ + background-image: url('/@/assets/images/wordtpl/images/cellcolor.svg'); +} + +.jeecg-menu-item-title i{ + background-image: url('/@/assets/images/wordtpl/images/title.svg'); +} + +.jeecg-menu-item-left i{ + background-image: url('/@/assets/images/wordtpl/images/left.svg'); +} + +.jeecg-menu-item-center i{ + background-image: url('/@/assets/images/wordtpl/images/center.svg'); +} + +.jeecg-menu-item-right i{ + background-image: url('/@/assets/images/wordtpl/images/right.svg'); +} + +.jeecg-menu-item-alignment i{ + background-image: url('/@/assets/images/wordtpl/images/alignment.svg'); +} + +.jeecg-menu-item-row-margin i{ + background-image: url('/@/assets/images/wordtpl/images/row-margin.svg'); +} + +.jeecg-menu-item-list i{ + background-image: url('/@/assets/images/wordtpl/images/list.svg'); +} + +.jeecg-menu-item-download i{ + background-image: url('/@/assets/images/wordtpl/images/download.svg'); +} + +.jeecg-menu-item-chart i { + background-image: url('/@/assets/images/wordtpl/images/chart.svg'); +} + +.jeecg-menu-item-table i { + background-image: url('/@/assets/images/wordtpl/images/table.svg'); +} + +.jeecg-menu-item-image i { + background-image: url('/@/assets/images/wordtpl/images/image.svg'); +} + +.jeecg-menu-item-barcode i { + background-image: url('/@/assets/images/wordtpl/images/barcode.svg'); +} + +.jeecg-menu-item-qrcode i { + background-image: url('/@/assets/images/wordtpl/images/qrcode.svg'); +} + +.jeecg-menu-item-hyperlink i { + background-image: url('/@/assets/images/wordtpl/images/hyperlink.svg'); +} + +.jeecg-menu-item-separator>i { + background-image: url('/@/assets/images/wordtpl/images/separator.svg'); +} + +.jeecg-menu-item-watermark i { + background-image: url('/@/assets/images/wordtpl/images/watermark.svg'); +} + +.jeecg-menu-item-page-break i { + background-image: url('/@/assets/images/wordtpl/images/page-break.svg'); +} +.jeecg-menu-item-search i { + background-image: url('/@/assets/images/wordtpl/images/search.svg'); +} + +.jeecg-menu-item-print i { + background-image: url('/@/assets/images/wordtpl/images/print.svg'); +} + + +.jeecg-menu-item-color, +.jeecg-menu-item-highlight, +.jeecg-menu-item-cellcolor { + display: flex; + flex-direction: column; +} + +.jeecg-menu-item-color #color, +.jeecg-menu-item-highlight #highlight, +.jeecg-menu-item-cellcolor #cellcolor { + width: 1px; + height: 1px; + visibility: hidden; + outline: none; + appearance: none; +} + +.jeecg-menu-item-highlight span{ + background-color: #ffff00; +} + +.jeecg-menu-item-color span { + background-color: #000000; +} +.menu-item .jeecg-menu-item-underline .options { + width: 128px !important; +} + +.menu-item .jeecg-menu-item-underline .options li{ + padding: 1px 5px; +} + +ul{ + list-style: none; +} + + +.editor-container { + height: calc(100vh - 164px); + overflow-y: auto; +} + +.no-allow { + color: #c0c4cc; + cursor: not-allowed; + opacity: 0.4; + pointer-events: none; +} + +.menu-item .menu-item__font { + width: 70px !important; + position: relative; + font-size: 14px; +} + +.menu-item .select { + border: none; + font-size: 14px; + line-height: 24px; + user-select: none; +} + +.menu-item .select::after { + position: absolute; + content: ''; + top: 11px; + width: 0; + height: 0; + right: 2px; + border-color: #767c85 transparent transparent; + border-style: solid solid none; + border-width: 4px 4px 0; +} + +.menu-item .options { + width: 110px; + position: fixed; + top: 113px; + padding: 10px; + font-size: 14px; + border: 1px solid #e2e6ed; + display: none; + box-sizing: border-box; + margin: 0; + color: rgba(0, 0, 0, 0.88); + line-height: 1.5; + list-style: none; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + z-index: 1050; + overflow: hidden; + font-variant: initial; + background-color: #ffffff; + border-radius: 8px; + outline: none; + box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05); + align-items: center; +} + +.menu-item .options.visible { + display: block; + z-index: 99; +} + +.menu-item .options li { + position: relative; + display: block; + min-height: 32px; + padding: 5px 12px; + color: rgba(0, 0, 0, 0.88); + font-weight: normal; + font-size: 14px; + line-height: 1.5; + box-sizing: border-box; + cursor: pointer; + transition: background 0.3s ease; + border-radius: 4px; +} + +.menu-item .options li:hover { + background-color: #ebecef; +} + +.menu-item .options li.active { + color: rgba(0, 0, 0, 0.88); + font-weight: 600; + background-color: #e6f4ff; +} + +.menu-item .jeecg-menu-item-font { + width: 65px; + position: relative; +} + +.menu-item .jeecg-menu-item-size { + width: 50px; + text-align: center; + position: relative; +} + +.jeecg-menu-item-font .select, +.jeecg-menu-item-size .select { + width: 100%; + height: 100%; +} + +.menu-item .jeecg-menu-item-font { + width: 70px!important;; + position: relative; +} + +.menu-item .jeecg-menu-item-size { + width: 50px !important; + text-align: center; + position: relative; +} + +.jeecg-menu-item-size .options,.jeecg-menu-item-font .options{ + height: 566px; + overflow-y: auto; +} + +.jeecg-menu-item-redo.no-allow, +.jeecg-menu-item-undo.no-allow, +.menu-item > div.disable { + color: #c0c4cc; + cursor: not-allowed; + opacity: 0.4; + pointer-events: none; +} + +.menu-item .options.visible { + display: block; + z-index: 99; +} + +.menu-item .jeecg-menu-item-underline { + width: 30px !important; + position: relative; +} + +.jeecg__editor { + display: grid; + justify-content: center; +} + +.menu-item .jeecg-menu-item-title { + width: 60px !important; + position: relative; +} + +.jeecg-menu-item-title .select { + width: calc(100% - 20px); + height: 100%; + font-size: 12px !important; +} + +.jeecg-menu-item-title .options { + width: 114px; +} + +.jeecg-menu-item-list { + position: relative; +} + +.jeecg-menu-item-list .options { + width: 150px !important; +} + +.jeecg-menu-item-list .options>ul>li * { + pointer-events: none !important; +} + +.jeecg-menu-item-list .options>ul>li li { + margin-left: 18px !important; +} +.jeecg-menu-item-list .options>ul>li ol { + margin: 0; + padding: 0; +} + +.menu-item .jeecg-menu-item-table-collapse { + width: 270px !important; + height: 310px; + background: #fff; + box-shadow: 0 2px 12px 0 rgb(56 56 56 / 20%); + border: 1px solid #e2e6ed; + box-sizing: border-box; + border-radius: 2px; + position: fixed; + z-index: 99; + top: 113px; + padding: 14px 27px; + cursor: auto !important; +} + +.menu-item .jeecg-menu-item-table-collapse .table-close { + position: absolute; + right: 10px; + top: 5px; + cursor: pointer; +} + +.menu-item .jeecg-menu-item-table-collapse .table-close:hover { + color: #7d7e80 !important;; +} + +.menu-item .jeecg-menu-item-table-collapse:hover { + background: #fff !important; +} + +.menu-item .jeecg-menu-item-table-collapse .table-title { + display: flex; + justify-content: flex-start; + padding-bottom: 5px; + border-bottom: 1px solid #e2e6ed; +} + +.table-title span { + font-size: 12px; + color: #3d4757; + display: inline; + margin: 0; +} + +:deep(.table-panel) { + cursor: pointer; +} + +:deep(.table-panel .table-row) { + display: flex; + flex-wrap: nowrap; + margin-top: 10px; + pointer-events: none; +} + +:deep(.table-panel .table-cel) { + width: 16px; + height: 16px; + box-sizing: border-box; + border: 1px solid #e2e6ed; + background: #fff; + position: relative; + margin-right: 6px; + pointer-events: none; +} + +:deep(.table-panel .table-cel.active) { + border: 1px solid rgba(73, 145, 242, .2); + background: rgba(73, 145, 242, .15); +} + +:deep(.table-panel .table-row .table-cel:last-child) { + margin-right: 0; +} + +.jeecg-menu-item-image input{ + display: none; +} + +.menu-item .jeecg-menu-item-separator .options { + width: 128px !important; +} + +.menu-item .jeecg-menu-item-separator li { + padding: 1px 5px; + min-height: 20px; +} + +.jeecg-menu-item-separator li i { + pointer-events: none; +} + +.jeecg-menu-item-separator .more input{ + width: 100%; +} +.jeecg-menu-item-separator .more div span{ + width: 30px; +} +.jeecg-menu-item-separator .more .menu{ + margin-top: 20px; +} +.jeecg-menu-item-separator .more .menu button:last-child{ + margin-left: 8px; +} + +.jeecg-menu-item-separator li[data-separator="0,0"] { + background: url('/@/assets/images/wordtpl/images/line-single.svg') no-repeat; +} + +.jeecg-menu-item-separator li[data-separator="1,1"] { + background: url('/@/assets/images/wordtpl/images/line-dot.svg') no-repeat; +} + +.jeecg-menu-item-separator li[data-separator="3,1"] { + background: url('/@/assets/images/wordtpl/images/line-dash-small-gap.svg') no-repeat; +} + +.jeecg-menu-item-separator li[data-separator="4,4"] { + background: url('/@/assets/images/wordtpl/images/line-dash-large-gap.svg') no-repeat; +} + +.jeecg-menu-item-separator li[data-separator="7,3,3,3"] { + background: url('/@/assets/images/wordtpl/images/line-dash-dot.svg') no-repeat; +} + +.jeecg-menu-item-separator li[data-separator="6,2,2,2,2,2"] { + background: url('/@/assets/images/wordtpl/images/line-dash-dot-dot.svg') no-repeat; +} + +.jeecg-menu-item-search { + position: relative; +} + + +.menu-item .jeecg-menu-item-search-collapse { + width: 260px !important; + height: 72px; + box-sizing: border-box; + position: fixed; + z-index: 99; + top: 113px; + left: 0; + background: #ffffff; + box-shadow: 0 5px 5px #e3dfdf; +} + +.menu-item .jeecg-menu-item-search-collapse:hover { + background: #ffffff; +} + +.menu-item .jeecg-menu-item-search-collapse>div { + width: 250px; + height: 36px; + padding: 0 5px; + line-height: 36px; + display: flex; + align-items: center; + justify-content: space-between; + border-radius: 4px; +} + +.menu-item .jeecg-menu-item-search-collapse>div input { + width: 205px; + height: 27px; + appearance: none; + background-color: #fff; + background-image: none; + border-radius: 4px; + border: 1px solid #ebebeb; + box-sizing: border-box; + color: #606266; + display: inline-block; + line-height: 27px; + outline: none; + padding: 0 5px; +} + +.menu-item .jeecg-menu-item-search-collapse>div span { + height: 100%; + color: #dcdfe6; + font-size: 25px; + display: inline-block; + border: 0; + padding: 0 10px; +} + +.menu-item .jeecg-menu-item-search-collapse button { + display: inline-block; + border: 1px solid #e2e6ed; + border-radius: 2px; + background: #fff; + line-height: 22px; + padding: 0 6px; + white-space: nowrap; + margin-left: 4px; + cursor: pointer; + font-size: 12px; +} + +.menu-item .jeecg-menu-item-search-collapse-replace button:hover { + background: rgba(25, 55, 88, .04); +} + +.menu-item .jeecg-menu-item-search-collapse-search { + position: relative; +} + +.menu-item .jeecg-menu-item-search-collapse-search label { + right: 110px; + font-size: 12px; + color: #3d4757; + position: absolute; +} + +.menu-item .jeecg-menu-item-search-collapse-search>input { + padding: 5px 90px 5px 5px !important; +} + +.menu-item .jeecg-menu-item-search-collapse-search>div { + width: 28px; + height: 27px; + display: flex; + justify-content: center; + align-items: center; + position: absolute; + border-left: 1px solid #e2e6ed; + transition: all .5s; +} + +.menu-item .jeecg-menu-item-search-collapse-search>div:hover { + background-color: rgba(25, 55, 88, 0.04); +} + +.menu-item .jeecg-menu-item-search-collapse-search i { + width: 6px; + height: 8px; + transform: translateY(1px); +} + +.menu-item .jeecg-menu-item-search-collapse-search .arrow-left { + right: 76px; +} + +.menu-item .jeecg-menu-item-search-collapse-search .arrow-left i { + background: url(/@/assets/images/wordtpl/images/arrow-left.svg) no-repeat; +} + +.menu-item .jeecg-menu-item-search-collapse-search .arrow-right { + right: 48px; +} + +.menu-item .jeecg-menu-item-search-collapse-search .arrow-right i { + background: url(/@/assets/images/wordtpl/images/arrow-right.svg) no-repeat; +} + +.footer { + width: 100%; + height: 30px; + display: flex; + align-items: center; + justify-content: space-between; + position: absolute; + background: #f2f4f7; + z-index: 9; + bottom: 0; + left: 0; + font-size: 12px; + padding: 0 4px 0 20px; + box-sizing: border-box; +} + +.footer>div:first-child { + display: flex; + align-items: center; +} + +.footer .catalog-mode { + padding: 1px; + position: relative; +} + +.footer .catalog-mode i { + width: 16px; + height: 16px; + margin-right: 5px; + cursor: pointer; + display: block; + background-image: url('/@/assets/images/wordtpl/images/catalog.svg'); +} + +.footer .page-mode { + padding: 1px; + position: relative; +} + +.footer .page-mode i { + width: 16px; + height: 16px; + margin-right: 5px; + cursor: pointer; + display: block; + background-image: url('/@/assets/images/wordtpl/images/page-mode.svg'); +} + +.footer .options { + width: 72px; + position: absolute; + bottom: 26px; + padding: 10px; + top: unset !important; + left: unset !important; + font-size: 14px; + border: 1px solid #e2e6ed; + display: none; + box-sizing: border-box; + margin: 0; + color: rgba(0, 0, 0, 0.88); + line-height: 1.5; + list-style: none; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + z-index: 1050; + overflow: hidden; + font-variant: initial; + background-color: #ffffff; + border-radius: 8px; + outline: none; + box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05); + align-items: center; +} + +.footer .options.visible { + display: block; +} + +.footer .options li { + position: relative; + display: block; + min-height: 32px; + padding: 5px 10px; + color: rgba(0, 0, 0, 0.88); + font-weight: normal; + font-size: 14px; + line-height: 1.5; + box-sizing: border-box; + cursor: pointer; + transition: background 0.3s ease; + border-radius: 4px; +} + +.footer .options li:hover { + background-color: #ebecef; +} + +.footer .options li.active { + color: rgba(0, 0, 0, 0.88); + font-weight: 600; + background-color: #e6f4ff; +} + +.footer>div:first-child>span { + display: inline-block; + margin-right: 5px; + letter-spacing: 1px; +} + +.footer>div:last-child { + display: flex; + align-items: center; + justify-content: space-between; +} + +.footer>div:last-child>div { + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; +} + +.footer>div:last-child>div:hover { + background: rgba(25, 55, 88, .04); +} + +.footer>div:last-child i { + width: 16px; + height: 16px; + display: inline-block; + cursor: pointer; +} + +.footer .editor-option i { + background-image: url('/@/assets/images/wordtpl/images/option.svg'); +} + +.footer .page-scale-minus i { + background-image: url('/@/assets/images/wordtpl/images/page-scale-minus.svg'); +} + +.footer .page-scale-add i { + background-image: url('/@/assets/images/wordtpl/images/page-scale-add.svg'); +} + +.footer .page-scale-percentage { + cursor: pointer; + user-select: none; +} + +.footer .fullscreen i { + background-image: url('/@/assets/images/wordtpl/images/request-fullscreen.svg'); +} + +.footer .fullscreen.exist i { + background-image: url('/@/assets/images/wordtpl/images/exit-fullscreen.svg'); +} + +.footer .paper-margin i { + background-image: url('/@/assets/images/wordtpl/images/paper-margin.svg'); +} + +.footer .editor-mode { + cursor: pointer; + user-select: none; +} + +.footer .paper-size { + position: relative; +} + +.footer .paper-size i { + background-image: url('/@/assets/images/wordtpl/images/paper-size.svg'); +} + +.footer .paper-size .options { + width: 100px !important; + height: 340px !important; + right: 0; + left: unset; +} + +.footer .paper-direction { + position: relative; +} + +.footer .paper-direction i { + background-image: url('/@/assets/images/wordtpl/images/paper-direction.svg'); +} + +.footer .paper-direction .options { + right: 0; + left: unset; + height: 84px; +} +.footer .page-mode .options{ + height: 80px; +} + +.catalog{ + width: 250px; + position: absolute; + bottom: 0; + top: 70px; + padding: 0 20px 40px 20px; +} + +.catalog .jeecg-catalog-header { + height: 48px; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid #e2e6ed; +} + +.catalog .jeecg-catalog-header span { + color: #3d4757; + font-size: 14px; + font-weight: bold; +} + +.catalog .jeecg-catalog-header i { + width: 16px; + height: 16px; + cursor: pointer; + display: inline-block; + background: url(/@/assets/images/wordtpl/images/close.svg) no-repeat; + transition: all .2s; +} + +.catalog .jeecg-catalog-header>div:hover { + background: rgba(235, 238, 241); +} + +.jeecg-catalog-main { + height: calc(100% - 60px); + padding: 10px 0; + overflow-y: auto; + overflow-x: hidden; +} + +.jeecg-catalog-main .catalog-item { + width: 100%; + padding-left: 10px; + box-sizing: border-box; +} + +.jeecg-catalog-main>.catalog-item { + padding-left: 0; +} + +:deep(.jeecg-catalog-main .catalog-item .jeecg-catalog-item-content) { + width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +:deep(.jeecg-catalog-main .catalog-item .jeecg-catalog-item-content:hover >span) { + color: #4991f2; +} + +:deep(.jeecg-catalog-main .catalog-item .jeecg-catalog-item-content span) { + color: #3d4757; + line-height: 30px; + font-size: 12px; + white-space: nowrap; + cursor: pointer; + user-select: none; +} + + +.jeecg-menu-item-row-margin .options{ + width: 76px; + height: 242px; +} + +.jeecg-menu-item-underline .options{ + height: 180px; +} + +ol, ul { + margin: 0; + padding: 0; + list-style: none; +} + +li { + display: list-item !important; + text-align: -webkit-match-parent; + unicode-bidi: isolate; +} diff --git a/src/design/ant/btn.less b/src/design/ant/btn.less new file mode 100644 index 0000000..eb4c1af --- /dev/null +++ b/src/design/ant/btn.less @@ -0,0 +1,323 @@ +// button reset +.ant-btn { + // display: inline-flex; + // justify-content: center; + // align-items: center; + // &.ant-btn-success:not(.ant-btn-link), + // &.ant-btn-error:not(.ant-btn-link), + // &.ant-btn-warning:not(.ant-btn-link), + // &.ant-btn-primary:not(.ant-btn-link) { + // box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.08) !important; + // } + // &-group { + // .ant-btn:not(:first-child) { + // bottom: 1px; + // } + // } + &-link:hover, + &-link:focus, + &-link:active { + border-color: transparent !important; + } + + &-primary { + // update-begin--author:liaozhiyang---date:20240223---for:【QQYUN-8327】btn样式显示不正确 + // color: @white; + // background-color: @button-primary-color; + // update-end--author:liaozhiyang---date:20240223---for:【QQYUN-8327】btn样式显示不正确 + + &:hover, + &:focus { + // update-begin--author:liaozhiyang---date:20240223---for:【QQYUN-8327】btn样式显示不正确 + // color: @white; + // background-color: @button-primary-hover-color; + // update-end--author:liaozhiyang---date:20240223---for:【QQYUN-8327】btn样式显示不正确 + } + // + //&[disabled], + //&[disabled]:hover { + // color: fade(@button-cancel-color, 40%) !important; + // background-color: fade(@button-cancel-bg-color, 40%) !important; + // border-color: fade(@button-cancel-border-color, 40%) !important; + //} + } + + &-primary:not(&-background-ghost):not([disabled]) { + color: @white; + } + + //&-primary:not(&-background-ghost) { + // border-width: 0; + //} + + &-default { + // update-begin--author:liaozhiyang---date:20240223---for:【QQYUN-8327】btn样式显示不正确 + // color: @button-cancel-color; + // background-color: @button-cancel-bg-color; + // border-color: @button-cancel-border-color; + + // &:hover, + // &:focus { + // color: @button-cancel-hover-color; + // background-color: @button-cancel-hover-bg-color; + // border-color: @button-cancel-hover-border-color; + // } + // update-end--author:liaozhiyang---date:20240223---for:【QQYUN-8327】btn样式显示不正确 + // + //&[disabled], + //&[disabled]:hover { + // color: fade(@button-cancel-color, 40%) !important; + // background: fade(@button-cancel-bg-color, 40%) !important; + // border-color: fade(@button-cancel-border-color, 40%) !important; + //} + } + + [data-theme='light'] &.ant-btn-link.is-disabled { + color: rgba(0, 0, 0, 0.25); + text-shadow: none; + cursor: not-allowed !important; + background-color: transparent !important; + border-color: transparent !important; + box-shadow: none; + } + + [data-theme='dark'] &.ant-btn-link.is-disabled { + color: rgba(255, 255, 255, 0.25) !important; + text-shadow: none; + cursor: not-allowed !important; + background-color: transparent !important; + border-color: transparent !important; + box-shadow: none; + } + + // color: @white; + + &-success.ant-btn-link:not([disabled='disabled']) { + color: @button-success-color; + + &:hover, + &:focus { + color: @button-success-hover-color; + border-color: transparent; + } + + &:active { + color: @button-success-active-color; + } + } + + &-success.ant-btn-link.ant-btn-loading, + &-warning.ant-btn-link.ant-btn-loading, + &-error.ant-btn-link.ant-btn-loading, + &-background-ghost.ant-btn-link.ant-btn-loading, + &.ant-btn-link.ant-btn-loading { + &::before { + background: transparent; + } + } + + &-success:not(.ant-btn-link, .is-disabled) { + color: @white; + background-color: @button-success-color; + border-color: @button-success-color; + //border-width: 0; + + &:hover, + &:focus { + color: @white; + background-color: @button-success-hover-color; + border-color: @button-success-hover-color; + } + + &:active { + background-color: @button-success-active-color; + border-color: @button-success-active-color; + } + + //&[disabled], + //&[disabled]:hover { + // color: @white; + // background-color: fade(@button-success-color, 40%); + // border-color: fade(@button-success-color, 40%); + //} + } + + &-warning.ant-btn-link:not([disabled='disabled']) { + color: @button-warn-color; + + &:hover, + &:focus { + color: @button-warn-hover-color; + border-color: transparent; + } + + &:active { + color: @button-warn-active-color; + } + } + + &-warning:not(.ant-btn-link, .is-disabled) { + color: @white; + background-color: @button-warn-color; + border-color: @button-warn-color; + //border-width: 0; + + &:hover, + &:focus { + color: @white; + background-color: @button-warn-hover-color; + border-color: @button-warn-hover-color; + } + + &:active { + background-color: @button-warn-active-color; + border-color: @button-warn-active-color; + } + + //&[disabled], + //&[disabled]:hover { + // color: @white; + // background-color: fade(@button-warn-color, 40%); + // border-color: fade(@button-warn-color, 40%); + //} + } + + &-error.ant-btn-link:not([disabled='disabled']) { + color: @button-error-color; + + &:hover, + &:focus { + color: @button-error-hover-color; + border-color: transparent; + } + + &:active { + color: @button-error-active-color; + } + } + + &-error:not(.ant-btn-link, .is-disabled) { + color: @white; + background-color: @button-error-color; + border-color: @button-error-color; + //border-width: 0; + + &:hover, + &:focus { + color: @white; + background-color: @button-error-hover-color; + border-color: @button-error-hover-color; + } + + &:active { + background-color: @button-error-active-color; + border-color: @button-error-active-color; + } + + //&[disabled], + //&[disabled]:hover { + // color: @white; + // background-color: fade(@button-error-color, 40%); + // border-color: fade(@button-error-color, 40%); + //} + } + + &-background-ghost { + border-width: 1px; + background-color: transparent !important; + + &[disabled], + &[disabled]:hover { + color: fade(@white, 40%) !important; + background-color: transparent !important; + border-color: fade(@white, 40%) !important; + } + } + + &-dashed&-background-ghost, + &-default&-background-ghost { + color: @button-ghost-color; + border-color: @button-ghost-color; + + &:hover, + &:focus { + color: @button-ghost-hover-color; + border-color: @button-ghost-hover-color; + } + + &:active { + color: @button-ghost-active-color; + border-color: @button-ghost-active-color; + } + + &[disabled], + &[disabled]:hover { + color: fade(@white, 40%) !important; + border-color: fade(@white, 40%) !important; + } + } + + &-background-ghost&-success:not(.ant-btn-link) { + color: @button-success-color; + background-color: transparent; + border-color: @button-success-color; + border-width: 1px; + + &:hover, + &:focus { + color: @button-success-hover-color !important; + border-color: @button-success-hover-color; + } + + &:active { + color: @button-success-active-color; + border-color: @button-success-active-color; + } + } + + &-background-ghost&-warning:not(.ant-btn-link) { + color: @button-warn-color; + background-color: transparent; + border-color: @button-warn-color; + border-width: 1px; + + &:hover, + &:focus { + color: @button-warn-hover-color !important; + border-color: @button-warn-hover-color; + } + + &:active { + color: @button-warn-active-color; + border-color: @button-warn-active-color; + } + } + + &-background-ghost&-error:not(.ant-btn-link) { + color: @button-error-color; + background-color: transparent; + border-color: @button-error-color; + border-width: 1px; + + &:hover, + &:focus { + color: @button-error-hover-color !important; + border-color: @button-error-hover-color; + } + + &:active { + color: @button-error-active-color; + border-color: @button-error-active-color; + } + } + + &-ghost.ant-btn-link:not([disabled='disabled']) { + color: @button-ghost-color; + + &:hover, + &:focus { + color: @button-ghost-hover-color; + border-color: transparent; + } + } +} diff --git a/src/design/ant/index.less b/src/design/ant/index.less new file mode 100644 index 0000000..e0dcf44 --- /dev/null +++ b/src/design/ant/index.less @@ -0,0 +1,212 @@ +@import './pagination.less'; +@import './input.less'; +// update-begin--author:liaozhiyang---date:20240130---for:【issues/5857】Button color类型颜色失效 +@import './btn.less'; +// update-end--author:liaozhiyang---date:20240130---for:【issues/5857】Button color类型颜色失效 +// @import './table.less'; + +// TODO beta.11 fix +.ant-col { + width: 100%; +} + +.ant-image-preview-root { + img { + display: unset; + } +} +//update-begin---author:scott ---date:2023-08-28 for����QQYUN-6374��UnoCSS���windicss����Ӧ����ʽ����-- +/*span.anticon:not(.app-iconify) { + vertical-align: 0.125em !important; +}*/ +//update-end---author:scott ---date::2023-08-28 for����QQYUN-6374��UnoCSS���windicss����Ӧ����ʽ����-- + +.ant-back-top { + right: 20px; + bottom: 20px; +} + +.collapse-container__body { + > .ant-descriptions { + margin-left: 6px; + } +} + +.ant-image-preview-operations { + background-color: rgba(0, 0, 0, 0.3); +} + +.ant-popover { + &-content { + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); + } +} + +// ================================= +// ==============modal message====== +// ================================= +.modal-icon-warning { + color: @warning-color !important; +} + +.modal-icon-success { + color: @success-color !important; +} + +.modal-icon-error { + color: @error-color !important; +} + +.modal-icon-info { + color: @primary-color !important; +} + +.ant-checkbox-checked .ant-checkbox-inner::after, +.ant-tree-checkbox-checked .ant-tree-checkbox-inner::after { + border-top: 0 !important; + border-left: 0 !important; +} + +// update-begin--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x +.ant-modal { + .ant-modal-close { + // update-begin--author:liaozhiyang---date:20241010---for:【issues/7260】原生a-modal关闭按钮位置偏移 + // position: absolute; + // top: 0; + // right: 0; + top: 13px; + // update-end--author:liaozhiyang---date:20241010---for:【issues/7260】原生a-modal关闭按钮位置偏移 + width: auto; + height: auto; + } + .ant-modal-content { + padding: 0; + } +} + +.ant-input-affix-wrapper > input.ant-input { + font-size: 14px; +} + +.ant-pagination-options-size-changer.ant-select { + display: inline-block; + width: auto; +} + +.ant-tree-select-dropdown .ant-select-tree .ant-select-tree-list-holder-inner { + align-items: stretch; +} + +.ant-list .ant-list-item {padding-left: 0;padding-right: 0;} + + +.ant-list-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 0; + color: #000000d9; +} +/** anticon-down跟3.x保持一致*/ +.ant-dropdown-trigger>.anticon.anticon-down, .ant-dropdown-link>.anticon.anticon-down, .ant-dropdown-button>.anticon.anticon-down { + font-size: 10px; + vertical-align: baseline; +} +/** 表格排序箭头尺寸保持跟3.x一致 */ +.ant-table-wrapper .ant-table-column-sorter-up, .ant-table-wrapper .ant-table-column-sorter-down { + font-size: 11px; +} + /** 表格头部文字颜色跟3.x版本保持一致 */ +.ant-table-wrapper .ant-table-thead >tr>th, .ant-table-wrapper .ant-table-thead >tr>td { + color: #000000d9; + font-weight: 500; +} +html[data-theme='dark'] .ant-table-wrapper .ant-table-thead >tr>th, .ant-table-wrapper .ant-table-thead >tr>td { + color:rgba(255,255,255,.65); +} + /** 下拉菜单文字和图标折叠了 */ +.ant-dropdown .ant-dropdown-menu .ant-dropdown-menu-title-content, .ant-dropdown-menu-submenu .ant-dropdown-menu .ant-dropdown-menu-title-content{ + flex: auto; + white-space:nowrap; +} +// update-end--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x + +// update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7493】多行文本内容过多时内容会覆盖掉清空按钮 +.ant-input-affix-wrapper-textarea-with-clear-btn { + .ant-input-clear-icon { + background-color: #fff; + } +} +html[data-theme='dark'] .ant-input-affix-wrapper-textarea-with-clear-btn { + .ant-input-clear-icon { + background-color: #141414; + } +} +// update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7493】多行文本内容过多时内容会覆盖掉清空按钮 + +// update-begin--author:liaozhiyang---date:20230108---for:【QQYUN-7855】table页码同步3.x页面效果 +.ant-table-pagination.ant-pagination { + .ant-pagination-item-active, + .ant-pagination-item-active:hover { + background-color: @primary-color; + border-color: transparent; + a { + color: #fff; + } + } + .ant-pagination-item:not(.ant-pagination-item-active) { + background-color: transparent !important; + border-color: transparent; + } + .ant-pagination-prev, + .ant-pagination-next, + .ant-pagination-item { + margin: 0 4px; + } +} +// update-end--author:liaozhiyang---date:20230108---for:【QQYUN-7855】table页码同步3.x页面效果 + +//update-begin--author:wangshuai---date:20240429---for:修改tinymce段落下拉框的字体和样式 +.tox .tox-tbtn__select-label{ + font-size: 14px; +} + +.tox .tox-tbtn--select{ + width: 80px !important; +} + +.tox .tox-collection__item-label { + font-size: 14px !important; +} +//update-end--author:wangshuai---date:20240429---for:修改tinymce段落下拉框的字体和样式 + +// update-begin--author:liaozhiyang---date:20240605---for:【TV360X-189】统一只读样式 +html[data-theme='light'] { + .ant-form:not(.jeecg-form-detail-effect) { + .ant-select.ant-select-disabled { + .ant-select-selection-item { + color: rgba(51, 51, 51, 0.25) !important; + // color: rgba(51, 51, 51, 0.25); + .ant-select-selection-item-content { + color: rgba(51, 51, 51, 0.25); + } + } + } + .ant-input-number.ant-input-number-disabled { + .ant-input-number-input { + color: rgba(51, 51, 51, 0.25); + } + } + } +} + +html[data-theme='dark'] { + .ant-form:not(.jeecg-form-detail-effect) { + .ant-input-number.ant-input-number-disabled { + .ant-input-number-input { + color:rgba(255, 255, 255, 0.25); + } + } + } +} +// update-end--author:liaozhiyang---date:20240605---for:【TV360X-189】统一只读样式 diff --git a/src/design/ant/input.less b/src/design/ant/input.less new file mode 100644 index 0000000..57f85e5 --- /dev/null +++ b/src/design/ant/input.less @@ -0,0 +1,24 @@ +@import (reference) '../color.less'; + +// input +.ant-input { + &-number { + min-width: 110px; + } +} + +.ant-input-affix-wrapper .ant-input-suffix { + right: 9px; +} + +.ant-input-clear-icon { + margin-right: 5px; +} + +.ant-input-affix-wrapper-textarea-with-clear-btn { + padding: 0 !important; + + textarea.ant-input { + padding: 4px; + } +} diff --git a/src/design/ant/pagination.less b/src/design/ant/pagination.less new file mode 100644 index 0000000..388edcc --- /dev/null +++ b/src/design/ant/pagination.less @@ -0,0 +1,98 @@ +html[data-theme='dark'] { + .ant-pagination { + &.mini { + .ant-pagination-prev, + .ant-pagination-next, + .ant-pagination-item { + background-color: rgb(255 255 255 / 4%) !important; + + a { + color: #8b949e !important; + } + } + + .ant-select-arrow { + color: @text-color-secondary !important; + } + + .ant-pagination-item-active { + background-color: @primary-color !important; + border: none; + border-radius: none !important; + + a { + color: @white !important; + } + } + } + } +} + +.ant-pagination { + &.mini { + .ant-pagination-prev, + .ant-pagination-next { + font-size: 12px; + color: @text-color-base; + border: 1px solid; + } + + .ant-pagination-prev:hover, + .ant-pagination-next:hover, + .ant-pagination-item:focus, + .ant-pagination-item:hover { + a { + color: @primary-color; + } + } + + .ant-pagination-prev, + .ant-pagination-next, + .ant-pagination-item { + margin: 0 4px !important; + //update-begin---author:scott ---date:2022-09-30 for:【美化】Table分页页面默认背景色丑,去掉----------- + //background-color: #f4f4f5 !important; + //update-end---author:scott ---date::2022-09-30 for:【美化】Table分页页面默认背景色丑,去掉------------ + border: none; + border-radius: none !important; + + a { + margin-top: 1px; + color: #606266; + } + + &:last-child { + margin-right: 0 !important; + } + } + + .ant-pagination-item-active { + background-color: @primary-color !important; + border: none; + border-radius: none !important; + + a { + color: @white !important; + } + } + + .ant-pagination-options { + margin-left: 12px; + } + + .ant-pagination-options-quick-jumper input { + height: 22px; + margin: 0 6px; + line-height: 22px; + text-align: center; + } + + .ant-select-arrow { + color: @border-color-shallow-dark; + } + } + + &-disabled { + display: none !important; + } +} diff --git a/src/design/ant/table.less b/src/design/ant/table.less new file mode 100644 index 0000000..fabe60e --- /dev/null +++ b/src/design/ant/table.less @@ -0,0 +1,76 @@ +@prefix-cls: ~'@{namespace}-basic-table'; + +// fix table unnecessary scrollbar +.@{prefix-cls} { + .hide-scrollbar-y { + .ant-spin-nested-loading { + .ant-spin-container { + .ant-table { + .ant-table-content { + .ant-table-scroll { + .ant-table-hide-scrollbar { + overflow-y: auto !important; + } + + .ant-table-content { + overflow-y: auto !important; + } + } + + .ant-table-fixed-right { + .ant-table-body-outer { + .ant-table-body-inner { + overflow-y: auto !important; + } + } + } + + .ant-table-fixed-left { + .ant-table-body-outer { + .ant-table-body-inner { + overflow-y: auto !important; + } + } + } + } + } + } + } + } + + .hide-scrollbar-x { + .ant-spin-nested-loading { + .ant-spin-container { + .ant-table { + .ant-table-content { + .ant-table-scroll { + .ant-table-hide-scrollbar { + //overflow-x: auto !important; + } + + .ant-table-content { + overflow: auto !important; + } + } + + .ant-table-fixed-right { + .ant-table-body-outer { + .ant-table-body-inner { + overflow-x: auto !important; + } + } + } + + .ant-table-fixed-left { + .ant-table-body-outer { + .ant-table-body-inner { + overflow-x: auto !important; + } + } + } + } + } + } + } + } +} diff --git a/src/design/color.less b/src/design/color.less new file mode 100644 index 0000000..b7ad7e2 --- /dev/null +++ b/src/design/color.less @@ -0,0 +1,140 @@ +html { + // header + --header-bg-color: #394664; + --header-bg-hover-color: #273352; + --header-active-menu-bg-color: #273352; + + // sider + --sider-dark-bg-color: #273352; + --sider-dark-darken-bg-color: #273352; + --sider-dark-lighten-bg-color: #273352; + --sider-logo-bg-color:linear-gradient(180deg, #000000, #021d37); +} + +@white: #fff; + +@content-bg: #f4f7f9; + +// :export { +// name: "less"; +// mainColor: @mainColor; +// fontSize: @fontSize; +// } +@iconify-bg-color: #5551; + +// ================================= +// ==============border-color======= +// ================================= + +// Dark-dark +@border-color-dark: #b6b7b9; + +// Dark-light +@border-color-shallow-dark: #cececd; + +// Light-dark +@border-color-light: @border-color-base; + +// ================================= +// ==============message============== +// ================================= + +// success-bg-color +@success-background-color: #f1f9ec; +// info-bg-color +@info-background-color: #e8eff8; +// warn-bg-color +@warning-background-color: #fdf6ed; +// danger-bg-color +@danger-background-color: #fef0f0; + +// ================================= +// ==============Header============= +// ================================= + +@header-dark-bg-color: var(--header-bg-color); +@header-dark-bg-hover-color: var(--header-bg-hover-color); +@header-light-bg-hover-color: #f6f6f6; +@header-light-desc-color: #7c8087; +@header-light-bottom-border-color: #eee; +// top-menu +@top-menu-active-bg-color: var(--header-active-menu-bg-color); + +// ================================= +// ==============Menu============ +// ================================= + +// let -menu +@sider-logo-bg-color: var(--sider-logo-bg-color); +@sider-dark-bg-color: var(--sider-dark-bg-color); +@sider-dark-darken-bg-color: var(--sider-dark-darken-bg-color); +@sider-dark-lighten-bg-color: var(--sider-dark-lighten-bg-color); + +// trigger +@trigger-dark-hover-bg-color: rgba(255, 255, 255, 0.2); +@trigger-dark-bg-color: rgba(255, 255, 255, 0.1); + +// ================================= +// ==============tree============ +// ================================= +// tree item hover background +@tree-hover-background-color: #f5f7fa; +// tree item hover font color +@tree-hover-font-color: #f5f7fa; + +// ================================= +// ==============link============ +// ================================= +@link-hover-color: @primary-color; +@link-active-color: darken(@primary-color, 10%); + +// ================================= +// ==============Text color-============= +// ================================= + +// Main text color +@text-color-base: @text-color; + +// Label color +@text-color-call-out: #606266; + +// Auxiliary information color-dark +@text-color-help-dark: #909399; + +// ================================= +// ==============breadcrumb========= +// ================================= +@breadcrumb-item-normal-color: #999; +// ================================= +// ==============button============= +// ================================= + +@button-primary-color: @primary-color; +@button-primary-hover-color: lighten(@primary-color, 5%); +@button-primary-active-color: darken(@primary-color, 5%); + +@button-ghost-color: @white; +@button-ghost-hover-color: lighten(@white, 10%); +@button-ghost-hover-bg-color: #e1ebf6; +@button-ghost-active-color: darken(@white, 10%); + +@button-success-color: @success-color; +@button-success-hover-color: lighten(@success-color, 10%); +@button-success-active-color: darken(@success-color, 10%); + +@button-warn-color: @warning-color; +@button-warn-hover-color: lighten(@warning-color, 10%); +@button-warn-active-color: darken(@warning-color, 10%); + +@button-error-color: @error-color; +@button-error-hover-color: lighten(@error-color, 10%); +@button-error-active-color: darken(@error-color, 10%); + +@button-cancel-color: @text-color-call-out; +@button-cancel-bg-color: @white; +@button-cancel-border-color: @border-color-shallow-dark; + +// Mouse over +@button-cancel-hover-color: @primary-color; +@button-cancel-hover-bg-color: @white; +@button-cancel-hover-border-color: @primary-color; diff --git a/src/design/config.less b/src/design/config.less new file mode 100644 index 0000000..64c33f6 --- /dev/null +++ b/src/design/config.less @@ -0,0 +1,2 @@ +@import (reference) 'color.less'; +@import (reference) 'var/index.less'; diff --git a/src/design/entry.css b/src/design/entry.css new file mode 100644 index 0000000..abc8bdb --- /dev/null +++ b/src/design/entry.css @@ -0,0 +1,181 @@ +* > .enter-x:nth-child(1) { + transform: translateX(50px); +} +* > .-enter-x:nth-child(1) { + transform: translateX(-50px); +} + +* > .enter-x:nth-child(1), +* > .-enter-x:nth-child(1) { + z-index: 9; + opacity: 0; + animation: enter-x-animation 0.4s ease-in-out 0.3s; + animation-fill-mode: forwards; + animation-delay: 0.1s; +} +* > .enter-x:nth-child(2) { + transform: translateX(50px); +} +* > .-enter-x:nth-child(2) { + transform: translateX(-50px); +} + +* > .enter-x:nth-child(2), +* > .-enter-x:nth-child(2) { + z-index: 8; + opacity: 0; + animation: enter-x-animation 0.4s ease-in-out 0.3s; + animation-fill-mode: forwards; + animation-delay: 0.2s; +} +* > .enter-x:nth-child(3) { + transform: translateX(50px); +} +* > .-enter-x:nth-child(3) { + transform: translateX(-50px); +} + +* > .enter-x:nth-child(3), +* > .-enter-x:nth-child(3) { + z-index: 7; + opacity: 0; + animation: enter-x-animation 0.4s ease-in-out 0.3s; + animation-fill-mode: forwards; + animation-delay: 0.3s; +} + +* > .enter-x:nth-child(4) { + transform: translateX(50px); +} +* > .-enter-x:nth-child(4) { + transform: translateX(-50px); +} + +* > .enter-x:nth-child(4), +* > .-enter-x:nth-child(4) { + z-index: 6; + opacity: 0; + animation: enter-x-animation 0.4s ease-in-out 0.3s; + animation-fill-mode: forwards; + animation-delay: 0.4s; +} + +* > .enter-x:nth-child(5) { + transform: translateX(50px); +} +* > .-enter-x:nth-child(5) { + transform: translateX(-50px); +} + +* > .enter-x:nth-child(5), +* > .-enter-x:nth-child(5) { + z-index: 5; + opacity: 0; + animation: enter-x-animation 0.4s ease-in-out 0.3s; + animation-fill-mode: forwards; + animation-delay: 0.5s; +} + +* > .enter-y:nth-child(1) { + transform: translateX(50px); +} +* > .-enter-y:nth-child(1) { + transform: translateX(-50px); +} + +* > .enter-y:nth-child(1), +* > .-enter-y:nth-child(1) { + z-index: 9; + opacity: 0; + animation: enter-y-animation 0.4s ease-in-out 0.3s; + animation-fill-mode: forwards; + animation-delay: 0.1s; +} +* > .enter-y:nth-child(2) { + transform: translateX(50px); +} +* > .-enter-y:nth-child(2) { + transform: translateX(-50px); +} + +* > .enter-y:nth-child(2), +* > .-enter-y:nth-child(2) { + z-index: 8; + opacity: 0; + animation: enter-y-animation 0.4s ease-in-out 0.3s; + animation-fill-mode: forwards; + animation-delay: 0.2s; +} +* > .enter-y:nth-child(3) { + transform: translateX(50px); +} +* > .-enter-y:nth-child(3) { + transform: translateX(-50px); +} + +* > .enter-y:nth-child(3), +* > .-enter-y:nth-child(3) { + z-index: 7; + opacity: 0; + animation: enter-y-animation 0.4s ease-in-out 0.3s; + animation-fill-mode: forwards; + animation-delay: 0.3s; +} + +* > .enter-y:nth-child(4) { + transform: translateX(50px); +} +* > .-enter-y:nth-child(4) { + transform: translateX(-50px); +} + +* > .enter-y:nth-child(4), +* > .-enter-y:nth-child(4) { + z-index: 6; + opacity: 0; + animation: enter-y-animation 0.4s ease-in-out 0.3s; + animation-fill-mode: forwards; + animation-delay: 0.4s; +} + +* > .enter-y:nth-child(5) { + transform: translateX(50px); +} +* > .-enter-y:nth-child(5) { + transform: translateX(-50px); +} + +* > .enter-y:nth-child(5), +* > .-enter-y:nth-child(5) { + z-index: 5; + opacity: 0; + animation: enter-y-animation 0.4s ease-in-out 0.3s; + animation-fill-mode: forwards; + animation-delay: 0.5s; +} + +@keyframes enter-x-animation { + to { + opacity: 1; + transform: translateX(0); + } +} +@keyframes enter-y-animation { + to { + opacity: 1; + transform: translateY(0); + } +} + +/*update-begin---author:wangshuai ---date:20230829 for:UnoCSS替代windicss 导致全局默认边框样丢失------------*/ +*, :before, :after { + box-sizing: border-box; + border-width: 0; + border-style: solid; + border-color: #e5e7eb; +} +.border-primary,.hover\:border-primary:hover { + --tw-border-opacity: 1; + border-color: rgba(24,144,255,var(--tw-border-opacity)) +} +/*update-end---author:wangshuai ---date:20230829 for:UnoCSS替代windicss 导致全局默认边框样丢失------------*/ \ No newline at end of file diff --git a/src/design/index.less b/src/design/index.less new file mode 100644 index 0000000..245a6d7 --- /dev/null +++ b/src/design/index.less @@ -0,0 +1,332 @@ +@import 'transition/index.less'; +@import 'var/index.less'; +@import 'public.less'; +@import 'ant/index.less'; +@import './theme.less'; +@import './entry.css'; + +input:-webkit-autofill { + -webkit-box-shadow: 0 0 0 1000px white inset !important; +} + +:-webkit-autofill { + transition: background-color 5000s ease-in-out 0s !important; +} + +html { + overflow: hidden; + -webkit-text-size-adjust: 100%; +} + +html, +body { + width: 100%; + height: 100%; + // body添加行高保持跟3.x一致 + line-height: 1.5715; + + &.color-weak { + filter: invert(80%); + } + + &.gray-mode { + filter: grayscale(100%); + filter: progid:dximagetransform.microsoft.basicimage(grayscale=1); + } +} + +/* 【LOWCOD-2300】【vue3】online--online表单开发,下拉框位置靠下时,点开下拉框,整屏跳 */ +body { + overflow: visible; + overflow-x: hidden; +} + +a:focus, +a:active, +button, +div, +svg, +span { + outline: none !important; +} + +// 保持 和 windi 一样的全局样式,减少升级带来的影响 +ul { + list-style: none; + margin: 0; + padding: 0; +} +img, video { + max-width: 100%; + height: auto; +} +// 保持 和 windi 一样的全局样式,减少升级带来的影响 + +// update-begin--author:liaozhiyang---date:20230925---for:【issues/5407】字段信息校验是多行提示会被遮挡 +.vxe-cell--valid-error-msg { + white-space: nowrap; +} +// update-end--author:liaozhiyang---date:20230925---for:【issues/5407】字段信息校验是多行提示会被遮挡 + +// update-begin--author:liaozhiyang---date:20231013---for:【QQYUN-5133】升级之后提示样式跟之前一致 +.vxe-table .vxe-body--row:last-child .vxe-cell--valid-error-hint { + margin-top: auto; +} +.vxe-cell--valid-error-hint { + margin-top: 6px; +} +.vxe-cell--valid-error-msg { + display: inline-block; + border-radius: 4px !important; + padding: 8px 12px !important; + color: #fff !important; + background-color: #f56c6c !important; + +} +// update-end--author:liaozhiyang---date:20231013---for:【QQYUN-5133】升级之后提示样式跟之前一致 +// update-begin--author:liaozhiyang---date:20231116---for:【QQYUN-7011】online表单多了一个蓝色的边框 +// .vxe-table.vxe-table--render-default .vxe-body--column.col--selected { +// box-shadow: none; +// } +// update-end--author:liaozhiyang---date:20231116---for:【QQYUN-7011】online表单多了一个蓝色的边框 + +// update-begin--author:liaozhiyang---date:20240424---for:【issues/1175】解决vxetable鼠标hover之后title显示不对的问题 +.vxe-cell { + pointer-events: none; + > * { + pointer-events: auto; + } +} +// update-end--author:liaozhiyang---date:20240424---for:【issues/1175】解决vxetable鼠标hover之后title显示不对的问题 + +// update-begin--author:liaozhiyang---date:20240429---for:【QQYUN-9023】引导样式调整 +.introjs-tooltipReferenceLayer { + .introjs-tooltip-title { + font-size: 15px; + } + .introjs-skipbutton { + font-size: 20px; + line-height: 35px; + height: 35px; + width: 35px; + font-weight: 500; + } + .introjs-tooltiptext { + padding: 16px; + font-size: 14px; + } + .introjs-bullets { + padding-top: 0; + padding-bottom: 8px; + } + .introjs-bullets ul li a { + width: 4px; + height: 4px; + } + .introjs-button { + padding: .2rem 0.5rem; + font-size: 13px; + } +} +// update-end--author:liaozhiyang---date:20240429---for:【QQYUN-9023】引导样式调整 + +// update-begin--author:liaozhiyang---date:20240605---for:【TV360X-857】online代码生成详情样式调整 + +html[data-theme='light'] { + .jeecg-form-detail-effect { + *:not(.ant-select-selection-placeholder){ + color: #606266!important; + .colorText { + color: #ffffff !important; + } + } + .ant-row label { + color: #797c81 !important; + } + .ant-select-selector, + .ant-btn, + .ant-input, + .ant-input-affix-wrapper, + .ant-picker, + .ant-input-number { + // border: none !important; + // color: rgba(51, 51, 51, 0.25) !important; + color: #606266!important; + background-color: #f9f9fa !important; + } + + a, + .anticon { + pointer-events: none; + cursor: text; + color: #606266!important; + &:hover { + background: transparent; + } + } + + .ant-select.ant-select-disabled .ant-select-selection-item .ant-select-selection-item-content { + color: #606266!important; + } + .ant-select-selection-item { + color: #606266!important; + } + + :where(.css-dev-only-do-not-override-dvamda).ant-picker .ant-picker-input >input-disabled, :where(.css-dev-only-do-not-override-dvamda).ant-picker .ant-picker-input >input[disabled] { + color: #606266!important; + } + .ant-select-selection-item { + border-color: #eee !important; + background-color: transparent !important; + } + //【QQYUN-13754】switch在禁用的方式下效果有问题:字体为黑色 + .ant-switch-disabled .ant-switch-inner-checked,.ant-switch-inner-unchecked{ + color: #fff !important; + } + } +} +html[data-theme='dark'] { + .jeecg-form-detail-effect { + * { + color: #606266; + } + .ant-upload-text-icon, a { + color:rgba(255, 255, 255, 0.25) ; + } + .ant-select-selector, + .ant-btn, + .ant-input, + .ant-input-affix-wrapper, + .ant-picker, + .ant-input-number { + background-color: transparent !important; + } + + .ant-select-selection-item { + background-color: transparent !important; + } + // 暗黑模式下输入框等icon隐藏 + .ant-picker-suffix,.ant-select-arrow { + content:" "; + display: none; + } + } +} +.jeecg-form-detail-effect { + .ant-select-selector, + .ant-btn, + .ant-input, + .ant-input-affix-wrapper, + .ant-picker, + .ant-input-number { + border: none !important; + } + a, + .anticon { + pointer-events: none; + cursor: text; + &:hover { + background: transparent; + } + } + .ant-picker { + width: 100%; + } + textarea { + resize: none !important; + } + input { + border: none !important; + } + input, textarea { + user-select: auto; + cursor: text !important; + } + .JSelectDept, + .JselectUser, + .JSelectPosition { + > div { + > .ant-row { + .left { + width: 100%; + } + .right { + display: none; + } + } + } + } + .ant-select-selection-item-remove { + display: none; + } + .jeecg-j-upload-container { + .ant-upload { + display: none; + } + .ant-upload-list-item-done { + a { + pointer-events: auto !important; + cursor: pointer !important; + } + .ant-upload-list-item-actions { + display: none; + } + } + } + .ant-upload-picture-card-wrapper { + .ant-upload { + pointer-events: none; + cursor: not-allowed; + .ant-upload-text,.anticon { + display: none; + } + } + .ant-upload-list-item-done { + a { + pointer-events: auto !important; + cursor: pointer !important; + } + .ant-btn { + display: none; + } + } + } +} +// update-end--author:liaozhiyang---date:20240605---for:【TV360X-857】online代码生成详情样式调整 + +// update-begin--author:wangshuai---date:20240611---for:【TV360X-1070】一对多内嵌,为什么多这一块,不从头对齐 +.ant-table-wrapper .ant-table.ant-table-middle .ant-table-tbody .ant-table-wrapper:only-child .ant-table{ + margin-block: 0; + margin-inline: 0; +} +// update-end--author:wangshuai---date:20240611---for:【TV360X-1070】一对多内嵌,为什么多这一块,不从头对齐 + +// 单行文本溢出省略号 +.ellipsis { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} +// 两行省略 +.ellipsis-2 { + display: -webkit-box; + overflow: hidden; + text-overflow: ellipsis; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + word-break: break-all; +} +// 三行省略 +.ellipsis-3 { + display: -webkit-box; + overflow: hidden; + text-overflow: ellipsis; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; +} + +.table-action-item { + &.color-red { + color: red !important; + } +} diff --git a/src/design/public.less b/src/design/public.less new file mode 100644 index 0000000..eda1bbf --- /dev/null +++ b/src/design/public.less @@ -0,0 +1,113 @@ +#app { + width: 100%; + height: 100%; +} + +// ================================= +// ==============scrollbar========== +// ================================= + +::-webkit-scrollbar { + width: 7px; + height: 8px; +} + +// ::-webkit-scrollbar-track { +// background: transparent; +// } + +::-webkit-scrollbar-track { + background-color: rgba(0, 0, 0, 0.05); +} + +::-webkit-scrollbar-thumb { + // background: rgba(0, 0, 0, 0.6); + background-color: rgba(144, 147, 153, 0.3); + // background-color: rgba(144, 147, 153, 0.3); + border-radius: 2px; + box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2); +} + +::-webkit-scrollbar-thumb:hover { + background-color: @border-color-dark; +} + +[data-theme='dark'] { + ::-webkit-scrollbar-thumb:hover { + background-color: #5e6063; + } +} + +// ================================= +// ==============nprogress========== +// ================================= +#nprogress { + pointer-events: none; + + .bar { + position: fixed; + top: 0; + left: 0; + z-index: 99999; + width: 100%; + height: 2px; + background-color: @primary-color; + opacity: 0.75; + } +} + +// ======================================= +// ============ [sjl] 按钮组样式 ========== +// ======================================= +.j-table-operator { + // Button按钮间距 + .ant-btn { + margin: 0 8px 8px 0; + transition: margin 0s; + } + + & > .ant-btn:last-of-type { + margin: 0 0 8px 0; + } + + .ant-btn-group, + &.ant-btn-group { + .ant-btn { + margin: 0; + transition: margin 0s; + } + + & > .ant-btn:last-of-type { + margin: 0 8px 8px 0; + } + } +} + +// ======================================== +// ============ [sjl] 底部按钮样式 ========== +// ======================================== +.j-box-bottom-button { + height: 28px; + + &-float { + position: absolute; + left: 0; + right: 0; + bottom: 0; + border-top: 1px solid #e8e8e8; + padding: 10px 16px; + text-align: right; + background: #fff; + border-radius: 0 0 2px 2px; + + & .ant-btn { + margin-left: 8px; + } + } + + &.offset-20 &-float { + left: -20px; + right: -20px; + bottom: -20px; + } +} diff --git a/src/design/theme.less b/src/design/theme.less new file mode 100644 index 0000000..489e113 --- /dev/null +++ b/src/design/theme.less @@ -0,0 +1,84 @@ +.bg-white { + background-color: @component-background !important; +} + +html[data-theme='light'] { + // update-begin--author:liaozhiyang---date:20240407---for:【QQYUN-8774】给body加上打底的字体颜色 + body{ + color: rgba(0, 0, 0, 0.65); + } + // update-end--author:liaozhiyang---date:20240407---for:【QQYUN-8774】给body加上打底的字体颜色 + .text-secondary { + color: rgba(0, 0, 0, 0.45); + } + /*【美化】自定义table字体颜色*/ + .ant-table { + color: rgba(0, 0, 0, 0.65); + } + /*【美化】自定义table字体颜色*/ + /*【美化】自定义form字体颜色*/ + .ant-select-multiple .ant-select-selection-item-content { + color: rgba(0, 0, 0, 0.65); + } + .ant-input-affix-wrapper > input.ant-input { + // update-begin--author:liaozhiyang---date:20240605---for:【TV360X-189】统一只读样式 + &:not([disabled]) { + color: rgba(0, 0, 0, 0.65); + } + // update-end--author:liaozhiyang---date:20240605---for:【TV360X-189】统一只读样式 + } + .ant-select-single.ant-select-show-arrow .ant-select-selection-item, .ant-select-single.ant-select-show-arrow { + color: rgba(0, 0, 0, 0.65); + } + /*【美化】自定义form字体颜色*/ + + .ant-alert-success { + background-color: #f6ffed; + border: 1px solid #b7eb8f; + } + + .ant-alert-error { + background-color: #fff2f0; + border: 1px solid #ffccc7; + } + + .ant-alert-warning { + background-color: #fffbe6; + border: 1px solid #ffe58f; + } + :not(:root):fullscreen::backdrop { + background-color: @layout-body-background !important; + } +} + +[data-theme='dark'] { + // update-begin--author:liaozhiyang---date:20240407---for:【QQYUN-8774】给body加上打底的字体颜色 + body{ + color: rgba(255, 255, 255, 0.85); + } + // update-end--author:liaozhiyang---date:20240407---for:【QQYUN-8774】给body加上打底的字体颜色 + // update-begin--author:liaozhiyang---date:20240407---for:【QQYUN-8641】黑色主题-流程办理 + .ant-list .ant-list-item { + color: rgba(255, 255, 255, 0.85); + } + // update-end--author:liaozhiyang---date:20240407---for:【QQYUN-8641】黑色主题-流程办理 + .text-secondary { + color: #8b949e; + } + + .ant-card-grid-hoverable:hover { + box-shadow: 0 3px 6px -4px rgb(0 0 0 / 48%), 0 6px 16px 0 rgb(0 0 0 / 32%), 0 9px 28px 8px rgb(0 0 0 / 20%); + } + + .ant-card-grid { + box-shadow: 1px 0 0 0 #434343, 0 1px 0 0 #434343, 1px 1px 0 0 #434343, 1px 0 0 0 #434343 inset, 0 1px 0 0 #434343 inset; + } + + .ant-calendar-selected-day .ant-calendar-date { + color: rgba(0, 0, 0, 0.8); + } + + .ant-select-tree li .ant-select-tree-node-content-wrapper.ant-select-tree-node-selected { + color: rgba(0, 0, 0, 0.9); + } +} diff --git a/src/design/transition/base.less b/src/design/transition/base.less new file mode 100644 index 0000000..7944c8b --- /dev/null +++ b/src/design/transition/base.less @@ -0,0 +1,18 @@ +.transition-default() { + &-enter-active, + &-leave-active { + transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1) !important; + } + + &-move { + transition: transform 0.4s; + } +} + +.expand-transition { + .transition-default(); +} + +.expand-x-transition { + .transition-default(); +} diff --git a/src/design/transition/fade.less b/src/design/transition/fade.less new file mode 100644 index 0000000..1f8e63e --- /dev/null +++ b/src/design/transition/fade.less @@ -0,0 +1,81 @@ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.2s ease-in-out; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +/* fade-slide */ +.fade-slide-leave-active, +.fade-slide-enter-active { + transition: all 0.3s; +} + +.fade-slide-enter-from { + opacity: 0; + transform: translateX(-30px); +} + +.fade-slide-leave-to { + opacity: 0; + transform: translateX(30px); +} + +// /////////////////////////////////////////////// +// Fade Bottom +// /////////////////////////////////////////////// + +// Speed: 1x +.fade-bottom-enter-active, +.fade-bottom-leave-active { + transition: opacity 0.25s, transform 0.3s; +} + +.fade-bottom-enter-from { + opacity: 0; + transform: translateY(-10%); +} + +.fade-bottom-leave-to { + opacity: 0; + transform: translateY(10%); +} + +// fade-scale +.fade-scale-leave-active, +.fade-scale-enter-active { + transition: all 0.28s; +} + +.fade-scale-enter-from { + opacity: 0; + transform: scale(1.2); +} + +.fade-scale-leave-to { + opacity: 0; + transform: scale(0.8); +} + +// /////////////////////////////////////////////// +// Fade Top +// /////////////////////////////////////////////// + +// Speed: 1x +.fade-top-enter-active, +.fade-top-leave-active { + transition: opacity 0.2s, transform 0.25s; +} + +.fade-top-enter-from { + opacity: 0; + transform: translateY(8%); +} + +.fade-top-leave-to { + opacity: 0; + transform: translateY(-8%); +} diff --git a/src/design/transition/index.less b/src/design/transition/index.less new file mode 100644 index 0000000..e372b25 --- /dev/null +++ b/src/design/transition/index.less @@ -0,0 +1,10 @@ +@import './base.less'; +@import './fade.less'; +@import './scale.less'; +@import './slide.less'; +@import './scroll.less'; +@import './zoom.less'; + +.collapse-transition { + transition: 0.2s height ease-in-out, 0.2s padding-top ease-in-out, 0.2s padding-bottom ease-in-out; +} diff --git a/src/design/transition/scale.less b/src/design/transition/scale.less new file mode 100644 index 0000000..c965493 --- /dev/null +++ b/src/design/transition/scale.less @@ -0,0 +1,21 @@ +.scale-transition { + .transition-default(); + + &-enter-from, + &-leave, + &-leave-to { + opacity: 0; + transform: scale(0); + } +} + +.scale-rotate-transition { + .transition-default(); + + &-enter-from, + &-leave, + &-leave-to { + opacity: 0; + transform: scale(0) rotate(-45deg); + } +} diff --git a/src/design/transition/scroll.less b/src/design/transition/scroll.less new file mode 100644 index 0000000..a5f45e4 --- /dev/null +++ b/src/design/transition/scroll.less @@ -0,0 +1,67 @@ +.scroll-y-transition { + .transition-default(); + + &-enter-from, + &-leave-to { + opacity: 0; + } + + &-enter-from { + transform: translateY(-15px); + } + + &-leave-to { + transform: translateY(15px); + } +} + +.scroll-y-reverse-transition { + .transition-default(); + + &-enter-from, + &-leave-to { + opacity: 0; + } + + &-enter-from { + transform: translateY(15px); + } + + &-leave-to { + transform: translateY(-15px); + } +} + +.scroll-x-transition { + .transition-default(); + + &-enter-from, + &-leave-to { + opacity: 0; + } + + &-enter-from { + transform: translateX(-15px); + } + + &-leave-to { + transform: translateX(15px); + } +} + +.scroll-x-reverse-transition { + .transition-default(); + + &-enter-from, + &-leave-to { + opacity: 0; + } + + &-enter-from { + transform: translateX(15px); + } + + &-leave-to { + transform: translateX(-15px); + } +} diff --git a/src/design/transition/slide.less b/src/design/transition/slide.less new file mode 100644 index 0000000..79b00df --- /dev/null +++ b/src/design/transition/slide.less @@ -0,0 +1,39 @@ +.slide-y-transition { + .transition-default(); + + &-enter-from, + &-leave-to { + opacity: 0; + transform: translateY(-15px); + } +} + +.slide-y-reverse-transition { + .transition-default(); + + &-enter-from, + &-leave-to { + opacity: 0; + transform: translateY(15px); + } +} + +.slide-x-transition { + .transition-default(); + + &-enter-from, + &-leave-to { + opacity: 0; + transform: translateX(-15px); + } +} + +.slide-x-reverse-transition { + .transition-default(); + + &-enter-from, + &-leave-to { + opacity: 0; + transform: translateX(15px); + } +} diff --git a/src/design/transition/zoom.less b/src/design/transition/zoom.less new file mode 100644 index 0000000..2ea378c --- /dev/null +++ b/src/design/transition/zoom.less @@ -0,0 +1,27 @@ +// zoom-out +.zoom-out-enter-active, +.zoom-out-leave-active { + transition: opacity 0.1 ease-in-out, transform 0.15s ease-out; +} + +.zoom-out-enter-from, +.zoom-out-leave-to { + opacity: 0; + transform: scale(0); +} + +// zoom-fade +.zoom-fade-enter-active, +.zoom-fade-leave-active { + transition: transform 0.2s, opacity 0.3s ease-out; +} + +.zoom-fade-enter-from { + opacity: 0; + transform: scale(0.92); +} + +.zoom-fade-leave-to { + opacity: 0; + transform: scale(1.06); +} diff --git a/src/design/var/breakpoint.less b/src/design/var/breakpoint.less new file mode 100644 index 0000000..793e826 --- /dev/null +++ b/src/design/var/breakpoint.less @@ -0,0 +1,33 @@ +// ================================= +// ==============屏幕断点============ +// ================================= + +// Extra small screen / phone +@screen-xs: 480px; +@screen-xs-min: @screen-xs; + +// Small screen / tablet +@screen-sm: 576px; +@screen-sm-min: @screen-sm; + +// Medium screen / desktop +@screen-md: 768px; +@screen-md-min: @screen-md; + +// Large screen / wide desktop +@screen-lg: 992px; +@screen-lg-min: @screen-lg; + +// Extra large screen / full hd +@screen-xl: 1200px; +@screen-xl-min: @screen-xl; + +// Extra extra large screen / large desktop +@screen-2xl: 1600px; +@screen-2xl-min: @screen-2xl; + +@screen-xs-max: (@screen-sm-min - 1px); +@screen-sm-max: (@screen-md-min - 1px); +@screen-md-max: (@screen-lg-min - 1px); +@screen-lg-max: (@screen-xl-min - 1px); +@screen-xl-max: (@screen-2xl-min - 1px); diff --git a/src/design/var/easing.less b/src/design/var/easing.less new file mode 100644 index 0000000..e19735f --- /dev/null +++ b/src/design/var/easing.less @@ -0,0 +1,18 @@ +// ================================= +// ==============动画函数-=========== +// ================================= + +@ease-base-out: cubic-bezier(0.7, 0.3, 0.1, 1); +@ease-base-in: cubic-bezier(0.9, 0, 0.3, 0.7); +@ease-out: cubic-bezier(0.215, 0.61, 0.355, 1); +@ease-in: cubic-bezier(0.55, 0.055, 0.675, 0.19); +@ease-in-out: cubic-bezier(0.645, 0.045, 0.355, 1); +@ease-out-back: cubic-bezier(0.12, 0.4, 0.29, 1.46); +@ease-in-back: cubic-bezier(0.71, -0.46, 0.88, 0.6); +@ease-in-out-back: cubic-bezier(0.71, -0.46, 0.29, 1.46); +@ease-out-circ: cubic-bezier(0.08, 0.82, 0.17, 1); +@ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.34); +@ease-in-out-circ: cubic-bezier(0.78, 0.14, 0.15, 0.86); +@ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1); +@ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06); +@ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1); diff --git a/src/design/var/index.less b/src/design/var/index.less new file mode 100644 index 0000000..f9af55f --- /dev/null +++ b/src/design/var/index.less @@ -0,0 +1,46 @@ +@import (reference) '../color.less'; +@import 'easing'; +@import 'breakpoint'; + +@namespace: jeecg; + +// tabs +// updateBy:sunjianlei---updateDate:2021-09-03---修改tab切换栏样式:更改高度 +@multiple-height: 30px; +@multiple-card-height: 50px; +// update-begin--author:liaozhiyang---date:20240407---for:【QQYUN-8762】标签页圆滑高度 +@multiple-smooth-height: 48px; +// update-end--author:liaozhiyang---date:20240407---for:【QQYUN-8762】标签页圆滑高度 + +// headers +// update-begin--author:liaozhiyang---date:20240407---for:【QQYUN-8762】顶栏高度 +@header-height: 60px; +// update-end--author:liaozhiyang---date:20240407---for:【QQYUN-8762】顶栏高度 + +// logo width +@logo-width: 32px; + +// +@side-drag-z-index: 200; + +@page-loading-z-index: 10000; + +@lock-page-z-index: 3000; + +@layout-header-fixed-z-index: 500; + +@multiple-tab-fixed-z-index: 505; + +@layout-sider-fixed-z-index: 510; + +@layout-mix-sider-fixed-z-index: 550; + +@preview-comp-z-index: 1000; + +@page-footer-z-index: 99; + +.bem(@n; @content) { + @{namespace}-@{n} { + @content(); + } +} diff --git a/src/directives/clickOutside.ts b/src/directives/clickOutside.ts new file mode 100644 index 0000000..fa580c9 --- /dev/null +++ b/src/directives/clickOutside.ts @@ -0,0 +1,78 @@ +import { on } from '/@/utils/domUtils'; +import { isServer } from '/@/utils/is'; +import type { ComponentPublicInstance, DirectiveBinding, ObjectDirective } from 'vue'; + +type DocumentHandler = (mouseup: T, mousedown: T) => void; + +type FlushList = Map< + HTMLElement, + { + documentHandler: DocumentHandler; + bindingFn: (...args: unknown[]) => unknown; + } +>; + +const nodeList: FlushList = new Map(); + +let startClick: MouseEvent; + +if (!isServer) { + on(document, 'mousedown', (e: MouseEvent) => (startClick = e)); + on(document, 'mouseup', (e: MouseEvent) => { + for (const { documentHandler } of nodeList.values()) { + documentHandler(e, startClick); + } + }); +} + +function createDocumentHandler(el: HTMLElement, binding: DirectiveBinding): DocumentHandler { + let excludes: HTMLElement[] = []; + if (Array.isArray(binding.arg)) { + excludes = binding.arg; + } else { + // due to current implementation on binding type is wrong the type casting is necessary here + excludes.push(binding.arg as unknown as HTMLElement); + } + return function (mouseup, mousedown) { + const popperRef = ( + binding.instance as ComponentPublicInstance<{ + popperRef: Nullable; + }> + ).popperRef; + const mouseUpTarget = mouseup.target as Node; + const mouseDownTarget = mousedown.target as Node; + const isBound = !binding || !binding.instance; + const isTargetExists = !mouseUpTarget || !mouseDownTarget; + const isContainedByEl = el.contains(mouseUpTarget) || el.contains(mouseDownTarget); + const isSelf = el === mouseUpTarget; + + const isTargetExcluded = + (excludes.length && excludes.some((item) => item?.contains(mouseUpTarget))) || + (excludes.length && excludes.includes(mouseDownTarget as HTMLElement)); + const isContainedByPopper = popperRef && (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget)); + if (isBound || isTargetExists || isContainedByEl || isSelf || isTargetExcluded || isContainedByPopper) { + return; + } + binding.value(); + }; +} + +const ClickOutside: ObjectDirective = { + beforeMount(el, binding) { + nodeList.set(el, { + documentHandler: createDocumentHandler(el, binding), + bindingFn: binding.value, + }); + }, + updated(el, binding) { + nodeList.set(el, { + documentHandler: createDocumentHandler(el, binding), + bindingFn: binding.value, + }); + }, + unmounted(el) { + nodeList.delete(el); + }, +}; + +export default ClickOutside; diff --git a/src/directives/index.ts b/src/directives/index.ts new file mode 100644 index 0000000..0329eb6 --- /dev/null +++ b/src/directives/index.ts @@ -0,0 +1,11 @@ +/** + * Configure and register global directives + */ +import type { App } from 'vue'; +import { setupPermissionDirective } from './permission'; +import { setupLoadingDirective } from './loading'; + +export function setupGlobDirectives(app: App) { + setupPermissionDirective(app); + setupLoadingDirective(app); +} diff --git a/src/directives/loading.ts b/src/directives/loading.ts new file mode 100644 index 0000000..c2f25c6 --- /dev/null +++ b/src/directives/loading.ts @@ -0,0 +1,41 @@ +import { createLoading } from '/@/components/Loading'; +import type { Directive, App } from 'vue'; + +const loadingDirective: Directive = { + mounted(el, binding) { + const tip = el.getAttribute('loading-tip'); + const background = el.getAttribute('loading-background'); + const size = el.getAttribute('loading-size'); + const fullscreen = !!binding.modifiers.fullscreen; + const instance = createLoading( + { + tip, + background, + size: size || 'large', + loading: !!binding.value, + absolute: !fullscreen, + }, + fullscreen ? document.body : el + ); + el.instance = instance; + }, + updated(el, binding) { + const instance = el.instance; + if (!instance) return; + instance.setTip(el.getAttribute('loading-tip')); + if (binding.oldValue !== binding.value) { + if (binding.oldValue !== binding.value) { + instance.setLoading?.(binding.value && !instance.loading); + } + } + }, + unmounted(el) { + el?.instance?.close(); + }, +}; + +export function setupLoadingDirective(app: App) { + app.directive('loading', loadingDirective); +} + +export default loadingDirective; diff --git a/src/directives/permission.ts b/src/directives/permission.ts new file mode 100644 index 0000000..e5f60ac --- /dev/null +++ b/src/directives/permission.ts @@ -0,0 +1,33 @@ +/** + * Global authority directive + * Used for fine-grained control of component permissions + * @Example v-auth="RoleEnum.TEST" + */ +import type { App, Directive, DirectiveBinding } from 'vue'; + +import { usePermission } from '/@/hooks/web/usePermission'; + +function isAuth(el: Element, binding: any) { + // update-begin--author:liaozhiyang---date:20240529---for【TV360X-460】basicForm支持v-auth指令(权限控制显隐) + const value = binding.value; + if (!value) return; + // update-end--author:liaozhiyang---date:20240529---for【TV360X-460】basicForm支持v-auth指令(权限控制显隐) + const { hasPermission } = usePermission(); + if (!hasPermission(value)) { + el.parentNode?.removeChild(el); + } +} + +const mounted = (el: Element, binding: DirectiveBinding) => { + isAuth(el, binding); +}; + +const authDirective: Directive = { + mounted, +}; + +export function setupPermissionDirective(app: App) { + app.directive('auth', authDirective); +} + +export default authDirective; diff --git a/src/directives/repeatClick.ts b/src/directives/repeatClick.ts new file mode 100644 index 0000000..d4ef150 --- /dev/null +++ b/src/directives/repeatClick.ts @@ -0,0 +1,31 @@ +/** + * Prevent repeated clicks + * @Example v-repeat-click="()=>{}" + */ +import { on, once } from '/@/utils/domUtils'; +import type { Directive, DirectiveBinding } from 'vue'; + +const repeatDirective: Directive = { + beforeMount(el: Element, binding: DirectiveBinding) { + let interval: Nullable = null; + let startTime = 0; + const handler = (): void => binding?.value(); + const clear = (): void => { + if (Date.now() - startTime < 100) { + handler(); + } + interval && clearInterval(interval); + interval = null; + }; + + on(el, 'mousedown', (e: MouseEvent): void => { + if ((e as any).button !== 0) return; + startTime = Date.now(); + once(document as any, 'mouseup', clear); + interval && clearInterval(interval); + interval = setInterval(handler, 100); + }); + }, +}; + +export default repeatDirective; diff --git a/src/directives/ripple/index.less b/src/directives/ripple/index.less new file mode 100644 index 0000000..9c0718e --- /dev/null +++ b/src/directives/ripple/index.less @@ -0,0 +1,21 @@ +.ripple-container { + position: absolute; + top: 0; + left: 0; + width: 0; + height: 0; + overflow: hidden; + pointer-events: none; +} + +.ripple-effect { + position: relative; + z-index: 9999; + width: 1px; + height: 1px; + margin-top: 0; + margin-left: 0; + pointer-events: none; + border-radius: 50%; + transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); +} diff --git a/src/directives/ripple/index.ts b/src/directives/ripple/index.ts new file mode 100644 index 0000000..6932264 --- /dev/null +++ b/src/directives/ripple/index.ts @@ -0,0 +1,180 @@ +import type { Directive } from 'vue'; +import './index.less'; +export interface RippleOptions { + event: string; + transition: number; +} + +export interface RippleProto { + background?: string; + zIndex?: string; +} + +export type EventType = Event & MouseEvent & TouchEvent; + +const options: RippleOptions = { + event: 'mousedown', + transition: 400, +}; + +const RippleDirective: Directive & RippleProto = { + beforeMount: (el: HTMLElement, binding) => { + if (binding.value === false) return; + + const bg = el.getAttribute('ripple-background'); + setProps(Object.keys(binding.modifiers), options); + + const background = bg || RippleDirective.background; + const zIndex = RippleDirective.zIndex; + + el.addEventListener(options.event, (event: EventType) => { + rippler({ + event, + el, + background, + zIndex, + }); + }); + }, + updated(el, binding) { + if (!binding.value) { + el?.clearRipple?.(); + return; + } + const bg = el.getAttribute('ripple-background'); + el?.setBackground?.(bg); + }, +}; + +function rippler({ event, el, zIndex, background }: { event: EventType; el: HTMLElement } & RippleProto) { + const targetBorder = parseInt(getComputedStyle(el).borderWidth.replace('px', '')); + const clientX = event.clientX || event.touches[0].clientX; + const clientY = event.clientY || event.touches[0].clientY; + + const rect = el.getBoundingClientRect(); + const { left, top } = rect; + const { offsetWidth: width, offsetHeight: height } = el; + const { transition } = options; + const dx = clientX - left; + const dy = clientY - top; + const maxX = Math.max(dx, width - dx); + const maxY = Math.max(dy, height - dy); + const style = window.getComputedStyle(el); + const radius = Math.sqrt(maxX * maxX + maxY * maxY); + const border = targetBorder > 0 ? targetBorder : 0; + + const ripple = document.createElement('div'); + const rippleContainer = document.createElement('div'); + + // Styles for ripple + ripple.className = 'ripple'; + + Object.assign(ripple.style ?? {}, { + marginTop: '0px', + marginLeft: '0px', + width: '1px', + height: '1px', + transition: `all ${transition}ms cubic-bezier(0.4, 0, 0.2, 1)`, + borderRadius: '50%', + pointerEvents: 'none', + position: 'relative', + zIndex: zIndex ?? '9999', + backgroundColor: background ?? 'rgba(0, 0, 0, 0.12)', + }); + + // Styles for rippleContainer + rippleContainer.className = 'ripple-container'; + Object.assign(rippleContainer.style ?? {}, { + position: 'absolute', + left: `${0 - border}px`, + top: `${0 - border}px`, + height: '0', + width: '0', + pointerEvents: 'none', + overflow: 'hidden', + }); + + const storedTargetPosition = el.style.position.length > 0 ? el.style.position : getComputedStyle(el).position; + + if (storedTargetPosition !== 'relative') { + el.style.position = 'relative'; + } + + rippleContainer.appendChild(ripple); + el.appendChild(rippleContainer); + + Object.assign(ripple.style, { + marginTop: `${dy}px`, + marginLeft: `${dx}px`, + }); + + const { borderTopLeftRadius, borderTopRightRadius, borderBottomLeftRadius, borderBottomRightRadius } = style; + Object.assign(rippleContainer.style, { + width: `${width}px`, + height: `${height}px`, + direction: 'ltr', + borderTopLeftRadius, + borderTopRightRadius, + borderBottomLeftRadius, + borderBottomRightRadius, + }); + + setTimeout(() => { + const wh = `${radius * 2}px`; + Object.assign(ripple.style ?? {}, { + width: wh, + height: wh, + marginLeft: `${dx - radius}px`, + marginTop: `${dy - radius}px`, + }); + }, 0); + + function clearRipple() { + setTimeout(() => { + ripple.style.backgroundColor = 'rgba(0, 0, 0, 0)'; + }, 250); + + setTimeout(() => { + rippleContainer?.parentNode?.removeChild(rippleContainer); + }, 850); + el.removeEventListener('mouseup', clearRipple, false); + el.removeEventListener('mouseleave', clearRipple, false); + el.removeEventListener('dragstart', clearRipple, false); + setTimeout(() => { + let clearPosition = true; + for (let i = 0; i < el.childNodes.length; i++) { + if ((el.childNodes[i] as Recordable).className === 'ripple-container') { + clearPosition = false; + } + } + + if (clearPosition) { + el.style.position = storedTargetPosition !== 'static' ? storedTargetPosition : ''; + } + }, options.transition + 260); + } + + if (event.type === 'mousedown') { + el.addEventListener('mouseup', clearRipple, false); + el.addEventListener('mouseleave', clearRipple, false); + el.addEventListener('dragstart', clearRipple, false); + } else { + clearRipple(); + } + + (el as Recordable).setBackground = (bgColor: string) => { + if (!bgColor) { + return; + } + ripple.style.backgroundColor = bgColor; + }; +} + +function setProps(modifiers: Recordable, props: Recordable) { + modifiers.forEach((item: Recordable) => { + if (isNaN(Number(item))) props.event = item; + else props.transition = item; + }); +} + +export default RippleDirective; diff --git a/src/electron/index.ts b/src/electron/index.ts new file mode 100644 index 0000000..856e9ef --- /dev/null +++ b/src/electron/index.ts @@ -0,0 +1,72 @@ +import type {App} from "vue"; +import {router} from "@/router"; +import {useGlobSetting} from "@/hooks/setting"; +import { ElectronEnum } from '/@/enums/jeecgEnum' +const glob = useGlobSetting(); + +const _PRELOAD_UTILS = ElectronEnum.ELECTRON_API; + +export const $electron = { + // 当前是否为Electron平台 + isElectron: () => glob.isElectronPlatform, + + // 通过浏览器打开链接 + openInBrowser: bindUtils('openInBrowser') as (url: string) => void, + + resolveRoutePath, +} + +function bindUtils(n: string) { + const fn = window[_PRELOAD_UTILS]?.[n]; + if (typeof fn?.bind === 'function') { + return fn.bind(null); + } + return () => console.warn(`Electron preload util ${n} is not a function`); +} + +// 解析路由路径 +function resolveRoutePath(path: string) { + return window.location.origin + window.location.pathname + router.resolve(path).href; +} + +/** + * 配置Electron + */ +export function setupElectron(_: App) { + // 非Electron平台不执行 + if (!$electron.isElectron()) { + return; + } + hookWindowOpen(); + // 代码逻辑说明: 【JHHB-13】桌面应用消息通知 + hookNavigate(); +} +function hookNavigate() { + // @ts-ignore + window[ElectronEnum.ELECTRON_API].onNavigate((path) => { + router.push({ path }); + }); +} +function hookWindowOpen() { + // 保存原生方法引用 + const originFunc = window.open; + // 重写window.open方法 + window['open'] = function (url, windowName, windowFeatures) { + url = typeof url === 'string' ? url.trim() : ''; + if (!url) { + throw new Error('window.open: url is required'); + } + // 判断是否以http或https开头 + if (/^https?:\/\//.test(url)) { + // 判断是否为本地地址 + if (url.startsWith(window.location.origin) || url.startsWith(window['_CONFIG']['domianURL'])) { + // 直接打开 + return originFunc(url, windowName, windowFeatures); + } + // 调用Electron进行外部浏览器打开 + return $electron.openInBrowser(url) as any; + } + // 自定义逻辑 + return originFunc(url, windowName, windowFeatures) + } +} diff --git a/src/enums/CompTypeEnum.ts b/src/enums/CompTypeEnum.ts new file mode 100644 index 0000000..2ac5a19 --- /dev/null +++ b/src/enums/CompTypeEnum.ts @@ -0,0 +1,32 @@ +/** + * 组件类型 + */ +export enum CompTypeEnum { + //单选 + Radio = 'radio', + //按钮样式单选 + RadioButton = 'radioButton', + //下拉框 + Select = 'select', + //列表 + List = 'list', + //开关 + Switch = 'switch', + //下拉树 + SelTree = 'sel_tree', + //分类字典树 + CatTree = 'cat_tree', + //下拉搜索 + SelSearch = 'search', + //用户现在框 + SelUser = 'sel_user', + //复选框 + Checkbox = 'checkbox', + //多选列表 + ListMulti = 'list_multi', + //区域选择 + Pca = 'pca', + Popup = 'popup', + //部门选择 + SelDepart = 'sel_depart', +} diff --git a/src/enums/DateTypeEnum.ts b/src/enums/DateTypeEnum.ts new file mode 100644 index 0000000..9ccf88c --- /dev/null +++ b/src/enums/DateTypeEnum.ts @@ -0,0 +1,8 @@ +/** + * 日期类型 + */ +export enum DateTypeEnum { + Date = 'date', + Datetime = 'datetime', + Time = 'time', +} diff --git a/src/enums/appEnum.ts b/src/enums/appEnum.ts new file mode 100644 index 0000000..5d4b1b1 --- /dev/null +++ b/src/enums/appEnum.ts @@ -0,0 +1,58 @@ +export const SIDE_BAR_MINI_WIDTH = 48; +export const SIDE_BAR_SHOW_TIT_MINI_WIDTH = 80; + +// 标签页样式 +export enum TabsThemeEnum { + // 圆滑 + SMOOTH = 'smooth', + // 卡片 + CARD = 'card', + // 极简 + SIMPLE = 'simple', +} + +export enum ContentEnum { + // auto width + FULL = 'full', + // fixed width + FIXED = 'fixed', +} + +// menu theme enum +export enum ThemeEnum { + DARK = 'dark', + LIGHT = 'light', +} + +export enum SettingButtonPositionEnum { + AUTO = 'auto', + HEADER = 'header', + FIXED = 'fixed', +} + +export enum SessionTimeoutProcessingEnum { + ROUTE_JUMP, + PAGE_COVERAGE, +} + +/** + * 权限模式 + */ +export enum PermissionModeEnum { + // role + ROLE = 'ROLE', + // 后台 + BACK = 'BACK', + // route mapping + ROUTE_MAPPING = 'ROUTE_MAPPING', +} + +// Route switching animation +export enum RouterTransitionEnum { + ZOOM_FADE = 'zoom-fade', + ZOOM_OUT = 'zoom-out', + FADE_SIDE = 'fade-slide', + FADE = 'fade', + FADE_BOTTOM = 'fade-bottom', + FADE_SCALE = 'fade-scale', +} diff --git a/src/enums/breakpointEnum.ts b/src/enums/breakpointEnum.ts new file mode 100644 index 0000000..93acc1a --- /dev/null +++ b/src/enums/breakpointEnum.ts @@ -0,0 +1,28 @@ +export enum sizeEnum { + XS = 'XS', + SM = 'SM', + MD = 'MD', + LG = 'LG', + XL = 'XL', + XXL = 'XXL', +} + +export enum screenEnum { + XS = 480, + SM = 576, + MD = 768, + LG = 992, + XL = 1200, + XXL = 1600, +} + +const screenMap = new Map(); + +screenMap.set(sizeEnum.XS, screenEnum.XS); +screenMap.set(sizeEnum.SM, screenEnum.SM); +screenMap.set(sizeEnum.MD, screenEnum.MD); +screenMap.set(sizeEnum.LG, screenEnum.LG); +screenMap.set(sizeEnum.XL, screenEnum.XL); +screenMap.set(sizeEnum.XXL, screenEnum.XXL); + +export { screenMap }; diff --git a/src/enums/cacheEnum.ts b/src/enums/cacheEnum.ts new file mode 100644 index 0000000..929d312 --- /dev/null +++ b/src/enums/cacheEnum.ts @@ -0,0 +1,63 @@ +// token key +export const TOKEN_KEY = 'TOKEN__'; + +export const LOCALE_KEY = 'LOCALE__'; + +// user info key +export const USER_INFO_KEY = 'USER__INFO__'; + +// role info key +export const ROLES_KEY = 'ROLES__KEY__'; + +// dict info key +export const DB_DICT_DATA_KEY = 'UI_CACHE_DB_DICT_DATA'; + +// project config key +export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__'; + +// lock info +export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__'; + +export const MULTIPLE_TABS_KEY = 'MULTIPLE_TABS__KEY__'; + +export const APP_DARK_MODE_KEY_ = '__APP__DARK__MODE__'; + +// base global local key +export const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY__'; + +// base global session key +export const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY__'; +// 租户 key +export const TENANT_ID = 'TENANT_ID'; +// login info key +export const LOGIN_INFO_KEY = 'LOGIN__INFO__'; + +// 聊天UID key +export const JEECG_CHAT_UID = 'JEECG_CHAT_UID'; + +// 免登录租户id,与系统分开,避免重复 +export const OAUTH2_THIRD_LOGIN_TENANT_ID = 'THIRD_LOGIN_TENANT_ID'; + +// ai助手标识(退出需要记录一下) +export const AIDE_FLAG = 'AIDE_FLAG'; + +// ai助手标识(退出需要记录一下) +export const JEECG_CHAT_KEY = 'JEECG-CHAT-KEY'; + +// 【QQYUN-8925】系统主题颜色(供页面加载使用) +export const APP__THEME__COLOR = '__APP__THEME__COLOR__'; + +// +export const ROLE_AUTH_CONFIG_KEY = 'ROLE__AUTH__CONFIG__KEY__'; +// 部门角色权限 +export const DEPART_ROLE_AUTH_CONFIG_KEY = 'DEPART__ROLE__AUTH__CONFIG__KEY__'; +// 部门管理权限 +export const DEPART_MANGE_AUTH_CONFIG_KEY = 'DEPART__MANGE__AUTH__CONFIG__KEY__'; + +//产品包管理权限 +export const PACK_AUTH_CONFIG_KEY = 'PACK__AUTH__CONFIG__KEY__'; + +export enum CacheTypeEnum { + SESSION, + LOCAL, +} diff --git a/src/enums/exceptionEnum.ts b/src/enums/exceptionEnum.ts new file mode 100644 index 0000000..b02ac21 --- /dev/null +++ b/src/enums/exceptionEnum.ts @@ -0,0 +1,29 @@ +/** + * @description: Exception related enumeration + */ +export enum ExceptionEnum { + // page not access + PAGE_NOT_ACCESS = 403, + + // page not found + PAGE_NOT_FOUND = 404, + + // error + ERROR = 500, + + // net work error + NET_WORK_ERROR = 10000, + + // No data on the page. In fact, it is not an exception page + PAGE_NOT_DATA = 10100, + //短信验证码次数太多失败code,用于判断是否打开弹窗 + PHONE_SMS_FAIL_CODE = 40002, +} + +export enum ErrorTypeEnum { + VUE = 'vue', + SCRIPT = 'script', + RESOURCE = 'resource', + AJAX = 'ajax', + PROMISE = 'promise', +} diff --git a/src/enums/httpEnum.ts b/src/enums/httpEnum.ts new file mode 100644 index 0000000..7ce5819 --- /dev/null +++ b/src/enums/httpEnum.ts @@ -0,0 +1,50 @@ +/** + * @description: Request result set + */ +export enum ResultEnum { + SUCCESS = 0, + ERROR = 1, + TIMEOUT = 401, + TYPE = 'success', +} + +/** + * @description: request method + */ +export enum RequestEnum { + GET = 'GET', + POST = 'POST', + PUT = 'PUT', + DELETE = 'DELETE', +} + +/** + * @description: contentTyp + */ +export enum ContentTypeEnum { + // json + JSON = 'application/json;charset=UTF-8', + // form-data qs + FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8', + // form-data upload + FORM_DATA = 'multipart/form-data;charset=UTF-8', +} + +/** + * 请求header + * @description: contentTyp + */ +export enum ConfigEnum { + // TOKEN + TOKEN = 'X-Access-Token', + // TIMESTAMP + TIMESTAMP = 'X-TIMESTAMP', + // Sign + Sign = 'X-Sign', + // 租户id + TENANT_ID = 'X-Tenant-Id', + // 版本 + VERSION = 'X-Version', + // 低代码应用ID + X_LOW_APP_ID = 'X-Low-App-ID', +} diff --git a/src/enums/jeecgEnum.ts b/src/enums/jeecgEnum.ts new file mode 100644 index 0000000..600e01f --- /dev/null +++ b/src/enums/jeecgEnum.ts @@ -0,0 +1,27 @@ +/** + * JInput组件类型 + */ +export enum JInputTypeEnum { + //模糊 + JINPUT_QUERY_LIKE = 'like', + //非 + JINPUT_QUERY_NE = 'ne', + //大于等于 + JINPUT_QUERY_GE = 'ge', + //小于等于 + JINPUT_QUERY_LE = 'le', +} + +/** + * 面板设计器需要的常量定义 + */ +export enum JDragConfigEnum { + //baseURL + DRAG_BASE_URL = 'drag-base-url', + //拖拽缓存前缀 + DRAG_CACHE_PREFIX = 'drag-cache:', +} +// electron 枚举 +export enum ElectronEnum { + ELECTRON_API = '_ELECTRON_PRELOAD_UTILS_', +} diff --git a/src/enums/menuEnum.ts b/src/enums/menuEnum.ts new file mode 100644 index 0000000..1ba0068 --- /dev/null +++ b/src/enums/menuEnum.ts @@ -0,0 +1,50 @@ +/** + * @description: 默认四种菜单主题 + */ +export enum MenuTypeEnum { + // 左侧边菜单导航风格 + SIDEBAR = 'sidebar', + // 顶部栏导航风格 + MIX = 'mix', + // 侧边折叠导航风格 + MIX_SIDEBAR = 'mix-sidebar', + //顶部混合导航风格 + TOP_MENU = 'top-menu', +} + +// 折叠触发器位置 +export enum TriggerEnum { + // 不显示 + NONE = 'NONE', + // 菜单底部 + FOOTER = 'FOOTER', + // 头部 + HEADER = 'HEADER', +} + +export type Mode = 'vertical' | 'vertical-right' | 'horizontal' | 'inline'; + +// menu mode +export enum MenuModeEnum { + VERTICAL = 'vertical', + HORIZONTAL = 'horizontal', + VERTICAL_RIGHT = 'vertical-right', + INLINE = 'inline', +} + +export enum MenuSplitTyeEnum { + NONE, + TOP, + LEFT, +} + +export enum TopMenuAlignEnum { + CENTER = 'center', + START = 'start', + END = 'end', +} + +export enum MixSidebarTriggerEnum { + HOVER = 'hover', + CLICK = 'click', +} diff --git a/src/enums/pageEnum.ts b/src/enums/pageEnum.ts new file mode 100644 index 0000000..58651ac --- /dev/null +++ b/src/enums/pageEnum.ts @@ -0,0 +1,16 @@ +export enum PageEnum { + // basic login path + BASE_LOGIN = '/login', + // basic home path + BASE_HOME = '/dashboard/analysis', + // error page path + ERROR_PAGE = '/exception', + // error log page path + ERROR_LOG_PAGE = '/error-log/list', + // auth2登录路由路径 + OAUTH2_LOGIN_PAGE_PATH = '/oauth2-app/login', + //文件路由 + SYS_FILES_PATH = '/file/share', + // 邮件中的跳转地址 + TOKEN_LOGIN = '/tokenLogin' +} diff --git a/src/enums/roleEnum.ts b/src/enums/roleEnum.ts new file mode 100644 index 0000000..857868d --- /dev/null +++ b/src/enums/roleEnum.ts @@ -0,0 +1,7 @@ +export enum RoleEnum { + // super admin + SUPER = 'super', + + // tester + TEST = 'test', +} diff --git a/src/enums/sizeEnum.ts b/src/enums/sizeEnum.ts new file mode 100644 index 0000000..4348c2c --- /dev/null +++ b/src/enums/sizeEnum.ts @@ -0,0 +1,27 @@ +export enum SizeEnum { + DEFAULT = 'default', + SMALL = 'small', + LARGE = 'large', +} + +export enum SizeNumberEnum { + DEFAULT = 48, + SMALL = 16, + LARGE = 64, +} + +export enum ScreenSizeEnum { + XS = 480, + SM = 576, + MD = 768, + LG = 992, + XL = 1200, +} + +export const sizeMap: Map = (() => { + const map = new Map(); + map.set(SizeEnum.DEFAULT, SizeNumberEnum.DEFAULT); + map.set(SizeEnum.SMALL, SizeNumberEnum.SMALL); + map.set(SizeEnum.LARGE, SizeNumberEnum.LARGE); + return map; +})(); diff --git a/src/hooks/component/useFormItem.ts b/src/hooks/component/useFormItem.ts new file mode 100644 index 0000000..8f9eeb9 --- /dev/null +++ b/src/hooks/component/useFormItem.ts @@ -0,0 +1,51 @@ +import type { UnwrapRef, Ref, WritableComputedRef, DeepReadonly } from 'vue'; +import { reactive, readonly, computed, getCurrentInstance, watchEffect, unref, nextTick, toRaw } from 'vue'; +import { Form } from 'ant-design-vue'; +import { FormItemContext } from 'ant-design-vue/es/form/FormItemContext'; + +import { isEqual } from 'lodash-es'; +export function useRuleFormItem>( + props: T, + key?: K, + changeEvent?, + emitData?: Ref +): [WritableComputedRef, (val: V) => void, DeepReadonly, FormItemContext]; +export function useRuleFormItem(props: T, key: keyof T = 'value', changeEvent = 'change', emitData?: Ref) { + const instance = getCurrentInstance(); + const emit = instance?.emit; + const formItemContext = Form.useInjectFormItemContext(); + + const innerState = reactive({ + value: props[key], + }); + + const defaultState = readonly(innerState); + + const setState = (val: UnwrapRef): void => { + innerState.value = val as T[keyof T]; + }; + + watchEffect(() => { + innerState.value = props[key]; + }); + + const state: any = computed({ + get() { + //修复多选时空值显示问题(兼容值为0的情况) + return innerState.value == null || innerState.value === '' ? [] : innerState.value; + }, + set(value) { + if (isEqual(value, defaultState.value)) return; + + innerState.value = value as T[keyof T]; + nextTick(() => { + emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || [])); + // https://antdv.com/docs/vue/migration-v3-cn + // antDv3升级后需要调用这个方法更新校验的值 + nextTick(() => formItemContext.onFieldChange()); + }); + }, + }); + + return [state, setState, defaultState, formItemContext]; +} diff --git a/src/hooks/component/useFormItemSingle.ts b/src/hooks/component/useFormItemSingle.ts new file mode 100644 index 0000000..a8b08fe --- /dev/null +++ b/src/hooks/component/useFormItemSingle.ts @@ -0,0 +1,50 @@ +import type { UnwrapRef, Ref, WritableComputedRef, DeepReadonly } from 'vue'; +import { reactive, readonly, computed, getCurrentInstance, watchEffect, unref, nextTick, toRaw } from 'vue'; +import { Form } from 'ant-design-vue'; +import { FormItemContext } from 'ant-design-vue/es/form/FormItemContext'; + +import { isEqual } from 'lodash-es'; +export function useRuleFormItem>( + props: T, + key?: K, + changeEvent?, + emitData?: Ref +): [WritableComputedRef, (val: V) => void, DeepReadonly, FormItemContext]; +export function useRuleFormItem(props: T, key: keyof T = 'value', changeEvent = 'change', emitData?: Ref) { + const instance = getCurrentInstance(); + const emit = instance?.emit; + const formItemContext = Form.useInjectFormItemContext(); + + const innerState = reactive({ + value: props[key], + }); + + const defaultState = readonly(innerState); + + const setState = (val: UnwrapRef): void => { + innerState.value = val as T[keyof T]; + }; + + watchEffect(() => { + innerState.value = props[key]; + }); + + const state: any = computed({ + get() { + return innerState.value == null ? "" : innerState.value; + }, + set(value) { + if (isEqual(value, defaultState.value)) return; + + innerState.value = value as T[keyof T]; + nextTick(() => { + emit?.(changeEvent, value, ...(toRaw(unref(emitData)) || [])); + // https://antdv.com/docs/vue/migration-v3-cn + // antDv3升级后需要调用这个方法更新校验的值 + nextTick(() => formItemContext.onFieldChange()); + }); + }, + }); + + return [state, setState, defaultState, formItemContext]; +} diff --git a/src/hooks/component/usePageContext.ts b/src/hooks/component/usePageContext.ts new file mode 100644 index 0000000..12cc160 --- /dev/null +++ b/src/hooks/component/usePageContext.ts @@ -0,0 +1,18 @@ +import type { InjectionKey, ComputedRef, Ref } from 'vue'; +import { createContext, useContext } from '/@/hooks/core/useContext'; + +export interface PageContextProps { + contentHeight: ComputedRef; + pageHeight: Ref; + setPageHeight: (height: number) => Promise; +} + +const key: InjectionKey = Symbol(); + +export function createPageContext(context: PageContextProps) { + return createContext(context, key, { native: true }); +} + +export function usePageContext() { + return useContext(key); +} diff --git a/src/hooks/core/onMountedOrActivated.ts b/src/hooks/core/onMountedOrActivated.ts new file mode 100644 index 0000000..859083d --- /dev/null +++ b/src/hooks/core/onMountedOrActivated.ts @@ -0,0 +1,22 @@ +import { nextTick, onMounted, onActivated } from 'vue'; + +type HookArgs = { + type: 'mounted' | 'activated'; +} + +export function onMountedOrActivated(hook: Fn) { + let mounted: boolean; + + onMounted(() => { + hook({type: 'mounted'}); + nextTick(() => { + mounted = true; + }); + }); + + onActivated(() => { + if (mounted) { + hook({type: 'activated'}); + } + }); +} diff --git a/src/hooks/core/useAttrs.ts b/src/hooks/core/useAttrs.ts new file mode 100644 index 0000000..ea96575 --- /dev/null +++ b/src/hooks/core/useAttrs.ts @@ -0,0 +1,41 @@ +import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue'; +import type { Ref } from 'vue'; + +interface Params { + excludeListeners?: boolean; + excludeKeys?: string[]; + excludeDefaultKeys?: boolean; +} + +const DEFAULT_EXCLUDE_KEYS = ['class', 'style']; +const LISTENER_PREFIX = /^on[A-Z]/; + +export function entries(obj: Recordable): [string, T][] { + return Object.keys(obj).map((key: string) => [key, obj[key]]); +} + +export function useAttrs(params: Params = {}): Ref | {} { + const instance = getCurrentInstance(); + if (!instance) return {}; + + const { excludeListeners = false, excludeKeys = [], excludeDefaultKeys = true } = params; + const attrs = shallowRef({}); + const allExcludeKeys = excludeKeys.concat(excludeDefaultKeys ? DEFAULT_EXCLUDE_KEYS : []); + + // Since attrs are not reactive, make it reactive instead of doing in `onUpdated` hook for better performance + instance.attrs = reactive(instance.attrs); + + watchEffect(() => { + const res = entries(instance.attrs).reduce((acm, [key, val]) => { + if (!allExcludeKeys.includes(key) && !(excludeListeners && LISTENER_PREFIX.test(key))) { + acm[key] = val; + } + + return acm; + }, {} as Recordable); + + attrs.value = res; + }); + + return attrs; +} diff --git a/src/hooks/core/useContext.ts b/src/hooks/core/useContext.ts new file mode 100644 index 0000000..0f039eb --- /dev/null +++ b/src/hooks/core/useContext.ts @@ -0,0 +1,38 @@ +import { + InjectionKey, + provide, + inject, + reactive, + readonly as defineReadonly, + // defineComponent, + UnwrapRef, +} from 'vue'; + +export interface CreateContextOptions { + readonly?: boolean; + createProvider?: boolean; + native?: boolean; +} + +type ShallowUnwrap = { + [P in keyof T]: UnwrapRef; +}; + +export function createContext(context: any, key: InjectionKey = Symbol(), options: CreateContextOptions = {}) { + const { readonly = true, createProvider = false, native = false } = options; + + const state = reactive(context); + const provideData = readonly ? defineReadonly(state) : state; + !createProvider && provide(key, native ? context : provideData); + + return { + state, + }; +} + +export function useContext(key: InjectionKey, native?: boolean): T; +export function useContext(key: InjectionKey, defaultValue?: any, native?: boolean): T; + +export function useContext(key: InjectionKey = Symbol(), defaultValue?: any): ShallowUnwrap { + return inject(key, defaultValue || {}); +} diff --git a/src/hooks/core/useLockFn.ts b/src/hooks/core/useLockFn.ts new file mode 100644 index 0000000..141073b --- /dev/null +++ b/src/hooks/core/useLockFn.ts @@ -0,0 +1,17 @@ +import { ref, unref } from 'vue'; + +export function useLockFn

(fn: (...args: P) => Promise) { + const lockRef = ref(false); + return async function (...args: P) { + if (unref(lockRef)) return; + lockRef.value = true; + try { + const ret = await fn(...args); + lockRef.value = false; + return ret; + } catch (e) { + lockRef.value = false; + throw e; + } + }; +} diff --git a/src/hooks/core/useRefs.ts b/src/hooks/core/useRefs.ts new file mode 100644 index 0000000..180bb14 --- /dev/null +++ b/src/hooks/core/useRefs.ts @@ -0,0 +1,16 @@ +import type { Ref } from 'vue'; +import { ref, onBeforeUpdate } from 'vue'; + +export function useRefs(): [Ref, (index: number) => (el: HTMLElement) => void] { + const refs = ref([]) as Ref; + + onBeforeUpdate(() => { + refs.value = []; + }); + + const setRefs = (index: number) => (el: HTMLElement) => { + refs.value[index] = el; + }; + + return [refs, setRefs]; +} diff --git a/src/hooks/core/useTimeout.ts b/src/hooks/core/useTimeout.ts new file mode 100644 index 0000000..a549ac2 --- /dev/null +++ b/src/hooks/core/useTimeout.ts @@ -0,0 +1,45 @@ +import { ref, watch } from 'vue'; +import { tryOnUnmounted } from '@vueuse/core'; +import { isFunction } from '/@/utils/is'; + +export function useTimeoutFn(handle: Fn, wait: number, native = false) { + if (!isFunction(handle)) { + throw new Error('handle is not Function!'); + } + + const { readyRef, stop, start } = useTimeoutRef(wait); + if (native) { + handle(); + } else { + watch( + readyRef, + (maturity) => { + maturity && handle(); + }, + { immediate: false } + ); + } + return { readyRef, stop, start }; +} + +export function useTimeoutRef(wait: number) { + const readyRef = ref(false); + + let timer: TimeoutHandle; + function stop(): void { + readyRef.value = false; + timer && window.clearTimeout(timer); + } + function start(): void { + stop(); + timer = setTimeout(() => { + readyRef.value = true; + }, wait); + } + + start(); + + tryOnUnmounted(stop); + + return { readyRef, stop, start }; +} diff --git a/src/hooks/event/useBreakpoint.ts b/src/hooks/event/useBreakpoint.ts new file mode 100644 index 0000000..01bbbec --- /dev/null +++ b/src/hooks/event/useBreakpoint.ts @@ -0,0 +1,89 @@ +import { ref, computed, ComputedRef, unref } from 'vue'; +import { useEventListener } from '/@/hooks/event/useEventListener'; +import { screenMap, sizeEnum, screenEnum } from '/@/enums/breakpointEnum'; + +let globalScreenRef: ComputedRef; +let globalWidthRef: ComputedRef; +let globalRealWidthRef: ComputedRef; + +export interface CreateCallbackParams { + screen: ComputedRef; + width: ComputedRef; + realWidth: ComputedRef; + screenEnum: typeof screenEnum; + screenMap: Map; + sizeEnum: typeof sizeEnum; +} + +export function useBreakpoint() { + return { + screenRef: computed(() => unref(globalScreenRef)), + widthRef: globalWidthRef, + screenEnum, + realWidthRef: globalRealWidthRef, + }; +} + +// Just call it once +export function createBreakpointListen(fn?: (opt: CreateCallbackParams) => void) { + const screenRef = ref(sizeEnum.XL); + const realWidthRef = ref(window.innerWidth); + + function getWindowWidth() { + const width = document.body.clientWidth; + const xs = screenMap.get(sizeEnum.XS)!; + const sm = screenMap.get(sizeEnum.SM)!; + const md = screenMap.get(sizeEnum.MD)!; + const lg = screenMap.get(sizeEnum.LG)!; + const xl = screenMap.get(sizeEnum.XL)!; + if (width < xs) { + screenRef.value = sizeEnum.XS; + } else if (width < sm) { + screenRef.value = sizeEnum.SM; + } else if (width < md) { + screenRef.value = sizeEnum.MD; + } else if (width < lg) { + screenRef.value = sizeEnum.LG; + } else if (width < xl) { + screenRef.value = sizeEnum.XL; + } else { + screenRef.value = sizeEnum.XXL; + } + realWidthRef.value = width; + } + + useEventListener({ + el: window, + name: 'resize', + + listener: () => { + getWindowWidth(); + resizeFn(); + }, + // wait: 100, + }); + + getWindowWidth(); + globalScreenRef = computed(() => unref(screenRef)); + globalWidthRef = computed((): number => screenMap.get(unref(screenRef)!)!); + globalRealWidthRef = computed((): number => unref(realWidthRef)); + + function resizeFn() { + fn?.({ + screen: globalScreenRef, + width: globalWidthRef, + realWidth: globalRealWidthRef, + screenEnum, + screenMap, + sizeEnum, + }); + } + + resizeFn(); + return { + screenRef: globalScreenRef, + screenEnum, + widthRef: globalWidthRef, + realWidthRef: globalRealWidthRef, + }; +} diff --git a/src/hooks/event/useEventListener.ts b/src/hooks/event/useEventListener.ts new file mode 100644 index 0000000..35e58be --- /dev/null +++ b/src/hooks/event/useEventListener.ts @@ -0,0 +1,52 @@ +import type { Ref } from 'vue'; +import { ref, watch, unref } from 'vue'; +import { useThrottleFn, useDebounceFn } from '@vueuse/core'; + +export type RemoveEventFn = () => void; +export interface UseEventParams { + el?: Element | Ref | Window | any; + name: string; + listener: EventListener; + options?: boolean | AddEventListenerOptions; + autoRemove?: boolean; + isDebounce?: boolean; + wait?: number; +} +export function useEventListener({ el = window, name, listener, options, autoRemove = true, isDebounce = true, wait = 80 }: UseEventParams): { + removeEvent: RemoveEventFn; +} { + /* eslint-disable-next-line */ + let remove: RemoveEventFn = () => {}; + const isAddRef = ref(false); + + if (el) { + const element = ref(el as Element) as Ref; + + const handler = isDebounce ? useDebounceFn(listener, wait) : useThrottleFn(listener, wait); + const realHandler = wait ? handler : listener; + const removeEventListener = (e: Element) => { + isAddRef.value = true; + e.removeEventListener(name, realHandler, options); + }; + const addEventListener = (e: Element) => e.addEventListener(name, realHandler, options); + + const removeWatch = watch( + element, + (v, _ov, cleanUp) => { + if (v) { + !unref(isAddRef) && addEventListener(v); + cleanUp(() => { + autoRemove && removeEventListener(v); + }); + } + }, + { immediate: true } + ); + + remove = () => { + removeEventListener(element.value); + removeWatch(); + }; + } + return { removeEvent: remove }; +} diff --git a/src/hooks/event/useIntersectionObserver.ts b/src/hooks/event/useIntersectionObserver.ts new file mode 100644 index 0000000..44ed699 --- /dev/null +++ b/src/hooks/event/useIntersectionObserver.ts @@ -0,0 +1,42 @@ +import { Ref, watchEffect, ref } from 'vue'; + +interface IntersectionObserverProps { + target: Ref; + root?: Ref; + onIntersect: IntersectionObserverCallback; + rootMargin?: string; + threshold?: number; +} + +export function useIntersectionObserver({ target, root, onIntersect, rootMargin = '0px', threshold = 0.1 }: IntersectionObserverProps) { + let cleanup = () => {}; + const observer: Ref> = ref(null); + const stopEffect = watchEffect(() => { + cleanup(); + + observer.value = new IntersectionObserver(onIntersect, { + root: root ? root.value : null, + rootMargin, + threshold, + }); + + const current = target.value; + + current && observer.value.observe(current); + + cleanup = () => { + if (observer.value) { + observer.value.disconnect(); + target.value && observer.value.unobserve(target.value); + } + }; + }); + + return { + observer, + stop: () => { + cleanup(); + stopEffect(); + }, + }; +} diff --git a/src/hooks/event/useScroll.ts b/src/hooks/event/useScroll.ts new file mode 100644 index 0000000..2a4b7bc --- /dev/null +++ b/src/hooks/event/useScroll.ts @@ -0,0 +1,65 @@ +import type { Ref } from 'vue'; + +import { ref, onMounted, watch, onUnmounted } from 'vue'; +import { isWindow, isObject } from '/@/utils/is'; +import { useThrottleFn } from '@vueuse/core'; + +export function useScroll( + refEl: Ref, + options?: { + wait?: number; + leading?: boolean; + trailing?: boolean; + } +) { + const refX = ref(0); + const refY = ref(0); + let handler = () => { + if (isWindow(refEl.value)) { + refX.value = refEl.value.scrollX; + refY.value = refEl.value.scrollY; + } else if (refEl.value) { + refX.value = (refEl.value as Element).scrollLeft; + refY.value = (refEl.value as Element).scrollTop; + } + }; + + if (isObject(options)) { + let wait = 0; + if (options.wait && options.wait > 0) { + wait = options.wait; + Reflect.deleteProperty(options, 'wait'); + } + + handler = useThrottleFn(handler, wait); + } + + let stopWatch: () => void; + onMounted(() => { + stopWatch = watch( + refEl, + (el, prevEl, onCleanup) => { + if (el) { + el.addEventListener('scroll', handler); + } else if (prevEl) { + prevEl.removeEventListener('scroll', handler); + } + onCleanup(() => { + refX.value = refY.value = 0; + el && el.removeEventListener('scroll', handler); + }); + }, + { immediate: true } + ); + }); + + onUnmounted(() => { + refEl.value && refEl.value.removeEventListener('scroll', handler); + }); + + function stop() { + stopWatch && stopWatch(); + } + + return { refX, refY, stop }; +} diff --git a/src/hooks/event/useScrollTo.ts b/src/hooks/event/useScrollTo.ts new file mode 100644 index 0000000..f6d5dc6 --- /dev/null +++ b/src/hooks/event/useScrollTo.ts @@ -0,0 +1,59 @@ +import { isFunction, isUnDef } from '/@/utils/is'; +import { ref, unref } from 'vue'; + +export interface ScrollToParams { + el: any; + to: number; + duration?: number; + callback?: () => any; +} + +const easeInOutQuad = (t: number, b: number, c: number, d: number) => { + t /= d / 2; + if (t < 1) { + return (c / 2) * t * t + b; + } + t--; + return (-c / 2) * (t * (t - 2) - 1) + b; +}; +const move = (el: HTMLElement, amount: number) => { + el.scrollTop = amount; +}; + +const position = (el: HTMLElement) => { + return el.scrollTop; +}; +export function useScrollTo({ el, to, duration = 500, callback }: ScrollToParams) { + const isActiveRef = ref(false); + const start = position(el); + const change = to - start; + const increment = 20; + let currentTime = 0; + duration = isUnDef(duration) ? 500 : duration; + + const animateScroll = function () { + if (!unref(isActiveRef)) { + return; + } + currentTime += increment; + const val = easeInOutQuad(currentTime, start, change, duration); + move(el, val); + if (currentTime < duration && unref(isActiveRef)) { + requestAnimationFrame(animateScroll); + } else { + if (callback && isFunction(callback)) { + callback(); + } + } + }; + const run = () => { + isActiveRef.value = true; + animateScroll(); + }; + + const stop = () => { + isActiveRef.value = false; + }; + + return { start: run, stop }; +} diff --git a/src/hooks/event/useWindowSizeFn.ts b/src/hooks/event/useWindowSizeFn.ts new file mode 100644 index 0000000..7b18ca0 --- /dev/null +++ b/src/hooks/event/useWindowSizeFn.ts @@ -0,0 +1,36 @@ +import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'; +import { useDebounceFn } from '@vueuse/core'; + +interface WindowSizeOptions { + once?: boolean; + immediate?: boolean; + listenerOptions?: AddEventListenerOptions | boolean; +} + +export function useWindowSizeFn(fn: Fn, wait = 150, options?: WindowSizeOptions) { + let handler = () => { + fn(); + }; + const handleSize = useDebounceFn(handler, wait); + handler = handleSize; + + const start = () => { + if (options && options.immediate) { + handler(); + } + window.addEventListener('resize', handler); + }; + + const stop = () => { + window.removeEventListener('resize', handler); + }; + + tryOnMounted(() => { + start(); + }); + + tryOnUnmounted(() => { + stop(); + }); + return [start, stop]; +} diff --git a/src/hooks/jeecg/useAdaptiveWidth.ts b/src/hooks/jeecg/useAdaptiveWidth.ts new file mode 100644 index 0000000..2eb88ec --- /dev/null +++ b/src/hooks/jeecg/useAdaptiveWidth.ts @@ -0,0 +1,88 @@ +/** + * 自适应宽度构造器 + * + * @time 2022-4-8 + * @author sunjianlei + */ +import { ref } from 'vue'; +import { useDebounceFn, tryOnUnmounted } from '@vueuse/core'; +import { useEventListener } from '/@/hooks/event/useEventListener'; + +// key = js运算符+数字 +const defWidthConfig: configType = { + '<=565': '100%', + '<=1366': '800px', + '<=1600': '600px', + '<=1920': '600px', + '>1920': '500px', +}; + +type configType = Record; + +/** + * 自适应宽度 + * + * @param widthConfig 宽度配置,可参考 defWidthConfig 配置 + * @param assign 是否合并默认配置 + * @param debounce 去抖毫秒数 + */ +export function useAdaptiveWidth(widthConfig = defWidthConfig, assign = true, debounce = 50) { + const widthConfigAssign = assign ? Object.assign({}, defWidthConfig, widthConfig) : widthConfig; + const configKeys = Object.keys(widthConfigAssign); + + const adaptiveWidth = ref(); + + /** + * 进行计算宽度 + * @param innerWidth + */ + function calcWidth(innerWidth) { + let width; + for (const key of configKeys) { + try { + // 通过js运算 + let flag = new Function(`return ${innerWidth} ${key}`)(); + if (flag) { + width = widthConfigAssign[key]; + break; + } + } catch (e) { + console.error(e); + } + } + if (width) { + adaptiveWidth.value = width; + } else { + console.warn('没有找到匹配的自适应宽度'); + } + } + + // 初始计算 + calcWidth(window.innerWidth); + + // 监听 resize 事件 + const { removeEvent } = useEventListener({ + el: window, + name: 'resize', + listener: useDebounceFn(() => calcWidth(window.innerWidth), debounce), + }); + // 卸载组件时取消监听事件 + tryOnUnmounted(() => removeEvent()); + + return { adaptiveWidth }; +} + +/** + * 抽屉自适应宽度 + */ +export function useDrawerAdaptiveWidth() { + return useAdaptiveWidth( + { + '<=620': '100%', + '<=1600': 600, + '<=1920': 650, + '>1920': 700, + }, + false + ); +} diff --git a/src/hooks/setting/index.ts b/src/hooks/setting/index.ts new file mode 100644 index 0000000..e849e1c --- /dev/null +++ b/src/hooks/setting/index.ts @@ -0,0 +1,75 @@ +import type { GlobConfig } from '/#/config'; + +import { getAppEnvConfig } from '/@/utils/env'; + +export const useGlobSetting = (): Readonly => { + const { + VITE_GLOB_APP_TITLE, + VITE_GLOB_API_URL, + VITE_GLOB_APP_SHORT_NAME, + VITE_GLOB_API_URL_PREFIX, + VITE_GLOB_APP_CAS_BASE_URL, + VITE_GLOB_APP_OPEN_SSO, + VITE_GLOB_APP_OPEN_QIANKUN, + VITE_GLOB_DOMAIN_URL, + VITE_GLOB_ONLINE_VIEW_URL, + VITE_GLOB_RUN_PLATFORM, + + // 【JEECG作为乾坤子应用】 + VITE_GLOB_QIANKUN_MICRO_APP_NAME, + VITE_GLOB_QIANKUN_MICRO_APP_ENTRY, + } = getAppEnvConfig(); + + // if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) { + // warn( + // `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.` + // ); + // } + + // 短标题:替换shortName的下划线为空格 + const shortTitle = VITE_GLOB_APP_SHORT_NAME.replace(/_/g, " "); + // Take global configuration + const glob: Readonly = { + title: VITE_GLOB_APP_TITLE, + domainUrl: VITE_GLOB_DOMAIN_URL, + apiUrl: VITE_GLOB_API_URL, + shortName: VITE_GLOB_APP_SHORT_NAME, + shortTitle: shortTitle, + openSso: VITE_GLOB_APP_OPEN_SSO, + openQianKun: VITE_GLOB_APP_OPEN_QIANKUN, + casBaseUrl: VITE_GLOB_APP_CAS_BASE_URL, + urlPrefix: VITE_GLOB_API_URL_PREFIX, + uploadUrl: VITE_GLOB_DOMAIN_URL, + viewUrl: VITE_GLOB_ONLINE_VIEW_URL, + // true: 新任务办理页面弹窗, false:旧的任务办理页面弹窗 + useNewTaskModal: true, + // 当前是否运行在 electron 平台 + isElectronPlatform: VITE_GLOB_RUN_PLATFORM === 'electron', + + // 【JEECG作为乾坤子应用】是否以乾坤子应用模式启动 + isQiankunMicro: VITE_GLOB_QIANKUN_MICRO_APP_NAME != null && VITE_GLOB_QIANKUN_MICRO_APP_NAME !== '', + // 【JEECG作为乾坤子应用】乾坤子应用入口 + qiankunMicroAppEntry: VITE_GLOB_QIANKUN_MICRO_APP_ENTRY, + }; + + // 【JEECG作为乾坤子应用】乾坤子应用下,需要定义一下 + if (!window['_CONFIG']) { + window['_CONFIG'] = {} + } + + // 代码逻辑说明: 【QQYUN-10956】配置了自定义前缀,外部连接打不开,需要兼容处理 + let domainURL = VITE_GLOB_DOMAIN_URL; + + // 如果不是以http(s)开头的,也不是以域名开头的,那么就是拼接当前域名 + if (!/^http(s)?/.test(domainURL) && !/^(\/\/)?(.*\.)?.+\..+/.test(domainURL)) { + if (!domainURL.startsWith('/')) { + domainURL = '/' + domainURL; + } + domainURL = window.location.origin + domainURL; + } + + // @ts-ignore + window._CONFIG['domianURL'] = domainURL; + + return glob as Readonly; +}; diff --git a/src/hooks/setting/useHeaderSetting.ts b/src/hooks/setting/useHeaderSetting.ts new file mode 100644 index 0000000..098ed39 --- /dev/null +++ b/src/hooks/setting/useHeaderSetting.ts @@ -0,0 +1,90 @@ +import type { HeaderSetting } from '/#/config'; + +import { computed, unref } from 'vue'; + +import { useAppStore } from '/@/store/modules/app'; + +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { useRootSetting } from '/@/hooks/setting/useRootSetting'; +import { useFullContent } from '/@/hooks/web/useFullContent'; +import { MenuModeEnum } from '/@/enums/menuEnum'; + +export function useHeaderSetting() { + const { getFullContent } = useFullContent(); + const appStore = useAppStore(); + + const getShowFullHeaderRef = computed(() => { + return !unref(getFullContent) && unref(getShowMixHeaderRef) && unref(getShowHeader) && !unref(getIsTopMenu) && !unref(getIsMixSidebar); + }); + + const getUnFixedAndFull = computed(() => !unref(getFixed) && !unref(getShowFullHeaderRef)); + + const getShowInsetHeaderRef = computed(() => { + const need = !unref(getFullContent) && unref(getShowHeader); + return (need && !unref(getShowMixHeaderRef)) || (need && unref(getIsTopMenu)) || (need && unref(getIsMixSidebar)); + }); + + const { getMenuMode, getSplit, getShowHeaderTrigger, getIsSidebarType, getIsMixSidebar, getIsTopMenu } = useMenuSetting(); + const { getShowBreadCrumb, getShowLogo } = useRootSetting(); + + const getShowMixHeaderRef = computed(() => !unref(getIsSidebarType) && unref(getShowHeader)); + + const getShowDoc = computed(() => appStore.getHeaderSetting.showDoc); + + const getHeaderTheme = computed(() => appStore.getHeaderSetting.theme); + + const getShowHeader = computed(() => appStore.getHeaderSetting.show); + + const getFixed = computed(() => appStore.getHeaderSetting.fixed); + + const getHeaderBgColor = computed(() => appStore.getHeaderSetting.bgColor); + + const getShowSearch = computed(() => appStore.getHeaderSetting.showSearch); + + const getUseLockPage = computed(() => appStore.getHeaderSetting.useLockPage); + + const getShowFullScreen = computed(() => appStore.getHeaderSetting.showFullScreen); + + const getShowNotice = computed(() => appStore.getHeaderSetting.showNotice); + + const getShowBread = computed(() => { + return unref(getMenuMode) !== MenuModeEnum.HORIZONTAL && unref(getShowBreadCrumb) && !unref(getSplit); + }); + const getShowBreadTitle = computed(() => { + return unref(getMenuMode) !== MenuModeEnum.HORIZONTAL && !unref(getShowBreadCrumb) && !unref(getSplit); + }); + + const getShowHeaderLogo = computed(() => { + return unref(getShowLogo) && !unref(getIsSidebarType) && !unref(getIsMixSidebar); + }); + + const getShowContent = computed(() => { + return unref(getShowBread) || unref(getShowHeaderTrigger); + }); + + // Set header configuration + function setHeaderSetting(headerSetting: Partial) { + appStore.setProjectConfig({ headerSetting }); + } + return { + setHeaderSetting, + + getShowDoc, + getShowSearch, + getHeaderTheme, + getUseLockPage, + getShowFullScreen, + getShowNotice, + getShowBread, + getShowContent, + getShowHeaderLogo, + getShowHeader, + getFixed, + getShowMixHeaderRef, + getShowFullHeaderRef, + getShowInsetHeaderRef, + getUnFixedAndFull, + getHeaderBgColor, + getShowBreadTitle + }; +} diff --git a/src/hooks/setting/useMenuSetting.ts b/src/hooks/setting/useMenuSetting.ts new file mode 100644 index 0000000..495df5a --- /dev/null +++ b/src/hooks/setting/useMenuSetting.ts @@ -0,0 +1,156 @@ +import type { MenuSetting } from '/#/config'; + +import { computed, unref, ref } from 'vue'; + +import { useAppStore } from '/@/store/modules/app'; + +import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'; +import { MenuModeEnum, MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum'; +import { useFullContent } from '/@/hooks/web/useFullContent'; + +const mixSideHasChildren = ref(false); + +export function useMenuSetting() { + const { getFullContent: fullContent } = useFullContent(); + const appStore = useAppStore(); + + const getShowSidebar = computed(() => { + return unref(getSplit) || (unref(getShowMenu) && unref(getMenuMode) !== MenuModeEnum.HORIZONTAL && !unref(fullContent)); + }); + + const getCollapsed = computed(() => appStore.getMenuSetting.collapsed); + + const getMenuType = computed(() => appStore.getMenuSetting.type); + + const getMenuMode = computed(() => appStore.getMenuSetting.mode); + + const getMenuFixed = computed(() => appStore.getMenuSetting.fixed); + + const getShowMenu = computed(() => appStore.getMenuSetting.show); + + const getMenuHidden = computed(() => appStore.getMenuSetting.hidden); + + const getMenuWidth = computed(() => appStore.getMenuSetting.menuWidth); + + const getTrigger = computed(() => appStore.getMenuSetting.trigger); + + const getMenuTheme = computed(() => appStore.getMenuSetting.theme); + + const getSplit = computed(() => appStore.getMenuSetting.split); + + const getMenuBgColor = computed(() => appStore.getMenuSetting.bgColor); + + const getMixSideTrigger = computed(() => appStore.getMenuSetting.mixSideTrigger); + + const getCanDrag = computed(() => appStore.getMenuSetting.canDrag); + + const getAccordion = computed(() => appStore.getMenuSetting.accordion); + + const getMixSideFixed = computed(() => appStore.getMenuSetting.mixSideFixed); + + const getTopMenuAlign = computed(() => appStore.getMenuSetting.topMenuAlign); + + const getCloseMixSidebarOnChange = computed(() => appStore.getMenuSetting.closeMixSidebarOnChange); + + const getIsSidebarType = computed(() => unref(getMenuType) === MenuTypeEnum.SIDEBAR); + + const getIsTopMenu = computed(() => unref(getMenuType) === MenuTypeEnum.TOP_MENU); + + const getCollapsedShowTitle = computed(() => appStore.getMenuSetting.collapsedShowTitle); + + const getShowTopMenu = computed(() => { + return unref(getMenuMode) === MenuModeEnum.HORIZONTAL || unref(getSplit); + }); + + const getShowHeaderTrigger = computed(() => { + if (unref(getMenuType) === MenuTypeEnum.TOP_MENU || !unref(getShowMenu) || unref(getMenuHidden)) { + return false; + } + + return unref(getTrigger) === TriggerEnum.HEADER; + }); + + const getIsHorizontal = computed(() => { + return unref(getMenuMode) === MenuModeEnum.HORIZONTAL; + }); + + const getIsMixSidebar = computed(() => { + return unref(getMenuType) === MenuTypeEnum.MIX_SIDEBAR; + }); + + const getIsMixMode = computed(() => { + return unref(getMenuMode) === MenuModeEnum.INLINE && unref(getMenuType) === MenuTypeEnum.MIX; + }); + + const getRealWidth = computed(() => { + if (unref(getIsMixSidebar)) { + // 代码逻辑说明: 【QQYUN-8774】侧边混合导航菜单宽度调整 + return unref(getCollapsed) && !unref(getMixSideFixed) ? unref(getMiniWidthNumber) : unref(getMenuWidth) - 60; + } + return unref(getCollapsed) ? unref(getMiniWidthNumber) : unref(getMenuWidth); + }); + + const getMiniWidthNumber = computed(() => { + const { collapsedShowTitle } = appStore.getMenuSetting; + return collapsedShowTitle ? SIDE_BAR_SHOW_TIT_MINI_WIDTH : SIDE_BAR_MINI_WIDTH; + }); + + const getCalcContentWidth = computed(() => { + const width = + unref(getIsTopMenu) || !unref(getShowMenu) || (unref(getSplit) && unref(getMenuHidden)) + ? 0 + : unref(getIsMixSidebar) + ? (unref(getCollapsed) ? SIDE_BAR_MINI_WIDTH : SIDE_BAR_SHOW_TIT_MINI_WIDTH) + + (unref(getMixSideFixed) && unref(mixSideHasChildren) ? unref(getRealWidth) : 0) + : unref(getRealWidth); + + return `calc(100% - ${unref(width)}px)`; + }); + + // Set menu configuration + function setMenuSetting(menuSetting: Partial): void { + appStore.setProjectConfig({ menuSetting }); + } + + function toggleCollapsed() { + setMenuSetting({ + collapsed: !unref(getCollapsed), + }); + } + return { + setMenuSetting, + + toggleCollapsed, + + getMenuFixed, + getRealWidth, + getMenuType, + getMenuMode, + getShowMenu, + getCollapsed, + getMiniWidthNumber, + getCalcContentWidth, + getMenuWidth, + getTrigger, + getSplit, + getMenuTheme, + getCanDrag, + getCollapsedShowTitle, + getIsHorizontal, + getIsSidebarType, + getAccordion, + getShowTopMenu, + getShowHeaderTrigger, + getTopMenuAlign, + getMenuHidden, + getIsTopMenu, + getMenuBgColor, + getShowSidebar, + getIsMixMode, + getIsMixSidebar, + getCloseMixSidebarOnChange, + getMixSideTrigger, + getMixSideFixed, + mixSideHasChildren, + }; +} diff --git a/src/hooks/setting/useMultipleTabSetting.ts b/src/hooks/setting/useMultipleTabSetting.ts new file mode 100644 index 0000000..7c7ab01 --- /dev/null +++ b/src/hooks/setting/useMultipleTabSetting.ts @@ -0,0 +1,32 @@ +import type { MultiTabsSetting } from '/#/config'; + +import { computed } from 'vue'; + +import { useAppStore } from '/@/store/modules/app'; + +export function useMultipleTabSetting() { + const appStore = useAppStore(); + + const getShowMultipleTab = computed(() => appStore.getMultiTabsSetting.show); + + const getShowQuick = computed(() => appStore.getMultiTabsSetting.showQuick); + + const getShowRedo = computed(() => appStore.getMultiTabsSetting.showRedo); + + const getShowFold = computed(() => appStore.getMultiTabsSetting.showFold); + + // 获取标签页样式 + const getTabsTheme = computed(() => appStore.getMultiTabsSetting.theme); + + function setMultipleTabSetting(multiTabsSetting: Partial) { + appStore.setProjectConfig({ multiTabsSetting }); + } + return { + setMultipleTabSetting, + getShowMultipleTab, + getShowQuick, + getShowRedo, + getShowFold, + getTabsTheme, + }; +} diff --git a/src/hooks/setting/useRootSetting.ts b/src/hooks/setting/useRootSetting.ts new file mode 100644 index 0000000..508fae7 --- /dev/null +++ b/src/hooks/setting/useRootSetting.ts @@ -0,0 +1,90 @@ +import type { ProjectConfig } from '/#/config'; + +import { computed } from 'vue'; + +import { useAppStore } from '/@/store/modules/app'; +import { ContentEnum, ThemeEnum } from '/@/enums/appEnum'; + +type RootSetting = Omit; + +export function useRootSetting() { + const appStore = useAppStore(); + + const getPageLoading = computed(() => appStore.getPageLoading); + + const getOpenKeepAlive = computed(() => appStore.getProjectConfig.openKeepAlive); + + const getSettingButtonPosition = computed(() => appStore.getProjectConfig.settingButtonPosition); + + const getCanEmbedIFramePage = computed(() => appStore.getProjectConfig.canEmbedIFramePage); + + const getPermissionMode = computed(() => appStore.getProjectConfig.permissionMode); + + const getShowLogo = computed(() => appStore.getProjectConfig.showLogo); + + const getContentMode = computed(() => appStore.getProjectConfig.contentMode); + + const getUseOpenBackTop = computed(() => appStore.getProjectConfig.useOpenBackTop); + + const getShowSettingButton = computed(() => appStore.getProjectConfig.showSettingButton); + + const getUseErrorHandle = computed(() => appStore.getProjectConfig.useErrorHandle); + + const getShowFooter = computed(() => appStore.getProjectConfig.showFooter); + + const getShowBreadCrumb = computed(() => appStore.getProjectConfig.showBreadCrumb); + + const getThemeColor = computed(() => appStore.getProjectConfig.themeColor); + + const getShowBreadCrumbIcon = computed(() => appStore.getProjectConfig.showBreadCrumbIcon); + + const getFullContent = computed(() => appStore.getProjectConfig.fullContent); + + const getColorWeak = computed(() => appStore.getProjectConfig.colorWeak); + + const getGrayMode = computed(() => appStore.getProjectConfig.grayMode); + // 代码逻辑说明: 【QQYUN-10952】AI助手支持通过设置来配置是否显示 + const getAiIconShow = computed(() => appStore.getProjectConfig.aiIconShow); + const getLockTime = computed(() => appStore.getProjectConfig.lockTime); + + const getShowDarkModeToggle = computed(() => appStore.getProjectConfig.showDarkModeToggle); + + const getDarkMode = computed(() => appStore.getDarkMode); + + const getLayoutContentMode = computed(() => (appStore.getProjectConfig.contentMode === ContentEnum.FULL ? ContentEnum.FULL : ContentEnum.FIXED)); + + function setRootSetting(setting: Partial) { + appStore.setProjectConfig(setting); + } + + function setDarkMode(mode: ThemeEnum) { + appStore.setDarkMode(mode); + } + return { + setRootSetting, + + getSettingButtonPosition, + getFullContent, + getColorWeak, + getGrayMode, + getLayoutContentMode, + getPageLoading, + getOpenKeepAlive, + getCanEmbedIFramePage, + getPermissionMode, + getShowLogo, + getUseErrorHandle, + getShowBreadCrumb, + getShowBreadCrumbIcon, + getUseOpenBackTop, + getShowSettingButton, + getShowFooter, + getContentMode, + getLockTime, + getThemeColor, + getDarkMode, + setDarkMode, + getShowDarkModeToggle, + getAiIconShow, + }; +} diff --git a/src/hooks/setting/useTransitionSetting.ts b/src/hooks/setting/useTransitionSetting.ts new file mode 100644 index 0000000..b6d421a --- /dev/null +++ b/src/hooks/setting/useTransitionSetting.ts @@ -0,0 +1,31 @@ +import type { TransitionSetting } from '/#/config'; + +import { computed } from 'vue'; + +import { useAppStore } from '/@/store/modules/app'; + +export function useTransitionSetting() { + const appStore = useAppStore(); + + const getEnableTransition = computed(() => appStore.getTransitionSetting?.enable); + + const getOpenNProgress = computed(() => appStore.getTransitionSetting?.openNProgress); + + const getOpenPageLoading = computed((): boolean => { + return !!appStore.getTransitionSetting?.openPageLoading; + }); + + const getBasicTransition = computed(() => appStore.getTransitionSetting?.basicTransition); + + function setTransitionSetting(transitionSetting: Partial) { + appStore.setProjectConfig({ transitionSetting }); + } + return { + setTransitionSetting, + + getEnableTransition, + getOpenNProgress, + getOpenPageLoading, + getBasicTransition, + }; +} diff --git a/src/hooks/system/useAutoAdapt.ts b/src/hooks/system/useAutoAdapt.ts new file mode 100644 index 0000000..7f5c71b --- /dev/null +++ b/src/hooks/system/useAutoAdapt.ts @@ -0,0 +1,51 @@ +import { ref } from 'vue'; +import { ScreenSizeEnum } from '/@/enums/sizeEnum'; +import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; +// 定义 useAdapt 方法参数 +interface AdaptOptions { + // xl>1200 + xl?: string | number; + // xl>992 + lg?: string | number; + // xl>768 + md?: string | number; + // xl>576 + sm?: string | number; + // xl>480 + xs?: string | number; + //xl<480默认值 + mindef?: string | number; + //默认值 + def?: string | number; +} +export function useAdapt(props?: AdaptOptions) { + //默认宽度 + const width = ref(props?.def || '600px'); + //获取宽度 + useWindowSizeFn(calcWidth, 100, { immediate: true }); + //计算宽度 + function calcWidth() { + let windowWidth = document.documentElement.clientWidth; + switch (true) { + case windowWidth > ScreenSizeEnum.XL: + width.value = props?.xl || '600px'; + break; + case windowWidth > ScreenSizeEnum.LG: + width.value = props?.lg || '600px'; + break; + case windowWidth > ScreenSizeEnum.MD: + width.value = props?.md || '600px'; + break; + case windowWidth > ScreenSizeEnum.SM: + width.value = props?.sm || '500px'; + break; + case windowWidth > ScreenSizeEnum.XS: + width.value = props?.xs || '400px'; + break; + default: + width.value = props?.mindef || '300px'; + break; + } + } + return { width, calcWidth }; +} diff --git a/src/hooks/system/useJvxeMethods.ts b/src/hooks/system/useJvxeMethods.ts new file mode 100644 index 0000000..25989dc --- /dev/null +++ b/src/hooks/system/useJvxeMethods.ts @@ -0,0 +1,192 @@ +import { defHttp } from '/@/utils/http/axios'; +import { ref, unref } from 'vue'; +import { VALIDATE_FAILED, validateFormModelAndTables } from '/@/utils/common/vxeUtils'; + +export function useJvxeMethod(requestAddOrEdit, classifyIntoFormData, tableRefs, activeKey, refKeys, validateSubForm?) { + const formRef = ref(); + /** 查询某个tab的数据 */ + function requestSubTableData(url, params, tab, success) { + tab.loading = true; + defHttp + .get({ url, params }, { isTransformResponse: false }) + .then((res) => { + let { result } = res; + if (res.success && result) { + if (Array.isArray(result)) { + tab.dataSource = result; + } else if (Array.isArray(result.records)) { + tab.dataSource = result.records; + } + } + typeof success === 'function' ? success(res) : ''; + }) + .finally(() => { + tab.loading = false; + }); + } + + /* --- handle 事件 --- */ + + /** ATab 选项卡切换事件 */ + function handleChangeTabs(key) { + // 自动重置scrollTop状态,防止出现白屏 + tableRefs[key]?.value?.resetScrollTop(0); + } + + /** 获取所有的editableTable实例*/ + function getAllTable() { + let values = Object.values(tableRefs); + return Promise.all(values); + } + /** 确定按钮点击事件 */ + function handleSubmit() { + /** 触发表单验证 */ + getAllTable() + .then((tables) => { + let values = formRef.value.getFieldsValue(); + return validateFormModelAndTables(formRef.value.validate, values, tables, formRef.value.getProps, false); + }) + .then((allValues) => { + /** 一次性验证一对一的所有子表 */ + return validateSubForm && typeof validateSubForm === 'function' ? validateSubForm(allValues) : validateAllSubOne(allValues); + }) + .then((allValues) => { + if (typeof classifyIntoFormData !== 'function') { + throw throwNotFunction('classifyIntoFormData'); + } + let formData = classifyIntoFormData(allValues); + // 发起请求 + return requestAddOrEdit(formData); + }) + .catch((e) => { + if (e.error === VALIDATE_FAILED) { + // 如果有未通过表单验证的子表,就自动跳转到它所在的tab + // 代码逻辑说明: VUEN-2866【代码生成】Tab风格 一对多子表校验不通过时,点击提交表单空白了,流程附加页面也有此问题 + if(e.paneKey){ + activeKey.value = e.paneKey + }else{ + // 代码逻辑说明: TV360X-478 一对多tab,校验未通过时,tab没有跳转 + activeKey.value = e.subIndex == null ? (e.index == null ? unref(activeKey) : refKeys.value[e.index]) : Object.keys(tableRefs)[e.subIndex]; + } + // 代码逻辑说明: 【TV360X-1064】非原生提交表单滚动校验没通过的项--- + if (e?.errorFields) { + const firstField = e.errorFields[0]; + if (firstField) { + formRef.value.scrollToField(firstField.name, { behavior: 'smooth', block: 'end' }); + } + } + return Promise.reject(e?.errorFields); + } else { + console.error(e); + } + }); + } + //校验所有子表表单 + function validateAllSubOne(allValues) { + return new Promise((resolve) => { + resolve(allValues); + }); + } + /* --- throw --- */ + + /** not a function */ + function throwNotFunction(name) { + return `${name} 未定义或不是一个函数`; + } + + /** not a array */ + function throwNotArray(name) { + return `${name} 未定义或不是一个数组`; + } + return [handleChangeTabs, handleSubmit, requestSubTableData, formRef]; +} + +/** + * 校验多个表单和子表table,用于原生的antd-vue的表单 + * @param activeKey 子表表单/vxe-table 所在tabs的 activeKey + * @param refMap 子表表单/vxe-table对应的ref对象 map结构 + * 示例: + * useValidateAntFormAndTable(activeKey, { + * 'tableA': tableARef, + * 'formB': formBRef + * }) + */ +export function useValidateAntFormAndTable(activeKey, refMap) { + /** + * 获取所有子表数据 + */ + async function getSubFormAndTableData() { + let formData = {}; + let all = Object.keys(refMap); + let key = ''; + for (let i = 0; i < all.length; i++) { + key = all[i]; + let instance = refMap[key].value; + if (instance.isForm) { + let subFormData = await validateFormAndGetData(instance, key); + if (subFormData) { + formData[key + 'List'] = [subFormData]; + } + } else { + let arr = await validateTableAndGetData(instance, key); + if (arr && arr.length > 0) { + formData[key + 'List'] = arr; + } + } + } + return formData; + } + + /** + * 转换数据用 如果有数组转成逗号分割的格式 + * @param data + */ + function transformData(data) { + if (data) { + Object.keys(data).map((k) => { + if (data[k] instanceof Array) { + data[k] = data[k].join(','); + } + }); + } + return data; + } + + /** + * 子表table + * @param instance + * @param key + */ + async function validateTableAndGetData(instance, key) { + const errors = await instance.validateTable(); + if (!errors) { + return instance.getTableData(); + } else { + activeKey.value = key; + // 自动重置scrollTop状态,防止出现白屏 + instance.resetScrollTop(0); + return Promise.reject(1); + } + } + + /** + * 子表表单 + * @param instance + * @param key + */ + async function validateFormAndGetData(instance, key) { + try { + let data = await instance.getFormData(); + transformData(data); + return data; + } catch (e) { + activeKey.value = key; + return Promise.reject(e); + } + } + + return { + getSubFormAndTableData, + transformData, + }; +} diff --git a/src/hooks/system/useListPage.ts b/src/hooks/system/useListPage.ts new file mode 100644 index 0000000..525ad88 --- /dev/null +++ b/src/hooks/system/useListPage.ts @@ -0,0 +1,399 @@ +import { reactive, ref, Ref, unref } from 'vue'; +import { merge } from 'lodash-es'; +import { DynamicProps } from '/#/utils'; +import { BasicTableProps, TableActionType, useTable } from '/@/components/Table'; +import { ColEx } from '/@/components/Form/src/types'; +import { FormActionType } from '/@/components/Form'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { useMethods } from '/@/hooks/system/useMethods'; +import { useDesign } from '/@/hooks/web/useDesign'; +import { filterObj } from '/@/utils/common/compUtils'; +import { isFunction } from '@/utils/is'; +const { handleExportXls, handleImportXls } = useMethods(); + +// 定义 useListPage 方法所需参数 +interface ListPageOptions { + // 样式作用域范围 + designScope?: string; + // 【必填】表格参数配置 + tableProps: TableProps; + // 是否分页 + pagination?: boolean; + // 导出配置 + exportConfig?: { + url: string | (() => string); + // 导出文件名 + name?: string | (() => string); + //导出参数 + params?: object | (() => object); + }; + // 导入配置 + importConfig?: { + // 代码逻辑说明: erp代码生成 子表 导入地址是动态的 + url: string | (() => string); + // 导出成功后的回调 + success?: (fileInfo?: any) => void; + }; +} + +interface IDoRequestOptions { + // 是否显示确认对话框,默认 true + confirm?: boolean; + // 是否自动刷新表格,默认 true + reload?: boolean; + // 是否自动清空选择,默认 true + clearSelection?: boolean; +} + +/** + * listPage页面公共方法 + * + * @param options + */ +export function useListPage(options: ListPageOptions) { + const $message = useMessage(); + let $design = {} as ReturnType; + if (options.designScope) { + $design = useDesign(options.designScope); + } + + const tableContext = useListTable(options.tableProps); + + const [, { getForm, reload, setLoading, getColumns }, { selectedRowKeys }] = tableContext; + + // 导出 excel + async function onExportXls() { + // 代码逻辑说明: 导出新增自定义参数------------ + let { url, name, params } = options?.exportConfig ?? {}; + let realUrl = typeof url === 'function' ? url() : url; + if (realUrl) { + let title = typeof name === 'function' ? name() : name; + // 代码逻辑说明: erp代码生成 子表 导出报错,原因未知- + let paramsForm:any = {}; + try { + //当useSearchFor不等于false的时候,才去触发validate + if (options?.tableProps?.useSearchForm !== false) { + paramsForm = await getForm().validate(); + console.log('paramsForm', paramsForm); + // 在这里把执行beforeFetch + if (options?.tableProps?.beforeFetch) { + paramsForm = await options?.tableProps?.beforeFetch(paramsForm); + } + } + } catch (e) { + console.warn(e); + } + + // 代码逻辑说明: [/issues/409]导出功能没有按排序结果导出,设置导出默认排序,创建时间倒序 + if(!paramsForm?.column){ + Object.assign(paramsForm,{column:'createTime',order:'desc'}); + } + + //如果参数不为空,则整合到一起 + // 代码逻辑说明: erp代码生成 子表 导出动态设置mainId + if (params) { + // 代码逻辑说明: [QQYUN-11627]代码生成原生表单,数据导出,前端报错,并且范围参数没有转换 #7962 + const realParams = isFunction(params) ? await params() : { ...(params || {}) }; + Object.keys(realParams).map((k) => { + let temp = (realParams as object)[k]; + if (temp) { + paramsForm[k] = unref(temp); + } + }); + } + if (selectedRowKeys.value && selectedRowKeys.value.length > 0) { + paramsForm['selections'] = selectedRowKeys.value.join(','); + } + + //是否显示表格设置 + if(options?.tableProps?.showTableSetting !== false){ + //是否显示配置字段 + if(options?.tableProps?.tableSetting?.setting !== false){ + await exportColumns(paramsForm) + } + } + + return handleExportXls(title as string, realUrl, filterObj(paramsForm)); + } else { + $message.createMessage.warn('没有传递 exportConfig.url 参数'); + return Promise.reject(); + } + } + + /** + * 设置导出的列 + * + * @param paramsForm + */ + async function exportColumns(paramsForm: any) { + //获取表格的列 + let columns = getColumns(); + if(columns && columns.length >0){ + //需要导出的字段 + let exportFields:any = []; + //是否有隐藏列 + let hiddenColumns = false; + for (const column of columns) { + if(!column.defaultHidden){ + let dataIndex = column?.dataIndex; + if(column?.dataIndex?.toString()?.indexOf('_dictText') !== -1){ + dataIndex = column?.dataIndex?.toString().replace('_dictText','') + } + exportFields.push(dataIndex); + } else { + hiddenColumns = true; + } + } + if(hiddenColumns){ + paramsForm['exportFields'] = exportFields.join(","); + } + } + } + + // 导入 excel + function onImportXls(file) { + let { url, success } = options?.importConfig ?? {}; + // 代码逻辑说明: erp代码生成 子表 导入地址是动态的 + let realUrl = typeof url === 'function' ? url() : url; + if (realUrl) { + return handleImportXls(file, realUrl, success || reload); + } else { + $message.createMessage.warn('没有传递 importConfig.url 参数'); + return Promise.reject(); + } + } + + /** + * 通用请求处理方法,可自动刷新表格,自动清空选择 + * @param api 请求api + * @param options 是否显示确认框 + */ + function doRequest(api: () => Promise, options?: IDoRequestOptions) { + return new Promise((resolve, reject) => { + const execute = async () => { + try { + setLoading(true); + const res = await api(); + if (options?.reload ?? true) { + reload(); + } + if (options?.clearSelection ?? true) { + selectedRowKeys.value = []; + } + resolve(res); + } catch (e) { + reject(e); + } finally { + setLoading(false); + } + }; + if (options?.confirm ?? true) { + $message.createConfirm({ + iconType: 'warning', + title: '删除', + content: '确定要删除吗?', + onOk: () => execute(), + onCancel: () => reject(), + }); + } else { + execute(); + } + }); + } + + /** 执行单个删除操作 */ + function doDeleteRecord(api: () => Promise) { + return doRequest(api, { confirm: false, clearSelection: false }); + } + + return { + ...$design, + ...$message, + onExportXls, + onImportXls, + doRequest, + doDeleteRecord, + tableContext, + }; +} + +// 定义表格所需参数 +type TableProps = Partial>; +type UseTableMethod = TableActionType & { + getForm: () => FormActionType; +}; + +/** + * useListTable 列表页面标准表格参数 + * + * @param tableProps 表格参数 + */ +export function useListTable(tableProps: TableProps): [ + (instance: TableActionType, formInstance: UseTableMethod) => void, + TableActionType & { + getForm: () => FormActionType; + }, + { + rowSelection: any; + selectedRows: Ref; + selectedRowKeys: Ref; + } +] { + // 自适应列配置 + const adaptiveColProps: Partial = { + xs: 24, // <576px + sm: 12, // ≥576px + md: 12, // ≥768px + lg: 8, // ≥992px + xl: 8, // ≥1200px + xxl: 6, // ≥1600px + }; + const defaultTableProps: TableProps = { + rowKey: 'id', + // 使用查询条件区域 + useSearchForm: true, + // 查询条件区域配置 + formConfig: { + // 紧凑模式 + compact: true, + // label默认宽度 + // labelWidth: 120, + // 按下回车后自动提交 + autoSubmitOnEnter: true, + // 默认 row 配置 + rowProps: { gutter: 8 }, + // 默认 col 配置 + baseColProps: { + ...adaptiveColProps, + }, + labelCol: { + xs: 24, + sm: 8, + md: 6, + lg: 8, + xl: 6, + xxl: 6, + }, + wrapperCol: {}, + // 是否显示 展开/收起 按钮 + showAdvancedButton: true, + // 超过指定列数默认折叠 + autoAdvancedCol: 3, + // 操作按钮配置 + actionColOptions: { + ...adaptiveColProps, + style: { textAlign: 'left' }, + }, + }, + // 斑马纹 + striped: false, + // 是否可以自适应高度 + canResize: true, + // 表格最小高度 + // update-begin--author:liaozhiyang---date:20240603---for【TV360X-861】列表查询区域不可往上滚动 + minHeight: 300, + // update-end--author:liaozhiyang---date:20240603---for【TV360X-861】列表查询区域不可往上滚动 + // 点击行选中 + clickToRowSelect: false, + // 是否显示边框 + bordered: true, + // 是否显示序号列 + showIndexColumn: false, + // 显示表格设置 + showTableSetting: true, + // 表格全屏设置 + tableSetting: { + fullScreen: false, + }, + // 是否显示操作列 + showActionColumn: true, + // 操作列 + actionColumn: { + width: 120, + title: '操作', + //是否锁定操作列取值 right ,left,false + fixed: false, + dataIndex: 'action', + slots: { customRender: 'action' }, + }, + }; + // 合并用户个性化配置 + if (tableProps) { + // 代码逻辑说明: 【issues/6180】前端代码配置表变查询条件显示列不生效--- + if(tableProps.formConfig){ + setTableProps(tableProps.formConfig); + } + // merge 方法可深度合并对象 + merge(defaultTableProps, tableProps); + } + + // 发送请求之前调用的方法 + function beforeFetch(params) { + // 判断是否已有排序参数(defSort 为数组时会转换为 defSortString) + const hasSortParams = params.column || params.defSortString || params.order; + // 默认以 createTime 降序排序 + return Object.assign(hasSortParams ? {} : { column: 'createTime', order: 'desc' }, params); + } + + // 合并方法 + Object.assign(defaultTableProps, { beforeFetch }); + if (typeof tableProps.beforeFetch === 'function') { + defaultTableProps.beforeFetch = function (params) { + params = beforeFetch(params); + // @ts-ignore + tableProps.beforeFetch(params); + return params; + }; + } + + // 当前选择的行 + const selectedRowKeys = ref([]); + // 选择的行记录 + const selectedRows = ref([]); + + // 表格选择列配置 + const rowSelection: any = tableProps?.rowSelection ?? {}; + const defaultRowSelection = reactive({ + ...rowSelection, + type: rowSelection.type ?? 'checkbox', + // 选择列宽度,默认 50 + columnWidth: rowSelection.columnWidth ?? 50, + selectedRows: selectedRows, + selectedRowKeys: selectedRowKeys, + onChange(...args) { + selectedRowKeys.value = args[0]; + selectedRows.value = args[1]; + if (typeof rowSelection.onChange === 'function') { + rowSelection.onChange(...args); + } + }, + }); + delete defaultTableProps.rowSelection; + + /** + * 设置表格参数 + * + * @param formConfig + */ + function setTableProps(formConfig: any) { + const replaceAttributeArray: string[] = ['baseColProps','labelCol']; + for (let item of replaceAttributeArray) { + if(formConfig && formConfig[item]){ + if(defaultTableProps.formConfig){ + let defaultFormConfig:any = defaultTableProps.formConfig; + defaultFormConfig[item] = formConfig[item]; + } + formConfig[item] = {}; + } + } + } + + return [ + ...useTable(defaultTableProps), + { + selectedRows, + selectedRowKeys, + rowSelection: defaultRowSelection, + }, + ]; +} diff --git a/src/hooks/system/useMethods.ts b/src/hooks/system/useMethods.ts new file mode 100644 index 0000000..dff3741 --- /dev/null +++ b/src/hooks/system/useMethods.ts @@ -0,0 +1,149 @@ +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { useGlobSetting } from '/@/hooks/setting'; + +const { createMessage, createWarningModal } = useMessage(); +const glob = useGlobSetting(); + +/** + * 导出文件xlsx的mime-type + */ +export const XLSX_MIME_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; +/** + * 导出文件xlsx的文件后缀 + */ +export const XLSX_FILE_SUFFIX = '.xlsx'; + +export function useMethods() { + /** + * 导出xls + * @param name + * @param url + * @param params + * @param isXlsx + * @param timeout 超时时间(毫秒),默认 60000 + */ + async function exportXls(name, url, params, isXlsx = false, timeout = 60000) { + // 代码逻辑说明: 【JHHB-794】用户管理,跨页全选后,点击用户导出没反应--- + if(params?.selections){ + let split = params.selections.split(","); + if(split && split.length > 100){ + createMessage.warning('最多可选择 100 项进行导出!'); + return; + } + } + // 修改为返回原生 response,便于获取 headers + const response = await defHttp.get( + { url: url, params: params, responseType: 'blob', timeout: timeout }, + { isTransformResponse: false, isReturnNativeResponse: true } + ); + if (!response || !response.data) { + createMessage.warning('文件下载失败'); + return; + } + // 判断 header 中 content-disposition 是否包含 .xlsx + let isXlsxByHeader = isXlsx; + const disposition = response.headers && response.headers['content-disposition']; + if (disposition && disposition.indexOf('.xlsx') !== -1) { + isXlsxByHeader = true; + } + const data = response.data; + // 代码逻辑说明: 导出excel失败提示,不进行导出--- + let reader = new FileReader() + reader.readAsText(data, 'utf-8') + reader.onload = async () => { + if(reader.result){ + if(reader.result.toString().indexOf("success") !=-1){ + // 代码逻辑说明: 【issues/7738】文件中带"success"导出报错 --- + try { + const { success, message } = JSON.parse(reader.result.toString()); + if (!success) { + createMessage.warning('导出失败,失败原因:' + message); + } else { + exportExcel(name, isXlsxByHeader, data); + } + return; + } catch (error) { + exportExcel(name, isXlsxByHeader, data); + } + } + } + exportExcel(name, isXlsxByHeader, data); + } + } + + /** + * 导入xls + * @param data 导入的数据 + * @param url + * @param success 成功后的回调 + */ + async function importXls(data, url, success) { + const isReturn = (fileInfo) => { + try { + if (fileInfo.code === 201) { + let { + message, + result: { msg, fileUrl, fileName }, + } = fileInfo; + let href = glob.uploadUrl + fileUrl; + createWarningModal({ + title: message, + centered: false, + content: `

+ ${msg}
+ 具体详情请 点击下载 +
`, + }); + // 代码逻辑说明: [VUEN-2827]导入无权限,提示图标错误------------ + } else if (fileInfo.code === 500 || fileInfo.code === 510) { + createMessage.error(fileInfo.message || `${data.file.name} 导入失败`); + } else { + createMessage.success(fileInfo.message || `${data.file.name} 文件上传成功`); + } + } catch (error) { + console.log('导入的数据异常', error); + } finally { + typeof success === 'function' ? success(fileInfo) : ''; + } + }; + await defHttp.uploadFile({ url }, { file: data.file }, { success: isReturn }); + } + + return { + handleExportXls: (name: string, url: string, params?: object, timeout?: number) => exportXls(name, url, params, false, timeout), + handleImportXls: (data, url, success) => importXls(data, url, success), + handleExportXlsx: (name: string, url: string, params?: object, timeout?: number) => exportXls(name, url, params, true, timeout), + }; + + /** + * 导出excel + * @param name + * @param isXlsx + * @param data + */ + function exportExcel(name, isXlsx, data) { + if (!name || typeof name != 'string') { + name = '导出文件'; + } + let blobOptions = { type: 'application/vnd.ms-excel' }; + let fileSuffix = '.xls'; + if (isXlsx) { + blobOptions['type'] = XLSX_MIME_TYPE; + fileSuffix = XLSX_FILE_SUFFIX; + } + if (typeof window.navigator.msSaveBlob !== 'undefined') { + window.navigator.msSaveBlob(new Blob([data], blobOptions), name + fileSuffix); + } else { + let url = window.URL.createObjectURL(new Blob([data], blobOptions)); + let link = document.createElement('a'); + link.style.display = 'none'; + link.href = url; + link.setAttribute('download', name + fileSuffix); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); //下载完成移除元素 + window.URL.revokeObjectURL(url); //释放掉blob对象 + } + } +} diff --git a/src/hooks/system/useThirdLogin.ts b/src/hooks/system/useThirdLogin.ts new file mode 100644 index 0000000..0413bc7 --- /dev/null +++ b/src/hooks/system/useThirdLogin.ts @@ -0,0 +1,204 @@ +import { ref, unref } from 'vue'; +import { defHttp } from '/@/utils/http/axios'; +import { useGlobSetting } from '/@/hooks/setting'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { useUserStore } from '/@/store/modules/user'; +import { setThirdCaptcha, getCaptcha } from '/@/api/sys/user'; +import { useI18n } from '/@/hooks/web/useI18n'; + +export function useThirdLogin() { + const { createMessage, notification } = useMessage(); + const { t } = useI18n(); + const glob = useGlobSetting(); + const userStore = useUserStore(); + //第三方类型 + const thirdType = ref(''); + //第三方登录相关信息 + const thirdLoginInfo = ref({}); + //状态 + const thirdLoginState = ref(false); + //绑定手机号弹窗 + const bindingPhoneModal = ref(false); + //第三方用户UUID + const thirdUserUuid = ref(''); + //提示窗 + const thirdConfirmShow = ref(false); + //绑定密码弹窗 + const thirdPasswordShow = ref(false); + //绑定密码 + const thirdLoginPassword = ref(''); + //绑定用户 + const thirdLoginUser = ref(''); + //加载中 + const thirdCreateUserLoding = ref(false); + //绑定手机号 + const thirdPhone = ref(''); + //验证码 + const thirdCaptcha = ref(''); + //第三方登录 + function onThirdLogin(source) { + let url = `${glob.uploadUrl}/sys/thirdLogin/render/${source}`; + const openWin = window.open( + url, + `login ${source}`, + 'height=500, width=500, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, resizable=no,location=n o, status=no' + ); + thirdType.value = source; + thirdLoginInfo.value = {}; + thirdLoginState.value = false; + let receiveMessage = function (event) { + let token = event.data; + if (typeof token === 'string') { + //如果是字符串类型 说明是token信息 + if (token === '登录失败') { + createMessage.warning(token); + } else if (token.includes('绑定手机号')) { + bindingPhoneModal.value = true; + let strings = token.split(','); + thirdUserUuid.value = strings[1]; + } else { + doThirdLogin(token); + } + } else if (typeof token === 'object') { + //对象类型 说明需要提示是否绑定现有账号 + if (token['isObj'] === true) { + thirdConfirmShow.value = true; + thirdLoginInfo.value = { ...token }; + } + } else { + createMessage.warning('不识别的信息传递'); + } + // 代码逻辑说明: 【TV360X-1827】mac系统谷歌浏览器企业微信第三方登录成功后没有弹出绑定手机弹窗 + if (openWin?.closed) { + window.removeEventListener('message', receiveMessage, false); + } + }; + // 代码逻辑说明: 【TV360X-1827】mac系统谷歌浏览器企业微信第三方登录成功后没有弹出绑定手机弹窗 + window.removeEventListener('message', receiveMessage, false); + window.addEventListener('message', receiveMessage, false); + } + // 根据token执行登录 + function doThirdLogin(token) { + if (unref(thirdLoginState) === false) { + thirdLoginState.value = true; + userStore.ThirdLogin({ token, thirdType: unref(thirdType) }).then((res) => { + console.log('res====>doThirdLogin', res); + if (res && res.userInfo) { + notification.success({ + message: t('sys.login.loginSuccessTitle'), + description: `${t('sys.login.loginSuccessDesc')}: ${res.userInfo.realname}`, + duration: 3, + }); + } else { + requestFailed(res); + } + }); + } + } + + function requestFailed(err) { + notification.error({ + message: '登录失败', + description: ((err.response || {}).data || {}).message || err.message || '请求出现错误,请稍后再试', + duration: 4, + }); + } + // 绑定已有账号 需要输入密码 + function thirdLoginUserBind() { + thirdLoginPassword.value = ''; + thirdLoginUser.value = thirdLoginInfo.value.uuid; + thirdConfirmShow.value = false; + thirdPasswordShow.value = true; + } + //创建新账号 + function thirdLoginUserCreate() { + thirdCreateUserLoding.value = true; + // 账号名后面添加两位随机数 + thirdLoginInfo.value.suffix = parseInt(Math.random() * 98 + 1); + defHttp + .post({ url: '/sys/third/user/create', params: { thirdLoginInfo: unref(thirdLoginInfo) } }, { isTransformResponse: false }) + .then((res) => { + if (res.success) { + let token = res.result; + doThirdLogin(token); + thirdConfirmShow.value = false; + } else { + createMessage.warning(res.message); + } + }) + .finally(() => { + thirdCreateUserLoding.value = false; + }); + } + // 核实密码 + function thirdLoginCheckPassword() { + let params = Object.assign({}, unref(thirdLoginInfo), { password: unref(thirdLoginPassword) }); + defHttp.post({ url: '/sys/third/user/checkPassword', params }, { isTransformResponse: false }).then((res) => { + if (res.success) { + thirdLoginNoPassword(); + doThirdLogin(res.result); + } else { + createMessage.warning(res.message); + } + }); + } + // 没有密码 取消操作 + function thirdLoginNoPassword() { + thirdPasswordShow.value = false; + thirdLoginPassword.value = ''; + thirdLoginUser.value = ''; + } + + //倒计时执行前的函数 + function sendCodeApi() { + //return setThirdCaptcha({mobile:unref(thirdPhone)}); + return getCaptcha({ mobile: unref(thirdPhone), smsmode: '0' }); + } + //绑定手机号点击确定按钮 + function thirdHandleOk() { + if (!unref(thirdPhone)) { + cmsFailed('请输入手机号'); + } + if (!unref(thirdCaptcha)) { + cmsFailed('请输入验证码'); + } + let params = { + mobile: unref(thirdPhone), + captcha: unref(thirdCaptcha), + thirdUserUuid: unref(thirdUserUuid), + }; + defHttp.post({ url: '/sys/thirdLogin/bindingThirdPhone', params }, { isTransformResponse: false }).then((res) => { + if (res.success) { + bindingPhoneModal.value = false; + doThirdLogin(res.result); + } else { + createMessage.warning(res.message); + } + }); + } + function cmsFailed(err) { + notification.error({ + message: '登录失败', + description: err, + duration: 4, + }); + return; + } + //返回数据和方法 + return { + thirdPasswordShow, + thirdLoginCheckPassword, + thirdLoginNoPassword, + thirdLoginPassword, + thirdConfirmShow, + thirdCreateUserLoding, + thirdLoginUserCreate, + thirdLoginUserBind, + bindingPhoneModal, + thirdHandleOk, + thirdPhone, + thirdCaptcha, + onThirdLogin, + sendCodeApi, + }; +} diff --git a/src/hooks/web/useAppInject.ts b/src/hooks/web/useAppInject.ts new file mode 100644 index 0000000..7d6efb2 --- /dev/null +++ b/src/hooks/web/useAppInject.ts @@ -0,0 +1,10 @@ +import { useAppProviderContext } from '/@/components/Application'; +import { computed, unref } from 'vue'; + +export function useAppInject() { + const values = useAppProviderContext(); + + return { + getIsMobile: computed(() => unref(values.isMobile)), + }; +} diff --git a/src/hooks/web/useContentHeight.ts b/src/hooks/web/useContentHeight.ts new file mode 100644 index 0000000..b41f300 --- /dev/null +++ b/src/hooks/web/useContentHeight.ts @@ -0,0 +1,183 @@ +import { ComputedRef, isRef, nextTick, Ref, ref, unref, watch } from 'vue'; +import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated'; +import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; +import { useLayoutHeight } from '/@/layouts/default/content/useContentViewHeight'; +import { getViewportOffset } from '/@/utils/domUtils'; +import { isNumber, isString } from '/@/utils/is'; + +export interface CompensationHeight { + // 使用 layout Footer 高度作为判断补偿高度的条件 + useLayoutFooter: boolean; + // refs HTMLElement + elements?: Ref[]; +} + +type Upward = number | string | null | undefined; + +/** + * 动态计算内容高度,根据锚点dom最下坐标到屏幕最下坐标,根据传入dom的高度、padding、margin等值进行动态计算 + * 最终获取合适的内容高度 + * + * @param flag 用于开启计算的响应式标识 + * @param anchorRef 锚点组件 Ref + * @param subtractHeightRefs 待减去高度的组件列表 Ref + * @param substractSpaceRefs 待减去空闲空间(margins/paddings)的组件列表 Ref + * @param offsetHeightRef 计算偏移的响应式高度,计算高度时将直接减去此值 + * @param upwardSpace 向上递归减去空闲空间的 层级 或 直到指定class为止 数值为2代表向上递归两次|数值为ant-layout表示向上递归直到碰见.ant-layout为止 + * @returns 响应式高度 + */ +export function useContentHeight( + flag: ComputedRef, + anchorRef: Ref, + subtractHeightRefs: Ref[], + substractSpaceRefs: Ref[], + upwardSpace: Ref | ComputedRef | Upward = 0, + offsetHeightRef: Ref = ref(0) +) { + const contentHeight: Ref> = ref(null); + const { footerHeightRef: layoutFooterHeightRef } = useLayoutHeight(); + let compensationHeight: CompensationHeight = { + useLayoutFooter: true, + }; + + const setCompensation = (params: CompensationHeight) => { + compensationHeight = params; + }; + + function redoHeight() { + nextTick(() => { + calcContentHeight(); + }); + } + + function calcSubtractSpace(element: Element | null | undefined, direction: 'all' | 'top' | 'bottom' = 'all'): number { + function numberPx(px: string) { + return Number(px.replace(/[^\d]/g, '')); + } + let subtractHeight = 0; + const ZERO_PX = '0px'; + if (element) { + const cssStyle = getComputedStyle(element); + const marginTop = numberPx(cssStyle?.marginTop ?? ZERO_PX); + const marginBottom = numberPx(cssStyle?.marginBottom ?? ZERO_PX); + const paddingTop = numberPx(cssStyle?.paddingTop ?? ZERO_PX); + const paddingBottom = numberPx(cssStyle?.paddingBottom ?? ZERO_PX); + if (direction === 'all') { + subtractHeight += marginTop; + subtractHeight += marginBottom; + subtractHeight += paddingTop; + subtractHeight += paddingBottom; + } else if (direction === 'top') { + subtractHeight += marginTop; + subtractHeight += paddingTop; + } else { + subtractHeight += marginBottom; + subtractHeight += paddingBottom; + } + } + return subtractHeight; + } + + function getEl(element: any): Nullable { + if (element == null) { + return null; + } + return (element instanceof HTMLDivElement ? element : element.$el) as HTMLDivElement; + } + + async function calcContentHeight() { + if (!flag.value) { + return; + } + // Add a delay to get the correct height + await nextTick(); + + const anchorEl = getEl(unref(anchorRef)); + if (!anchorEl) { + return; + } + const { bottomIncludeBody } = getViewportOffset(anchorEl); + + // substract elements height + let substractHeight = 0; + subtractHeightRefs.forEach((item) => { + substractHeight += getEl(unref(item))?.offsetHeight ?? 0; + }); + + // subtract margins / paddings + let substractSpaceHeight = calcSubtractSpace(anchorEl) ?? 0; + substractSpaceRefs.forEach((item) => { + substractSpaceHeight += calcSubtractSpace(getEl(unref(item))); + }); + + // upwardSpace + let upwardSpaceHeight = 0; + function upward(element: Element | null, upwardLvlOrClass: number | string | null | undefined) { + if (element && upwardLvlOrClass) { + const parent = element.parentElement; + if (parent) { + if (isString(upwardLvlOrClass)) { + if (!parent.classList.contains(upwardLvlOrClass)) { + upwardSpaceHeight += calcSubtractSpace(parent, 'bottom'); + upward(parent, upwardLvlOrClass); + } else { + upwardSpaceHeight += calcSubtractSpace(parent, 'bottom'); + } + } else if (isNumber(upwardLvlOrClass)) { + if (upwardLvlOrClass > 0) { + upwardSpaceHeight += calcSubtractSpace(parent, 'bottom'); + upward(parent, --upwardLvlOrClass); + } + } + } + } + } + if (isRef(upwardSpace)) { + upward(anchorEl, unref(upwardSpace)); + } else { + upward(anchorEl, upwardSpace); + } + + let height = + bottomIncludeBody - unref(layoutFooterHeightRef) - unref(offsetHeightRef) - substractHeight - substractSpaceHeight - upwardSpaceHeight; + + // compensation height + const calcCompensationHeight = () => { + compensationHeight.elements?.forEach((item) => { + height += getEl(unref(item))?.offsetHeight ?? 0; + }); + }; + if (compensationHeight.useLayoutFooter && unref(layoutFooterHeightRef) > 0) { + calcCompensationHeight(); + } else { + calcCompensationHeight(); + } + + contentHeight.value = height; + } + + onMountedOrActivated(() => { + nextTick(() => { + calcContentHeight(); + }); + }); + useWindowSizeFn( + () => { + calcContentHeight(); + }, + 50, + { immediate: true } + ); + watch( + () => [layoutFooterHeightRef.value], + () => { + calcContentHeight(); + }, + { + flush: 'post', + immediate: true, + } + ); + + return { redoHeight, setCompensation, contentHeight }; +} diff --git a/src/hooks/web/useContextMenu.ts b/src/hooks/web/useContextMenu.ts new file mode 100644 index 0000000..d3c53ce --- /dev/null +++ b/src/hooks/web/useContextMenu.ts @@ -0,0 +1,12 @@ +import { onUnmounted, getCurrentInstance } from 'vue'; +import { createContextMenu, destroyContextMenu } from '/@/components/ContextMenu'; +import type { ContextMenuItem } from '/@/components/ContextMenu'; +export type { ContextMenuItem }; +export function useContextMenu(authRemove = true) { + if (getCurrentInstance() && authRemove) { + onUnmounted(() => { + destroyContextMenu(); + }); + } + return [createContextMenu, destroyContextMenu]; +} diff --git a/src/hooks/web/useCopyModal.ts b/src/hooks/web/useCopyModal.ts new file mode 100644 index 0000000..694630b --- /dev/null +++ b/src/hooks/web/useCopyModal.ts @@ -0,0 +1,64 @@ +import { isRef, unref, watch, Ref, ComputedRef } from 'vue'; +import Clipboard from 'clipboard'; +import { ModalOptionsEx, useMessage } from '/@/hooks/web/useMessage'; + +/** 带复制按钮的弹窗 */ +interface IOptions extends ModalOptionsEx { + // 要复制的文本,可以是一个 ref 对象,动态更新 + copyText: string | Ref | ComputedRef; +} + +const COPY_CLASS = 'copy-this-text'; +const CLIPBOARD_TEXT = 'data-clipboard-text'; + +export function useCopyModal() { + return { createCopyModal }; +} + +const { createMessage, createConfirm } = useMessage(); + +/** 创建复制弹窗 */ +function createCopyModal(options: Partial) { + let modal = createConfirm({ + ...options, + iconType: options.iconType ?? 'info', + width: options.width ?? 500, + title: options.title ?? '复制', + maskClosable: options.maskClosable ?? true, + okText: options.okText ?? '复制', + okButtonProps: { + ...options.okButtonProps, + class: COPY_CLASS, + [CLIPBOARD_TEXT]: unref(options.copyText), + } as any, + onOk() { + return new Promise((resolve: any) => { + const clipboard = new Clipboard('.' + COPY_CLASS); + clipboard.on('success', () => { + clipboard.destroy(); + createMessage.success('复制成功'); + resolve(); + }); + clipboard.on('error', () => { + createMessage.error('该浏览器不支持自动复制'); + clipboard.destroy(); + resolve(); + }); + }); + }, + }); + + // 动态更新 copyText + if (isRef(options.copyText)) { + watch(options.copyText, (copyText) => { + modal.update({ + okButtonProps: { + ...options.okButtonProps, + class: COPY_CLASS, + [CLIPBOARD_TEXT]: copyText, + } as any, + }); + }); + } + return modal; +} diff --git a/src/hooks/web/useCopyToClipboard.ts b/src/hooks/web/useCopyToClipboard.ts new file mode 100644 index 0000000..7a7fea8 --- /dev/null +++ b/src/hooks/web/useCopyToClipboard.ts @@ -0,0 +1,69 @@ +import { ref, watch } from 'vue'; + +import { isDef } from '/@/utils/is'; +interface Options { + target?: HTMLElement; +} +export function useCopyToClipboard(initial?: string) { + const clipboardRef = ref(initial || ''); + const isSuccessRef = ref(false); + const copiedRef = ref(false); + + watch( + clipboardRef, + (str?: string) => { + if (isDef(str)) { + copiedRef.value = true; + isSuccessRef.value = copyTextToClipboard(str); + } + }, + { immediate: !!initial, flush: 'sync' } + ); + + return { clipboardRef, isSuccessRef, copiedRef }; +} + +export function copyTextToClipboard(input: string, { target = document.body }: Options = {}) { + const element = document.createElement('textarea'); + const previouslyFocusedElement = document.activeElement; + + element.value = input; + + element.setAttribute('readonly', ''); + + (element.style as any).contain = 'strict'; + element.style.position = 'absolute'; + element.style.left = '-9999px'; + element.style.fontSize = '12pt'; + + const selection = document.getSelection(); + let originalRange; + if (selection && selection.rangeCount > 0) { + originalRange = selection.getRangeAt(0); + } + + target.append(element); + element.select(); + + element.selectionStart = 0; + element.selectionEnd = input.length; + + let isSuccess = false; + try { + isSuccess = document.execCommand('copy'); + } catch (e) { + throw new Error(e); + } + + element.remove(); + + if (originalRange && selection) { + selection.removeAllRanges(); + selection.addRange(originalRange); + } + + if (previouslyFocusedElement) { + (previouslyFocusedElement as HTMLElement).focus(); + } + return isSuccess; +} diff --git a/src/hooks/web/useDesign.ts b/src/hooks/web/useDesign.ts new file mode 100644 index 0000000..046674b --- /dev/null +++ b/src/hooks/web/useDesign.ts @@ -0,0 +1,22 @@ +import { useAppProviderContext } from '/@/components/Application'; +// import { computed } from 'vue'; +// import { lowerFirst } from 'lodash-es'; +export function useDesign(scope: string) { + const values = useAppProviderContext(); + // const $style = cssModule ? useCssModule() : {}; + + // const style: Record = {}; + // if (cssModule) { + // Object.keys($style).forEach((key) => { + // // const moduleCls = $style[key]; + // const k = key.replace(new RegExp(`^${values.prefixCls}-?`, 'ig'), ''); + // style[lowerFirst(k)] = $style[key]; + // }); + // } + return { + // prefixCls: computed(() => `${values.prefixCls}-${scope}`), + prefixCls: `${values.prefixCls}-${scope}`, + prefixVar: values.prefixCls, + // style, + }; +} diff --git a/src/hooks/web/useDragNotice.ts b/src/hooks/web/useDragNotice.ts new file mode 100644 index 0000000..7432738 --- /dev/null +++ b/src/hooks/web/useDragNotice.ts @@ -0,0 +1,165 @@ +import { ref, nextTick, getCurrentInstance, watch } from 'vue'; +import { getToken } from '/@/utils/auth'; +import md5 from 'crypto-js/md5'; +import { connectWebSocket, onWebSocket } from '/@/hooks/web/useWebSocket'; +import { useGlobSetting } from '/@/hooks/setting'; +import { useModal } from '/@/components/Modal'; +import { useUserStore } from '/@/store/modules/user'; +import { isUrl } from '@/utils/is'; +import { getQueryVariable, getUrlParams } from '@/utils'; +import { useRouter } from 'vue-router'; +import { useMessage } from '@/hooks/web/useMessage'; +const { createMessage } = useMessage(); +export function useDragNotice() { + //*********************************websocket配置begin****************************************** + const glob = useGlobSetting(); + const { push, currentRoute } = useRouter(); + const userStore = useUserStore(); + const instance: any = getCurrentInstance(); + // 初始化 WebSocket + function initWebSocket() { + const token = getToken(); + //将登录token生成一个短的标识 + const wsClientId = md5(token); + // WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https + const url = glob.domainUrl?.replace('https://', 'wss://').replace('http://', 'ws://') + '/dragChannelSocket/' + wsClientId; + connectWebSocket(url); + onWebSocket(onWebSocketMessage); + } + + async function onWebSocketMessage(data) { + console.log('仪表盘监听按钮点击事件websocket', data); + if (data?.CMD === 'drag') { + //触发动作: url:路径 modal:弹窗 + const action = data.result.action; + //弹窗类型: 点击按钮打开什么弹窗,根据type打开不同的弹窗 + const type = data.result.type; + //url地址,可以是路由,也可以是外部链接 + let url = data.result.url; + //弹窗参数或者url参数 + const record = data.result.records || {}; + console.log('仪表盘监听点击事件类型type', type); + console.log('仪表盘监听点击事件动作action', action); + console.log('仪表盘监听点击事件路径url', url); + console.log('仪表盘监听点击事件参数', record); + //1.路径的话,判断外部链接还是内部路由跳转 + if (action == 'url') { + //常用下載特殊处理 + if (url == 'fileUrl') { + url = record[url]; + } + const urlParamsObj = getUrlParams(url); + if (url.startsWith('http')) { + window.open(url, '_blank'); + } else { + push({ path: urlParamsObj.url, query: { ...urlParamsObj.params, ...record } }); + } + } else { + //2.弹窗方式打开项目组件 + switch (type) { + case 'email': + //邮箱查看弹窗 + handleOpenType('email', { record }); + break; + default: + break; + } + } + } + } + //*********************************websocket配置end****************************************** + + //*********************************打开弹窗修改,动态设置弹窗begin******************************* + //当前表单弹窗 + const currentModal = ref(null); + //当前表单参数 + const modalParams = ref({}); + //表单注册缓存 + const modalRegCache = ref({}); + //组件绑定参数 + const bindParams = ref({}); + /** + * 根据类型打开不同弹窗 + * @param type + * @param params + */ + async function handleOpenType(type, params) { + currentModal.value = null; + modalParams.value = { ...params }; + switch (type) { + case 'email': + //邮件查看 + currentModal.value = 'EoaMailBoxInModal'; + break; + default: + currentModal.value = null; + break; + } + //注册表单弹窗 + initModalRegister(); + await nextTick(() => { + if (modalRegCache.value[currentModal.value!]?.isRegister) { + console.log('已注冊,走缓存'); + modalRegCache.value[currentModal.value!].modalMethods.openModal(true, modalParams.value); + } + }); + } + /** + * 初始化弹窗注册 + */ + function initModalRegister() { + //如果当前选择表单为null,就不处理 + if (!currentModal.value) { + return; + } + //判断缓存中是否存在,不存在就走缓存逻辑 + if (!modalRegCache.value[currentModal.value]) { + const [registerModal, modalMethods] = useModal(); + modalRegCache.value[currentModal.value] = { + isRegister: false, + register: bindRegisterModal(registerModal, modalMethods), + modalMethods, + }; + } + } + + /** + * 绑定注册弹窗 + * @param regFn + * @param modalMethod + */ + function bindRegisterModal(regFn, modalMethod) { + return async (...args) => { + console.log('开始注册:', currentModal.value); + await regFn(...args); + console.log('注册完成:', currentModal.value); + //打开弹窗 + modalMethod.openModal(true, modalParams.value); + //设置缓存标识 + modalRegCache.value[currentModal.value!].isRegister = true; + }; + } + //*********************************打开弹窗修改,动态设置弹窗end****************************************** + //刷新页面 + function reloadPage() { + const iframes: any = document.getElementsByClassName('jeecg-iframe-page__main'); + // 将 HTMLCollection 转换为数组 + const iframeArray = Array.from(iframes); + if (currentRoute.value?.meta?.frameSrc && currentRoute.value?.meta?.frameSrc.indexOf('/drag/view?pageId=') >= 0) { + const targetIframe: any = iframeArray.find((iframe: any) => iframe.src == currentRoute.value?.meta?.frameSrc); + console.log('targetIframe', targetIframe); + if (targetIframe) { + targetIframe.contentWindow.postMessage({ reload: true }, '*'); + } + } + } + return { + initDragWebSocket: initWebSocket, + handleOpenType, + currentModal, + modalParams, + modalRegCache, + bindParams, + reloadPage, + }; +} diff --git a/src/hooks/web/useECharts.ts b/src/hooks/web/useECharts.ts new file mode 100644 index 0000000..66e0fa1 --- /dev/null +++ b/src/hooks/web/useECharts.ts @@ -0,0 +1,115 @@ +import type { EChartsOption } from 'echarts'; +import type { Ref } from 'vue'; +import { useTimeoutFn } from '/@/hooks/core/useTimeout'; +import { tryOnUnmounted } from '@vueuse/core'; +import { unref, nextTick, watch, computed, ref } from 'vue'; +import { useDebounceFn } from '@vueuse/core'; +import { useEventListener } from '/@/hooks/event/useEventListener'; +import { useBreakpoint } from '/@/hooks/event/useBreakpoint'; +import echarts from '/@/utils/lib/echarts'; +import { useRootSetting } from '/@/hooks/setting/useRootSetting'; + +export function useECharts(elRef: Ref, theme: 'light' | 'dark' | 'default' = 'default') { + console.log("---useECharts---初始化加载---") + + const { getDarkMode: getSysDarkMode } = useRootSetting(); + + const getDarkMode = computed(() => { + return theme === 'default' ? getSysDarkMode.value : theme; + }); + let chartInstance: echarts.ECharts | null = null; + let resizeFn: Fn = resize; + const cacheOptions = ref({}) as Ref; + let removeResizeFn: Fn = () => {}; + + resizeFn = useDebounceFn(resize, 200); + + const getOptions = computed(() => { + if (getDarkMode.value !== 'dark') { + return cacheOptions.value as EChartsOption; + } + return { + backgroundColor: 'transparent', + ...cacheOptions.value, + } as EChartsOption; + }); + + function initCharts(t = theme) { + const el = unref(elRef); + if (!el || !unref(el)) { + return; + } + + chartInstance = echarts.init(el, t); + const { removeEvent } = useEventListener({ + el: window, + name: 'resize', + listener: resizeFn, + }); + removeResizeFn = removeEvent; + const { widthRef, screenEnum } = useBreakpoint(); + if (unref(widthRef) <= screenEnum.MD || el.offsetHeight === 0) { + useTimeoutFn(() => { + resizeFn(); + }, 30); + } + } + + function setOptions(options: EChartsOption, clear = true) { + cacheOptions.value = options; + if (unref(elRef)?.offsetHeight === 0) { + useTimeoutFn(() => { + setOptions(unref(getOptions)); + }, 30); + return; + } + nextTick(() => { + useTimeoutFn(() => { + if (!chartInstance) { + initCharts(getDarkMode.value as 'default'); + + if (!chartInstance) return; + } + clear && chartInstance?.clear(); + + chartInstance?.setOption(unref(getOptions)); + }, 30); + }); + } + + function resize() { + chartInstance?.resize(); + } + + watch( + () => getDarkMode.value, + (theme) => { + if (chartInstance) { + chartInstance.dispose(); + initCharts(theme as 'default'); + setOptions(cacheOptions.value); + } + } + ); + + tryOnUnmounted(() => { + if (!chartInstance) return; + removeResizeFn(); + chartInstance.dispose(); + chartInstance = null; + }); + + function getInstance(): echarts.ECharts | null { + if (!chartInstance) { + initCharts(getDarkMode.value as 'default'); + } + return chartInstance; + } + + return { + setOptions, + resize, + echarts, + getInstance, + }; +} diff --git a/src/hooks/web/useFullContent.ts b/src/hooks/web/useFullContent.ts new file mode 100644 index 0000000..7dea077 --- /dev/null +++ b/src/hooks/web/useFullContent.ts @@ -0,0 +1,28 @@ +import { computed, unref } from 'vue'; + +import { useAppStore } from '/@/store/modules/app'; + +import { useRouter } from 'vue-router'; + +/** + * @description: Full screen display content + */ +export const useFullContent = () => { + const appStore = useAppStore(); + const router = useRouter(); + const { currentRoute } = router; + + // Whether to display the content in full screen without displaying the menu + const getFullContent = computed(() => { + // Query parameters, the full screen is displayed when the address bar has a full parameter + const route = unref(currentRoute); + const query = route.query; + if (query && Reflect.has(query, '__full__')) { + return true; + } + // Return to the configuration in the configuration file + return appStore.getProjectConfig.fullContent; + }); + + return { getFullContent }; +}; diff --git a/src/hooks/web/useI18n.ts b/src/hooks/web/useI18n.ts new file mode 100644 index 0000000..2a777b7 --- /dev/null +++ b/src/hooks/web/useI18n.ts @@ -0,0 +1,55 @@ +import { i18n } from '/@/locales/setupI18n'; + +type I18nGlobalTranslation = { + (key: string): string; + (key: string, locale: string): string; + (key: string, locale: string, list: unknown[]): string; + (key: string, locale: string, named: Record): string; + (key: string, list: unknown[]): string; + (key: string, named: Record): string; +}; + +type I18nTranslationRestParameters = [string, any]; + +function getKey(namespace: string | undefined, key: string) { + if (!namespace) { + return key; + } + if (key.startsWith(namespace)) { + return key; + } + return `${namespace}.${key}`; +} + +export function useI18n(namespace?: string): { + t: I18nGlobalTranslation; +} { + const normalFn = { + t: (key: string) => { + return getKey(namespace, key); + }, + }; + + if (!i18n) { + return normalFn; + } + + const { t, ...methods } = i18n.global; + + const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => { + if (!key) return ''; + if (!key.includes('.') && !namespace) return key; + return t(getKey(namespace, key), ...(arg as I18nTranslationRestParameters)); + }; + return { + ...methods, + t: tFn, + }; +} + +// Why write this function? +// Mainly to configure the vscode i18nn ally plugin. This function is only used for routing and menus. Please use useI18n for other places + +// 为什么要编写此函数? +// 主要用于配合vscode i18nn ally插件。此功能仅用于路由和菜单。请在其他地方使用useI18n +export const t = (key: string) => key; diff --git a/src/hooks/web/useLockPage.ts b/src/hooks/web/useLockPage.ts new file mode 100644 index 0000000..c543be9 --- /dev/null +++ b/src/hooks/web/useLockPage.ts @@ -0,0 +1,72 @@ +import { computed, onUnmounted, unref, watchEffect } from 'vue'; +import { useThrottleFn } from '@vueuse/core'; + +import { useAppStore } from '/@/store/modules/app'; +import { useLockStore } from '/@/store/modules/lock'; + +import { useUserStore } from '/@/store/modules/user'; +import { useRootSetting } from '../setting/useRootSetting'; + +export function useLockPage() { + const { getLockTime } = useRootSetting(); + const lockStore = useLockStore(); + const userStore = useUserStore(); + const appStore = useAppStore(); + + let timeId: TimeoutHandle; + + function clear(): void { + window.clearTimeout(timeId); + } + + function resetCalcLockTimeout(): void { + // not login + if (!userStore.getToken) { + clear(); + return; + } + const lockTime = appStore.getProjectConfig.lockTime; + if (!lockTime || lockTime < 1) { + clear(); + return; + } + clear(); + + timeId = setTimeout(() => { + lockPage(); + }, lockTime * 60 * 1000); + } + + function lockPage(): void { + lockStore.setLockInfo({ + isLock: true, + pwd: undefined, + }); + } + + watchEffect((onClean) => { + if (userStore.getToken) { + resetCalcLockTimeout(); + } else { + clear(); + } + onClean(() => { + clear(); + }); + }); + + onUnmounted(() => { + clear(); + }); + + const keyupFn = useThrottleFn(resetCalcLockTimeout, 2000); + + return computed(() => { + if (unref(getLockTime)) { + return { onKeyup: keyupFn, onMousemove: keyupFn }; + } else { + clear(); + return {}; + } + }); +} diff --git a/src/hooks/web/useMessage.ts b/src/hooks/web/useMessage.ts new file mode 100644 index 0000000..00f671e --- /dev/null +++ b/src/hooks/web/useMessage.ts @@ -0,0 +1,157 @@ +import type { ModalFunc, ModalFuncProps } from 'ant-design-vue/lib/modal/Modal'; + +import { Modal, message as Message, notification } from 'ant-design-vue'; +import { InfoCircleFilled, CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons-vue'; + +import { NotificationArgsProps, ConfigProps } from 'ant-design-vue/lib/notification'; +import { useI18n } from './useI18n'; +import { isString } from '/@/utils/is'; +import { h } from 'vue'; + +export interface NotifyApi { + info(config: NotificationArgsProps): void; + success(config: NotificationArgsProps): void; + error(config: NotificationArgsProps): void; + warn(config: NotificationArgsProps): void; + warning(config: NotificationArgsProps): void; + open(args: NotificationArgsProps): void; + close(key: String): void; + config(options: ConfigProps): void; + destroy(): void; +} + +export declare type NotificationPlacement = 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'; +export declare type IconType = 'success' | 'info' | 'error' | 'warning'; +export interface ModalOptionsEx extends Omit { + iconType: 'warning' | 'success' | 'error' | 'info'; +} +export type ModalOptionsPartial = Partial & Pick; + +interface ConfirmOptions { + info: ModalFunc; + success: ModalFunc; + error: ModalFunc; + warn: ModalFunc; + warning: ModalFunc; +} + +function getIcon(iconType: string) { + try { + if (iconType === 'warning') { + return h(InfoCircleFilled,{"class":"modal-icon-warning"}) + } else if (iconType === 'success') { + return h(CheckCircleFilled,{"class": "modal-icon-success"}); + } else if (iconType === 'info') { + return h(InfoCircleFilled,{"class": "modal-icon-info"}); + } else { + return h(CloseCircleFilled,{"class":"modal-icon-error"}); + } + } catch (e) { + console.log(e); + } +} + +function renderContent({ content }: Pick) { + try { + if (isString(content)) { + return h('div', h('div', {'innerHTML':content as string})); + } else { + return content; + } + } catch (e) { + console.log(e); + return content; + } +} + +/** + * @description: Create confirmation box + */ +function createConfirm(options: ModalOptionsEx): ReturnType { + const iconType = options.iconType || 'warning'; + Reflect.deleteProperty(options, 'iconType'); + const opt: ModalFuncProps = { + centered: true, + icon: getIcon(iconType), + ...options, + content: renderContent(options), + }; + return Modal.confirm(opt); +} + +const getBaseOptions = () => { + const { t } = useI18n(); + return { + okText: t('common.okText'), + centered: true, + }; +}; + +function createModalOptions(options: ModalOptionsPartial, icon: string): ModalOptionsPartial { + // 代码逻辑说明: 可以自定义图标 + let titleIcon:any = '' + if(options.icon){ + titleIcon = options.icon; + }else{ + titleIcon = getIcon(icon) + } + return { + ...getBaseOptions(), + ...options, + content: renderContent(options), + icon: titleIcon + }; +} + +function createSuccessModal(options: ModalOptionsPartial) { + return Modal.success(createModalOptions(options, 'success')); +} + +function createErrorModal(options: ModalOptionsPartial) { + return Modal.error(createModalOptions(options, 'close')); +} + +function createInfoModal(options: ModalOptionsPartial) { + return Modal.info(createModalOptions(options, 'info')); +} + +function createWarningModal(options: ModalOptionsPartial) { + return Modal.warning(createModalOptions(options, 'warning')); +} + +interface MOE extends Omit { + iconType?: ModalOptionsEx['iconType']; +} + +// 提示框,无需传入iconType,默认为warning +function createConfirmSync(options: MOE) { + return new Promise((resolve) => { + createConfirm({ + iconType: 'warning', + ...options, + onOk: () => resolve(true), + onCancel: () => resolve(false), + }); + }); +} + +notification.config({ + placement: 'topRight', + duration: 3, +}); + +/** + * @description: message + */ +export function useMessage() { + return { + createMessage: Message, + notification: notification as NotifyApi, + createConfirm: createConfirm, + createConfirmSync, + createSuccessModal, + createErrorModal, + createInfoModal, + createWarningModal, + }; +} diff --git a/src/hooks/web/useMessage.tsx_backup b/src/hooks/web/useMessage.tsx_backup new file mode 100644 index 0000000..ad64eda --- /dev/null +++ b/src/hooks/web/useMessage.tsx_backup @@ -0,0 +1,132 @@ +import type { ModalFunc, ModalFuncProps } from 'ant-design-vue/lib/modal/Modal'; + +import { Modal, message as Message, notification } from 'ant-design-vue'; +import { InfoCircleFilled, CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons-vue'; + +import { NotificationArgsProps, ConfigProps } from 'ant-design-vue/lib/notification'; +import { useI18n } from './useI18n'; +import { isString } from '/@/utils/is'; + +export interface NotifyApi { + info(config: NotificationArgsProps): void; + success(config: NotificationArgsProps): void; + error(config: NotificationArgsProps): void; + warn(config: NotificationArgsProps): void; + warning(config: NotificationArgsProps): void; + open(args: NotificationArgsProps): void; + close(key: String): void; + config(options: ConfigProps): void; + destroy(): void; +} + +export declare type NotificationPlacement = 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'; +export declare type IconType = 'success' | 'info' | 'error' | 'warning'; +export interface ModalOptionsEx extends Omit { + iconType: 'warning' | 'success' | 'error' | 'info'; +} +export type ModalOptionsPartial = Partial & Pick; + +interface ConfirmOptions { + info: ModalFunc; + success: ModalFunc; + error: ModalFunc; + warn: ModalFunc; + warning: ModalFunc; +} + +function getIcon(iconType: string) { + try { + if (iconType === 'warning') { + return ; + } else if (iconType === 'success') { + return ; + } else if (iconType === 'info') { + return ; + } else { + return ; + } + } catch (e) { + console.log(e); + } +} + +function renderContent({ content }: Pick) { + try { + if (isString(content)) { + return
${content as string}
`}>; + } else { + return content; + } + } catch (e) { + console.log(e); + return content; + } +} + +/** + * @description: Create confirmation box + */ +function createConfirm(options: ModalOptionsEx): ReturnType { + const iconType = options.iconType || 'warning'; + Reflect.deleteProperty(options, 'iconType'); + const opt: ModalFuncProps = { + centered: true, + icon: getIcon(iconType), + ...options, + content: renderContent(options), + }; + return Modal.confirm(opt); +} + +const getBaseOptions = () => { + const { t } = useI18n(); + return { + okText: t('common.okText'), + centered: true, + }; +}; + +function createModalOptions(options: ModalOptionsPartial, icon: string): ModalOptionsPartial { + return { + ...getBaseOptions(), + ...options, + content: renderContent(options), + icon: getIcon(icon), + }; +} + +function createSuccessModal(options: ModalOptionsPartial) { + return Modal.success(createModalOptions(options, 'success')); +} + +function createErrorModal(options: ModalOptionsPartial) { + return Modal.error(createModalOptions(options, 'close')); +} + +function createInfoModal(options: ModalOptionsPartial) { + return Modal.info(createModalOptions(options, 'info')); +} + +function createWarningModal(options: ModalOptionsPartial) { + return Modal.warning(createModalOptions(options, 'warning')); +} + +notification.config({ + placement: 'topRight', + duration: 3, +}); + +/** + * @description: message + */ +export function useMessage() { + return { + createMessage: Message, + notification: notification as NotifyApi, + createConfirm: createConfirm, + createSuccessModal, + createErrorModal, + createInfoModal, + createWarningModal, + }; +} diff --git a/src/hooks/web/usePage.ts b/src/hooks/web/usePage.ts new file mode 100644 index 0000000..57a7aeb --- /dev/null +++ b/src/hooks/web/usePage.ts @@ -0,0 +1,81 @@ +import type { RouteLocationRaw, Router } from 'vue-router'; + +import { PageEnum } from '/@/enums/pageEnum'; +import { isString } from '/@/utils/is'; +import { unref } from 'vue'; + +import { useRouter } from 'vue-router'; +import { REDIRECT_NAME } from '/@/router/constant'; +import { useUserStore } from '/@/store/modules/user'; +import { useMultipleTabStore } from '/@/store/modules/multipleTab'; + +export type RouteLocationRawEx = Omit & { path: PageEnum }; + +function handleError(e: Error) { + console.error(e); +} + +// page switch +export function useGo(_router?: Router) { + // 代码逻辑说明: 【issues/694】404返回首页问题 + const userStore = useUserStore(); + const homePath = userStore.getUserInfo.homePath || PageEnum.BASE_HOME; + let router; + if (!_router) { + router = useRouter(); + } + const { push, replace } = _router || router; + function go(opt: PageEnum | RouteLocationRawEx | string = homePath, isReplace = false) { + if (!opt) { + return; + } + if (isString(opt)) { + isReplace ? replace(opt).catch(handleError) : push(opt).catch(handleError); + } else { + const o = opt as RouteLocationRaw; + isReplace ? replace(o).catch(handleError) : push(o).catch(handleError); + } + } + return go; +} + +/** + * @description: redo current page + */ +export const useRedo = (_router?: Router, otherQuery?: Recordable) => { + const { push, currentRoute } = _router || useRouter(); + const { query, params = {}, name, fullPath } = unref(currentRoute.value); + function redo(): Promise { + return new Promise((resolve) => { + if (name === REDIRECT_NAME) { + resolve(false); + return; + } + // 代码逻辑说明: 【QQYUN-7099】动态路由匹配右键重新加载404 + const tabStore = useMultipleTabStore(); + if (otherQuery && Object.keys(otherQuery).length > 0) { + Object.keys(otherQuery).forEach((key) => { + params[key] = otherQuery[key]; + }); + } + if (name && Object.keys(params).length > 0) { + tabStore.setRedirectPageParam({ + redirect_type: 'name', + name: String(name), + params, + query, + }); + params['path'] = String(name); + } else { + tabStore.setRedirectPageParam({ + redirect_type: 'path', + path: fullPath, + query, + }); + params['path'] = fullPath; + } + push({ name: REDIRECT_NAME, params, query }).then(() => resolve(true)); + }); + } + return redo; +}; diff --git a/src/hooks/web/usePagination.ts b/src/hooks/web/usePagination.ts new file mode 100644 index 0000000..ea234ef --- /dev/null +++ b/src/hooks/web/usePagination.ts @@ -0,0 +1,31 @@ +import type { Ref } from 'vue'; +import { ref, unref, computed } from 'vue'; + +function pagination(list: T[], pageNo: number, pageSize: number): T[] { + const offset = (pageNo - 1) * Number(pageSize); + const ret = offset + Number(pageSize) >= list.length ? list.slice(offset, list.length) : list.slice(offset, offset + Number(pageSize)); + return ret; +} + +export function usePagination(list: Ref, pageSize: number) { + const currentPage = ref(1); + const pageSizeRef = ref(pageSize); + + const getPaginationList = computed(() => { + return pagination(unref(list), unref(currentPage), unref(pageSizeRef)); + }); + + const getTotal = computed(() => { + return unref(list).length; + }); + + function setCurrentPage(page: number) { + currentPage.value = page; + } + + function setPageSize(pageSize: number) { + pageSizeRef.value = pageSize; + } + + return { setCurrentPage, getTotal, setPageSize, getPaginationList }; +} diff --git a/src/hooks/web/usePermission.ts b/src/hooks/web/usePermission.ts new file mode 100644 index 0000000..93c0b28 --- /dev/null +++ b/src/hooks/web/usePermission.ts @@ -0,0 +1,172 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { useAppStore } from '/@/store/modules/app'; +import { usePermissionStore } from '/@/store/modules/permission'; +import { useUserStore } from '/@/store/modules/user'; + +import { useTabs } from './useTabs'; + +import { router, resetRouter } from '/@/router'; +// import { RootRoute } from '/@/router/routes'; + +import projectSetting from '/@/settings/projectSetting'; +import { PermissionModeEnum } from '/@/enums/appEnum'; +import { RoleEnum } from '/@/enums/roleEnum'; + +import { intersection } from 'lodash-es'; +import { isArray } from '/@/utils/is'; +import { useMultipleTabStore } from '/@/store/modules/multipleTab'; + +// User permissions related operations +export function usePermission() { + const userStore = useUserStore(); + const appStore = useAppStore(); + const permissionStore = usePermissionStore(); + //动态加载流程节点表单权限 + let formData: any = {}; + function initBpmFormData(_bpmFormData) { + formData = _bpmFormData; + } + const { closeAll } = useTabs(router); + + //==================================工作流权限判断-begin========================================= + function hasBpmPermission(code, type) { + // 禁用-type=2 + // 显示-type=1 + let codeList: string[] = []; + let permissionList = formData.permissionList; + if (permissionList && permissionList.length > 0) { + for (let item of permissionList) { + if (item.type == type) { + codeList.push(item.action); + } + } + } + return codeList.indexOf(code) >= 0; + } + //==================================工作流权限判断-end========================================= + + /** + * Change permission mode + */ + async function togglePermissionMode() { + appStore.setProjectConfig({ + permissionMode: projectSetting.permissionMode === PermissionModeEnum.BACK ? PermissionModeEnum.ROUTE_MAPPING : PermissionModeEnum.BACK, + }); + location.reload(); + } + + /** + * Reset and regain authority resource information + * @param id + */ + async function resume() { + const tabStore = useMultipleTabStore(); + tabStore.clearCacheTabs(); + resetRouter(); + const routes = await permissionStore.buildRoutesAction(); + routes.forEach((route) => { + router.addRoute(route as unknown as RouteRecordRaw); + }); + permissionStore.setLastBuildMenuTime(); + closeAll(); + } + + /** + * 确定是否存在权限 + */ + function hasPermission(value?: RoleEnum | RoleEnum[] | string | string[], def = true): boolean { + // Visible by default + if (!value) { + return def; + } + + const permMode = projectSetting.permissionMode; + + if ([PermissionModeEnum.ROUTE_MAPPING, PermissionModeEnum.ROLE].includes(permMode)) { + if (!isArray(value)) { + return userStore.getRoleList?.includes(value as RoleEnum); + } + return (intersection(value, userStore.getRoleList) as RoleEnum[]).length > 0; + } + + if (PermissionModeEnum.BACK === permMode) { + const allCodeList = permissionStore.getPermCodeList as string[]; + if (!isArray(value) && allCodeList && allCodeList.length > 0) { + //=============================工作流权限判断-显示-begin============================================== + if (formData) { + let code = value as string; + if (hasBpmPermission(code, '1') === true) { + return true; + } + } + //=============================工作流权限判断-显示-end============================================== + return allCodeList.includes(value); + } + return (intersection(value, allCodeList) as string[]).length > 0; + } + return true; + } + /** + * 是否禁用组件 + */ + function isDisabledAuth(value?: RoleEnum | RoleEnum[] | string | string[], def = true): boolean { + //=============================工作流权限判断-禁用-begin============================================== + if (formData) { + let code = value as string; + if (hasBpmPermission(code, '2') === true) { + return true; + } + // 代码逻辑说明: VUEN-1342【流程】编码方式 节点权限配置好后,未生效 + if (isCodingButNoConfig(code) == true) { + return false; + } + } + //=============================工作流权限判断-禁用-end============================================== + return !hasPermission(value); + } + + /** + * Change roles + * @param roles + */ + async function changeRole(roles: RoleEnum | RoleEnum[]): Promise { + if (projectSetting.permissionMode !== PermissionModeEnum.ROUTE_MAPPING) { + throw new Error('Please switch PermissionModeEnum to ROUTE_MAPPING mode in the configuration to operate!'); + } + + if (!isArray(roles)) { + roles = [roles]; + } + userStore.setRoleList(roles); + await resume(); + } + + /** + * refresh menu data + */ + async function refreshMenu() { + resume(); + } + + /** + * 判断是不是 代码里写了逻辑但是没有配置权限这种情况 + */ + function isCodingButNoConfig(code) { + let all = permissionStore.allAuthList; + if (all && all instanceof Array) { + let temp = all.filter((item) => item.action == code); + if (temp && temp.length > 0) { + if (temp[0].status == '0') { + return true; + } + } else { + // 代码逻辑说明: 【TV360X-1604】按钮禁用权限在接口中查不到也禁用 + return false; + } + } + return false; + } + + return { changeRole, hasPermission, togglePermissionMode, refreshMenu, isDisabledAuth, initBpmFormData }; +} diff --git a/src/hooks/web/usePrintJS.ts b/src/hooks/web/usePrintJS.ts new file mode 100644 index 0000000..8bedf8b --- /dev/null +++ b/src/hooks/web/usePrintJS.ts @@ -0,0 +1,44 @@ +import { nextTick } from 'vue'; +import $printJS, { Configuration } from 'print-js'; +import Print from 'vue-print-nb-jeecg/src/printarea'; + +/** + * 调用 printJS,如果type = html,就走 printNB 的方法 + */ +export function printJS(configuration: Configuration) { + if (configuration?.type === 'html') { + printNb(configuration.printable); + } else { + return $printJS(configuration); + } +} + +/** 调用 printNB 打印 */ +export function printNb(domId, endCallback?) { + if (domId) { + localPrint(domId, endCallback); + } else { + window.print(); + endCallback && endCallback(); + } +} + +let closeBtn = true; + +function localPrint(domId, endCallback) { + if (typeof domId === 'string' && !domId.startsWith('#')) { + domId = '#' + domId; + } + nextTick(() => { + if (closeBtn) { + closeBtn = false; + new Print({ + el: domId, + endCallback() { + closeBtn = true; + endCallback && endCallback(); + }, + }); + } + }); +} diff --git a/src/hooks/web/useScript.ts b/src/hooks/web/useScript.ts new file mode 100644 index 0000000..585c267 --- /dev/null +++ b/src/hooks/web/useScript.ts @@ -0,0 +1,48 @@ +import { onMounted, onUnmounted, ref } from 'vue'; + +interface ScriptOptions { + src: string; +} + +export function useScript(opts: ScriptOptions) { + // date-begin--author:liaozhiyang---date:20250716---for:【issues/8552】useScript的isLoading默认值应该是true + const isLoading = ref(true); + // date-end--author:liaozhiyang---date:20250716---for:【issues/8552】useScript的isLoading默认值应该是true + const error = ref(false); + const success = ref(false); + let script: HTMLScriptElement; + + const promise = new Promise((resolve, reject) => { + onMounted(() => { + script = document.createElement('script'); + script.type = 'text/javascript'; + script.onload = function () { + isLoading.value = false; + success.value = true; + error.value = false; + resolve(''); + }; + + script.onerror = function (err) { + isLoading.value = false; + success.value = false; + error.value = true; + reject(err); + }; + + script.src = opts.src; + document.head.appendChild(script); + }); + }); + + onUnmounted(() => { + script && script.remove(); + }); + + return { + isLoading, + error, + success, + toPromise: () => promise, + }; +} diff --git a/src/hooks/web/useSortable.ts b/src/hooks/web/useSortable.ts new file mode 100644 index 0000000..4c66b6a --- /dev/null +++ b/src/hooks/web/useSortable.ts @@ -0,0 +1,21 @@ +import { nextTick, unref } from 'vue'; +import type { Ref } from 'vue'; +import type { Options } from 'sortablejs'; + +export function useSortable(el: HTMLElement | Ref, options?: Options) { + function initSortable() { + nextTick(async () => { + if (!el) return; + + const Sortable = (await import('sortablejs')).default; + Sortable.create(unref(el), { + animation: 500, + delay: 400, + delayOnTouchOnly: true, + ...options, + }); + }); + } + + return { initSortable }; +} diff --git a/src/hooks/web/useSso.ts b/src/hooks/web/useSso.ts new file mode 100644 index 0000000..61be98a --- /dev/null +++ b/src/hooks/web/useSso.ts @@ -0,0 +1,44 @@ +// 单点登录核心类 +import { getToken } from '/@/utils/auth'; +import { getUrlParam } from '/@/utils'; +import { useGlobSetting } from '/@/hooks/setting'; +import { validateCasLogin } from '/@/api/sys/user'; +import { useUserStore } from '/@/store/modules/user'; +const globSetting = useGlobSetting(); +const openSso = globSetting.openSso; +export function useSso() { + // 代码逻辑说明: 【QQYUN-7805】SSO登录强制用http #957--- + let locationUrl = document.location.protocol +"//" + window.location.host + '/'; + + /** + * 单点登录 + */ + async function ssoLogin() { + if (openSso == 'true') { + let token = getToken(); + let ticket = getUrlParam('ticket'); + if (!token) { + if (ticket) { + await validateCasLogin({ + ticket: ticket, + service: locationUrl, + }).then((res) => { + const userStore = useUserStore(); + userStore.setToken(res.token); + return userStore.afterLoginAction(true, {}); + }); + } else { + window.location.href = globSetting.casBaseUrl + '/login?service=' + encodeURIComponent(locationUrl); + } + } + } + } + + /** + * 退出登录 + */ + async function ssoLoginOut() { + window.location.href = globSetting.casBaseUrl + '/logout?service=' + encodeURIComponent(locationUrl); + } + return { ssoLogin, ssoLoginOut }; +} diff --git a/src/hooks/web/useTabs.ts b/src/hooks/web/useTabs.ts new file mode 100644 index 0000000..ab6e88c --- /dev/null +++ b/src/hooks/web/useTabs.ts @@ -0,0 +1,126 @@ +import type { RouteLocationNormalized, Router } from 'vue-router'; + +import { useRouter } from 'vue-router'; +import { unref } from 'vue'; + +import { useMultipleTabStore } from '/@/store/modules/multipleTab'; +import { useAppStore } from '/@/store/modules/app'; + +enum TableActionEnum { + REFRESH, + CLOSE_ALL, + CLOSE_LEFT, + CLOSE_RIGHT, + CLOSE_OTHER, + CLOSE_CURRENT, + HOME_DESIGN, + CLOSE, +} + +export function useTabs(_router?: Router) { + const appStore = useAppStore(); + + function canIUseTabs(): boolean { + const { show } = appStore.getMultiTabsSetting; + if (!show) { + throw new Error('The multi-tab page is currently not open, please open it in the settings!'); + } + return !!show; + } + + const tabStore = useMultipleTabStore(); + const router = _router || useRouter(); + + const { currentRoute } = router; + + function getCurrentTab() { + const route = unref(currentRoute); + return tabStore.getTabList.find((item) => item.path === route.path)!; + } + + async function updateTabTitle(title: string, tab?: RouteLocationNormalized) { + const canIUse = canIUseTabs; + if (!canIUse) { + return; + } + const targetTab = tab || getCurrentTab(); + await tabStore.setTabTitle(title, targetTab); + } + + async function updateTabPath(path: string, tab?: RouteLocationNormalized) { + const canIUse = canIUseTabs; + if (!canIUse) { + return; + } + const targetTab = tab || getCurrentTab(); + await tabStore.updateTabPath(path, targetTab); + } + + async function handleTabAction(action: TableActionEnum, tab?: RouteLocationNormalized) { + const canIUse = canIUseTabs; + if (!canIUse) { + return; + } + const currentTab = getCurrentTab(); + switch (action) { + case TableActionEnum.REFRESH: + await tabStore.refreshPage(router); + break; + + case TableActionEnum.HOME_DESIGN: + await tabStore.changeDesign(router); + break; + + case TableActionEnum.CLOSE_ALL: + await tabStore.closeAllTab(router); + break; + + case TableActionEnum.CLOSE_LEFT: + // 代码逻辑说明: 【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用 + await tabStore.closeLeftTabs(tab || currentTab, router); + break; + + case TableActionEnum.CLOSE_RIGHT: + await tabStore.closeRightTabs(tab || currentTab, router); + break; + + case TableActionEnum.CLOSE_OTHER: + await tabStore.closeOtherTabs(tab || currentTab, router); + break; + + case TableActionEnum.CLOSE_CURRENT: + case TableActionEnum.CLOSE: + await tabStore.closeTab(tab || currentTab, router); + break; + } + } + + /** + * 关闭相同的路由 + * @param path + */ + function closeSameRoute(path) { + if(path.indexOf('?')>0){ + path = path.split('?')[0]; + } + let tab = tabStore.getTabList.find((item) => item.path.indexOf(path)>=0)!; + if(tab){ + tabStore.closeTab(tab, router); + } + } + + return { + refreshPage: () => handleTabAction(TableActionEnum.REFRESH), + changeDesign: () => handleTabAction(TableActionEnum.HOME_DESIGN), + // 代码逻辑说明: 【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用 + closeAll: (tab) => handleTabAction(TableActionEnum.CLOSE_ALL, tab), + closeLeft: (tab) => handleTabAction(TableActionEnum.CLOSE_LEFT, tab), + closeRight: (tab) => handleTabAction(TableActionEnum.CLOSE_RIGHT, tab), + closeOther: (tab) => handleTabAction(TableActionEnum.CLOSE_OTHER, tab), + closeCurrent: () => handleTabAction(TableActionEnum.CLOSE_CURRENT), + close: (tab?: RouteLocationNormalized) => handleTabAction(TableActionEnum.CLOSE, tab), + setTitle: (title: string, tab?: RouteLocationNormalized) => updateTabTitle(title, tab), + updatePath: (fullPath: string, tab?: RouteLocationNormalized) => updateTabPath(fullPath, tab), + closeSameRoute + }; +} diff --git a/src/hooks/web/useTitle.ts b/src/hooks/web/useTitle.ts new file mode 100644 index 0000000..7747d0d --- /dev/null +++ b/src/hooks/web/useTitle.ts @@ -0,0 +1,71 @@ +import type {Menu} from "@/router/types"; +import { ref, watch, unref } from 'vue'; +import { useI18n } from '/@/hooks/web/useI18n'; +import { useTitle as usePageTitle } from '@vueuse/core'; +import { useGlobSetting } from '/@/hooks/setting'; +import { useRouter } from 'vue-router'; +import { useLocaleStore } from '/@/store/modules/locale'; +import { REDIRECT_NAME } from '/@/router/constant'; +import { getMenus } from '/@/router/menus'; + +/** + * Listening to page changes and dynamically changing site titles + */ +export function useTitle() { + const { title } = useGlobSetting(); + const { t } = useI18n(); + const { currentRoute } = useRouter(); + const localeStore = useLocaleStore(); + + const pageTitle = usePageTitle(); + + const menus = ref(null) + + watch( + [() => currentRoute.value.path, () => localeStore.getLocale], + async () => { + const route = unref(currentRoute); + + if (route.name === REDIRECT_NAME) { + return; + } + // 代码逻辑说明: 【QQYUN-6938】online菜单名字和页面title不一致 + if (route.params && Object.keys(route.params).length) { + if (!menus.value) { + menus.value = await getMenus(); + } + const getTitle = getMatchingRouterName(menus.value, route.fullPath); + let tTitle = ''; + if (getTitle) { + tTitle = t(getTitle); + } else { + tTitle = t(route?.meta?.title as string); + } + pageTitle.value = tTitle ? ` ${tTitle} - ${title} ` : `${title}`; + } else { + const tTitle = t(route?.meta?.title as string); + pageTitle.value = tTitle ? ` ${tTitle} - ${title} ` : `${title}`; + } + }, + { immediate: true } + ); +} +/** + 2023-11-09 + liaozhiyang + 获取路由匹配模式的真实页面名字 +*/ +function getMatchingRouterName(menus, path) { + for (let i = 0, len = menus.length; i < len; i++) { + const item = menus[i]; + if (item.path === path && !item.redirect && !item.paramPath) { + return item.meta?.title; + } else if (item.children?.length) { + const result = getMatchingRouterName(item.children, path); + if (result) { + return result; + } + } + } + return ''; +} diff --git a/src/hooks/web/useWatermark.ts b/src/hooks/web/useWatermark.ts new file mode 100644 index 0000000..bde1549 --- /dev/null +++ b/src/hooks/web/useWatermark.ts @@ -0,0 +1,98 @@ +import { getCurrentInstance, onBeforeUnmount, ref, Ref, shallowRef, unref } from 'vue'; +import { useRafThrottle } from '/@/utils/domUtils'; +import { addResizeListener, removeResizeListener } from '/@/utils/event'; +import { isDef } from '/@/utils/is'; + +const domSymbol = Symbol('watermark-dom'); + +export function useWatermark(appendEl: Ref = ref(document.body) as Ref) { + const func = useRafThrottle(function () { + const el = unref(appendEl); + if (!el) return; + const { clientHeight: height, clientWidth: width } = el; + updateWatermark({ height, width }); + }); + const id = domSymbol.toString(); + const watermarkEl = shallowRef(); + + const clear = () => { + const domId = unref(watermarkEl); + watermarkEl.value = undefined; + const el = unref(appendEl); + if (!el) return; + domId && el.removeChild(domId); + removeResizeListener(el, func); + }; + + function createBase64(str: string) { + const can = document.createElement('canvas'); + const width = 300; + const height = 240; + Object.assign(can, { width, height }); + + const cans = can.getContext('2d'); + if (cans) { + cans.rotate((-20 * Math.PI) / 120); + cans.font = '15px Vedana'; + cans.fillStyle = 'rgba(0, 0, 0, 0.15)'; + cans.textAlign = 'left'; + cans.textBaseline = 'middle'; + cans.fillText(str, width / 20, height); + } + return can.toDataURL('image/png'); + } + + function updateWatermark( + options: { + width?: number; + height?: number; + str?: string; + } = {} + ) { + const el = unref(watermarkEl); + if (!el) return; + if (isDef(options.width)) { + el.style.width = `${options.width}px`; + } + if (isDef(options.height)) { + el.style.height = `${options.height}px`; + } + if (isDef(options.str)) { + el.style.background = `url(${createBase64(options.str)}) left top repeat`; + } + } + + const createWatermark = (str: string) => { + if (unref(watermarkEl)) { + updateWatermark({ str }); + return id; + } + const div = document.createElement('div'); + watermarkEl.value = div; + div.id = id; + div.style.pointerEvents = 'none'; + div.style.top = '0px'; + div.style.left = '0px'; + div.style.position = 'absolute'; + div.style.zIndex = '100000'; + const el = unref(appendEl); + if (!el) return id; + const { clientHeight: height, clientWidth: width } = el; + updateWatermark({ str, width, height }); + el.appendChild(div); + return id; + }; + + function setWatermark(str: string) { + createWatermark(str); + addResizeListener(document.documentElement, func); + const instance = getCurrentInstance(); + if (instance) { + onBeforeUnmount(() => { + clear(); + }); + } + } + + return { setWatermark, clear }; +} diff --git a/src/hooks/web/useWebSocket.ts b/src/hooks/web/useWebSocket.ts new file mode 100644 index 0000000..76bfbce --- /dev/null +++ b/src/hooks/web/useWebSocket.ts @@ -0,0 +1,132 @@ +// noinspection JSUnusedGlobalSymbols + +import { unref } from 'vue'; +import { useWebSocket, WebSocketResult } from '@vueuse/core'; +import { getToken } from '/@/utils/auth'; + +let result: WebSocketResult; +const listeners = new Map(); + +/** + * 开启 WebSocket 链接,全局只需执行一次 + * @param url + */ +export function connectWebSocket(url: string) { + // 代码逻辑说明: v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278 + const token = (getToken() || '') as string; + result = useWebSocket(url, { + // 自动重连 (遇到错误最多重复连接10次) + autoReconnect: { + retries : 10, + delay : 5000 + }, + // 心跳检测 + heartbeat: { + message: "ping", + interval: 55000 + }, + protocols: [token], + // 代码逻辑说明: [issues/6662] 演示系统socket总断,换一个写法 + onConnected: function (ws) { + console.log('[WebSocket] 连接成功', ws); + }, + onDisconnected: function (ws, event) { + console.log('[WebSocket] 连接断开:', ws, event); + }, + onError: function (ws, event) { + console.log('[WebSocket] 连接发生错误: ', ws, event); + }, + onMessage: function (_ws, e) { + console.debug('[WebSocket] -----接收消息-------', e.data); + try { + // 代码逻辑说明: 【issues/1161】前端websocket因心跳导致监听不起作用--- + if (e.data === 'ping') { + return; + } + const data = JSON.parse(e.data); + for (const callback of listeners.keys()) { + try { + callback(data); + } catch (err) { + console.error(err); + } + } + } catch (err) { + console.error('[WebSocket] data解析失败:', err); + } + }, + }); + // if (result) { + // result.open = onOpen; + // result.close = onClose; + + // const ws = unref(result.ws); + // if(ws!=null){ + // ws.onerror = onError; + // ws.onmessage = onMessage; + // ws.onopen = onOpen; + // ws.onclose = onClose; + // + // } + // } +} + +function onOpen() { + console.log('[WebSocket] 连接成功'); +} + +function onClose(e) { + console.log('[WebSocket] 连接断开:', e); +} + +function onError(e) { + console.log('[WebSocket] 连接发生错误: ', e); +} + +function onMessage(e) { + console.debug('[WebSocket] -----接收消息-------', e.data); + try { + // 代码逻辑说明: 【issues/1161】前端websocket因心跳导致监听不起作用--- + if(e==='ping'){ + return; + } + const data = JSON.parse(e.data); + for (const callback of listeners.keys()) { + try { + callback(data); + } catch (err) { + console.error(err); + } + } + } catch (err) { + console.error('[WebSocket] data解析失败:', err); + } +} + + +/** + * 添加 WebSocket 消息监听 + * @param callback + */ +export function onWebSocket(callback: (data: object) => any) { + if (!listeners.has(callback)) { + if (typeof callback === 'function') { + listeners.set(callback, null); + } else { + console.debug('[WebSocket] 添加 WebSocket 消息监听失败:传入的参数不是一个方法'); + } + } +} + +/** + * 解除 WebSocket 消息监听 + * + * @param callback + */ +export function offWebSocket(callback: (data: object) => any) { + listeners.delete(callback); +} + +export function useMyWebSocket() { + return result; +} diff --git a/src/layouts/default/content/index.vue b/src/layouts/default/content/index.vue new file mode 100644 index 0000000..2dc3267 --- /dev/null +++ b/src/layouts/default/content/index.vue @@ -0,0 +1,64 @@ + + + diff --git a/src/layouts/default/content/useContentContext.ts b/src/layouts/default/content/useContentContext.ts new file mode 100644 index 0000000..f12e77b --- /dev/null +++ b/src/layouts/default/content/useContentContext.ts @@ -0,0 +1,17 @@ +import type { InjectionKey, ComputedRef } from 'vue'; +import { createContext, useContext } from '/@/hooks/core/useContext'; + +export interface ContentContextProps { + contentHeight: ComputedRef; + setPageHeight: (height: number) => Promise; +} + +const key: InjectionKey = Symbol(); + +export function createContentContext(context: ContentContextProps) { + return createContext(context, key, { native: true }); +} + +export function useContentContext() { + return useContext(key); +} diff --git a/src/layouts/default/content/useContentViewHeight.ts b/src/layouts/default/content/useContentViewHeight.ts new file mode 100644 index 0000000..b55b7e8 --- /dev/null +++ b/src/layouts/default/content/useContentViewHeight.ts @@ -0,0 +1,42 @@ +import { ref, computed, unref } from 'vue'; +import { createPageContext } from '/@/hooks/component/usePageContext'; +import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; + +const headerHeightRef = ref(0); +const footerHeightRef = ref(0); + +export function useLayoutHeight() { + function setHeaderHeight(val) { + headerHeightRef.value = val; + } + function setFooterHeight(val) { + footerHeightRef.value = val; + } + return { headerHeightRef, footerHeightRef, setHeaderHeight, setFooterHeight }; +} + +export function useContentViewHeight() { + const contentHeight = ref(window.innerHeight); + const pageHeight = ref(window.innerHeight); + const getViewHeight = computed(() => { + return unref(contentHeight) - unref(headerHeightRef) - unref(footerHeightRef) || 0; + }); + + useWindowSizeFn( + () => { + contentHeight.value = window.innerHeight; + }, + 100, + { immediate: true } + ); + + async function setPageHeight(height: number) { + pageHeight.value = height; + } + + createPageContext({ + contentHeight: getViewHeight, + setPageHeight, + pageHeight, + }); +} diff --git a/src/layouts/default/feature/index.vue b/src/layouts/default/feature/index.vue new file mode 100644 index 0000000..99c83cb --- /dev/null +++ b/src/layouts/default/feature/index.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/src/layouts/default/footer/index.vue b/src/layouts/default/footer/index.vue new file mode 100644 index 0000000..084a4b0 --- /dev/null +++ b/src/layouts/default/footer/index.vue @@ -0,0 +1,102 @@ + + + + diff --git a/src/layouts/default/header/MultipleHeader.vue b/src/layouts/default/header/MultipleHeader.vue new file mode 100644 index 0000000..210119b --- /dev/null +++ b/src/layouts/default/header/MultipleHeader.vue @@ -0,0 +1,164 @@ + + + diff --git a/src/layouts/default/header/components/Breadcrumb.vue b/src/layouts/default/header/components/Breadcrumb.vue new file mode 100644 index 0000000..2ec637a --- /dev/null +++ b/src/layouts/default/header/components/Breadcrumb.vue @@ -0,0 +1,222 @@ + + + diff --git a/src/layouts/default/header/components/ErrorAction.vue b/src/layouts/default/header/components/ErrorAction.vue new file mode 100644 index 0000000..ff173ae --- /dev/null +++ b/src/layouts/default/header/components/ErrorAction.vue @@ -0,0 +1,43 @@ + + diff --git a/src/layouts/default/header/components/FullScreen.vue b/src/layouts/default/header/components/FullScreen.vue new file mode 100644 index 0000000..9efbfab --- /dev/null +++ b/src/layouts/default/header/components/FullScreen.vue @@ -0,0 +1,35 @@ + + diff --git a/src/layouts/default/header/components/LockScreen.vue b/src/layouts/default/header/components/LockScreen.vue new file mode 100644 index 0000000..f7efc36 --- /dev/null +++ b/src/layouts/default/header/components/LockScreen.vue @@ -0,0 +1,46 @@ + + diff --git a/src/layouts/default/header/components/index.ts b/src/layouts/default/header/components/index.ts new file mode 100644 index 0000000..1256a19 --- /dev/null +++ b/src/layouts/default/header/components/index.ts @@ -0,0 +1,16 @@ +import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; +import FullScreen from './FullScreen.vue'; + +export const UserDropDown = createAsyncComponent(() => import('./user-dropdown/index.vue'), { + loading: true, +}); + +export const LayoutBreadcrumb = createAsyncComponent(() => import('./Breadcrumb.vue')); + +export const Notify = createAsyncComponent(() => import('./notify/index.vue')); + +export const ErrorAction = createAsyncComponent(() => import('./ErrorAction.vue')); + +export const LockScreen = createAsyncComponent(() => import('./LockScreen.vue')); + +export { FullScreen }; diff --git a/src/layouts/default/header/components/lock/LockModal.vue b/src/layouts/default/header/components/lock/LockModal.vue new file mode 100644 index 0000000..664ab6c --- /dev/null +++ b/src/layouts/default/header/components/lock/LockModal.vue @@ -0,0 +1,124 @@ + + + diff --git a/src/layouts/default/header/components/notify/ChangePasswordModal.vue b/src/layouts/default/header/components/notify/ChangePasswordModal.vue new file mode 100644 index 0000000..f1ccbf5 --- /dev/null +++ b/src/layouts/default/header/components/notify/ChangePasswordModal.vue @@ -0,0 +1,207 @@ + + + + + diff --git a/src/layouts/default/header/components/notify/NoticeList.vue b/src/layouts/default/header/components/notify/NoticeList.vue new file mode 100644 index 0000000..de42306 --- /dev/null +++ b/src/layouts/default/header/components/notify/NoticeList.vue @@ -0,0 +1,232 @@ + + + diff --git a/src/layouts/default/header/components/notify/data.ts b/src/layouts/default/header/components/notify/data.ts new file mode 100644 index 0000000..0570923 --- /dev/null +++ b/src/layouts/default/header/components/notify/data.ts @@ -0,0 +1,206 @@ +export interface ListItem { + id: string; + avatar: string; + // 通知的标题内容 + title: string; + // 是否在标题上显示删除线 + titleDelete?: boolean; + datetime: string; + type: string; + read?: boolean; + description: string; + clickClose?: boolean; + extra?: string; + color?: string; + // 优先级 + priority?: string; +} + +export enum PriorityTypes { + // 低优先级,一般消息 + L = 'L', + // 中优先级,重要消息 + M = 'M', + // 高优先级,紧急消息 + H = 'H', +} + +export interface TabItem { + key: string; + name: string; + list: ListItem[]; + unreadlist?: ListItem[]; + count: number; +} + +export const tabListData: TabItem[] = [ + { + key: '1', + name: '通知', + list: [ + { + id: '000000001', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', + title: '你收到了 14 份新周报', + description: '', + datetime: '2017-08-09', + type: '1', + }, + { + id: '000000002', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png', + title: '你推荐的 曲妮妮 已通过第三轮面试', + description: '', + datetime: '2017-08-08', + type: '1', + }, + { + id: '000000003', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png', + title: '这种模板可以区分多种通知类型', + description: '', + datetime: '2017-08-07', + // read: true, + type: '1', + }, + { + id: '000000004', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: '左侧图标用于区分不同的类型', + description: '', + datetime: '2017-08-07', + type: '1', + }, + { + id: '000000005', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: '标题可以设置自动显示省略号,本例中标题行数已设为1行,如果内容超过1行将自动截断并支持tooltip显示完整标题。', + description: '', + datetime: '2017-08-07', + type: '1', + }, + { + id: '000000006', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: '左侧图标用于区分不同的类型', + description: '', + datetime: '2017-08-07', + type: '1', + }, + { + id: '000000007', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: '左侧图标用于区分不同的类型', + description: '', + datetime: '2017-08-07', + type: '1', + }, + { + id: '000000008', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: '左侧图标用于区分不同的类型', + description: '', + datetime: '2017-08-07', + type: '1', + }, + { + id: '000000009', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: '左侧图标用于区分不同的类型', + description: '', + datetime: '2017-08-07', + type: '1', + }, + { + id: '000000010', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', + title: '左侧图标用于区分不同的类型', + description: '', + datetime: '2017-08-07', + type: '1', + }, + ], + count: 0, + }, + { + key: '2', + name: '系统消息', + list: [ + { + id: '000000006', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', + title: '曲丽丽 评论了你', + description: '描述信息描述信息描述信息', + datetime: '2017-08-07', + type: '2', + clickClose: true, + }, + { + id: '000000007', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', + title: '朱偏右 回复了你', + description: '这种模板用于提醒谁与你发生了互动', + datetime: '2017-08-07', + type: '2', + clickClose: true, + }, + { + id: '000000008', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', + title: '标题', + description: + '请将鼠标移动到此处,以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2,超过2行的描述内容将被省略并且可以通过tooltip查看完整内容', + datetime: '2017-08-07', + type: '2', + clickClose: true, + }, + ], + count: 0, + }, + // { + // key: '3', + // name: '待办', + // list: [ + // { + // id: '000000009', + // avatar: '', + // title: '任务名称', + // description: '任务需要在 2017-01-12 20:00 前启动', + // datetime: '', + // extra: '未开始', + // color: '', + // type: '3', + // }, + // { + // id: '000000010', + // avatar: '', + // title: '第三方紧急代码变更', + // description: '冠霖 需在 2017-01-07 前完成代码变更任务', + // datetime: '', + // extra: '马上到期', + // color: 'red', + // type: '3', + // }, + // { + // id: '000000011', + // avatar: '', + // title: '信息安全考试', + // description: '指派竹尔于 2017-01-09 前完成更新并发布', + // datetime: '', + // extra: '已耗时 8 天', + // color: 'gold', + // type: '3', + // }, + // { + // id: '000000012', + // avatar: '', + // title: 'ABCD 版本发布', + // description: '指派竹尔于 2017-01-09 前完成更新并发布', + // datetime: '', + // extra: '进行中', + // color: 'blue', + // type: '3', + // }, + // ], + // }, +]; diff --git a/src/layouts/default/header/components/notify/index.vue b/src/layouts/default/header/components/notify/index.vue new file mode 100644 index 0000000..154c921 --- /dev/null +++ b/src/layouts/default/header/components/notify/index.vue @@ -0,0 +1,376 @@ + + + diff --git a/src/layouts/default/header/components/notify/index_old.vue b/src/layouts/default/header/components/notify/index_old.vue new file mode 100644 index 0000000..a2e62c2 --- /dev/null +++ b/src/layouts/default/header/components/notify/index_old.vue @@ -0,0 +1,270 @@ + + + diff --git a/src/layouts/default/header/components/notify/notify.api.ts b/src/layouts/default/header/components/notify/notify.api.ts new file mode 100644 index 0000000..7a196ad --- /dev/null +++ b/src/layouts/default/header/components/notify/notify.api.ts @@ -0,0 +1,27 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + listCementByUser = '/sys/annountCement/listByUser', + getUnreadMessageCount = '/sys/annountCement/getUnreadMessageCount', + editCementSend = '/sys/sysAnnouncementSend/editByAnntIdAndUserId', + clearAllUnReadMessage = '/sys/annountCement/clearAllUnReadMessage', +} + +/** + * 获取系统通知消息列表 + * @param params + */ +export const listCementByUser = (params?) => defHttp.get({ url: Api.listCementByUser, params }); + +/** + * 获取用户近两个月未读消息数量 + * @param params + */ +export const getUnreadMessageCount = (params?) => defHttp.get({ url: Api.getUnreadMessageCount, params }); + +export const editCementSend = (anntId, params?) => defHttp.put({ url: Api.editCementSend, params: { anntId, ...params } }); + +/** + * 清空全部未读消息 + */ +export const clearAllUnReadMessage = () => defHttp.post({ url: Api.clearAllUnReadMessage },{ isTransformResponse: false }); diff --git a/src/layouts/default/header/components/user-dropdown/DepartSelect.vue b/src/layouts/default/header/components/user-dropdown/DepartSelect.vue new file mode 100644 index 0000000..40fcf56 --- /dev/null +++ b/src/layouts/default/header/components/user-dropdown/DepartSelect.vue @@ -0,0 +1,285 @@ + + + diff --git a/src/layouts/default/header/components/user-dropdown/DropMenuItem.vue b/src/layouts/default/header/components/user-dropdown/DropMenuItem.vue new file mode 100644 index 0000000..c9b8536 --- /dev/null +++ b/src/layouts/default/header/components/user-dropdown/DropMenuItem.vue @@ -0,0 +1,33 @@ + + diff --git a/src/layouts/default/header/components/user-dropdown/UpdatePassword.vue b/src/layouts/default/header/components/user-dropdown/UpdatePassword.vue new file mode 100644 index 0000000..a755b99 --- /dev/null +++ b/src/layouts/default/header/components/user-dropdown/UpdatePassword.vue @@ -0,0 +1,103 @@ + + diff --git a/src/layouts/default/header/components/user-dropdown/index.vue b/src/layouts/default/header/components/user-dropdown/index.vue new file mode 100644 index 0000000..00ed266 --- /dev/null +++ b/src/layouts/default/header/components/user-dropdown/index.vue @@ -0,0 +1,256 @@ + + + diff --git a/src/layouts/default/header/index.less b/src/layouts/default/header/index.less new file mode 100644 index 0000000..38ccde9 --- /dev/null +++ b/src/layouts/default/header/index.less @@ -0,0 +1,209 @@ +@header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger'; +@header-prefix-cls: ~'@{namespace}-layout-header'; +@breadcrumb-prefix-cls: ~'@{namespace}-layout-breadcrumb'; +@logo-prefix-cls: ~'@{namespace}-app-logo'; + +.@{header-prefix-cls} { + display: flex; + height: @header-height; + padding: 0; + // update-begin--author:liaozhiyang---date:20250818---for:【issues/8709】LayoutContent样式多出1px + // margin-left: -1px; + // update-end--author:liaozhiyang---date:20250818---for:【issues/8709】LayoutContent样式多出1px + line-height: @header-height; + color: @white; + background-color: @white; + align-items: center; + justify-content: space-between; + + &--mobile { + .@{breadcrumb-prefix-cls}, + .error-action, + .notify-item, + .lock-item, + .fullscreen-item { + display: none; + } + + .@{logo-prefix-cls} { + min-width: unset; + padding-right: 0; + + &__title { + display: none; + } + } + + .@{header-trigger-prefix-cls} { + padding: 0 4px 0 8px !important; + } + + .@{header-prefix-cls}-action { + padding-right: 4px; + } + } + + &--fixed { + position: fixed; + top: 0; + left: 0; + z-index: @layout-header-fixed-z-index; + width: 100%; + } + + // 【JEECG作为乾坤子应用】 + &--qiankun-micro { + position: absolute; + } + + &-logo { + height: @header-height; + min-width: 192px; + padding: 0 10px; + font-size: 14px; + + img { + width: @logo-width; + height: @logo-width; + margin-right: 2px; + } + } + + &-left { + display: flex; + height: 100%; + align-items: center; + + .@{header-trigger-prefix-cls} { + display: flex; + height: 100%; + padding: 1px 10px 0 10px; + cursor: pointer; + align-items: center; + + .anticon { + font-size: 22px; + } + + &.light { + &:hover { + background-color: @header-light-bg-hover-color; + } + + svg { + fill: #000; + } + } + + &.dark { + &:hover { + background-color: @header-dark-bg-hover-color; + } + } + } + } + + &-menu { + height: 100%; + min-width: 0; + flex: 1; + align-items: center; + } + + &-action { + display: flex; + min-width: 180px; + // padding-right: 12px; + align-items: center; + + &__item { + display: flex !important; + height: @header-height; + padding: 0 2px; + font-size: 1.2em; + cursor: pointer; + align-items: center; + + .ant-badge { + height: @header-height; + line-height: @header-height; + } + + .ant-badge-dot { + top: 10px; + right: 2px; + } + } + + span[role='img'] { + padding: 0 8px; + } + } + + &--light { + background-color: @white !important; + border-bottom: 1px solid @header-light-bottom-border-color; + border-left: 1px solid @header-light-bottom-border-color; + + .@{header-prefix-cls}-logo { + color: @text-color-base; + + &:hover { + background-color: @header-light-bg-hover-color; + } + } + + .@{header-prefix-cls}-action { + &__item { + color: @text-color-base; + + .app-iconify { + padding: 0 10px; + font-size: 16px !important; + } + + &:hover { + background-color: @header-light-bg-hover-color; + } + } + + &-icon, + span[role='img'] { + color: @text-color-base; + } + } + } + + &--dark { + background-color: @header-dark-bg-color !important; + // border-bottom: 1px solid @border-color-base; + // update-begin--author:liaozhiyang---date:20250818---for:【issues/8709】LayoutContent样式多出1px + // border-left: 1px solid @border-color-base; + // update-end--author:liaozhiyang---date:20250818---for:【issues/8709】LayoutContent样式多出1px + + .@{header-prefix-cls}-logo { + &:hover { + background-color: @header-dark-bg-hover-color; + } + } + + .@{header-prefix-cls}-action { + &__item { + .app-iconify { + padding: 0 10px; + font-size: 16px !important; + } + + .ant-badge { + span { + color: @white; + } + } + + &:hover { + background-color: @header-dark-bg-hover-color; + } + } + } + } +} diff --git a/src/layouts/default/header/index.vue b/src/layouts/default/header/index.vue new file mode 100644 index 0000000..453a692 --- /dev/null +++ b/src/layouts/default/header/index.vue @@ -0,0 +1,258 @@ + + + diff --git a/src/layouts/default/index.vue b/src/layouts/default/index.vue new file mode 100644 index 0000000..4c09370 --- /dev/null +++ b/src/layouts/default/index.vue @@ -0,0 +1,149 @@ + + + + diff --git a/src/layouts/default/menu/index.vue b/src/layouts/default/menu/index.vue new file mode 100644 index 0000000..58121b2 --- /dev/null +++ b/src/layouts/default/menu/index.vue @@ -0,0 +1,203 @@ + + + diff --git a/src/layouts/default/menu/useLayoutMenu.ts b/src/layouts/default/menu/useLayoutMenu.ts new file mode 100644 index 0000000..3200db1 --- /dev/null +++ b/src/layouts/default/menu/useLayoutMenu.ts @@ -0,0 +1,117 @@ +import type { Menu } from '/@/router/types'; +import type { Ref } from 'vue'; +import { watch, unref, ref, computed } from 'vue'; +import { useRouter } from 'vue-router'; +import { MenuSplitTyeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; +import { useThrottleFn } from '@vueuse/core'; +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { getChildrenMenus, getCurrentParentPath, getMenus, getShallowMenus } from '/@/router/menus'; +import { usePermissionStore } from '/@/store/modules/permission'; +import { useAppInject } from '/@/hooks/web/useAppInject'; +import { PAGE_NOT_FOUND_NAME_404 } from '/@/router/constant'; + +export function useSplitMenu(splitType: Ref) { + // Menu array + const menusRef = ref([]); + const { currentRoute } = useRouter(); + const { getIsMobile } = useAppInject(); + const permissionStore = usePermissionStore(); + const { setMenuSetting, getIsHorizontal, getSplit, getMenuType } = useMenuSetting(); + + const throttleHandleSplitLeftMenu = useThrottleFn(handleSplitLeftMenu, 50); + + const splitNotLeft = computed(() => unref(splitType) !== MenuSplitTyeEnum.LEFT && !unref(getIsHorizontal)); + + const getSplitLeft = computed(() => !unref(getSplit) || unref(splitType) !== MenuSplitTyeEnum.LEFT); + + const getSpiltTop = computed(() => unref(splitType) === MenuSplitTyeEnum.TOP); + + const normalType = computed(() => { + return unref(splitType) === MenuSplitTyeEnum.NONE || !unref(getSplit); + }); + + watch( + [() => unref(currentRoute).path, () => unref(splitType)], + async ([path]: [string, MenuSplitTyeEnum]) => { + if (unref(splitNotLeft) || unref(getIsMobile)) return; + const { meta } = unref(currentRoute); + const currentActiveMenu = meta.currentActiveMenu as string; + // 顶部混合模式且顶部左侧组合菜单开始时 + if (unref(getMenuType) === MenuTypeEnum.MIX && unref(getSplit)) { + // 404页面时,跳转到重定向的路径 + if (unref(currentRoute).name === PAGE_NOT_FOUND_NAME_404 && unref(currentRoute)?.redirectedFrom?.path) { + const menus = await getMenus(); + const findItem = menus.find((item:any) => item.redirect === unref(currentRoute).path); + if (findItem) { + // 说明是从一级菜单重定向过来的 + path = findItem.path; + } + } + } + let parentPath = await getCurrentParentPath(path); + if (!parentPath) { + parentPath = await getCurrentParentPath(currentActiveMenu); + } + parentPath && throttleHandleSplitLeftMenu(parentPath); + }, + { + immediate: true, + } + ); + + // Menu changes + watch( + [() => permissionStore.getLastBuildMenuTime, () => permissionStore.getBackMenuList], + () => { + genMenus(); + }, + { + immediate: true, + } + ); + + // split Menu changes + watch( + () => getSplit.value, + () => { + // if (unref(splitNotLeft)) return; + genMenus(); + } + ); + + // Handle left menu split + async function handleSplitLeftMenu(parentPath: string) { + if (unref(getSplitLeft) || unref(getIsMobile)) return; + + // spilt mode left + const children = await getChildrenMenus(parentPath); + + if (!children || !children.length) { + setMenuSetting({ hidden: true }); + menusRef.value = []; + return; + } + + setMenuSetting({ hidden: false }); + menusRef.value = children; + } + + // get menus + async function genMenus() { + // normal mode + if (unref(normalType) || unref(getIsMobile)) { + menusRef.value = await getMenus(); + return; + } + + // split-top + if (unref(getSpiltTop)) { + const shallowMenus = await getShallowMenus(); + + menusRef.value = shallowMenus; + return; + } + } + + return { menusRef }; +} diff --git a/src/layouts/default/setting/SettingDrawer.tsx b/src/layouts/default/setting/SettingDrawer.tsx new file mode 100644 index 0000000..ff43bca --- /dev/null +++ b/src/layouts/default/setting/SettingDrawer.tsx @@ -0,0 +1,367 @@ +import { defineComponent, computed, unref } from 'vue'; +import { BasicDrawer } from '/@/components/Drawer/index'; +import { Divider } from 'ant-design-vue'; +import { TypePicker, ThemeColorPicker, SettingFooter, SwitchItem, SelectItem, InputNumberItem } from './components'; + +import { AppDarkModeToggle } from '/@/components/Application'; + +import { MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum'; + +import { useRootSetting } from '/@/hooks/setting/useRootSetting'; +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; +import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; +import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'; +import { useI18n } from '/@/hooks/web/useI18n'; + +import { layoutHandler } from './handler'; + +import { + HandlerEnum, + contentModeOptions, + topMenuAlignOptions, + getMenuTriggerOptions, + routerTransitionOptions, + menuTypeList, + mixSidebarTriggerOptions, + tabsThemeOptions, +} from './enum'; + +import { HEADER_PRESET_BG_COLOR_LIST, SIDE_BAR_BG_COLOR_LIST, APP_PRESET_COLOR_LIST } from '/@/settings/designSetting'; + +const { t } = useI18n(); + +export default defineComponent({ + name: 'SettingDrawer', + setup(_, { attrs }) { + const { + getContentMode, + getShowFooter, + getShowBreadCrumb, + getShowBreadCrumbIcon, + getShowLogo, + getFullContent, + getColorWeak, + getGrayMode, + getLockTime, + getShowDarkModeToggle, + getThemeColor, + getAiIconShow, + } = useRootSetting(); + + const { getOpenPageLoading, getBasicTransition, getEnableTransition, getOpenNProgress } = useTransitionSetting(); + + const { + getIsHorizontal, + getShowMenu, + getMenuType, + getTrigger, + getCollapsedShowTitle, + getMenuFixed, + getCollapsed, + getCanDrag, + getTopMenuAlign, + getAccordion, + getMenuWidth, + getMenuBgColor, + getIsTopMenu, + getSplit, + getIsMixSidebar, + getCloseMixSidebarOnChange, + getMixSideTrigger, + getMixSideFixed, + } = useMenuSetting(); + + const { getShowHeader, getFixed: getHeaderFixed, getHeaderBgColor, getShowSearch } = useHeaderSetting(); + + const { getShowMultipleTab, getShowQuick, getShowRedo, getShowFold, getTabsTheme } = useMultipleTabSetting(); + + const getShowMenuRef = computed(() => { + return unref(getShowMenu) && !unref(getIsHorizontal); + }); + + const isDev= import.meta.env.DEV + + function renderSidebar() { + return ( + <> + { + layoutHandler(HandlerEnum.CHANGE_LAYOUT, { + mode: item.mode, + type: item.type, + split: unref(getIsHorizontal) ? false : undefined, + }); + }} + def={unref(getMenuType)} + /> + + ); + } + + function renderHeaderTheme() { + return ; + } + + function renderSiderTheme() { + return ; + } + + function renderMainTheme() { + return ; + } + + /** + * @description: + */ + function renderFeatures() { + let triggerDef = unref(getTrigger); + + const triggerOptions = getMenuTriggerOptions(unref(getSplit)); + const some = triggerOptions.some((item) => item.value === triggerDef); + if (!some) { + triggerDef = TriggerEnum.FOOTER; + } + + return ( + <> + + {/**/} + + {/**/} + {/**/} + + {/**/} + {/**/} + + {/**/} + {/**/} + {/**/} + + + + { + isDev && + } + { + isDev && { + return parseInt(value) === 0 ? `0(${t('layout.setting.notAutoScreenLock')})` : `${value}${t('layout.setting.minute')}`; + }} + /> + } + { + isDev && `${parseInt(value)}px`} + /> + } + + ); + } + + function renderContent() { + return ( + <> + { + isDev && + } + { + isDev && + } + + + + {/**/} + + {/**/} + + {/**/} + {/**/} + + {/**/} + + {/**/} + {/**/} + + {/**/} + + + + + + + + ); + } + + function renderTransition() { + return ( + <> + + + + + + + + ); + } + + return () => ( + + {unref(getShowDarkModeToggle) && {() => t('layout.setting.darkMode')}} + {unref(getShowDarkModeToggle) && } + {() => t('layout.setting.navMode')} + {renderSidebar()} + {() => t('layout.setting.sysTheme')} + {renderMainTheme()} + {() => t('layout.setting.headerTheme')} + {renderHeaderTheme()} + {() => t('layout.setting.sidebarTheme')} + {renderSiderTheme()} + {() => t('layout.setting.interfaceFunction')} + {renderFeatures()} + {/*{() => t('layout.setting.interfaceDisplay')}*/} + {renderContent()} + {/*{() => t('layout.setting.animation')}*/} + {/*{renderTransition()}*/} + + + + ); + }, +}); diff --git a/src/layouts/default/setting/components/InputNumberItem.vue b/src/layouts/default/setting/components/InputNumberItem.vue new file mode 100644 index 0000000..237f7d6 --- /dev/null +++ b/src/layouts/default/setting/components/InputNumberItem.vue @@ -0,0 +1,56 @@ + + + diff --git a/src/layouts/default/setting/components/SelectItem.vue b/src/layouts/default/setting/components/SelectItem.vue new file mode 100644 index 0000000..393c78a --- /dev/null +++ b/src/layouts/default/setting/components/SelectItem.vue @@ -0,0 +1,73 @@ + + + diff --git a/src/layouts/default/setting/components/SettingFooter.vue b/src/layouts/default/setting/components/SettingFooter.vue new file mode 100644 index 0000000..54471a9 --- /dev/null +++ b/src/layouts/default/setting/components/SettingFooter.vue @@ -0,0 +1,99 @@ + + + diff --git a/src/layouts/default/setting/components/SwitchItem.vue b/src/layouts/default/setting/components/SwitchItem.vue new file mode 100644 index 0000000..09962f4 --- /dev/null +++ b/src/layouts/default/setting/components/SwitchItem.vue @@ -0,0 +1,71 @@ + + + diff --git a/src/layouts/default/setting/components/ThemeColorPicker.vue b/src/layouts/default/setting/components/ThemeColorPicker.vue new file mode 100644 index 0000000..c014626 --- /dev/null +++ b/src/layouts/default/setting/components/ThemeColorPicker.vue @@ -0,0 +1,110 @@ + + + diff --git a/src/layouts/default/setting/components/TypePicker.vue b/src/layouts/default/setting/components/TypePicker.vue new file mode 100644 index 0000000..ab46aa8 --- /dev/null +++ b/src/layouts/default/setting/components/TypePicker.vue @@ -0,0 +1,178 @@ + + + diff --git a/src/layouts/default/setting/components/index.ts b/src/layouts/default/setting/components/index.ts new file mode 100644 index 0000000..bd24888 --- /dev/null +++ b/src/layouts/default/setting/components/index.ts @@ -0,0 +1,8 @@ +import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; + +export const TypePicker = createAsyncComponent(() => import('./TypePicker.vue')); +export const ThemeColorPicker = createAsyncComponent(() => import('./ThemeColorPicker.vue')); +export const SettingFooter = createAsyncComponent(() => import('./SettingFooter.vue')); +export const SwitchItem = createAsyncComponent(() => import('./SwitchItem.vue')); +export const SelectItem = createAsyncComponent(() => import('./SelectItem.vue')); +export const InputNumberItem = createAsyncComponent(() => import('./InputNumberItem.vue')); diff --git a/src/layouts/default/setting/enum.ts b/src/layouts/default/setting/enum.ts new file mode 100644 index 0000000..8111d8e --- /dev/null +++ b/src/layouts/default/setting/enum.ts @@ -0,0 +1,168 @@ +import { TabsThemeEnum, ContentEnum, RouterTransitionEnum } from '/@/enums/appEnum'; +import { MenuModeEnum, MenuTypeEnum, TopMenuAlignEnum, TriggerEnum, MixSidebarTriggerEnum } from '/@/enums/menuEnum'; + +import { useI18n } from '/@/hooks/web/useI18n'; + +const { t } = useI18n(); + +export enum HandlerEnum { + CHANGE_LAYOUT, + CHANGE_THEME_COLOR, + CHANGE_THEME, + // menu + MENU_HAS_DRAG, + MENU_ACCORDION, + MENU_TRIGGER, + MENU_TOP_ALIGN, + MENU_COLLAPSED, + MENU_COLLAPSED_SHOW_TITLE, + MENU_WIDTH, + MENU_SHOW_SIDEBAR, + MENU_THEME, + MENU_SPLIT, + MENU_FIXED, + MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE, + MENU_TRIGGER_MIX_SIDEBAR, + MENU_FIXED_MIX_SIDEBAR, + + // header + HEADER_SHOW, + HEADER_THEME, + HEADER_FIXED, + + HEADER_SEARCH, + + TABS_SHOW_QUICK, + TABS_SHOW_REDO, + TABS_SHOW, + TABS_SHOW_FOLD, + TABS_THEME, + + LOCK_TIME, + FULL_CONTENT, + CONTENT_MODE, + SHOW_BREADCRUMB, + SHOW_BREADCRUMB_ICON, + GRAY_MODE, + COLOR_WEAK, + SHOW_LOGO, + SHOW_FOOTER, + + ROUTER_TRANSITION, + OPEN_PROGRESS, + OPEN_PAGE_LOADING, + OPEN_ROUTE_TRANSITION, + AI_ICON_SHOW, +} + +// 标签页样式 +export const tabsThemeOptions = [ + { + value: TabsThemeEnum.SMOOTH, + label: t('layout.setting.tabsThemeSmooth'), + }, + { + value: TabsThemeEnum.CARD, + label: t('layout.setting.tabsThemeCard'), + }, + { + value: TabsThemeEnum.SIMPLE, + label: t('layout.setting.tabsThemeSimple'), + }, +]; + +export const contentModeOptions = [ + { + value: ContentEnum.FULL, + label: t('layout.setting.contentModeFull'), + }, + { + value: ContentEnum.FIXED, + label: t('layout.setting.contentModeFixed'), + }, +]; + +export const topMenuAlignOptions = [ + { + value: TopMenuAlignEnum.CENTER, + label: t('layout.setting.topMenuAlignRight'), + }, + { + value: TopMenuAlignEnum.START, + label: t('layout.setting.topMenuAlignLeft'), + }, + { + value: TopMenuAlignEnum.END, + label: t('layout.setting.topMenuAlignCenter'), + }, +]; + +export const getMenuTriggerOptions = (hideTop: boolean) => { + return [ + { + value: TriggerEnum.NONE, + label: t('layout.setting.menuTriggerNone'), + }, + { + value: TriggerEnum.FOOTER, + label: t('layout.setting.menuTriggerBottom'), + }, + ...(hideTop + ? [] + : [ + { + value: TriggerEnum.HEADER, + label: t('layout.setting.menuTriggerTop'), + }, + ]), + ]; +}; + +export const routerTransitionOptions = [ + RouterTransitionEnum.ZOOM_FADE, + RouterTransitionEnum.FADE, + RouterTransitionEnum.ZOOM_OUT, + RouterTransitionEnum.FADE_SIDE, + RouterTransitionEnum.FADE_BOTTOM, + RouterTransitionEnum.FADE_SCALE, +].map((item) => { + return { + label: item, + value: item, + }; +}); + +export const menuTypeList = [ + { + title: t('layout.setting.menuTypeSidebar'), + mode: MenuModeEnum.INLINE, + type: MenuTypeEnum.SIDEBAR, + }, + { + title: t('layout.setting.menuTypeMix'), + mode: MenuModeEnum.INLINE, + type: MenuTypeEnum.MIX, + }, + + { + title: t('layout.setting.menuTypeTopMenu'), + mode: MenuModeEnum.HORIZONTAL, + type: MenuTypeEnum.TOP_MENU, + }, + { + title: t('layout.setting.menuTypeMixSidebar'), + mode: MenuModeEnum.INLINE, + type: MenuTypeEnum.MIX_SIDEBAR, + }, +]; + +export const mixSidebarTriggerOptions = [ + { + value: MixSidebarTriggerEnum.HOVER, + label: t('layout.setting.triggerHover'), + }, + { + value: MixSidebarTriggerEnum.CLICK, + label: t('layout.setting.triggerClick'), + }, +]; diff --git a/src/layouts/default/setting/handler.ts b/src/layouts/default/setting/handler.ts new file mode 100644 index 0000000..e70d0d4 --- /dev/null +++ b/src/layouts/default/setting/handler.ts @@ -0,0 +1,248 @@ +import { HandlerEnum, tabsThemeOptions} from './enum'; +import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground'; +import { updateColorWeak } from '/@/logics/theme/updateColorWeak'; +import { updateGrayMode } from '/@/logics/theme/updateGrayMode'; + +import { useAppStore } from '/@/store/modules/app'; +import { ProjectConfig } from '/#/config'; +import { changeTheme } from '/@/logics/theme'; +import { updateDarkTheme } from '/@/logics/theme/dark'; +import { useRootSetting } from '/@/hooks/setting/useRootSetting'; +import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; +import { getConfigByMenuType } from '../../../utils/getConfigByMenuType'; +import { isObject } from '/@/utils/is'; +import { ThemeEnum } from '/@/enums/appEnum'; +import { APP__THEME__COLOR } from '/@/enums/cacheEnum'; + +/** + * 2024-04-07 + * liaozhiyang + * 切换导航栏模式都走这个方法,每个模式都会有固定的顶部和菜单颜色搭配。暗黑模式则不走固定搭配 + * */ +export function layoutHandler(event: HandlerEnum, value: any) { + const isHTopMenu = isObject(value) && value.type == MenuTypeEnum.TOP_MENU && value.mode == MenuModeEnum.HORIZONTAL; + const isMixMenu = isObject(value) && value.type == MenuTypeEnum.MIX && value.mode == MenuModeEnum.INLINE; + const isMixSidebarMenu = isObject(value) && value.type == MenuTypeEnum.MIX_SIDEBAR && value.mode == MenuModeEnum.INLINE; + const appStore = useAppStore(); + const darkMode = appStore.getDarkMode === ThemeEnum.DARK; + // 根据菜单类型动态获取主题色 + const {themeColor: dynamicThemeColor, headerBgColor, sideBgColor } = getConfigByMenuType(value.type); + if (isHTopMenu) { + baseHandler(event, value); + baseHandler(HandlerEnum.HEADER_THEME, headerBgColor); + baseHandler(HandlerEnum.CHANGE_THEME_COLOR, dynamicThemeColor); + if (darkMode) { + updateHeaderBgColor(); + updateSidebarBgColor(); + } + baseHandler(HandlerEnum.TABS_THEME, tabsThemeOptions[1].value); + } else if (isMixMenu) { + baseHandler(event, value); + baseHandler(HandlerEnum.HEADER_THEME, headerBgColor); + baseHandler(HandlerEnum.MENU_THEME, sideBgColor); + if (darkMode) { + updateHeaderBgColor(); + updateSidebarBgColor(); + } + // 顶部混合导航模式使用动态主题色 + baseHandler(HandlerEnum.CHANGE_THEME_COLOR, dynamicThemeColor); + baseHandler(HandlerEnum.TABS_THEME, tabsThemeOptions[1].value); + } else if (isMixSidebarMenu) { + baseHandler(event, value); + baseHandler(HandlerEnum.CHANGE_THEME_COLOR, dynamicThemeColor); + baseHandler(HandlerEnum.HEADER_THEME, headerBgColor); + baseHandler(HandlerEnum.MENU_THEME, sideBgColor); + if (darkMode) { + updateHeaderBgColor(); + updateSidebarBgColor(); + } + baseHandler(HandlerEnum.TABS_THEME, tabsThemeOptions[1].value); + } else { + baseHandler(event, value); + baseHandler(HandlerEnum.HEADER_THEME, headerBgColor); + baseHandler(HandlerEnum.MENU_THEME, sideBgColor); + if (darkMode) { + updateHeaderBgColor(); + updateSidebarBgColor(); + } + baseHandler(HandlerEnum.CHANGE_THEME_COLOR, dynamicThemeColor); + baseHandler(HandlerEnum.TABS_THEME, tabsThemeOptions[1].value); + } + // 代码逻辑说明: 【QQYUN-13600】默认顶部混合导航模式且启用顶部左侧导航,切换到其他模式时导航刷新后菜单样式混乱 + if (isMixMenu) { + baseHandler(HandlerEnum.MENU_SPLIT, true); + } else { + baseHandler(HandlerEnum.MENU_SPLIT, false); + } +} + +export function baseHandler(event: HandlerEnum, value: any) { + const appStore = useAppStore(); + const config = handler(event, value); + appStore.setProjectConfig(config); + if (event === HandlerEnum.CHANGE_THEME) { + updateHeaderBgColor(); + updateSidebarBgColor(); + } +} + +export function handler(event: HandlerEnum, value: any): DeepPartial { + const appStore = useAppStore(); + + const { getThemeColor, getDarkMode } = useRootSetting(); + switch (event) { + case HandlerEnum.CHANGE_LAYOUT: + const { mode, type, split } = value; + const splitOpt = split === undefined ? { split } : {}; + + return { + menuSetting: { + mode, + type, + collapsed: false, + show: true, + hidden: false, + ...splitOpt, + }, + }; + + case HandlerEnum.CHANGE_THEME_COLOR: + if (getThemeColor.value === value) { + return {}; + } + // 代码逻辑说明: 【QQYUN-8925】系统主题颜色(供页面加载使用) + localStorage.setItem(APP__THEME__COLOR, value); + changeTheme(value); + + return { themeColor: value }; + + case HandlerEnum.CHANGE_THEME: + if (getDarkMode.value === value) { + return {}; + } + updateDarkTheme(value); + + return {}; + + case HandlerEnum.MENU_HAS_DRAG: + return { menuSetting: { canDrag: value } }; + + case HandlerEnum.MENU_ACCORDION: + return { menuSetting: { accordion: value } }; + + case HandlerEnum.MENU_TRIGGER: + return { menuSetting: { trigger: value } }; + + case HandlerEnum.MENU_TOP_ALIGN: + return { menuSetting: { topMenuAlign: value } }; + + case HandlerEnum.MENU_COLLAPSED: + return { menuSetting: { collapsed: value } }; + + case HandlerEnum.MENU_WIDTH: + return { menuSetting: { menuWidth: value } }; + + case HandlerEnum.MENU_SHOW_SIDEBAR: + return { menuSetting: { show: value } }; + + case HandlerEnum.MENU_COLLAPSED_SHOW_TITLE: + return { menuSetting: { collapsedShowTitle: value } }; + + case HandlerEnum.MENU_THEME: + updateSidebarBgColor(value); + return { menuSetting: { bgColor: value } }; + + case HandlerEnum.MENU_SPLIT: + return { menuSetting: { split: value } }; + + case HandlerEnum.MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE: + return { menuSetting: { closeMixSidebarOnChange: value } }; + + case HandlerEnum.MENU_FIXED: + return { menuSetting: { fixed: value } }; + + case HandlerEnum.MENU_TRIGGER_MIX_SIDEBAR: + return { menuSetting: { mixSideTrigger: value } }; + + case HandlerEnum.MENU_FIXED_MIX_SIDEBAR: + return { menuSetting: { mixSideFixed: value } }; + + // ============transition================== + case HandlerEnum.OPEN_PAGE_LOADING: + appStore.setPageLoading(false); + return { transitionSetting: { openPageLoading: value } }; + + case HandlerEnum.ROUTER_TRANSITION: + return { transitionSetting: { basicTransition: value } }; + + case HandlerEnum.OPEN_ROUTE_TRANSITION: + return { transitionSetting: { enable: value } }; + + case HandlerEnum.OPEN_PROGRESS: + return { transitionSetting: { openNProgress: value } }; + // ============root================== + + case HandlerEnum.LOCK_TIME: + return { lockTime: value }; + + case HandlerEnum.FULL_CONTENT: + return { fullContent: value }; + + case HandlerEnum.CONTENT_MODE: + return { contentMode: value }; + + case HandlerEnum.SHOW_BREADCRUMB: + return { showBreadCrumb: value }; + + case HandlerEnum.SHOW_BREADCRUMB_ICON: + return { showBreadCrumbIcon: value }; + + case HandlerEnum.GRAY_MODE: + updateGrayMode(value); + return { grayMode: value }; + + case HandlerEnum.SHOW_FOOTER: + return { showFooter: value }; + + case HandlerEnum.COLOR_WEAK: + updateColorWeak(value); + return { colorWeak: value }; + // 代码逻辑说明: 【QQYUN-10952】AI助手支持通过设置来配置是否显示 + case HandlerEnum.AI_ICON_SHOW: + return { aiIconShow: value }; + case HandlerEnum.SHOW_LOGO: + return { showLogo: value }; + + // ============tabs================== + case HandlerEnum.TABS_SHOW_QUICK: + return { multiTabsSetting: { showQuick: value } }; + + case HandlerEnum.TABS_SHOW: + return { multiTabsSetting: { show: value } }; + + case HandlerEnum.TABS_SHOW_REDO: + return { multiTabsSetting: { showRedo: value } }; + + case HandlerEnum.TABS_SHOW_FOLD: + return { multiTabsSetting: { showFold: value } }; + + case HandlerEnum.TABS_THEME: + return { multiTabsSetting: { theme: value } }; + + // ============header================== + case HandlerEnum.HEADER_THEME: + updateHeaderBgColor(value); + return { headerSetting: { bgColor: value } }; + + case HandlerEnum.HEADER_SEARCH: + return { headerSetting: { showSearch: value } }; + + case HandlerEnum.HEADER_FIXED: + return { headerSetting: { fixed: value } }; + + case HandlerEnum.HEADER_SHOW: + return { headerSetting: { show: value } }; + default: + return {}; + } +} diff --git a/src/layouts/default/setting/index.vue b/src/layouts/default/setting/index.vue new file mode 100644 index 0000000..9c5bb87 --- /dev/null +++ b/src/layouts/default/setting/index.vue @@ -0,0 +1,26 @@ + + diff --git a/src/layouts/default/sider/DragBar.vue b/src/layouts/default/sider/DragBar.vue new file mode 100644 index 0000000..3bc6fb9 --- /dev/null +++ b/src/layouts/default/sider/DragBar.vue @@ -0,0 +1,66 @@ + + + diff --git a/src/layouts/default/sider/LayoutSider.vue b/src/layouts/default/sider/LayoutSider.vue new file mode 100644 index 0000000..d785280 --- /dev/null +++ b/src/layouts/default/sider/LayoutSider.vue @@ -0,0 +1,189 @@ + + + diff --git a/src/layouts/default/sider/MixSider.vue b/src/layouts/default/sider/MixSider.vue new file mode 100644 index 0000000..5f6a36a --- /dev/null +++ b/src/layouts/default/sider/MixSider.vue @@ -0,0 +1,593 @@ + + + diff --git a/src/layouts/default/sider/index.vue b/src/layouts/default/sider/index.vue new file mode 100644 index 0000000..546a4cd --- /dev/null +++ b/src/layouts/default/sider/index.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/layouts/default/sider/useLayoutSider.ts b/src/layouts/default/sider/useLayoutSider.ts new file mode 100644 index 0000000..48f2814 --- /dev/null +++ b/src/layouts/default/sider/useLayoutSider.ts @@ -0,0 +1,133 @@ +import type { Ref } from 'vue'; + +import { computed, unref, onMounted, nextTick, ref } from 'vue'; + +import { TriggerEnum } from '/@/enums/menuEnum'; + +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { useDebounceFn } from '@vueuse/core'; + +/** + * Handle related operations of menu events + */ +export function useSiderEvent() { + const brokenRef = ref(false); + + const { getMiniWidthNumber } = useMenuSetting(); + + const getCollapsedWidth = computed(() => { + return unref(brokenRef) ? 0 : unref(getMiniWidthNumber); + }); + + function onBreakpointChange(broken: boolean) { + brokenRef.value = broken; + } + + return { getCollapsedWidth, onBreakpointChange }; +} + +/** + * Handle related operations of menu folding + */ +export function useTrigger(getIsMobile: Ref) { + const { getTrigger, getSplit } = useMenuSetting(); + + const getShowTrigger = computed(() => { + const trigger = unref(getTrigger); + + return trigger !== TriggerEnum.NONE && !unref(getIsMobile) && (trigger === TriggerEnum.FOOTER || unref(getSplit)); + }); + + const getTriggerAttr = computed(() => { + if (unref(getShowTrigger)) { + return {}; + } + return { + trigger: null, + }; + }); + + return { getTriggerAttr, getShowTrigger }; +} + +/** + * Handle menu drag and drop related operations + * @param siderRef + * @param dragBarRef + */ +export function useDragLine(siderRef: Ref, dragBarRef: Ref, mix = false) { + const { getMiniWidthNumber, getCollapsed, setMenuSetting } = useMenuSetting(); + + onMounted(() => { + nextTick(() => { + const exec = useDebounceFn(changeWrapWidth, 80); + exec(); + }); + }); + + function getEl(elRef: Ref): any { + const el = unref(elRef); + if (!el) return null; + if (Reflect.has(el, '$el')) { + return (unref(elRef) as ComponentRef)?.$el; + } + return unref(elRef); + } + + function handleMouseMove(ele: HTMLElement, wrap: HTMLElement, clientX: number) { + document.onmousemove = function (innerE) { + let iT = (ele as any).left + (innerE.clientX - clientX); + innerE = innerE || window.event; + const maxT = 800; + const minT = unref(getMiniWidthNumber); + iT < 0 && (iT = 0); + iT > maxT && (iT = maxT); + iT < minT && (iT = minT); + ele.style.left = wrap.style.width = iT + 'px'; + return false; + }; + } + + // Drag and drop in the menu area-release the mouse + function removeMouseup(ele: any) { + const wrap = getEl(siderRef); + document.onmouseup = function () { + document.onmousemove = null; + document.onmouseup = null; + wrap.style.transition = 'width 0.2s'; + const width = parseInt(wrap.style.width); + + if (!mix) { + const miniWidth = unref(getMiniWidthNumber); + if (!unref(getCollapsed)) { + width > miniWidth + 20 ? setMenuSetting({ menuWidth: width }) : setMenuSetting({ collapsed: true }); + } else { + width > miniWidth && setMenuSetting({ collapsed: false, menuWidth: width }); + } + } else { + setMenuSetting({ menuWidth: width }); + } + + ele.releaseCapture?.(); + }; + } + + function changeWrapWidth() { + const ele = getEl(dragBarRef); + if (!ele) return; + const wrap = getEl(siderRef); + if (!wrap) return; + + ele.onmousedown = (e: any) => { + wrap.style.transition = 'unset'; + const clientX = e?.clientX; + ele.left = ele.offsetLeft; + handleMouseMove(ele, wrap, clientX); + removeMouseup(ele); + ele.setCapture?.(); + return false; + }; + } + + return {}; +} diff --git a/src/layouts/default/tabs/components/FoldButton.vue b/src/layouts/default/tabs/components/FoldButton.vue new file mode 100644 index 0000000..6ed5b36 --- /dev/null +++ b/src/layouts/default/tabs/components/FoldButton.vue @@ -0,0 +1,40 @@ + + diff --git a/src/layouts/default/tabs/components/TabContent.vue b/src/layouts/default/tabs/components/TabContent.vue new file mode 100644 index 0000000..1c3cc9e --- /dev/null +++ b/src/layouts/default/tabs/components/TabContent.vue @@ -0,0 +1,112 @@ + + + diff --git a/src/layouts/default/tabs/components/TabRedo.vue b/src/layouts/default/tabs/components/TabRedo.vue new file mode 100644 index 0000000..9a122dd --- /dev/null +++ b/src/layouts/default/tabs/components/TabRedo.vue @@ -0,0 +1,32 @@ + + diff --git a/src/layouts/default/tabs/index.less b/src/layouts/default/tabs/index.less new file mode 100644 index 0000000..c9bdec2 --- /dev/null +++ b/src/layouts/default/tabs/index.less @@ -0,0 +1,228 @@ +@prefix-cls: ~'@{namespace}-multiple-tabs'; + +html[data-theme='dark'] { + .@{prefix-cls} { + .ant-tabs-tab { + border-bottom: 1px solid @border-color-base; + } + } +} + +html[data-theme='light'] { + .@{prefix-cls} { + .ant-tabs-tab:not(.ant-tabs-tab-active) { + border: 1px solid #e6e6e6; + } + } +} + +.@{prefix-cls} { + z-index: 10; + height: @multiple-height + 2; + line-height: @multiple-height + 2; + background-color: @component-background; + border-bottom: 1px solid @border-color-base; + box-shadow: 0 4px 4px rgb(0 21 41 / 8%); + + .ant-tabs-small { + height: calc(@multiple-height + 4px); + } + + .ant-tabs.ant-tabs-card { + padding-left: 0px; + + .ant-tabs-nav { + height: calc(@multiple-height); + margin: 0; + background-color: @component-background; + border: 0; + box-shadow: none; + + .ant-tabs-nav-wrap { + height: @multiple-height; + margin-top: 2px; + } + + .ant-tabs-tab { + height: calc(@multiple-height - 4px); + padding-right: 12px; + line-height: calc(@multiple-height - 4px); + color: @text-color-base; + background-color: @component-background; + transition: none; + + .ant-tabs-tab-btn { + color: @text-color-base; + transition: none; + } + + &:hover { + .ant-tabs-tab-remove .anticon-close { + opacity: 1; + } + } + + .ant-tabs-tab-remove { + margin: 0; + padding: 0; + position: relative; + top: 0; + left: 4px; + + .anticon-close { + width: 8px; + height: 12px; + font-size: 12px; + color: inherit; + opacity: 0; + transition: none; + + &:hover { + svg { + width: 0.8em; + } + } + } + } + + > div { + display: flex; + justify-content: center; + align-items: center; + } + + svg { + fill: @text-color-base; + } + } + + .ant-tabs-tab:not(.ant-tabs-tab-active) { + &:hover { + color: @primary-color; + } + } + + .ant-tabs-tab-active { + position: relative; + padding-left: 18px; + color: @white !important; + background: @primary-color; + border: 1px solid transparent; + transition: none; + + .ant-tabs-tab-btn { + color: @white; + } + + .ant-tabs-tab-remove .anticon-close { + opacity: 1; + } + + svg { + width: 0.7em; + fill: @white; + } + } + } + + .ant-tabs-nav > div:nth-child(1) { + padding: 0 6px; + + .ant-tabs-tab { + margin-right: 6px !important; + } + } + } + + .ant-tabs-tab:not(.ant-tabs-tab-active) { + .ant-tabs-tab-remove .anticon-close { + font-size: 12px; + + svg { + width: 0.6em; + } + } + } + + .ant-tabs-extra-content { + // update-begin--author:liaozhiyang---date:20241016---for:【issues/7345】标签样式切换到极简模式样式错乱 + // margin-top: 2px; + // update-end--author:liaozhiyang---date:20241016---for:【issues/7345】标签样式切换到极简模式样式错乱 + line-height: @multiple-height !important; + } + + .ant-dropdown-trigger { + display: inline-flex; + } + + &--hide-close { + .ant-tabs-tab-remove .anticon-close { + opacity: 0 !important; + } + } + + &-content { + &__extra-quick, + &__extra-redo, + &__extra-fold { + display: inline-block; + width: 36px; + height: @multiple-height; + line-height: @multiple-height; + color: @text-color-secondary; + text-align: center; + cursor: pointer; + border-left: 1px solid @border-color-base; + + &:hover { + color: @text-color-base; + } + + span[role='img'] { + transform: rotate(90deg); + } + } + + &__extra-redo { + span[role='img'] { + transform: rotate(0deg); + } + } + + &__info { + display: inline-block; + width: 100%; + height: @multiple-height - 2; + padding-left: 0; + margin-left: -10px; + font-size: 12px; + cursor: pointer; + user-select: none; + } + } +} + +.ant-tabs-dropdown-menu { + &-title-content { + display: flex; + align-items: center; + + .@{prefix-cls} { + &-content__info { + width: auto; + margin-left: 0; + line-height: 28px; + } + } + } + + &-item-remove { + margin-left: auto; + } +} + +.multiple-tabs__dropdown { + .ant-dropdown-content { + width: 172px; + } +} diff --git a/src/layouts/default/tabs/index.vue b/src/layouts/default/tabs/index.vue new file mode 100644 index 0000000..7da3fdb --- /dev/null +++ b/src/layouts/default/tabs/index.vue @@ -0,0 +1,193 @@ + + + + diff --git a/src/layouts/default/tabs/tabs.theme.card.less b/src/layouts/default/tabs/tabs.theme.card.less new file mode 100644 index 0000000..6b87e52 --- /dev/null +++ b/src/layouts/default/tabs/tabs.theme.card.less @@ -0,0 +1,236 @@ +// tabs卡片样式 +@prefix-cls-theme-card: ~'@{prefix-cls}.@{prefix-cls}--theme-card'; + +html[data-theme='dark'] { + .@{prefix-cls-theme-card} { + .ant-tabs-tab { + border-top: none !important; + border-left: none !important; + border-right: none !important; + } + } +} + +html[data-theme='light'] { + .@{prefix-cls-theme-card} { + .ant-tabs-tab:not(.ant-tabs-tab-active) { + border-top: none !important; + border-left: none !important; + border-right: none !important; + } + } +} + +.@{prefix-cls-theme-card} { + @tabHeight: calc(@multiple-card-height - 10px); + + z-index: 10; + height: @multiple-card-height; + line-height: @multiple-card-height; + background-color: @component-background; + box-shadow: 0 1px 4px rgb(0 21 41 / 8%); + + .ant-tabs-small { + height: @multiple-card-height; + } + + .ant-tabs.ant-tabs-card { + .ant-tabs-nav { + height: @multiple-card-height; + margin: 0; + background-color: @component-background; + border: 0; + box-shadow: none; + padding-left: 10px; + + .ant-tabs-nav-wrap { + height: @tabHeight; + margin-top: 4px; + padding-top: 0; + } + + .ant-tabs-tab { + height: @tabHeight; + line-height: @tabHeight; + color: @text-color-base; + background-color: @component-background; + padding: 0 20px 0 30px; + margin: 0 10px 0 0 !important; + + .ant-tabs-tab-btn { + color: @text-color-call-out; + } + + &:hover { + //padding: 0 36px 0 30px; + + .ant-tabs-tab-remove .anticon-close { + opacity: 1; + + &:hover { + color: #fff; + background-color: #c0c4cc; + } + } + } + + .ant-tabs-tab-remove { + //update-begin---author:scott ---date:2023-08-28 for:【QQYUN-6374】UnoCSS替代windicss导致应用样式问题-- + /* top: 5px;*/ + //update-end---author:scott ---date::2023-08-28 for:【QQYUN-6374】UnoCSS替代windicss导致应用样式问题-- + left: 4px; + + .anticon-close { + position: relative; + width: 14px; + height: 14px; + font-size: 13px; + color: inherit; + opacity: 0; + transition: opacity 0.15s; + top: 0; + left: 6px; + vertical-align: middle; + line-height: 10px; + overflow: hidden; + transform-origin: 100% 50%; + border-radius: 100%; + + &:hover { + svg { + fill: #fff; + } + } + } + } + + > div { + display: flex; + justify-content: center; + align-items: center; + } + + svg { + fill: @text-color-base; + } + + &:first-child { + } + } + + .ant-tabs-tab:not(.ant-tabs-tab-active) { + border: none !important; + + &:hover { + color: @primary-color !important; + background-color: inherit; + } + } + + .ant-tabs-tab-active { + position: relative; + color: @primary-color !important; + border: 1px solid transparent; + border-bottom: 1px solid @primary-color !important; + font-weight: inherit; + + .ant-tabs-tab-btn { + color: @primary-color; + } + + .ant-tabs-tab-remove .anticon-close { + opacity: 0; + + svg { + width: 0.6em; + } + } + + svg { + width: inherit; + fill: @primary-color; + } + } + } + + .ant-tabs-nav > div:nth-child(1) { + padding: 0 6px; + + .ant-tabs-tab { + margin-right: 10px !important; + } + } + } + + .ant-tabs-tab:not(.ant-tabs-tab-active) { + .ant-tabs-tab-remove .anticon-close { + font-size: 12px; + + svg { + width: 0.6em; + } + } + } + + .ant-tabs-extra-content { + position: relative; + top: 0; + line-height: @multiple-card-height !important; + } + + .ant-dropdown-trigger { + display: inline-flex; + } + + .@{prefix-cls}--hide-close { + .ant-tabs-tab-remove .anticon-close { + opacity: 0 !important; + } + } + + .@{prefix-cls}-content { + &__extra-quick, + &__extra-redo, + &__extra-fold { + display: inline-block; + width: 36px; + height: @multiple-card-height; + line-height: @multiple-card-height; + color: @text-color-secondary; + text-align: center; + cursor: pointer; + border-left: 1px solid @border-color-base; + + &:hover { + color: @text-color-base; + } + + span[role='img'] { + transform: rotate(90deg); + } + } + + &__extra-redo { + span[role='img'] { + transform: rotate(0deg); + } + } + + &__info { + display: inline-block; + width: 100%; + height: @tabHeight; + padding-left: 0; + font-size: 14px; + cursor: pointer; + user-select: none; + } + + // tab 前缀图标样式 + &__prefix-icon { + & .app-iconify.anticon { + margin-right: 4px; + } + } + } +} diff --git a/src/layouts/default/tabs/tabs.theme.simple.less b/src/layouts/default/tabs/tabs.theme.simple.less new file mode 100644 index 0000000..af98359 --- /dev/null +++ b/src/layouts/default/tabs/tabs.theme.simple.less @@ -0,0 +1,237 @@ +// tabs极简样式 +@prefix-cls-theme-simple: ~'@{prefix-cls}.@{prefix-cls}--theme-simple'; + +@multiple-simple-height: 38px; + +html[data-theme='dark'] { + .@{prefix-cls-theme-simple} { + .ant-tabs-tab { + border: none !important; + } + } +} + +html[data-theme='light'] { + .@{prefix-cls-theme-simple} { + .ant-tabs-tab:not(.ant-tabs-tab-active) { + border: none !important; + } + } +} + +.@{prefix-cls-theme-simple} { + z-index: 10; + height: @multiple-simple-height; + line-height: @multiple-simple-height; + background-color: @component-background; + border-bottom: 1px solid @border-color-base; + box-shadow: none; + + .ant-tabs-small { + height: @multiple-simple-height; + } + + .ant-tabs.ant-tabs-card { + .ant-tabs-nav { + height: @multiple-simple-height; + margin: 0; + background-color: @component-background; + border: 0; + box-shadow: none; + padding-left: 6px; + + .ant-tabs-nav-wrap { + height: @multiple-simple-height; + margin-top: 0; + } + + .ant-tabs-tab { + height: @multiple-simple-height; + line-height: calc(@multiple-simple-height - 6px); + color: @text-color-secondary; + background-color: transparent; + border: none !important; + border-radius: 0; + padding: 0 16px; + margin: 0 !important; + position: relative; + transition: color 0.2s; + + &::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + width: 0; + height: 2px; + background-color: @primary-color; + transition: all 0.2s ease; + transform: translateX(-50%); + } + + .ant-tabs-tab-btn { + color: inherit; + transition: none; + } + + &:hover { + color: @text-color-base; + + .ant-tabs-tab-remove .anticon-close { + opacity: 1; + } + } + + .ant-tabs-tab-remove { + margin: 0; + padding: 0; + position: relative; + top: 0; + left: 2px; + + .anticon-close { + width: 14px; + height: 14px; + font-size: 12px; + color: inherit; + opacity: 0; + transition: opacity 0.15s; + border-radius: 100%; + vertical-align: middle; + line-height: 10px; + overflow: hidden; + + &:hover { + color: #fff; + background-color: #c0c4cc; + + svg { + fill: #fff; + } + } + } + } + + > div { + display: flex; + justify-content: center; + align-items: center; + } + + svg { + fill: @text-color-secondary; + } + } + + .ant-tabs-tab:not(.ant-tabs-tab-active) { + &:hover { + color: @primary-color; + + svg { + fill: @primary-color; + } + } + } + + .ant-tabs-tab-active { + color: @primary-color !important; + background-color: transparent; + border: none !important; + + &::after { + width: 100%; + } + + .ant-tabs-tab-btn { + color: @primary-color; + font-weight: 500; + } + + .ant-tabs-tab-remove .anticon-close { + opacity: 1; + + svg { + width: 0.6em; + } + } + + svg { + fill: @primary-color; + } + } + } + + .ant-tabs-nav > div:nth-child(1) { + padding: 0 2px; + + .ant-tabs-tab { + margin-right: 0 !important; + } + } + } + + .ant-tabs-tab:not(.ant-tabs-tab-active) { + .ant-tabs-tab-remove .anticon-close { + font-size: 12px; + + svg { + width: 0.6em; + } + } + } + + .ant-tabs-extra-content { + position: relative; + top: 0; + line-height: @multiple-simple-height !important; + } + + .ant-dropdown-trigger { + display: inline-flex; + } + + .@{prefix-cls}--hide-close { + .ant-tabs-tab-remove .anticon-close { + opacity: 0 !important; + } + } + + .@{prefix-cls}-content { + &__extra-quick, + &__extra-redo, + &__extra-fold { + display: inline-block; + width: 36px; + height: @multiple-simple-height; + line-height: @multiple-simple-height; + color: @text-color-secondary; + text-align: center; + cursor: pointer; + border-left: 1px solid @border-color-base; + + &:hover { + color: @text-color-base; + } + + span[role='img'] { + transform: rotate(90deg); + } + } + + &__extra-redo { + span[role='img'] { + transform: rotate(0deg); + } + } + + &__info { + display: inline-block; + width: 100%; + height: @multiple-simple-height; + padding-left: 0; + font-size: 13px; + cursor: pointer; + user-select: none; + } + } +} diff --git a/src/layouts/default/tabs/tabs.theme.smooth.less b/src/layouts/default/tabs/tabs.theme.smooth.less new file mode 100644 index 0000000..05a3960 --- /dev/null +++ b/src/layouts/default/tabs/tabs.theme.smooth.less @@ -0,0 +1,233 @@ +// tabs圆滑样式 +@prefix-cls-theme-smooth: ~'@{prefix-cls}.@{prefix-cls}--theme-smooth'; + +html[data-theme='dark'] { + .@{prefix-cls-theme-smooth} { + .ant-tabs-tab { + border: none !important; + } + } +} + +html[data-theme='light'] { + .@{prefix-cls-theme-smooth} { + .ant-tabs-tab:not(.ant-tabs-tab-active) { + border: none !important; + } + } +} + +.@{prefix-cls-theme-smooth} { + @tabHeight: calc(@multiple-smooth-height - 12px); + z-index: 10; + height: @multiple-smooth-height; + line-height: @multiple-smooth-height; + background-color: @component-background; + box-shadow: 0 1px 4px rgb(0 21 41 / 8%); + + .ant-tabs-small { + height: @multiple-smooth-height; + } + + .ant-tabs.ant-tabs-card { + .ant-tabs-nav { + height: @multiple-smooth-height; + margin: 0; + background-color: @component-background; + border: 0; + box-shadow: none; + padding-left: 10px; + + .ant-tabs-nav-wrap { + height: @tabHeight; + margin-top: 12px; + } + + .ant-tabs-tab { + height: @tabHeight; + line-height: @tabHeight; + color: @text-color-base; + background-color: @component-background; + transition: padding 0.3s; + padding: 0 20px 0 26px; + margin: 0 -14px 0 0 !important; + mask: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANoAAAAkBAMAAAAdqzmBAAAAMFBMVEVHcEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlTPQ5AAAAD3RSTlMAr3DvEM8wgCBA379gj5//tJBPAAAAnUlEQVRIx2NgAAM27fj/tAO/xBsYkIHyf9qCT8iWMf6nNQhAsk2f5rYheY7Dnua2/U+A28ZEe8v+F9Ax2v7/F4DbxkUH2wzgtvHTwbYPo7aN2jZq26hto7aN2jZq25Cy7Qvctnw62PYNbls9HWz7S8/G6//PsI6H4396gAUQy1je08W2jxDbpv6nD4gB2uWp+J9eYPsEhv/0BPS1DQBvoBLVZ3BppgAAAABJRU5ErkJggg==); + mask-size: 100% 100%; + position: relative; + z-index: 1; + + .ant-tabs-tab-btn { + color: @text-color-base; + } + + &:hover { + z-index: 2; + padding: 0 20px 0 26px; + + .ant-tabs-tab-remove .anticon-close { + opacity: 1; + + &:hover { + color: #fff; + background-color: #c0c4cc; + } + } + } + + .ant-tabs-tab-remove { + top: -1px; + left: 8px; + + .anticon-close { + position: relative; + width: 14px; + height: 14px; + font-size: 13px; + color: inherit; + opacity: 0; + transition: opacity 0.15s; + vertical-align: middle; + line-height: 10px; + overflow: hidden; + transform-origin: 100% 50%; + border-radius: 100%; + + &:hover { + svg { + fill: #fff; + } + } + } + } + > div { + display: flex; + justify-content: center; + align-items: center; + } + + svg { + fill: @text-color-base; + } + + &:first-child { + padding: 0 30px 0 30px !important; + } + span{font-weight: 200;} + } + + .ant-tabs-tab:not(.ant-tabs-tab-active) { + &:hover { + color: inherit; + background-color: #f5f5f5; + } + } + + .ant-tabs-tab.ant-tabs-tab-active { + position: relative; + padding: 0 20px 0 26px; + color: @primary-color !important; + background: #f5f5f5; + border: 0; + z-index: 3; + + .ant-tabs-tab-btn { + color: @primary-color; + } + + .ant-tabs-tab-remove .anticon-close { + opacity: 1; + + svg { + width: 0.6em; + } + } + + svg { + width: inherit; + fill: @primary-color; + } + } + } + + .ant-tabs-nav > div:nth-child(1) { + padding: 0 6px; + + .ant-tabs-tab { + margin-right: -15px !important; + } + } + } + + .ant-tabs-tab:not(.ant-tabs-tab-active) { + .anticon-close { + font-size: 12px; + + svg { + width: 0.6em; + } + } + } + + .ant-tabs-extra-content { + position: relative; + top: 0; + line-height: @multiple-smooth-height !important; + } + + .ant-dropdown-trigger { + display: inline-flex; + } + + .@{prefix-cls}--hide-close { + .ant-tabs-tab-remove .anticon-close { + opacity: 0 !important; + } + } + + .@{prefix-cls}-content { + &__extra-quick, + &__extra-redo, + &__extra-fold { + display: inline-block; + width: 36px; + height: @multiple-smooth-height; + line-height: @multiple-smooth-height; + color: @text-color-secondary; + text-align: center; + cursor: pointer; + border-left: 1px solid @border-color-base; + + &:hover { + color: @text-color-base; + } + + span[role='img'] { + transform: rotate(90deg); + } + } + + &__extra-redo { + span[role='img'] { + transform: rotate(0deg); + } + } + + &__info { + display: inline-block; + width: 100%; + height: @tabHeight; + line-height: 32px; + padding-left: 0; + font-size: 14px; + cursor: pointer; + user-select: none; + } + + // tab 前缀图标样式 + &__prefix-icon { + & .app-iconify.anticon { + margin-right: 4px; + } + } + } +} diff --git a/src/layouts/default/tabs/types.ts b/src/layouts/default/tabs/types.ts new file mode 100644 index 0000000..72c13f5 --- /dev/null +++ b/src/layouts/default/tabs/types.ts @@ -0,0 +1,26 @@ +import type { DropMenu } from '/@/components/Dropdown/index'; +import type { RouteLocationNormalized } from 'vue-router'; + +export enum TabContentEnum { + TAB_TYPE, + EXTRA_TYPE, +} + +export type { DropMenu }; + +export interface TabContentProps { + tabItem: RouteLocationNormalized; + type?: TabContentEnum; + trigger?: ('click' | 'hover' | 'contextmenu')[]; +} + +export enum MenuEventEnum { + REFRESH_PAGE, + CLOSE_CURRENT, + CLOSE_LEFT, + CLOSE_RIGHT, + CLOSE_OTHER, + CLOSE_ALL, + SCALE, + HOME_DESIGN, +} diff --git a/src/layouts/default/tabs/useHideHomeDesign.ts b/src/layouts/default/tabs/useHideHomeDesign.ts new file mode 100644 index 0000000..9cc2bca --- /dev/null +++ b/src/layouts/default/tabs/useHideHomeDesign.ts @@ -0,0 +1,65 @@ +import { ref } from 'vue'; +import { getMenus } from '/@/router/menus'; + +export const useHideHomeDesign = (currentRoute) => { + let menus: any = []; + // 是否隐藏门户设计 + const isHideHomeDesign = ref(true); + const getHideHomeDesign = (isCurItem, path) => { + if (/^\/portal-view\/[^/]+$/.test(path) && isCurItem) { + if (['/portal-view/system', '/portal-view/template'].includes(path)) { + // 主门户、模板门户 (需要检查是否存在设计列表,存在则显示门户设计,不存在则隐藏门户设计) + getIsHasPortalDesignList(); + } else if (['/portal-view/default'].includes(path)) { + // 设计器打开的预览需隐藏设计模式 + isHideHomeDesign.value = true; + } else { + // 个人工作台或者普通门户都可显示门户设计 + isHideHomeDesign.value = false; + } + } else { + // 非门户页面隐藏门户设计 + isHideHomeDesign.value = true; + } + }; + const getMenusContainPath = async (ptah) => { + if (!menus.length) { + menus = await getMenus(); + } + const result = getMatchingRouterName(menus, ptah); + return !!result; + }; + const getIsHasPortalDesignList = async () => { + if (['/portal-view/system', '/portal-view/template'].includes(currentRoute.value.path)) { + // 主门户、模板门户时才需要查询菜单中是否有portalDesignList + getMenusContainPath('/super/eoa/portalapp/portalDesignList').then((result) => { + isHideHomeDesign.value = !result; + }); + } + }; + getIsHasPortalDesignList(); + return { + getHideHomeDesign, + isHideHomeDesign, + }; +}; + +/* + * 20250701 + * liaozhiyang + * 通过path匹配菜单中的项 + * */ +function getMatchingRouterName(menus, path) { + for (let i = 0, len = menus.length; i < len; i++) { + const item = menus[i]; + if (item.path === path && !item.redirect && !item.paramPath) { + return item; + } else if (item.children?.length) { + const result = getMatchingRouterName(item.children, path); + if (result) { + return result; + } + } + } + return null; +} diff --git a/src/layouts/default/tabs/useMultipleTabs.ts b/src/layouts/default/tabs/useMultipleTabs.ts new file mode 100644 index 0000000..35b553b --- /dev/null +++ b/src/layouts/default/tabs/useMultipleTabs.ts @@ -0,0 +1,78 @@ +import { toRaw, ref, nextTick } from 'vue'; +import type { RouteLocationNormalized } from 'vue-router'; +import { useDesign } from '/@/hooks/web/useDesign'; +import { useSortable } from '/@/hooks/web/useSortable'; +import { useMultipleTabStore } from '/@/store/modules/multipleTab'; +import { isNullAndUnDef } from '/@/utils/is'; +import projectSetting from '/@/settings/projectSetting'; +import { useRouter } from 'vue-router'; + +export function initAffixTabs(): string[] { + const affixList = ref([]); + + const tabStore = useMultipleTabStore(); + const router = useRouter(); + /** + * @description: Filter all fixed routes + */ + function filterAffixTabs(routes: RouteLocationNormalized[]) { + const tabs: RouteLocationNormalized[] = []; + routes && + routes.forEach((route) => { + if (route.meta && route.meta.affix) { + tabs.push(toRaw(route)); + } + }); + return tabs; + } + + /** + * @description: Set fixed tabs + */ + function addAffixTabs(): void { + const affixTabs = filterAffixTabs(router.getRoutes() as unknown as RouteLocationNormalized[]); + affixList.value = affixTabs; + for (const tab of affixTabs) { + tabStore.addTab({ + meta: tab.meta, + name: tab.name, + path: tab.path, + } as unknown as RouteLocationNormalized); + } + } + + let isAddAffix = false; + + if (!isAddAffix) { + addAffixTabs(); + isAddAffix = true; + } + return affixList.value.map((item) => item.meta?.title).filter(Boolean) as string[]; +} + +export function useTabsDrag(affixTextList: string[]) { + const tabStore = useMultipleTabStore(); + const { multiTabsSetting } = projectSetting; + const { prefixCls } = useDesign('multiple-tabs'); + nextTick(() => { + if (!multiTabsSetting.canDrag) return; + const el = document.querySelectorAll(`.${prefixCls} .ant-tabs-nav > div`)?.[0] as HTMLElement; + const { initSortable } = useSortable(el, { + filter: (e: ChangeEvent) => { + const text = e?.target?.innerText; + if (!text) return false; + return affixTextList.includes(text); + }, + onEnd: (evt) => { + const { oldIndex, newIndex } = evt; + + if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) { + return; + } + + tabStore.sortTabs(oldIndex, newIndex); + }, + }); + initSortable(); + }); +} diff --git a/src/layouts/default/tabs/useTabDropdown.ts b/src/layouts/default/tabs/useTabDropdown.ts new file mode 100644 index 0000000..58645ae --- /dev/null +++ b/src/layouts/default/tabs/useTabDropdown.ts @@ -0,0 +1,175 @@ +import type { TabContentProps } from './types'; +import type { DropMenu } from '/@/components/Dropdown'; +import type { ComputedRef } from 'vue'; + +import { computed, unref, reactive } from 'vue'; +import { MenuEventEnum } from './types'; +import { useMultipleTabStore } from '/@/store/modules/multipleTab'; +import { RouteLocationNormalized, useRouter } from 'vue-router'; +import { useTabs } from '/@/hooks/web/useTabs'; +import { useI18n } from '/@/hooks/web/useI18n'; +import { useHideHomeDesign } from './useHideHomeDesign'; + +export function useTabDropdown(tabContentProps: TabContentProps, getIsTabs: ComputedRef) { + const state = reactive({ + current: null as Nullable, + currentIndex: 0, + }); + + const { t } = useI18n(); + const tabStore = useMultipleTabStore(); + const { currentRoute } = useRouter(); + const { refreshPage, closeAll, close, closeLeft, closeOther, closeRight, changeDesign } = useTabs(); + + const getTargetTab = computed((): RouteLocationNormalized => { + return unref(getIsTabs) ? tabContentProps.tabItem : unref(currentRoute); + }); + // 隐藏下拉菜单中的门户设计项 + const { getHideHomeDesign, isHideHomeDesign } = useHideHomeDesign(currentRoute); + + /** + * @description: drop-down list + */ + const getDropMenuList = computed(() => { + if (!unref(getTargetTab)) { + return; + } + const { meta } = unref(getTargetTab); + const { path } = unref(currentRoute); + + // Refresh button + const curItem = state.current; + + const isCurItem = curItem ? curItem.path === path : false; + const index = state.currentIndex; + const refreshDisabled = !isCurItem; + // Close left + const closeLeftDisabled = () => { + if (index === 0) { + return true; + } else { + // 【TV360X-1039】当只有首页和另一个tab页时关闭左侧禁用 + const validTabList = tabStore.getTabList.filter((item) => !item?.meta?.affix); + // update-begin--author:liaozhiyang---date:20251128---for:【issues/9098】tabs标签页关闭异常 + return validTabList[0]?.path === state.current?.path; + // update-end--author:liaozhiyang---date:20251128---for:【issues/9098】tabs标签页关闭异常 + } + }; + // Close other + const closeOtherDisabled = () => { + if (tabStore.getTabList.length === 1) { + return true; + } else { + // 【TV360X-1039】当只有首页和另一个tab页时关闭其它禁用 + const validTabList = tabStore.getTabList.filter((item) => !item?.meta?.affix); + return validTabList.length == 1; + } + }; + + // Close right + const closeRightDisabled = index === tabStore.getTabList.length - 1 && tabStore.getLastDragEndIndex >= 0; + // 隐藏下拉菜单中的门户设计项 + getHideHomeDesign(isCurItem, path); + const dropMenuList: DropMenu[] = [ + { + icon: 'jam:refresh-reverse', + event: MenuEventEnum.REFRESH_PAGE, + text: t('layout.multipleTab.reload'), + disabled: refreshDisabled, + }, + { + icon: 'ant-design:setting-outlined', + event: MenuEventEnum.HOME_DESIGN, + text: t('layout.multipleTab.homeDesign'), + disabled: !/^\/portal-view\/[^/]+$/.test(path), + hide: isHideHomeDesign.value, + divider: true, + }, + // { + // icon: 'ic:twotone-close', + // event: MenuEventEnum.CLOSE_CURRENT, + // text: t('layout.multipleTab.close'), + // disabled: !!meta?.affix || disabled, + // divider: true, + // }, + { + icon: 'mdi:arrow-left', + event: MenuEventEnum.CLOSE_LEFT, + text: t('layout.multipleTab.closeLeft'), + // 代码逻辑说明: 【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用 + disabled: closeLeftDisabled(), + divider: false, + }, + { + icon: 'mdi:arrow-right', + event: MenuEventEnum.CLOSE_RIGHT, + text: t('layout.multipleTab.closeRight'), + disabled: closeRightDisabled, + divider: true, + }, + { + icon: 'material-symbols:arrows-outward', + event: MenuEventEnum.CLOSE_OTHER, + text: t('layout.multipleTab.closeOther'), + // 代码逻辑说明: 【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用 + disabled: closeOtherDisabled(), + }, + // { + // icon: 'clarity:minus-line', + // event: MenuEventEnum.CLOSE_ALL, + // text: t('layout.multipleTab.closeAll'), + // disabled: disabled, + // }, + ]; + + return dropMenuList; + }); + + function handleContextMenu(tabItem: RouteLocationNormalized) { + return (e: Event) => { + if (!tabItem) { + return; + } + e?.preventDefault(); + const index = tabStore.getTabList.findIndex((tab) => tab.path === tabItem.path); + state.current = tabItem; + state.currentIndex = index; + }; + } + + // Handle right click event + function handleMenuEvent(menu: DropMenu): void { + const { event } = menu; + switch (event) { + case MenuEventEnum.REFRESH_PAGE: + // refresh page + refreshPage(); + break; + // Close current + case MenuEventEnum.CLOSE_CURRENT: + close(tabContentProps.tabItem); + break; + // Close left + case MenuEventEnum.CLOSE_LEFT: + closeLeft(state.current); + break; + // Close right + case MenuEventEnum.CLOSE_RIGHT: + closeRight(state.current); + break; + // Close other + case MenuEventEnum.CLOSE_OTHER: + closeOther(state.current); + break; + // Close all + case MenuEventEnum.CLOSE_ALL: + closeAll(state.current); + break; + // Close all + case MenuEventEnum.HOME_DESIGN: + changeDesign(); + break; + } + } + return { getDropMenuList, handleMenuEvent, handleContextMenu }; +} diff --git a/src/layouts/default/trigger/HeaderTrigger.vue b/src/layouts/default/trigger/HeaderTrigger.vue new file mode 100644 index 0000000..33077ba --- /dev/null +++ b/src/layouts/default/trigger/HeaderTrigger.vue @@ -0,0 +1,23 @@ + + diff --git a/src/layouts/default/trigger/SiderTrigger.vue b/src/layouts/default/trigger/SiderTrigger.vue new file mode 100644 index 0000000..0eb38b5 --- /dev/null +++ b/src/layouts/default/trigger/SiderTrigger.vue @@ -0,0 +1,21 @@ + + diff --git a/src/layouts/default/trigger/index.vue b/src/layouts/default/trigger/index.vue new file mode 100644 index 0000000..61f43b3 --- /dev/null +++ b/src/layouts/default/trigger/index.vue @@ -0,0 +1,22 @@ + + diff --git a/src/layouts/iframe/index.vue b/src/layouts/iframe/index.vue new file mode 100644 index 0000000..09900cc --- /dev/null +++ b/src/layouts/iframe/index.vue @@ -0,0 +1,25 @@ + + diff --git a/src/layouts/iframe/useFrameKeepAlive.ts b/src/layouts/iframe/useFrameKeepAlive.ts new file mode 100644 index 0000000..e84c49f --- /dev/null +++ b/src/layouts/iframe/useFrameKeepAlive.ts @@ -0,0 +1,59 @@ +import type { AppRouteRecordRaw } from '/@/router/types'; + +import { computed, toRaw, unref } from 'vue'; + +import { useMultipleTabStore } from '/@/store/modules/multipleTab'; + +import { uniqBy } from 'lodash-es'; + +import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; + +import { useRouter } from 'vue-router'; + +export function useFrameKeepAlive() { + const router = useRouter(); + const { currentRoute } = router; + const { getShowMultipleTab } = useMultipleTabSetting(); + const tabStore = useMultipleTabStore(); + const getFramePages = computed(() => { + const ret = getAllFramePages(toRaw(router.getRoutes()) as unknown as AppRouteRecordRaw[]) || []; + return ret; + }); + + const getOpenTabList = computed((): string[] => { + return tabStore.getTabList.reduce((prev: string[], next) => { + if (next.meta && Reflect.has(next.meta, 'frameSrc')) { + prev.push(next.name as string); + } + return prev; + }, []); + }); + + function getAllFramePages(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] { + let res: AppRouteRecordRaw[] = []; + for (const route of routes) { + const { meta: { frameSrc } = {}, children } = route; + if (frameSrc) { + res.push(route); + } + if (children && children.length) { + res.push(...getAllFramePages(children)); + } + } + res = uniqBy(res, 'name'); + return res; + } + + function showIframe(item: AppRouteRecordRaw) { + return item.name === unref(currentRoute).name; + } + + function hasRenderFrame(name: string) { + if (!unref(getShowMultipleTab)) { + return router.currentRoute.value.name === name; + } + return unref(getOpenTabList).includes(name); + } + + return { hasRenderFrame, getFramePages, showIframe, getAllFramePages }; +} diff --git a/src/layouts/page/components/EmptyPage.vue b/src/layouts/page/components/EmptyPage.vue new file mode 100644 index 0000000..bb30f2d --- /dev/null +++ b/src/layouts/page/components/EmptyPage.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/src/layouts/page/index.vue b/src/layouts/page/index.vue new file mode 100644 index 0000000..3465ed5 --- /dev/null +++ b/src/layouts/page/index.vue @@ -0,0 +1,74 @@ + + + diff --git a/src/layouts/page/transition.ts b/src/layouts/page/transition.ts new file mode 100644 index 0000000..9e93009 --- /dev/null +++ b/src/layouts/page/transition.ts @@ -0,0 +1,33 @@ +import type { FunctionalComponent } from 'vue'; +import type { RouteLocation } from 'vue-router'; + +export interface DefaultContext { + Component: FunctionalComponent & { type: Recordable }; + route: RouteLocation; +} + +export function getTransitionName({ + route, + openCache, + cacheTabs, + enableTransition, + def, +}: Pick & { + enableTransition: boolean; + openCache: boolean; + def: string; + cacheTabs: string[]; +}): string | undefined { + if (!enableTransition) { + return undefined; + } + + const isInCache = cacheTabs.includes(route.name as string); + const transitionName = 'fade-slide'; + let name: string | undefined = transitionName; + + if (openCache) { + name = isInCache && route.meta.loaded ? transitionName : undefined; + } + return name || (route.meta.transitionName as string) || def; +} diff --git a/src/layouts/page/useEmpty.ts b/src/layouts/page/useEmpty.ts new file mode 100644 index 0000000..41e7d4c --- /dev/null +++ b/src/layouts/page/useEmpty.ts @@ -0,0 +1,87 @@ +import { computed, unref, ref, watch } from 'vue'; +import { getMenus } from '/@/router/menus'; +import { useRoute } from 'vue-router'; +import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { useRootSetting } from '/@/hooks/setting/useRootSetting'; +import { lighten, darken } from '/@/utils/color'; +export const useEmpty = () => { + const { getThemeColor, getDarkMode } = useRootSetting(); + const route = useRoute(); + const { getHeaderBgColor } = useHeaderSetting(); + const { getMenuBgColor } = useMenuSetting(); + const pageTip = ref(''); + const effectVars = computed(() => { + const primary = unref(getThemeColor) || '#1890ff'; + const menuBg = unref(getMenuBgColor) || '#ffffff'; + const headerBg = unref(getHeaderBgColor); + const isDark = unref(getDarkMode) === 'dark'; + // 以主题色为基色,派生三组渐变色 + const a1 = lighten(primary, 25); + const a2 = primary; + const b1 = lighten(headerBg, 45); + const b2 = lighten(headerBg, 10); + const c1 = lighten(menuBg, 35); + const c2 = darken(primary, 5); + const bg1 = isDark ? '#0f172a' : '#f7f8fa'; + const bg2 = isDark ? '#111827' : '#f2f5f9'; + const grid = isDark ? 'rgba(255,255,255,0.04)' : 'rgba(60,70,90,0.06)'; + const tipColor = isDark ? '#626262' : '#b9b9b9'; + const tipBg = isDark ? 'rgba(17,24,39,0.6)' : 'rgba(255,255,255,0.6)'; + const tipBorder = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)'; + return { + '--blob-a-1': a1, + '--blob-a-2': a2, + '--blob-b-1': b1, + '--blob-b-2': b2, + '--blob-c-1': c1, + '--blob-c-2': c2, + '--bg-1': bg1, + '--bg-2': bg2, + '--grid-color': grid, + '--tip-color': tipColor, + '--tip-bg': tipBg, + '--tip-border': tipBorder, + } as Record; + }); + + const getPageTip = async (route) => { + const menus = await getMenus(); + const menu = getMatchingPath(menus, route.path); + if (menu) { + if (['/layouts/default/index'].includes(menu.originComponent)) { + pageTip.value = '点击子菜单跳转到对应外部链接!'; + } else { + pageTip.value = '查看组件引用是否正确'; + } + } + }; + watch( + route, + () => { + getPageTip({ path: window.location.pathname }); + }, + { immediate: true } + ); + + function getMatchingPath(menus, path) { + for (let i = 0, len = menus.length; i < len; i++) { + const item = menus[i]; + if (item.path === path) { + return item; + } else if (item.children?.length) { + const result = getMatchingPath(item.children, path); + if (result) { + return result; + } + } + } + return null; + } + + return { + pageTip, + getPageTip, + effectVars, + }; +}; diff --git a/src/locales/helper.ts b/src/locales/helper.ts new file mode 100644 index 0000000..4f78439 --- /dev/null +++ b/src/locales/helper.ts @@ -0,0 +1,37 @@ +import type { LocaleType } from '/#/config'; + +import { set } from 'lodash-es'; + +export const loadLocalePool: LocaleType[] = []; + +export function setHtmlPageLang(locale: LocaleType) { + document.querySelector('html')?.setAttribute('lang', locale); +} + +export function setLoadLocalePool(cb: (loadLocalePool: LocaleType[]) => void) { + cb(loadLocalePool); +} + +export function genMessage(langs: Record>, prefix = 'lang') { + const obj: Recordable = {}; + + Object.keys(langs).forEach((key) => { + const langFileModule = langs[key].default; + let fileName = key.replace(`./${prefix}/`, '').replace(/^\.\//, ''); + const lastIndex = fileName.lastIndexOf('.'); + fileName = fileName.substring(0, lastIndex); + const keyList = fileName.split('/'); + const moduleName = keyList.shift(); + const objKey = keyList.join('.'); + + if (moduleName) { + if (objKey) { + set(obj, moduleName, obj[moduleName] || {}); + set(obj[moduleName], objKey, langFileModule); + } else { + set(obj, moduleName, langFileModule || {}); + } + } + }); + return obj; +} diff --git a/src/locales/lang/en.ts b/src/locales/lang/en.ts new file mode 100644 index 0000000..57d8aad --- /dev/null +++ b/src/locales/lang/en.ts @@ -0,0 +1,13 @@ +import { genMessage } from '../helper'; +import antdLocale from 'ant-design-vue/es/locale/en_US'; +//import momentLocale from 'moment/dist/locale/eu'; + +const modules = import.meta.glob('./en/**/*.ts', { eager: true }); +export default { + message: { + ...genMessage(modules as Recordable, 'en'), + antdLocale, + }, + dateLocale: null, + dateLocaleName: 'en', +}; diff --git a/src/locales/lang/en/common.ts b/src/locales/lang/en/common.ts new file mode 100644 index 0000000..f7cdce0 --- /dev/null +++ b/src/locales/lang/en/common.ts @@ -0,0 +1,20 @@ +export default { + okText: 'OK', + closeText: 'Close', + cancelText: 'Cancel', + loadingText: 'Loading...', + saveText: 'Save', + delText: 'Delete', + resetText: 'Reset', + searchText: 'Search', + queryText: 'Search', + + inputText: 'Please enter', + chooseText: 'Please choose', + + redo: 'Refresh', + back: 'Back', + + light: 'Light', + dark: 'Dark', +}; diff --git a/src/locales/lang/en/component.ts b/src/locales/lang/en/component.ts new file mode 100644 index 0000000..b93dbd5 --- /dev/null +++ b/src/locales/lang/en/component.ts @@ -0,0 +1,129 @@ +export default { + app: { + searchNotData: 'No search results yet', + toSearch: 'to search', + toNavigate: 'to navigate', + }, + countdown: { + normalText: 'Get SMS code', + sendText: 'Reacquire in {0}s', + }, + cropper: { + selectImage: 'Select Image', + uploadSuccess: 'Uploaded success!', + modalTitle: 'Avatar upload', + okText: 'Confirm and upload', + btn_reset: 'Reset', + btn_rotate_left: 'Counterclockwise rotation', + btn_rotate_right: 'Clockwise rotation', + btn_scale_x: 'Flip horizontal', + btn_scale_y: 'Flip vertical', + btn_zoom_in: 'Zoom in', + btn_zoom_out: 'Zoom out', + preview: 'Preivew', + }, + drawer: { + loadingText: 'Loading...', + cancelText: 'Close', + okText: 'Confirm', + }, + excel: { + exportModalTitle: 'Export data', + fileType: 'File type', + fileName: 'File name', + }, + form: { + putAway: 'Put away', + unfold: 'Unfold', + maxTip: 'The number of characters should be less than {0}', + apiSelectNotFound: 'Wait for data loading to complete...', + }, + icon: { + placeholder: 'Click the select icon', + search: 'Search icon', + copy: 'Copy icon successfully!', + }, + menu: { + search: 'Menu search', + }, + modal: { + cancelText: 'Close', + okText: 'Confirm', + close: 'Close', + maximize: 'Maximize', + restore: 'Restore', + }, + table: { + settingDens: 'Density', + settingDensDefault: 'Default', + settingDensMiddle: 'Middle', + settingDensSmall: 'Compact', + settingColumn: 'Column settings', + settingColumnShow: 'Column display', + settingIndexColumnShow: 'Index Column', + settingSelectColumnShow: 'Selection Column', + settingFixedLeft: 'Fixed Left', + settingFixedRight: 'Fixed Right', + settingFullScreen: 'Full Screen', + index: 'Index', + total: 'total of {total}', + }, + time: { + before: ' ago', + after: ' after', + just: 'just now', + seconds: ' seconds', + minutes: ' minutes', + hours: ' hours', + days: ' days', + }, + tree: { + selectAll: 'Select All', + unSelectAll: 'Cancel Select', + expandAll: 'Expand All', + unExpandAll: 'Collapse all', + + checkStrictly: 'Hierarchical association', + checkUnStrictly: 'Hierarchical independence', + }, + upload: { + save: 'Save', + upload: 'Upload', + imgUpload: 'ImageUpload', + uploaded: 'Uploaded', + + operating: 'Operating', + del: 'Delete', + download: 'download', + saveWarn: 'Please wait for the file to upload and save!', + saveError: 'There is no file successfully uploaded and cannot be saved!', + + preview: 'Preview', + choose: 'Select the file', + + accept: 'Support {0} format', + acceptUpload: 'Only upload files in {0} format', + maxSize: 'A single file does not exceed {0}MB ', + maxSizeMultiple: 'Only upload files up to {0}MB!', + maxNumber: 'Only upload up to {0} files', + + legend: 'Legend', + fileName: 'File name', + fileSize: 'File size', + fileStatue: 'File status', + + startUpload: 'Start upload', + uploadSuccess: 'Upload successfully', + uploadError: 'Upload failed', + uploading: 'Uploading', + uploadWait: 'Please wait for the file upload to finish', + reUploadFailed: 'Re-upload failed files', + }, + verify: { + error: 'verification failed!', + time: 'The verification is successful and it takes {time} seconds!', + redoTip: 'Click the picture to refresh', + dragText: 'Hold down the slider and drag', + successText: 'Verified', + }, +}; diff --git a/src/locales/lang/en/layout.ts b/src/locales/lang/en/layout.ts new file mode 100644 index 0000000..971b10d --- /dev/null +++ b/src/locales/lang/en/layout.ts @@ -0,0 +1,136 @@ +export default { + footer: { onlinePreview: 'Preview', onlineDocument: 'Document' }, + header: { + // user dropdown + dropdownItemDoc: 'Document', + dropdownItemLoginOut: 'Login Out', + dropdownItemSwitchPassword: 'Password Change', + dropdownItemSwitchDepart: 'Switch Department', + dropdownItemRefreshCache: 'Clean cache', + dropdownItemSwitchAccount: 'Account Setting', + dropdownItemSwitchDefaultHomePage: 'Switch Home Page', + dropdownItemSwitchDefaultWeb: 'Web Download', + dropdownItemSwitchDefaultAndroid: 'Android Apk Download', + + tooltipErrorLog: 'Error log', + tooltipLock: 'Lock screen', + tooltipNotify: 'Notification', + + tooltipEntryFull: 'Full Screen', + tooltipExitFull: 'Exit Full Screen', + + // lock + lockScreenPassword: 'Password', + lockScreen: 'Lock screen', + lockScreenBtn: 'Locking', + + home: 'Home', + welcomeIn: 'Welcome in', + refreshCacheComplete: 'Refresh cache complete', + refreshCacheFailure: 'Refresh cache failure', + }, + multipleTab: { + reload: 'Refresh current', + close: 'Close current', + closeLeft: 'Close Left', + closeRight: 'Close Right', + closeOther: 'Close Other', + closeAll: 'Close All', + homeDesign: 'Home Design', + }, + setting: { + // content mode + contentModeFull: 'Full', + contentModeFixed: 'Fixed width', + // topMenu align + topMenuAlignLeft: 'Left', + topMenuAlignRight: 'Center', + topMenuAlignCenter: 'Right', + // menu trigger + menuTriggerNone: 'Not Show', + menuTriggerBottom: 'Bottom', + menuTriggerTop: 'Top', + // menu type + menuTypeSidebar: 'Left menu mode', + menuTypeMixSidebar: 'Left menu mixed mode', + menuTypeMix: 'Top Menu Mix mode', + menuTypeTopMenu: 'Top menu mode', + + on: 'On', + off: 'Off', + minute: 'Minute', + + operatingTitle: 'Successful!', + operatingContent: 'The copy is successful, please go to src/settings/projectSetting.ts to modify the configuration!', + resetSuccess: 'Successfully reset!', + + copyBtn: 'Copy', + clearBtn: 'Clear cache and to the login page', + + drawerTitle: 'Configuration', + + darkMode: 'Dark mode', + navMode: 'Navigation mode', + interfaceFunction: 'Interface function', + interfaceDisplay: 'Interface display', + animation: 'Animation', + splitMenu: 'Split menu', + closeMixSidebarOnChange: 'Switch page to close menu', + + sysTheme: 'System theme', + headerTheme: 'Header theme', + sidebarTheme: 'Menu theme', + + menuDrag: 'Drag Sidebar', + menuSearch: 'Menu search', + menuAccordion: 'Sidebar accordion', + menuCollapse: 'Collapse menu', + collapseMenuDisplayName: 'Collapse menu display name', + topMenuLayout: 'Top menu layout', + menuCollapseButton: 'Menu collapse button', + contentMode: 'Content area width', + expandedMenuWidth: 'Expanded menu width', + + breadcrumb: 'Breadcrumbs', + breadcrumbIcon: 'Breadcrumbs Icon', + tabs: 'Tabs', + tabDetail: 'Tab Detail', + tabsQuickBtn: 'Tabs quick button', + tabsRedoBtn: 'Tabs redo button', + tabsFoldBtn: 'Tabs flod button', + tabsTheme: 'tabs theme', + tabsThemeSmooth: 'Smooth', + tabsThemeCard: 'Card', + tabsThemeSimple: 'Simple', + sidebar: 'Sidebar', + header: 'Header', + footer: 'Footer', + fullContent: 'Full content', + grayMode: 'Gray mode', + colorWeak: 'Color Weak Mode', + + progress: 'Progress', + switchLoading: 'Switch Loading', + switchAnimation: 'Switch animation', + animationType: 'Animation type', + + autoScreenLock: 'Auto screen lock', + notAutoScreenLock: 'Not auto lock', + + fixedHeader: 'Fixed header', + fixedSideBar: 'Fixed Sidebar', + + mixSidebarTrigger: 'Mixed menu Trigger', + triggerHover: 'Hover', + triggerClick: 'Click', + + mixSidebarFixed: 'Fixed expanded menu', + }, + changePassword: { + changePassword: 'Change password', + oldPassword: 'Old password', + newPassword: 'New password', + confirmNewPassword: 'Confirm new password', + pleaseEnterNewPassword: 'Please enter new password', + }, +}; diff --git a/src/locales/lang/en/routes/basic.ts b/src/locales/lang/en/routes/basic.ts new file mode 100644 index 0000000..15d9141 --- /dev/null +++ b/src/locales/lang/en/routes/basic.ts @@ -0,0 +1,5 @@ +export default { + login: 'Login', + errorLogList: 'Error Log', + defaultHomePage: 'Default Home Page', +}; diff --git a/src/locales/lang/en/routes/dashboard.ts b/src/locales/lang/en/routes/dashboard.ts new file mode 100644 index 0000000..6d047b5 --- /dev/null +++ b/src/locales/lang/en/routes/dashboard.ts @@ -0,0 +1,6 @@ +export default { + dashboard: 'Dashboard', + about: 'About', + workbench: 'Workbench', + analysis: 'Analysis', +}; diff --git a/src/locales/lang/en/routes/demo.ts b/src/locales/lang/en/routes/demo.ts new file mode 100644 index 0000000..b299192 --- /dev/null +++ b/src/locales/lang/en/routes/demo.ts @@ -0,0 +1,199 @@ +export default { + charts: { + baiduMap: 'Baidu map', + aMap: 'A map', + googleMap: 'Google map', + charts: 'Chart', + map: 'Map', + line: 'Line', + pie: 'Pie', + }, + comp: { + comp: 'Component', + basic: 'Basic', + transition: 'Animation', + countTo: 'Count To', + + scroll: 'Scroll', + scrollBasic: 'Basic', + scrollAction: 'Scroll Function', + virtualScroll: 'Virtual Scroll', + + tree: 'Tree', + + treeBasic: 'Basic', + editTree: 'Searchable/toolbar', + actionTree: 'Function operation', + + modal: 'Modal', + drawer: 'Drawer', + desc: 'Desc', + + lazy: 'Lazy', + lazyBasic: 'Basic', + lazyTransition: 'Animation', + + verify: 'Verify', + verifyDrag: 'Drag ', + verifyRotate: 'Picture Restore', + + qrcode: 'QR code', + strength: 'Password strength', + upload: 'Upload', + + loading: 'Loading', + + time: 'Relative Time', + cropperImage: 'Cropper Image', + cardList: 'Card List', + }, + editor: { + editor: 'Editor', + jsonEditor: 'Json editor', + markdown: 'Markdown editor', + + tinymce: 'Rich text', + tinymceBasic: 'Basic', + tinymceForm: 'embedded form', + }, + excel: { + excel: 'Excel', + customExport: 'Select export format', + jsonExport: 'JSON data export', + arrayExport: 'Array data export', + importExcel: 'Import', + }, + feat: { + feat: 'Page Function', + icon: 'Icon', + tabs: 'Tabs', + tabDetail: 'Tab Detail', + sessionTimeout: 'Session Timeout', + print: 'Print', + contextMenu: 'Context Menu', + download: 'Download', + clickOutSide: 'ClickOutSide', + imgPreview: 'Picture Preview', + copy: 'Clipboard', + msg: 'Message prompt', + watermark: 'Watermark', + ripple: 'Ripple', + fullScreen: 'Full Screen', + errorLog: 'Error Log', + tab: 'Tab with parameters', + tab1: 'Tab with parameter 1', + tab2: 'Tab with parameter 2', + menu: 'Menu with parameters', + menu1: 'Menu with parameters 1', + menu2: 'Menu with parameters 2', + + ws: 'Websocket test', + + breadcrumb: 'Breadcrumbs', + breadcrumbFlat: 'Flat Mode', + breadcrumbFlatDetail: 'Flat mode details', + + breadcrumbChildren: 'Level mode', + breadcrumbChildrenDetail: 'Level mode detail', + }, + flow: { + name: 'Graphics editor', + flowChart: 'FlowChart', + }, + form: { + form: 'Form', + basic: 'Basic', + useForm: 'useForm', + refForm: 'RefForm', + advancedForm: 'Shrinkable', + ruleForm: 'Form validation', + dynamicForm: 'Dynamic', + customerForm: 'Custom', + appendForm: 'Append', + }, + iframe: { + frame: 'External', + antv: 'antVue doc (embedded)', + doc: 'Project doc (embedded)', + docExternal: 'Project doc (external)', + }, + level: { level: 'MultiMenu' }, + page: { + page: 'Page', + + form: 'Form', + formBasic: 'Basic Form', + formStep: 'Step Form', + formHigh: 'Advanced Form', + + desc: 'Details', + descBasic: 'Basic Details', + descHigh: 'Advanced Details', + + result: 'Result', + resultSuccess: 'Success', + resultFail: 'Failed', + + account: 'Personal', + accountCenter: 'Personal Center', + accountSetting: 'Personal Settings', + + exception: 'Exception', + netWorkError: 'Network Error', + notData: 'No data', + + list: 'List page', + listCard: 'Card list', + basic: 'Basic list', + listBasic: 'Basic list', + listSearch: 'Search list', + }, + permission: { + permission: 'Permission', + + front: 'front-end', + frontPage: 'Page', + frontBtn: 'Button', + frontTestA: 'Test page A', + frontTestB: 'Test page B', + + back: 'background', + backPage: 'Page', + backBtn: 'Button', + }, + setup: { + page: 'Intro page', + }, + system: { + moduleName: 'System management', + + account: 'Account management', + account_detail: 'Account detail', + password: 'Change password', + + dept: 'Department management', + + menu: 'Menu management', + role: 'Role management', + }, + table: { + table: 'Table', + + basic: 'Basic', + treeTable: 'Tree', + fetchTable: 'Remote loading', + fixedColumn: 'Fixed column', + customerCell: 'Custom column', + formTable: 'Open search', + useTable: 'UseTable', + refTable: 'RefTable', + multipleHeader: 'MultiLevel header', + mergeHeader: 'Merge cells', + expandTable: 'Expandable table', + fixedHeight: 'Fixed height', + footerTable: 'Footer', + editCellTable: 'Editable cell', + editRowTable: 'Editable row', + authColumn: 'Auth column', + }, +}; diff --git a/src/locales/lang/en/sys.ts b/src/locales/lang/en/sys.ts new file mode 100644 index 0000000..3722557 --- /dev/null +++ b/src/locales/lang/en/sys.ts @@ -0,0 +1,112 @@ +export default { + api: { + operationFailed: 'Operation failed', + errorTip: 'Error Tip', + errorMessage: 'The operation failed, the system is abnormal!', + timeoutMessage: 'Login timed out, please log in again!', + apiTimeoutMessage: 'The interface request timed out, please refresh the page and try again!', + apiRequestFailed: 'The interface request failed, please try again later!', + networkException: 'network anomaly', + networkExceptionMsg: 'Please check if your network connection is normal! The network is abnormal', + + errMsg401: 'The user does not have permission (token, user name, password error)!', + errMsg403: 'The user is authorized, but access is forbidden!', + errMsg404: 'Network request error, the resource was not found!', + errMsg405: 'Network request error, request method not allowed!', + errMsg408: 'Network request timed out!', + errMsg500: 'Server error, please contact the administrator!', + errMsg501: 'The network is not implemented!', + errMsg502: 'Network Error!', + errMsg503: 'The service is unavailable, the server is temporarily overloaded or maintained!', + errMsg504: 'Network timeout!', + errMsg505: 'The http version does not support the request!', + }, + app: { + logoutTip: 'Reminder', + logoutMessage: 'Confirm to exit the system?', + menuLoading: 'Menu loading...', + }, + errorLog: { + tableTitle: 'Error log list', + tableColumnType: 'Type', + tableColumnDate: 'Time', + tableColumnFile: 'File', + tableColumnMsg: 'Error message', + tableColumnStackMsg: 'Stack info', + + tableActionDesc: 'Details', + + modalTitle: 'Error details', + + fireVueError: 'Fire vue error', + fireResourceError: 'Fire resource error', + fireAjaxError: 'Fire ajax error', + + enableMessage: 'Only effective when useErrorHandle=true in `/src/settings/projectSetting.ts`.', + }, + exception: { + backLogin: 'Back Login', + backHome: 'Back Home', + subTitle403: "Sorry, you don't have access to this page.", + subTitle404: 'Sorry, the page you visited does not exist.', + subTitle500: 'Sorry, the server is reporting an error.', + noDataTitle: 'No data on the current page.', + networkErrorTitle: 'Network Error', + networkErrorSubTitle: 'Sorry,Your network connection has been disconnected, please check your network!', + }, + lock: { + unlock: 'Click to unlock', + alert: 'Lock screen password error', + backToLogin: 'Back to login', + entry: 'Enter the system', + placeholder: 'lock screen password', + }, + login: { + backSignIn: 'Back sign in', + mobileSignInFormTitle: 'Mobile sign in', + qrSignInFormTitle: 'Qr code sign in', + signInFormTitle: 'Sign in', + signUpFormTitle: 'Sign up', + forgetFormTitle: 'Reset password', + + signInTitle: 'test Admin System', + signInDesc: 'A secure and efficient enterprise management platform.', + policy: 'I agree to the xxx Privacy Policy', + scanSign: `scanning the code to complete the login`, + + loginButton: 'Sign in', + registerButton: 'Sign up', + rememberMe: 'Remember me', + forgetPassword: 'Forget Password?', + otherSignIn: 'Sign in with', + + // notify + loginSuccessTitle: 'Login successful', + loginSuccessDesc: 'Welcome back', + + // placeholder + accountPlaceholder: 'Please input username', + passwordPlaceholder: 'Please input password', + smsPlaceholder: 'Please input sms code', + mobilePlaceholder: 'Please input mobile', + mobileCorrectPlaceholder: 'Please input correct mobile', + policyPlaceholder: 'Register after checking', + diffPwd: 'The two passwords are inconsistent', + + userName: 'Username', + password: 'Password', + loginOrgCode: 'Department', + inputCode: 'Verification code', + confirmPassword: 'Confirm Password', + email: 'Email', + smsCode: 'SMS code', + mobile: 'Mobile', + + //重置密码页面英文 + authentication:'authentication', + resetLoginPassword:'reset login password', + resetSuccess:'reset succeeded', + nextStep:'next step', + goToLogin:'go to login' + }, +}; diff --git a/src/locales/lang/zh-CN/common.ts b/src/locales/lang/zh-CN/common.ts new file mode 100644 index 0000000..478c625 --- /dev/null +++ b/src/locales/lang/zh-CN/common.ts @@ -0,0 +1,20 @@ +export default { + okText: '确认', + closeText: '关闭', + cancelText: '取消', + loadingText: '加载中...', + saveText: '保存', + delText: '删除', + resetText: '重置', + searchText: '搜索', + queryText: '查询', + + inputText: '请输入', + chooseText: '请选择', + + redo: '刷新', + back: '返回', + + light: '亮色主题', + dark: '黑暗主题', +}; diff --git a/src/locales/lang/zh-CN/component.ts b/src/locales/lang/zh-CN/component.ts new file mode 100644 index 0000000..e2ae6ac --- /dev/null +++ b/src/locales/lang/zh-CN/component.ts @@ -0,0 +1,135 @@ +export default { + app: { + searchNotData: '暂无搜索结果', + toSearch: '确认', + toNavigate: '切换', + }, + countdown: { + normalText: '获取验证码', + sendText: '{0}秒后重新获取', + }, + cropper: { + selectImage: '选择图片', + uploadSuccess: '上传成功', + modalTitle: '头像上传', + okText: '确认并上传', + btn_reset: '重置', + btn_rotate_left: '逆时针旋转', + btn_rotate_right: '顺时针旋转', + btn_scale_x: '水平翻转', + btn_scale_y: '垂直翻转', + btn_zoom_in: '放大', + btn_zoom_out: '缩小', + preview: '预览', + }, + drawer: { + loadingText: '加载中...', + cancelText: '关闭', + okText: '确认', + }, + excel: { + exportModalTitle: '导出数据', + fileType: '文件类型', + fileName: '文件名', + }, + form: { + putAway: '收起', + unfold: '展开', + + maxTip: '字符数应小于{0}位', + + apiSelectNotFound: '请等待数据加载完成...', + }, + icon: { + placeholder: '点击选择图标', + search: '搜索图标', + copy: '复制图标成功!', + }, + menu: { + search: '菜单搜索', + }, + modal: { + cancelText: '关闭', + okText: '确认', + close: '关闭', + maximize: '最大化', + restore: '还原', + }, + table: { + settingDens: '密度', + // settingDensDefault: '默认', + settingDensLarge: '宽松', + settingDensMiddle: '默认', + settingDensSmall: '紧凑', + settingColumn: '列设置', + settingColumnShow: '列展示', + settingIndexColumnShow: '序号列', + settingSelectColumnShow: '勾选列', + settingFixedLeft: '固定到左侧', + settingFixedRight: '固定到右侧', + settingFullScreen: '全屏', + + index: '序号', + + total: '共 {total} 条数据', + }, + time: { + before: '前', + after: '后', + just: '刚刚', + seconds: '秒', + minutes: '分钟', + hours: '小时', + days: '天', + }, + tree: { + selectAll: '选择全部', + unSelectAll: '取消选择', + expandAll: '展开全部', + unExpandAll: '折叠全部', + checkStrictly: '层级关联', + checkUnStrictly: '层级独立', + }, + upload: { + save: '保存', + upload: '上传', + imgUpload: '图片上传', + uploaded: '已上传', + + operating: '操作', + del: '删除', + download: '下载', + saveWarn: '请等待文件上传后,保存!', + saveError: '没有上传成功的文件,无法保存!', + + preview: '预览', + choose: '选择文件', + + accept: '支持{0}格式', + acceptUpload: '只能上传{0}格式文件', + maxSize: '单个文件不超过{0}MB', + maxSizeMultiple: '只能上传不超过{0}MB的文件!', + maxNumber: '最多只能上传{0}个文件', + + legend: '略缩图', + fileName: '文件名', + fileSize: '文件大小', + fileStatue: '状态', + + startUpload: '开始上传', + uploadSuccess: '上传成功', + uploadError: '上传失败', + uploading: '上传中', + uploadWait: '请等待文件上传结束后操作', + reUploadFailed: '重新上传失败文件', + }, + verify: { + error: '验证失败!', + time: '验证校验成功,耗时{time}秒!', + + redoTip: '点击图片可刷新', + + dragText: '请按住滑块拖动', + successText: '验证通过', + }, +}; diff --git a/src/locales/lang/zh-CN/layout.ts b/src/locales/lang/zh-CN/layout.ts new file mode 100644 index 0000000..b6aa05b --- /dev/null +++ b/src/locales/lang/zh-CN/layout.ts @@ -0,0 +1,139 @@ +export default { + footer: { onlinePreview: 'JEECG首页', onlineDocument: '在线文档' }, + header: { + // user dropdown + dropdownItemDoc: '官网', + dropdownItemLoginOut: '退出系统', + dropdownItemSwitchPassword: '密码修改', + dropdownItemSwitchDepart: '切换部门', + dropdownItemRefreshCache: '刷新缓存', + dropdownItemSwitchAccount: '账户设置', + dropdownItemSwitchDefaultHomePage: '切换首页', + + dropdownItemSwitchDefaultWeb: '桌面应用', + dropdownItemSwitchDefaultAndroid: '移动App', + + // tooltip + tooltipErrorLog: '错误日志', + tooltipLock: '锁定屏幕', + tooltipNotify: '消息通知', + + tooltipEntryFull: '全屏', + tooltipExitFull: '退出全屏', + + // lock + lockScreenPassword: '锁屏密码', + lockScreen: '锁定屏幕', + lockScreenBtn: '锁定', + + home: '首页', + welcomeIn:"欢迎进入", + refreshCacheComplete: "刷新缓存完成!", + refreshCacheFailure: "刷新缓存失败!", + }, + multipleTab: { + reload: '刷 新', + close: '关闭当前', + closeLeft: '关闭左侧', + closeRight: '关闭右侧', + closeOther: '关闭其它', + closeAll: '关闭全部', + homeDesign: '门户设计', + }, + setting: { + // content mode + contentModeFull: '流式', + contentModeFixed: '定宽', + // topMenu align + topMenuAlignLeft: '居左', + topMenuAlignRight: '居中', + topMenuAlignCenter: '居右', + // menu trigger + menuTriggerNone: '不显示', + menuTriggerBottom: '底部', + menuTriggerTop: '顶部', + // menu type + menuTypeSidebar: '侧边栏导航', + menuTypeMixSidebar: '侧边折叠导航', + menuTypeMix: '顶部混合导航', + menuTypeTopMenu: '顶部栏导航', + + on: '开', + off: '关', + minute: '分钟', + + operatingTitle: '操作成功', + operatingContent: '复制成功,请到 src/settings/projectSetting.ts 中修改配置!', + resetSuccess: '重置成功!', + + copyBtn: '拷贝', + clearBtn: '清空并返回登录', + + drawerTitle: '项目配置', + + darkMode: '主题', + navMode: '导航栏模式', + interfaceFunction: '界面设置', + interfaceDisplay: '界面显示', + animation: '动画', + splitMenu: '顶部左侧组合菜单', + closeMixSidebarOnChange: '切换页面关闭菜单', + + sysTheme: '系统主题', + headerTheme: '顶栏主题', + sidebarTheme: '菜单主题', + + menuDrag: '侧边菜单拖拽', + menuSearch: '菜单搜索', + menuAccordion: '侧边菜单手风琴模式', + menuCollapse: '折叠菜单', + collapseMenuDisplayName: '折叠菜单显示名称', + topMenuLayout: '顶部菜单布局', + menuCollapseButton: '菜单折叠按钮', + contentMode: '内容区域宽度', + expandedMenuWidth: '菜单展开宽度', + + breadcrumb: '面包屑', + breadcrumbIcon: '面包屑图标', + tabs: '标签页', + tabDetail: '标签详情页', + tabsQuickBtn: '标签页快捷按钮', + tabsRedoBtn: '标签页刷新按钮', + tabsFoldBtn: '标签页折叠按钮', + tabsTheme: '标签页样式', + tabsThemeSmooth: '圆滑', + tabsThemeCard: '卡片', + tabsThemeSimple: '极简', + sidebar: '左侧菜单', + header: '顶栏', + footer: '页脚', + fullContent: '全屏内容', + grayMode: '灰色模式', + colorWeak: '色弱模式', + aiIconSHow: 'Ai图标显示', + + progress: '顶部进度条', + switchLoading: '切换loading', + switchAnimation: '切换动画', + animationType: '动画类型', + + autoScreenLock: '自动锁屏', + notAutoScreenLock: '不自动锁屏', + + fixedHeader: '固定header', + fixedSideBar: '固定Sidebar', + + mixSidebarTrigger: '混合菜单触发方式', + triggerHover: '悬停', + triggerClick: '点击', + + mixSidebarFixed: '固定展开菜单', + }, + changePassword: { + changePassword: '修改密码', + oldPassword: '旧密码', + newPassword: '新密码', + confirmNewPassword: '确认新密码', + pleaseEnterNewPassword: '请输入新密码', + }, +}; diff --git a/src/locales/lang/zh-CN/routes/basic.ts b/src/locales/lang/zh-CN/routes/basic.ts new file mode 100644 index 0000000..d154a4e --- /dev/null +++ b/src/locales/lang/zh-CN/routes/basic.ts @@ -0,0 +1,5 @@ +export default { + login: '登录', + errorLogList: '错误日志列表', + defaultHomePage: '默认首页', +}; diff --git a/src/locales/lang/zh-CN/routes/dashboard.ts b/src/locales/lang/zh-CN/routes/dashboard.ts new file mode 100644 index 0000000..04b1b19 --- /dev/null +++ b/src/locales/lang/zh-CN/routes/dashboard.ts @@ -0,0 +1,6 @@ +export default { + dashboard: 'Dashboard', + about: '关于', + workbench: '工作台', + analysis: '分析页', +}; diff --git a/src/locales/lang/zh-CN/routes/demo.ts b/src/locales/lang/zh-CN/routes/demo.ts new file mode 100644 index 0000000..b7c2822 --- /dev/null +++ b/src/locales/lang/zh-CN/routes/demo.ts @@ -0,0 +1,207 @@ +export default { + charts: { + baiduMap: '百度地图', + aMap: '高德地图', + googleMap: '谷歌地图', + charts: '图表', + map: '地图', + line: '折线图', + pie: '饼图', + }, + comp: { + comp: '组件', + basic: '基础组件', + jeecg: 'Jeecg组件', + transition: '动画组件', + countTo: '数字动画', + third: '第三方组件', + + scroll: '滚动组件', + scrollBasic: '基础滚动', + scrollAction: '滚动函数', + virtualScroll: '虚拟滚动', + + tree: 'Tree', + treeBasic: '基础树', + editTree: '可搜索/工具栏', + actionTree: '函数操作示例', + + modal: '弹窗抽屉', + desc: '详情组件', + + lazy: '懒加载组件', + lazyBasic: '基础示例', + lazyTransition: '动画效果', + + verify: '验证组件', + verifyDrag: '拖拽校验', + verifyRotate: '图片还原', + + qrcode: '二维码组件', + strength: '密码强度组件', + upload: '上传组件', + + loading: 'Loading', + + time: '相对时间', + cropperImage: '图片裁剪', + cardList: '卡片列表', + oneToMore: '一对多示例', + vexTable: '一对多示例', + }, + basic: { + button: '按钮组件', + }, + editor: { + editor: '编辑器', + jsonEditor: 'Json编辑器', + markdown: 'markdown编辑器', + + tinymce: '富文本', + tinymceBasic: '基础使用', + tinymceForm: '嵌入form', + }, + excel: { + excel: 'Excel', + customExport: '选择导出格式', + jsonExport: 'JSON数据导出', + arrayExport: 'Array数据导出', + importExcel: '导入', + }, + feat: { + feat: '功能', + icon: '图标', + sessionTimeout: '登录过期', + tabs: '标签页操作', + tabDetail: '标签详情页', + print: '打印', + contextMenu: '右键菜单', + download: '文件下载', + clickOutSide: 'ClickOutSide组件', + imgPreview: '图片预览', + copy: '剪切板', + msg: '消息提示', + watermark: '水印', + ripple: '水波纹', + fullScreen: '全屏', + errorLog: '错误日志', + tab: 'Tab带参', + tab1: 'Tab带参1', + tab2: 'Tab带参2', + menu: 'Menu带参', + menu1: 'Menu带参1', + menu2: 'Menu带参2', + ws: 'websocket测试', + breadcrumb: '面包屑导航', + breadcrumbFlat: '平级模式', + breadcrumbFlatDetail: '平级详情', + breadcrumbChildren: '层级模式', + breadcrumbChildrenDetail: '层级详情', + fullCalendar: '日历(New)', + codemirror: '代码高亮(New)', + }, + flow: { + name: '图形编辑器', + flowChart: '流程图', + }, + form: { + form: 'Form', + basic: '基础表单', + useForm: 'useForm', + refForm: 'RefForm', + advancedForm: '可收缩表单', + ruleForm: '表单验证', + dynamicForm: '动态表单', + customerForm: '自定义组件', + appendForm: '表单增删示例', + }, + modal: { + basic: '弹窗扩展', + drawer: '抽屉扩展', + }, + iframe: { + frame: '外部页面', + antv: 'antVue文档(内嵌)', + doc: '项目文档(内嵌)', + docExternal: '项目文档(外链)', + }, + level: { level: '多级菜单' }, + page: { + page: '页面', + + form: '表单页', + formBasic: '基础表单', + formStep: '分步表单', + formHigh: '高级表单', + + desc: '详情页', + descBasic: '基础详情页', + descHigh: '高级详情页', + + result: '结果页', + resultSuccess: '成功页', + resultFail: '失败页', + + account: '个人页', + accountCenter: '个人中心', + accountSetting: '个人设置', + + exception: '异常页', + netWorkError: '网络错误', + notData: '无数据', + + list: '列表页', + listCard: '卡片列表', + listBasic: '标准列表', + listSearch: '搜索列表', + }, + permission: { + permission: '权限管理', + + front: '基于前端权限', + frontPage: '页面权限', + frontBtn: '按钮权限', + frontTestA: '权限测试页A', + frontTestB: '权限测试页B', + + back: '基于后台权限', + backPage: '页面权限', + backBtn: '按钮权限', + }, + setup: { + page: '引导页', + }, + system: { + moduleName: '系统管理', + account: '账号管理', + account_detail: '账号详情', + password: '修改密码', + dept: '部门管理', + menu: '菜单管理', + test: '测试功能', + role: '角色管理', + }, + table: { + table: 'Table', + basic: '基础表格', + treeTable: '树形表格', + fetchTable: '远程加载示例', + fixedColumn: '固定列', + customerCell: '自定义列', + formTable: '开启搜索区域', + useTable: 'UseTable', + refTable: 'RefTable', + multipleHeader: '多级表头', + mergeHeader: '合并单元格', + nestedTable: '嵌套子表格', + expandTable: '可展开表格', + fixedHeight: '定高/头部自定义', + footerTable: '表尾行合计', + editCellTable: '可编辑单元格', + editRowTable: '可编辑行', + authColumn: '权限列', + }, + jeecg: { + JAreaLinkage: '区域选择', + }, +}; diff --git a/src/locales/lang/zh-CN/sys.ts b/src/locales/lang/zh-CN/sys.ts new file mode 100644 index 0000000..962e40e --- /dev/null +++ b/src/locales/lang/zh-CN/sys.ts @@ -0,0 +1,114 @@ +export default { + api: { + operationFailed: '操作失败', + errorTip: '错误提示', + errorMessage: '操作失败,系统异常!', + timeoutMessage: '登录超时,请重新登录!', + apiTimeoutMessage: '接口请求超时,请刷新页面重试!', + apiRequestFailed: '请求出错,请稍候重试', + networkException: '网络异常', + networkExceptionMsg: '网络异常,请检查您的网络连接是否正常!', + + errMsg401: '用户没有权限(令牌、用户名、密码错误)!', + errMsg403: '用户得到授权,但是访问是被禁止的。!', + errMsg404: '网络请求错误,未找到该资源!', + errMsg405: '网络请求错误,请求方法未允许!', + errMsg408: '网络请求超时!', + errMsg500: '服务器错误,请联系管理员!', + errMsg501: '网络未实现!', + errMsg502: '网络错误!', + errMsg503: '服务不可用,服务器暂时过载或维护!', + errMsg504: '网络超时!', + errMsg505: 'http版本不支持该请求!', + + registerMsg: '注册成功', + }, + app: { logoutTip: '温馨提醒', logoutMessage: '是否确认退出系统?', menuLoading: '菜单加载中...' }, + errorLog: { + tableTitle: '错误日志列表', + tableColumnType: '类型', + tableColumnDate: '时间', + tableColumnFile: '文件', + tableColumnMsg: '错误信息', + tableColumnStackMsg: 'stack信息', + + tableActionDesc: '详情', + + modalTitle: '错误详情', + + fireVueError: '点击触发vue错误', + fireResourceError: '点击触发资源加载错误', + fireAjaxError: '点击触发ajax错误', + + enableMessage: '只在`/src/settings/projectSetting.ts` 内的useErrorHandle=true时生效.', + }, + exception: { + backLogin: '返回登录', + backHome: '返回首页', + subTitle403: '抱歉,您无权访问此页面。', + subTitle404: '抱歉,您访问的页面不存在。', + subTitle500: '抱歉,服务器报告错误。', + noDataTitle: '当前页无数据', + networkErrorTitle: '网络错误', + networkErrorSubTitle: '抱歉,您的网络连接已断开,请检查您的网络!', + }, + lock: { + unlock: '点击解锁', + alert: '锁屏密码错误', + backToLogin: '返回登录', + entry: '进入系统', + placeholder: '锁屏密码', + }, + login: { + backSignIn: '返回', + signInFormTitle: '登录', + mobileSignInFormTitle: '手机登录', + qrSignInFormTitle: '二维码登录', + signUpFormTitle: '注册', + forgetFormTitle: '重置密码', + + signInTitle: 'test 管理系统', + signInDesc: '高效、稳定、安全的企业级后台管理平台,助力业务快速落地。', + policy: '我同意隐私政策', + scanSign: `扫码后,即可完成登录`, + scanSuccess: `扫码成功,登录中`, + + loginButton: '登录', + registerButton: '注册', + rememberMe: '记住我', + forgetPassword: '忘记密码?', + otherSignIn: '其他登录方式', + + // notify + loginSuccessTitle: '登录成功', + loginSuccessDesc: '欢迎回来', + + // placeholder + accountPlaceholder: '请输入账号', + passwordPlaceholder: '请输入密码', + inputCodePlaceholder: '请输入验证码', + smsPlaceholder: '请输入验证码', + mobilePlaceholder: '请输入手机号码', + mobileCorrectPlaceholder: '请输入正确的手机号码', + policyPlaceholder: '勾选后才能注册', + diffPwd: '两次输入密码不一致', + + userName: '账号', + password: '密码', + loginOrgCode: '部门', + inputCode: '验证码', + confirmPassword: '确认密码', + email: '邮箱', + smsCode: '短信验证码', + mobile: '手机号码', + + subTitleText: '{0}秒后返回登录页面', + + //重置密码页面中文 + authentication:'验证身份', + resetLoginPassword:'重置登录密码', + resetSuccess:'重置成功', + nextStep:'下一步', + goToLogin:'去登录' + }, +}; diff --git a/src/locales/lang/zh_CN.ts b/src/locales/lang/zh_CN.ts new file mode 100644 index 0000000..8fc3305 --- /dev/null +++ b/src/locales/lang/zh_CN.ts @@ -0,0 +1,10 @@ +import { genMessage } from '../helper'; +import antdLocale from 'ant-design-vue/es/locale/zh_CN'; + +const modules = import.meta.glob('./zh-CN/**/*.ts', { eager: true }); +export default { + message: { + ...genMessage(modules as Recordable, 'zh-CN'), + antdLocale, + }, +}; diff --git a/src/locales/setupI18n.ts b/src/locales/setupI18n.ts new file mode 100644 index 0000000..405fb0c --- /dev/null +++ b/src/locales/setupI18n.ts @@ -0,0 +1,44 @@ +import type { App } from 'vue'; +import type { I18n, I18nOptions } from 'vue-i18n'; + +import { createI18n } from 'vue-i18n'; +import { setHtmlPageLang, setLoadLocalePool } from './helper'; +import { localeSetting } from '/@/settings/localeSetting'; +import { useLocaleStoreWithOut } from '/@/store/modules/locale'; + +const { fallback, availableLocales } = localeSetting; + +export let i18n: ReturnType; + +async function createI18nOptions(): Promise { + const localeStore = useLocaleStoreWithOut(); + const locale = localeStore.getLocale; + const defaultLocal = await import(`./lang/${locale}.ts`); + const message = defaultLocal.default?.message ?? {}; + + setHtmlPageLang(locale); + setLoadLocalePool((loadLocalePool) => { + loadLocalePool.push(locale); + }); + + return { + legacy: false, + locale, + fallbackLocale: fallback, + messages: { + [locale]: message, + }, + availableLocales: availableLocales, + sync: true, //If you don’t want to inherit locale from global scope, you need to set sync of i18n component option to false. + silentTranslationWarn: true, // true - warning off + missingWarn: false, + silentFallbackWarn: true, + }; +} + +// setup i18n instance with glob +export async function setupI18n(app: App) { + const options = await createI18nOptions(); + i18n = createI18n(options) as I18n; + app.use(i18n); +} diff --git a/src/locales/useLocale.ts b/src/locales/useLocale.ts new file mode 100644 index 0000000..64bd4a1 --- /dev/null +++ b/src/locales/useLocale.ts @@ -0,0 +1,69 @@ +/** + * Multi-language related operations + */ +import type { LocaleType } from '/#/config'; + +import { i18n } from './setupI18n'; +import { useLocaleStoreWithOut } from '/@/store/modules/locale'; +import { unref, computed } from 'vue'; +import { loadLocalePool, setHtmlPageLang } from './helper'; + +interface LangModule { + message: Recordable; + dateLocale: Recordable; + dateLocaleName: string; +} + +function setI18nLanguage(locale: LocaleType) { + const localeStore = useLocaleStoreWithOut(); + + if (i18n.mode === 'legacy') { + i18n.global.locale = locale; + } else { + (i18n.global.locale as any).value = locale; + } + localeStore.setLocaleInfo({ locale }); + setHtmlPageLang(locale); +} + +export function useLocale() { + const localeStore = useLocaleStoreWithOut(); + const getLocale = computed(() => localeStore.getLocale); + const getShowLocalePicker = computed(() => localeStore.getShowPicker); + + const getAntdLocale = computed((): any => { + return i18n.global.getLocaleMessage(unref(getLocale))?.antdLocale ?? {}; + }); + + // Switching the language will change the locale of useI18n + // And submit to configuration modification + async function changeLocale(locale: LocaleType) { + const globalI18n = i18n.global; + const currentLocale = unref(globalI18n.locale); + if (currentLocale === locale) { + return locale; + } + + if (loadLocalePool.includes(locale)) { + setI18nLanguage(locale); + return locale; + } + const langModule = ((await import(`./lang/${locale}.ts`)) as any).default as LangModule; + if (!langModule) return; + + const { message } = langModule; + + globalI18n.setLocaleMessage(locale, message); + loadLocalePool.push(locale); + + setI18nLanguage(locale); + return locale; + } + + return { + getLocale, + getShowLocalePicker, + changeLocale, + getAntdLocale, + }; +} diff --git a/src/logics/error-handle/index.ts b/src/logics/error-handle/index.ts new file mode 100644 index 0000000..d4d0c82 --- /dev/null +++ b/src/logics/error-handle/index.ts @@ -0,0 +1,178 @@ +/** + * Used to configure the global error handling function, which can monitor vue errors, script errors, static resource errors and Promise errors + */ + +import type { ErrorLogInfo } from '/#/store'; + +import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog'; + +import { ErrorTypeEnum } from '/@/enums/exceptionEnum'; +import { App } from 'vue'; +import projectSetting from '/@/settings/projectSetting'; + +/** + * Handling error stack information + * @param error + */ +function processStackMsg(error: Error) { + if (!error.stack) { + return ''; + } + let stack = error.stack + .replace(/\n/gi, '') // Remove line breaks to save the size of the transmitted content + .replace(/\bat\b/gi, '@') // At in chrome, @ in ff + .split('@') // Split information with @ + .slice(0, 9) // The maximum stack length (Error.stackTraceLimit = 10), so only take the first 10 + .map((v) => v.replace(/^\s*|\s*$/g, '')) // Remove extra spaces + .join('~') // Manually add separators for later display + .replace(/\?[^:]+/gi, ''); // Remove redundant parameters of js file links (?x=1 and the like) + const msg = error.toString(); + if (stack.indexOf(msg) < 0) { + stack = msg + '@' + stack; + } + return stack; +} + +/** + * get comp name + * @param vm + */ +function formatComponentName(vm: any) { + if (vm.$root === vm) { + return { + name: 'root', + path: 'root', + }; + } + + const options = vm.$options as any; + if (!options) { + return { + name: 'anonymous', + path: 'anonymous', + }; + } + const name = options.name || options._componentTag; + return { + name: name, + path: options.__file, + }; +} + +/** + * Configure Vue error handling function + */ + +function vueErrorHandler(err: Error, vm: any, info: string) { + const errorLogStore = useErrorLogStoreWithOut(); + const { name, path } = formatComponentName(vm); + errorLogStore.addErrorLogInfo({ + type: ErrorTypeEnum.VUE, + name, + file: path, + message: err.message, + stack: processStackMsg(err), + detail: info, + url: window.location.href, + }); +} + +/** + * Configure script error handling function + */ +export function scriptErrorHandler(event: Event | string, source?: string, lineno?: number, colno?: number, error?: Error) { + if (event === 'Script error.' && !source) { + return false; + } + const errorInfo: Partial = {}; + colno = colno || (window.event && (window.event as any).errorCharacter) || 0; + errorInfo.message = event as string; + if (error?.stack) { + errorInfo.stack = error.stack; + } else { + errorInfo.stack = ''; + } + const name = source ? source.substr(source.lastIndexOf('/') + 1) : 'script'; + const errorLogStore = useErrorLogStoreWithOut(); + errorLogStore.addErrorLogInfo({ + type: ErrorTypeEnum.SCRIPT, + name: name, + file: source as string, + detail: 'lineno' + lineno, + url: window.location.href, + ...(errorInfo as Pick), + }); + return true; +} + +/** + * Configure Promise error handling function + */ +function registerPromiseErrorHandler() { + window.addEventListener( + 'unhandledrejection', + function (event) { + const errorLogStore = useErrorLogStoreWithOut(); + errorLogStore.addErrorLogInfo({ + type: ErrorTypeEnum.PROMISE, + name: 'Promise Error!', + file: 'none', + detail: 'promise error!', + url: window.location.href, + stack: 'promise error!', + message: event.reason, + }); + }, + true + ); +} + +/** + * Configure monitoring resource loading error handling function + */ +function registerResourceErrorHandler() { + // Monitoring resource loading error(img,script,css,and jsonp) + window.addEventListener( + 'error', + function (e: Event) { + const target = e.target ? e.target : (e.srcElement as any); + const errorLogStore = useErrorLogStoreWithOut(); + errorLogStore.addErrorLogInfo({ + type: ErrorTypeEnum.RESOURCE, + name: 'Resource Error!', + file: (e.target || ({} as any)).currentSrc, + detail: JSON.stringify({ + tagName: target.localName, + html: target.outerHTML, + type: e.type, + }), + url: window.location.href, + stack: 'resource is not found', + message: (e.target || ({} as any)).localName + ' is load error', + }); + }, + true + ); +} + +/** + * Configure global error handling + * @param app + */ +export function setupErrorHandle(app: App) { + const { useErrorHandle } = projectSetting; + if (!useErrorHandle) { + return; + } + // Vue exception monitoring; + app.config.errorHandler = vueErrorHandler; + + // script error + window.onerror = scriptErrorHandler; + + // promise exception + registerPromiseErrorHandler(); + + // Static resource exception + registerResourceErrorHandler(); +} diff --git a/src/logics/initAppConfig.ts b/src/logics/initAppConfig.ts new file mode 100644 index 0000000..a186450 --- /dev/null +++ b/src/logics/initAppConfig.ts @@ -0,0 +1,84 @@ +/** + * Application configuration + */ +import type { ProjectConfig } from '/#/config'; + +import { PROJ_CFG_KEY } from '/@/enums/cacheEnum'; +import projectSetting from '/@/settings/projectSetting'; + +import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground'; +import { updateColorWeak } from '/@/logics/theme/updateColorWeak'; +import { updateGrayMode } from '/@/logics/theme/updateGrayMode'; +import { updateDarkTheme } from '/@/logics/theme/dark'; +import { changeTheme } from '/@/logics/theme'; + +import { useAppStore } from '/@/store/modules/app'; +import { useLocaleStore } from '/@/store/modules/locale'; + +import { getCommonStoragePrefix, getStorageShortName } from '/@/utils/env'; + +import { primaryColor } from '../../build/config/themeConfig'; +import { Persistent } from '/@/utils/cache/persistent'; +import { deepMerge } from '/@/utils'; +import { ThemeEnum } from '/@/enums/appEnum'; + +// Initial project configuration +export function initAppConfigStore() { + const localeStore = useLocaleStore(); + const appStore = useAppStore(); + let projCfg: ProjectConfig = Persistent.getLocal(PROJ_CFG_KEY) as ProjectConfig; + projCfg = deepMerge(projectSetting, projCfg || {}); + const darkMode = appStore.getDarkMode; + const { + colorWeak, + grayMode, + themeColor, + + headerSetting: { bgColor: headerBgColor } = {}, + menuSetting: { bgColor } = {}, + } = projCfg; + try { + if (themeColor && themeColor !== primaryColor) { + changeTheme(themeColor); + } + + grayMode && updateGrayMode(grayMode); + colorWeak && updateColorWeak(colorWeak); + } catch (error) { + console.log(error); + } + appStore.setProjectConfig(projCfg); + + // init dark mode + updateDarkTheme(darkMode); + if (darkMode === ThemeEnum.DARK) { + updateHeaderBgColor(); + updateSidebarBgColor(); + } else { + headerBgColor && updateHeaderBgColor(headerBgColor); + bgColor && updateSidebarBgColor(bgColor); + } + // init store + localeStore.initLocale(); + + setTimeout(() => { + clearObsoleteStorage(); + }, 16); +} + +/** + * As the version continues to iterate, there will be more and more cache keys stored in localStorage. + * This method is used to delete useless keys + */ +export function clearObsoleteStorage() { + const commonPrefix = getCommonStoragePrefix(); + const shortPrefix = getStorageShortName(); + + [localStorage, sessionStorage].forEach((item: Storage) => { + Object.keys(item).forEach((key) => { + if (key && key.startsWith(commonPrefix) && !key.startsWith(shortPrefix)) { + item.removeItem(key); + } + }); + }); +} diff --git a/src/logics/mitt/routeChange.ts b/src/logics/mitt/routeChange.ts new file mode 100644 index 0000000..1f842eb --- /dev/null +++ b/src/logics/mitt/routeChange.ts @@ -0,0 +1,28 @@ +/** + * Used to monitor routing changes to change the status of menus and tabs. There is no need to monitor the route, because the route status change is affected by the page rendering time, which will be slow + */ + +import mitt from '/@/utils/mitt'; +import type { RouteLocationNormalized } from 'vue-router'; +import { getRawRoute } from '/@/utils'; + +const emitter = mitt(); + +const key = Symbol(); + +let lastChangeTab: RouteLocationNormalized; + +export function setRouteChange(lastChangeRoute: RouteLocationNormalized) { + const r = getRawRoute(lastChangeRoute); + emitter.emit(key, r); + lastChangeTab = r; +} + +export function listenerRouteChange(callback: (route: RouteLocationNormalized) => void, immediate = true) { + emitter.on(key, callback); + immediate && lastChangeTab && callback(lastChangeTab); +} + +export function removeTabChangeListener() { + emitter.clear(); +} diff --git a/src/logics/theme/dark.ts b/src/logics/theme/dark.ts new file mode 100644 index 0000000..2991e09 --- /dev/null +++ b/src/logics/theme/dark.ts @@ -0,0 +1,24 @@ +import { darkCssIsReady, loadDarkThemeCss } from '@rys-fe/vite-plugin-theme/es/client'; +import { addClass, hasClass, removeClass } from '/@/utils/domUtils'; + +export async function updateDarkTheme(mode: string | null = 'light') { + const htmlRoot = document.getElementById('htmlRoot'); + if (!htmlRoot) { + return; + } + const hasDarkClass = hasClass(htmlRoot, 'dark'); + if (mode === 'dark') { + if (import.meta.env.PROD && !darkCssIsReady) { + await loadDarkThemeCss(); + } + htmlRoot.setAttribute('data-theme', 'dark'); + if (!hasDarkClass) { + addClass(htmlRoot, 'dark'); + } + } else { + htmlRoot.setAttribute('data-theme', 'light'); + if (hasDarkClass) { + removeClass(htmlRoot, 'dark'); + } + } +} diff --git a/src/logics/theme/index.ts b/src/logics/theme/index.ts new file mode 100644 index 0000000..6634b22 --- /dev/null +++ b/src/logics/theme/index.ts @@ -0,0 +1,54 @@ +import { getThemeColors, generateColors } from '../../../build/config/themeConfig'; +import { + replaceStyleVariables, + loadDarkThemeCss, + replaceCssColors, + darkCssIsReady, + linkID, + styleTagId, + appendCssToDom, + getStyleDom, +} from '@rys-fe/vite-plugin-theme/es/client'; +import { mixLighten, mixDarken, tinycolor } from '@rys-fe/vite-plugin-theme/es/colorUtils'; +import { useAppStore } from '/@/store/modules/app'; +import { defHttp } from '/@/utils/http/axios'; + +let cssText = ''; +export async function changeTheme(color: string) { + // 代码逻辑说明: 【QQYUN-6366】升级到antd4.x + const appStore = useAppStore(); + appStore.setProjectConfig({ themeColor: color }); + const colors = generateColors({ + mixDarken, + mixLighten, + tinycolor, + color, + }); + // 代码逻辑说明: 【QQYUN-8570】生产环境暗黑模式下主题色不生效 + if (import.meta.env.PROD && appStore.getDarkMode === 'dark') { + if (!darkCssIsReady && !cssText) { + await loadDarkThemeCss(); + } + const el: HTMLLinkElement = document.getElementById(linkID) as HTMLLinkElement; + if (el?.href) { + // cssText = await fetchCss(el.href) as string; + !cssText && (cssText = await defHttp.get({ url: el.href }, { isTransformResponse: false })); + const colorVariables = [...getThemeColors(color), ...colors]; + const processCss = await replaceCssColors(cssText, colorVariables); + appendCssToDom(getStyleDom(styleTagId) as HTMLStyleElement, processCss); + } + } else { + await replaceStyleVariables({ + colorVariables: [...getThemeColors(color), ...colors], + }); + fixDark(); + } +} +// 【LOWCOD-2262】修复黑暗模式下切换皮肤无效的问题 +async function fixDark() { + // 代码逻辑说明: 【QQYUN-8570】生产环境暗黑模式下主题色不生效 + const el = document.getElementById(styleTagId); + if (el) { + el.innerHTML = el.innerHTML.replace(/\\["']dark\\["']/g, `'dark'`); + } +} diff --git a/src/logics/theme/updateBackground.ts b/src/logics/theme/updateBackground.ts new file mode 100644 index 0000000..6459f3d --- /dev/null +++ b/src/logics/theme/updateBackground.ts @@ -0,0 +1,90 @@ +import { colorIsDark, lighten, darken } from '/@/utils/color'; +import { useAppStore } from '/@/store/modules/app'; +import { ThemeEnum } from '/@/enums/appEnum'; +import { setCssVar } from './util'; +import { SIDE_BAR_BG_COLOR_LIST, SIDER_LOGO_BG_COLOR_LIST } from '/@/settings/designSetting'; + +const HEADER_BG_COLOR_VAR = '--header-bg-color'; +const HEADER_BG_HOVER_COLOR_VAR = '--header-bg-hover-color'; +const HEADER_MENU_ACTIVE_BG_COLOR_VAR = '--header-active-menu-bg-color'; + +const SIDER_LOGO_BG_COLOR = '--sider-logo-bg-color'; +const SIDER_DARK_BG_COLOR = '--sider-dark-bg-color'; +const SIDER_DARK_DARKEN_BG_COLOR = '--sider-dark-darken-bg-color'; +const SIDER_LIGHTEN_BG_COLOR = '--sider-dark-lighten-bg-color'; + +/** + * Change the background color of the top header + * @param color + */ +export function updateHeaderBgColor(color?: string) { + const appStore = useAppStore(); + const darkMode = appStore.getDarkMode === ThemeEnum.DARK; + if (!color) { + if (darkMode) { + color = '#151515'; + } else { + color = appStore.getHeaderSetting.bgColor; + } + } + // bg color + setCssVar(HEADER_BG_COLOR_VAR, color); + + // hover color + const hoverColor = lighten(color!, 6); + setCssVar(HEADER_BG_HOVER_COLOR_VAR, hoverColor); + setCssVar(HEADER_MENU_ACTIVE_BG_COLOR_VAR, hoverColor); + + // Determine the depth of the color value and automatically switch the theme + const isDark = colorIsDark(color!); + + appStore.setProjectConfig({ + headerSetting: { + theme: isDark || darkMode ? ThemeEnum.DARK : ThemeEnum.LIGHT, + }, + }); +} + +/** + * Change the background color of the left menu + * @param color bg color + */ +export function updateSidebarBgColor(color?: string) { + const appStore = useAppStore(); + + // if (!isHexColor(color)) return; + const darkMode = appStore.getDarkMode === ThemeEnum.DARK; + if (!color) { + if (darkMode) { + color = '#212121'; + } else { + color = appStore.getMenuSetting.bgColor; + } + } + // 代码逻辑说明: 【QQYUN-5922】logo背景色渐变 + let findIndex = SIDE_BAR_BG_COLOR_LIST.findIndex((item) => item === color); + setCssVar(SIDER_LOGO_BG_COLOR, findIndex == -1 ? 'linear-gradient(180deg, #000000, #282828)' : SIDER_LOGO_BG_COLOR_LIST[findIndex]); + setCssVar(SIDER_DARK_BG_COLOR, color); + setCssVar(SIDER_DARK_DARKEN_BG_COLOR, darken(color!, 6)); + setCssVar(SIDER_LIGHTEN_BG_COLOR, lighten(color!, 5)); + + // only #ffffff is light + // Only when the background color is #fff, the theme of the menu will be changed to light + // 代码逻辑说明: 【QQYUN-8922】左侧导航栏文字颜色调整区分彩色和暗黑 + let theme; + let isThemeBright = false; + if (['#fff', '#ffffff'].includes(color!.toLowerCase()) && !darkMode) { + theme = ThemeEnum.LIGHT; + } else if (['#009688', '#e74c3c','#037bd5'].includes(color!.toLowerCase()) && !darkMode) { + theme = ThemeEnum.DARK; + isThemeBright = true; + } else { + theme = ThemeEnum.DARK; + } + appStore.setProjectConfig({ + menuSetting: { + theme, + isThemeBright, + }, + }); +} diff --git a/src/logics/theme/updateColorWeak.ts b/src/logics/theme/updateColorWeak.ts new file mode 100644 index 0000000..8a0e64a --- /dev/null +++ b/src/logics/theme/updateColorWeak.ts @@ -0,0 +1,9 @@ +import { toggleClass } from './util'; + +/** + * Change the status of the project's color weakness mode + * @param colorWeak + */ +export function updateColorWeak(colorWeak: boolean) { + toggleClass(colorWeak, 'color-weak', document.documentElement); +} diff --git a/src/logics/theme/updateGrayMode.ts b/src/logics/theme/updateGrayMode.ts new file mode 100644 index 0000000..0fd16fe --- /dev/null +++ b/src/logics/theme/updateGrayMode.ts @@ -0,0 +1,9 @@ +import { toggleClass } from './util'; + +/** + * Change project gray mode status + * @param gray + */ +export function updateGrayMode(gray: boolean) { + toggleClass(gray, 'gray-mode', document.documentElement); +} diff --git a/src/logics/theme/util.ts b/src/logics/theme/util.ts new file mode 100644 index 0000000..30aef37 --- /dev/null +++ b/src/logics/theme/util.ts @@ -0,0 +1,11 @@ +const docEle = document.documentElement; +export function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) { + const targetEl = target || document.body; + let { className } = targetEl; + className = className.replace(clsName, ''); + targetEl.className = flag ? `${className} ${clsName} ` : className; +} + +export function setCssVar(prop: string, val: any, dom = docEle) { + dom.style.setProperty(prop, val); +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..537c88d --- /dev/null +++ b/src/main.ts @@ -0,0 +1,141 @@ +import type { MainAppProps } from "#/main"; +import 'uno.css'; +import '/@/design/index.less'; +import 'ant-design-vue/dist/reset.css'; +// 注册图标 +import 'virtual:svg-icons-register'; + +import App from './App.vue'; +import { createApp } from 'vue'; +import { initAppConfigStore } from '/@/logics/initAppConfig'; +import { setupErrorHandle } from '/@/logics/error-handle'; +import { router, createRouter, setupRouter } from '/@/router'; +import { setupRouterGuard } from '/@/router/guard'; +import { setupStore } from '/@/store'; +import { setupGlobDirectives } from '/@/directives'; +import { setupI18n } from '/@/locales/setupI18n'; +import { setupElectron } from "@/electron"; +import { registerGlobComp } from '/@/components/registerGlobComp'; +import { registerThirdComp } from '/@/settings/registerThirdComp'; +import { registerSuper } from '/@/views/super/registerSuper'; +import { useSso } from '/@/hooks/web/useSso'; +import { checkIsQiankunMicro } from "/@/qiankun/micro"; +import { autoUseQiankunMicro } from "/@/qiankun/micro/qiankunMicro"; +import { useAppStoreWithOut } from "@/store/modules/app"; + +// 注册online模块lib +import { registerPackages } from '/@/utils/monorepo/registerPackages'; + +// 程序入口 +async function main() { + if (checkIsQiankunMicro()) { + // 【JEECG作为乾坤子应用】以乾坤子应用模式启动 + // await autoUseQiankunMicro(bootstrap) + await autoUseQiankunMicro(bootstrap) + } else { + // 获取参数 + const props = getMainAppProps(); + // 普通启动 + await bootstrap(props) + } +} + +main(); + +async function bootstrap(props?: MainAppProps) { + // 创建应用实例 + const app = createApp(App); + // 【QQYUN-6329】 + window['JAppRootInstance'] = app; + + // 创建路由 + createRouter(); + + // 配置存储 + setupStore(app); + + // 配置参数 + setupProps(props); + + // 多语言配置,异步情况:语言文件可以从服务器端获得 + await setupI18n(app); + + // 初始化内部系统配置 + initAppConfigStore(); + + // 注册外部模块路由(注册online模块lib) + registerPackages(app); + + // 注册全局组件 + registerGlobComp(app); + + //CAS单点登录 + await useSso().ssoLogin(); + + // 注册super应用路由 + await registerSuper(app); + + // 配置路由 + setupRouter(app); + + // 路由保护 + setupRouterGuard(router); + + // 注册全局指令 + setupGlobDirectives(app); + + // 配置全局错误处理 + setupErrorHandle(app); + + // 注册第三方组件 + await registerThirdComp(app); + + // 配置electron + setupElectron(app) + + // 当路由准备好时再执行挂载( https://next.router.vuejs.org/api/#isready) + await router.isReady(); + + // 挂载应用 + app.mount(getMountContainer(props), true); + + console.log(" vue3 app 加载完成!") + + return app +} + +// 获取应用挂载容器 +function getMountContainer(props?: MainAppProps) { + const id = '#app'; + if (!props?.container?.querySelector) { + return id; + } + return props.container.querySelector(id) ?? id; +} + +// 获取主应用参数 +function getMainAppProps(): MainAppProps { + // 从 queryString 中获取 + const searchParams = new URLSearchParams(window.location.search); + // 隐藏侧边栏(菜单) + let hideSider = searchParams.get('hideSider') === 'true'; + // 隐藏顶部 + let hideHeader = searchParams.get('hideHeader') === 'true'; + // 隐藏 多Tab 切换 + let hideMultiTabs = searchParams.get('hideMultiTabs') === 'true'; + + return { + hideSider, + hideHeader, + hideMultiTabs + } +} + +// 配置主应用参数 +function setupProps(props?: MainAppProps) { + if (!props) { + return + } + const appStore = useAppStoreWithOut(); + appStore.setMainAppProps(props); +} diff --git a/src/qiankun/apps.ts b/src/qiankun/apps.ts new file mode 100644 index 0000000..aa70314 --- /dev/null +++ b/src/qiankun/apps.ts @@ -0,0 +1,24 @@ +// export const containerId = 'qiankun-content' +// +// /** +// *微应用apps +// * @name: 微应用名称 - 具有唯一性 +// * @entry: 微应用入口.必选 - 通过该地址加载微应用, +// * @container: 微应用挂载节点 - 微应用加载完成后将挂载在该节点上 +// * @activeRule: 微应用触发的路由规则 - 触发路由规则后将加载该微应用 +// */ +// //子应用列表 +// const _apps: Recordable[] = []; +// for (const key in import.meta.env) { +// if (key.includes('VITE_APP_SUB_')) { +// const name = key.split('VITE_APP_SUB_')[1]; +// const obj = { +// name, +// entry: import.meta.env[key], +// container: '#' + containerId, +// activeRule: name, +// }; +// _apps.push(obj); +// } +// } +// export const apps = _apps; diff --git a/src/qiankun/index.ts b/src/qiankun/index.ts new file mode 100644 index 0000000..37b4a04 --- /dev/null +++ b/src/qiankun/index.ts @@ -0,0 +1,110 @@ +// /** +// * qiankun配置 +// */ +// import { +// start, +// registerMicroApps, +// runAfterFirstMounted, +// addGlobalUncaughtErrorHandler +// } from 'qiankun'; +// import { apps, containerId } from './apps'; +// import { getProps, initGlState } from './state'; +// import { registerQiankunRouter } from './route'; +// +// registerQiankunRouter(); +// +// /** +// * 重构apps +// */ +// function filterApps() { +// apps.forEach((item) => { +// //主应用需要传递给微应用的数据。 +// // @ts-ignore +// item.props = getProps(); +// //微应用触发的路由规则 +// // @ts-ignore +// item.activeRule = genActiveRule('/' + item.activeRule); +// }); +// return apps; +// } +// +// /** +// * 路由监听 +// * @param {*} routerPrefix 前缀 +// */ +// function genActiveRule(routerPrefix) { +// return (location) => location.pathname.startsWith(routerPrefix); +// } +// +// let retryCount = 0; +// +// /** +// * 微应用注册 +// */ +// function registerApps() { +// const container = document.querySelector('#' + containerId); +// if (!container) { +// // 如果容器不存在,递归尝试注册应用,最多尝试10次,每次间隔500毫秒 +// if (retryCount < 10) { +// retryCount++; +// setTimeout(() => registerApps(), 500); +// } +// } else { +// registerAppsNow(); +// } +// } +// +// registerApps['containerId'] = containerId; +// +// function registerAppsNow() { +// if (window.qiankunStarted) { +// return; +// } +// window.qiankunStarted = true; +// const _apps = filterApps(); +// // @ts-ignore +// registerMicroApps(_apps, { +// beforeLoad: [ +// // @ts-ignore +// (loadApp) => { +// console.log('[qiankun] before load', loadApp); +// }, +// ], +// beforeMount: [ +// // @ts-ignore +// (mountApp) => { +// console.log('[qiankun] before mount', mountApp); +// }, +// ], +// afterMount: [ +// // @ts-ignore +// (mountApp) => { +// console.log('[qiankun] after mount', mountApp); +// }, +// ], +// beforeUnmount: [ +// // @ts-ignore +// (unloadApp) => { +// console.log('[qiankun] before unmount', unloadApp); +// }, +// ], +// afterUnmount: [ +// // @ts-ignore +// (unloadApp) => { +// console.log('[qiankun] after unmount', unloadApp); +// }, +// ], +// }); +// // 设置默认子应用,与 genActiveRule中的参数保持一致 +// // setDefaultMountApp(); +// // 第一个微应用 mount 后需要调用的方法,比如开启一些监控或者埋点脚本。 +// runAfterFirstMounted(() => console.log('开启监控')); +// // 添加全局的未捕获异常处理器。 +// addGlobalUncaughtErrorHandler((event) => console.log(event)); +// // 定义全局状态 +// initGlState(); +// //启动qiankun +// start({}); +// } +// +// export default registerApps; diff --git a/src/qiankun/micro/index.ts b/src/qiankun/micro/index.ts new file mode 100644 index 0000000..859d287 --- /dev/null +++ b/src/qiankun/micro/index.ts @@ -0,0 +1,12 @@ +import {qiankunWindow} from 'vite-plugin-qiankun/dist/helper' + +/** + * 【JEECG作为乾坤子应用】【判断当前是否是以乾坤子应用的模式运行】 + */ +export function checkIsQiankunMicro(): boolean { + return !!qiankunWindow.__POWERED_BY_QIANKUN__; +} + +export function getGlobal() { + return (checkIsQiankunMicro() ? qiankunWindow : window) as Window +} diff --git a/src/qiankun/micro/qiankunMicro.ts b/src/qiankun/micro/qiankunMicro.ts new file mode 100644 index 0000000..4e9d5c1 --- /dev/null +++ b/src/qiankun/micro/qiankunMicro.ts @@ -0,0 +1,57 @@ +/* +* 【JEECG作为乾坤子应用】 +*/ +import type {App} from 'vue'; +import type {MainAppProps} from "#/main"; + +import {destroyStore} from "@/store"; +import {destroyRouter} from "@/router"; +import { clearComponent } from '@/components/jeecg/JVxeTable/src/componentMapStore'; + +import {renderWithQiankun} from 'vite-plugin-qiankun/dist/helper'; + +/** + * 以乾坤子应用模式运行 + * @param render + */ +export async function useQiankunMicroApp(render: (props?: MainAppProps) => Promise) { + let instance: Nullable = null; + + // 注册乾坤子应用生命周期函数 + renderWithQiankun({ + async mount(props) { + console.debug('[qiankun-micro] mount - props :', props) + instance = await render({ + container: props.container!, + hideSider: props.hideSider, + hideHeader: props.hideHeader, + hideMultiTabs: props.hideMultiTabs, + }); + }, + bootstrap() { + console.debug('[qiankun-micro] bootstrap'); + }, + update(props) { + console.debug('[qiankun-micro] update: ', props); + }, + unmount(props) { + console.debug('[qiankun-micro] unmount: ', props); + + destroyStore(); + destroyRouter(); + + if (instance) { + clearComponent(); + instance.unmount(); + instance._container.innerHTML = ''; + instance = null; + } + }, + }); + + return instance! +} + +export async function autoUseQiankunMicro(fn: Fn) { + return useQiankunMicroApp(fn) +} diff --git a/src/qiankun/route.ts b/src/qiankun/route.ts new file mode 100644 index 0000000..30ee8ff --- /dev/null +++ b/src/qiankun/route.ts @@ -0,0 +1,45 @@ +// import { router } from "@/router"; +// import { apps } from './apps'; +// +// export const {registerQiankunRouter} = (function () { +// +// let registered = false; +// +// /** +// * 注册qiankun路由 +// */ +// function registerQiankunRouter() { +// if (!router) { +// // 如果路由对象不存在,递归调用,直到路由对象可用 +// setTimeout(() => registerQiankunRouter(), 1); +// } else { +// registerQiankunRouterNow(); +// } +// } +// +// function registerQiankunRouterNow() { +// if (registered) { +// return; +// } +// registered = true; +// const checkQiankunRoute = (path: string) => apps.some(app => path.startsWith('/' + app.name)); +// // 添加路由守卫 +// // 路由守卫,判断是否是qiankun子应用路由 +// router.beforeEach(async (to, from, next) => { +// const isQiankunRoute = checkQiankunRoute(to.path); +// if (isQiankunRoute) { +// // 如果是qiankun子应用路由,设置meta属性 +// to.meta.isQiankunRoute = true; +// } else { +// // 如果不是qiankun子应用路由,清除meta属性 +// delete to.meta.isQiankunRoute; +// } +// next(); +// }); +// } +// +// +// return { +// registerQiankunRouter, +// } +// })(); diff --git a/src/qiankun/state.ts b/src/qiankun/state.ts new file mode 100644 index 0000000..92aaf03 --- /dev/null +++ b/src/qiankun/state.ts @@ -0,0 +1,38 @@ +// /** +// *公共数据 +// */ +// import { initGlobalState } from 'qiankun'; +// import { store } from '/@/store'; +// import { router } from '/@/router'; +// import { getToken } from '/@/utils/auth'; +// //定义传入子应用的数据 +// export function getProps() { +// return { +// data: { +// publicPath: '/', +// token: getToken(), +// store, +// router, +// }, +// }; +// } +// +// /** +// * 定义全局状态,并返回通信方法,在主应用使用,微应用通过 props 获取通信方法。 +// * @param state 主应用穿的公共数据 +// */ +// export function initGlState(info = { userName: 'admin' }) { +// // 初始化state +// const actions = initGlobalState(info); +// // 设置新的值 +// actions.setGlobalState(info); +// // 注册 观察者 函数 - 响应 globalState 变化,在 globalState 发生改变时触发该 观察者 函数。 +// actions.onGlobalStateChange((newState, prev) => { +// // state: 变更后的状态; prev 变更前的状态 +// console.info('newState', newState); +// console.info('prev', prev); +// for (const key in newState) { +// console.info('onGlobalStateChange', key); +// } +// }); +// } diff --git a/src/router/constant.ts b/src/router/constant.ts new file mode 100644 index 0000000..a641f59 --- /dev/null +++ b/src/router/constant.ts @@ -0,0 +1,26 @@ +export const REDIRECT_NAME = 'Redirect'; + +export const PARENT_LAYOUT_NAME = 'ParentLayout'; + +export const PAGE_NOT_FOUND_NAME = 'PageNotFound'; +// 代码逻辑说明: 【issues/7500】vue-router4.5.0版本路由name:PageNotFound同名导致登录进不去 +export const PAGE_NOT_FOUND_NAME_404 = 'PageNotFound404'; + +export const EXCEPTION_COMPONENT = () => import('/@/views/sys/exception/Exception.vue'); + +/** + * @description: default layout + */ +export const LAYOUT = () => import('/@/layouts/default/index.vue'); + +/** + * @description: parent-layout + */ +export const getParentLayout = (_name?: string) => { + return () => + new Promise((resolve) => { + resolve({ + name: _name || PARENT_LAYOUT_NAME, + }); + }); +}; diff --git a/src/router/guard/index.ts b/src/router/guard/index.ts new file mode 100644 index 0000000..c567749 --- /dev/null +++ b/src/router/guard/index.ts @@ -0,0 +1,147 @@ +import type { Router, RouteLocationNormalized } from 'vue-router'; +import { useAppStoreWithOut } from '/@/store/modules/app'; +import { useUserStoreWithOut } from '/@/store/modules/user'; +import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'; +import { AxiosCanceler } from '/@/utils/http/axios/axiosCancel'; +import { Modal, notification } from 'ant-design-vue'; +import { warn } from '/@/utils/log'; +import { unref } from 'vue'; +import { setRouteChange } from '/@/logics/mitt/routeChange'; +import { createPermissionGuard } from './permissionGuard'; +import { createStateGuard } from './stateGuard'; +import nProgress from 'nprogress'; +import projectSetting from '/@/settings/projectSetting'; +import { createParamMenuGuard } from './paramMenuGuard'; + +// Don't change the order of creation +export function setupRouterGuard(router: Router) { + createPageGuard(router); + createPageLoadingGuard(router); + createHttpGuard(router); + createScrollGuard(router); + createMessageGuard(router); + createProgressGuard(router); + createPermissionGuard(router); + createParamMenuGuard(router); // must after createPermissionGuard (menu has been built.) + createStateGuard(router); +} + +/** + * Hooks for handling page state + */ +function createPageGuard(router: Router) { + const loadedPageMap = new Map(); + + router.beforeEach(async (to) => { + // The page has already been loaded, it will be faster to open it again, you don’t need to do loading and other processing + to.meta.loaded = !!loadedPageMap.get(to.path); + // Notify routing changes + setRouteChange(to); + + return true; + }); + + router.afterEach((to) => { + loadedPageMap.set(to.path, true); + }); +} + +// Used to handle page loading status +function createPageLoadingGuard(router: Router) { + const userStore = useUserStoreWithOut(); + const appStore = useAppStoreWithOut(); + const { getOpenPageLoading } = useTransitionSetting(); + router.beforeEach(async (to) => { + if (!userStore.getToken) { + return true; + } + if (to.meta.loaded) { + return true; + } + + if (unref(getOpenPageLoading)) { + appStore.setPageLoadingAction(true); + return true; + } + + return true; + }); + router.afterEach(async () => { + if (unref(getOpenPageLoading)) { + // TODO Looking for a better way + // The timer simulates the loading time to prevent flashing too fast, + setTimeout(() => { + appStore.setPageLoading(false); + }, 220); + } + return true; + }); +} + +/** + * The interface used to close the current page to complete the request when the route is switched + * @param router + */ +function createHttpGuard(router: Router) { + const { removeAllHttpPending } = projectSetting; + let axiosCanceler: Nullable; + if (removeAllHttpPending) { + axiosCanceler = new AxiosCanceler(); + } + router.beforeEach(async () => { + // Switching the route will delete the previous request + axiosCanceler?.removeAllPending(); + return true; + }); +} + +// Routing switch back to the top +function createScrollGuard(router: Router) { + const isHash = (href: string) => { + return /^#/.test(href); + }; + + const body = document.body; + + router.afterEach(async (to) => { + // scroll top + isHash((to as RouteLocationNormalized & { href: string })?.href) && body.scrollTo(0, 0); + return true; + }); +} + +/** + * Used to close the message instance when the route is switched + * @param router + */ +export function createMessageGuard(router: Router) { + const { closeMessageOnSwitch } = projectSetting; + + router.beforeEach(async () => { + try { + if (closeMessageOnSwitch) { + Modal.destroyAll(); + notification.destroy(); + } + } catch (error) { + warn('message guard error:' + error); + } + return true; + }); +} + +export function createProgressGuard(router: Router) { + const { getOpenNProgress } = useTransitionSetting(); + router.beforeEach(async (to) => { + if (to.meta.loaded) { + return true; + } + unref(getOpenNProgress) && nProgress.start(); + return true; + }); + + router.afterEach(async () => { + unref(getOpenNProgress) && nProgress.done(); + return true; + }); +} diff --git a/src/router/guard/paramMenuGuard.ts b/src/router/guard/paramMenuGuard.ts new file mode 100644 index 0000000..1c75157 --- /dev/null +++ b/src/router/guard/paramMenuGuard.ts @@ -0,0 +1,47 @@ +import type { Router } from 'vue-router'; +import { configureDynamicParamsMenu } from '../helper/menuHelper'; +import { Menu } from '../types'; +import { PermissionModeEnum } from '/@/enums/appEnum'; +import { useAppStoreWithOut } from '/@/store/modules/app'; + +import { usePermissionStoreWithOut } from '/@/store/modules/permission'; + +export function createParamMenuGuard(router: Router) { + const permissionStore = usePermissionStoreWithOut(); + router.beforeEach(async (to, _, next) => { + // filter no name route + if (!to.name) { + next(); + return; + } + + // menu has been built. + if (!permissionStore.getIsDynamicAddedRoute) { + next(); + return; + } + + let menus: Menu[] = []; + if (isBackMode()) { + menus = permissionStore.getBackMenuList; + } else if (isRouteMappingMode()) { + menus = permissionStore.getFrontMenuList; + } + menus.forEach((item) => configureDynamicParamsMenu(item, to.params)); + + next(); + }); +} + +const getPermissionMode = () => { + const appStore = useAppStoreWithOut(); + return appStore.getProjectConfig.permissionMode; +}; + +const isBackMode = () => { + return getPermissionMode() === PermissionModeEnum.BACK; +}; + +const isRouteMappingMode = () => { + return getPermissionMode() === PermissionModeEnum.ROUTE_MAPPING; +}; diff --git a/src/router/guard/permissionGuard.ts b/src/router/guard/permissionGuard.ts new file mode 100644 index 0000000..e0c509b --- /dev/null +++ b/src/router/guard/permissionGuard.ts @@ -0,0 +1,202 @@ +import type { Router, RouteRecordRaw } from 'vue-router'; + +import { usePermissionStoreWithOut } from '/@/store/modules/permission'; + +import { PageEnum } from '/@/enums/pageEnum'; +import { useUserStoreWithOut } from '/@/store/modules/user'; + +import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic'; + +import { RootRoute } from '/@/router/routes'; + +import {isOAuth2AppEnv, isOAuth2DingAppEnv} from '/@/views/sys/login/useLogin'; +import { OAUTH2_THIRD_LOGIN_TENANT_ID } from "/@/enums/cacheEnum"; +import { setAuthCache } from "/@/utils/auth"; +import { PAGE_NOT_FOUND_NAME_404 } from '/@/router/constant'; + +const LOGIN_PATH = PageEnum.BASE_LOGIN; +//auth2登录路由 +const OAUTH2_LOGIN_PAGE_PATH = PageEnum.OAUTH2_LOGIN_PAGE_PATH; + +//分享免登录路由 +const SYS_FILES_PATH = PageEnum.SYS_FILES_PATH; + +// 邮件中的跳转地址,对应此路由,携带token免登录直接去办理页面 +const TOKEN_LOGIN = PageEnum.TOKEN_LOGIN; + +const ROOT_PATH = RootRoute.path; + +// 代码逻辑说明: [VUEN-2472]分享免登录------------ +const whitePathList: PageEnum[] = [LOGIN_PATH, OAUTH2_LOGIN_PAGE_PATH,SYS_FILES_PATH, TOKEN_LOGIN ]; + +export function createPermissionGuard(router: Router) { + const userStore = useUserStoreWithOut(); + const permissionStore = usePermissionStoreWithOut(); + + // 自定义首页跳转次数 + let homePathJumpCount = 0; + + router.beforeEach(async (to, from, next) => { + if ( + // 【#6861】跳转到自定义首页的逻辑,只跳转一次即可 + homePathJumpCount < 1 && + from.path === ROOT_PATH && + to.path === PageEnum.BASE_HOME && + userStore.getUserInfo.homePath && + userStore.getUserInfo.homePath !== PageEnum.BASE_HOME + ) { + homePathJumpCount++; + next(userStore.getUserInfo.homePath); + return; + } + + const token = userStore.getToken; + + // Whitelist can be directly entered + if (whitePathList.includes(to.path as PageEnum)) { + if (to.path === LOGIN_PATH && token) { + const isSessionTimeout = userStore.getSessionTimeout; + + //TODO vben默认写法,暂时不知目的,有问题暂时先注释掉 + //await userStore.afterLoginAction(); + + try { + if (!isSessionTimeout) { + next((to.query?.redirect as string) || '/'); + return; + } + } catch {} + // 代码逻辑说明: [issues/I5BG1I]vue3不支持auth2登录------------ + } else if (to.path === LOGIN_PATH && isOAuth2AppEnv() && !token) { + //退出登录进入此逻辑 + //如果进入的页面是login页面并且当前是OAuth2app环境,并且token为空,就进入OAuth2登录页面 + // 代码逻辑说明: [QQYUN-3440]新建企业微信和钉钉配置表,通过租户模式隔离------------ + if(to.query.tenantId){ + setAuthCache(OAUTH2_THIRD_LOGIN_TENANT_ID,to.query.tenantId) + } + next({ path: OAUTH2_LOGIN_PAGE_PATH }); + return; + } + next(); + return; + } + + // token does not exist + if (!token) { + // You can access without permission. You need to set the routing meta.ignoreAuth to true + if (to.meta.ignoreAuth) { + next(); + return; + } + + // 代码逻辑说明: [issues/I5BG1I]vue3 Auth2未实现------------ + let path = LOGIN_PATH; + if (whitePathList.includes(to.path as PageEnum)) { + // 在免登录白名单,如果进入的页面是login页面并且当前是OAuth2app环境,就进入OAuth2登录页面 + if (to.path === LOGIN_PATH && isOAuth2AppEnv()) { + next({ path: OAUTH2_LOGIN_PAGE_PATH }); + } else { + //在免登录白名单,直接进入 + next(); + } + } else { + //----------【首次登陆并且是企业微信或者钉钉的情况下才会调用】----------------------------------------------- + //只有首次登陆并且是企业微信或者钉钉的情况下才会调用 + let href = window.location.href; + //判断当前是auth2页面,并且是钉钉/企业微信,并且包含tenantId参数 + if(isOAuth2AppEnv() && href.indexOf("/tenantId/")!= -1){ + let params = to.params; + if(params && params.path && params.path.length>0){ + //直接获取参数最后一位 + setAuthCache(OAUTH2_THIRD_LOGIN_TENANT_ID,params.path[params.path.length-1]) + } + } + //---------【首次登陆并且是企业微信或者钉钉的情况下才会调用】------------------------------------------------ + // 如果当前是在OAuth2APP环境,就跳转到OAuth2登录页面,否则跳转到登录页面 + path = isOAuth2AppEnv() ? OAUTH2_LOGIN_PAGE_PATH : LOGIN_PATH; + } + // redirect login page + const redirectData: { path: string; replace: boolean; query?: Recordable } = { + // 代码逻辑说明: [issues/I5BG1I]vue3 Auth2未实现------------ + path: path, + replace: true, + }; + + // 代码逻辑说明: 【QQYUN-4713】登录代码调整逻辑有问题,改造待观察-- + if (to.fullPath) { + console.log("to.fullPath 1",to.fullPath) + console.log("to.path 2",to.path) + + let getFullPath = to.fullPath; + if(getFullPath=='/' || getFullPath=='/500' || getFullPath=='/400' || getFullPath=='/login?redirect=/' || getFullPath=='/login?redirect=/login?redirect=/'){ + return; + } + + redirectData.query = { + ...redirectData.query, + // 代码逻辑说明: 修复登录成功后,没有正确重定向的问题 + redirect: to.fullPath, + + }; + } + next(redirectData); + return; + } + + //==============================【首次登录并且是企业微信或者钉钉的情况下才会调用】================== + //判断是免登录页面,如果页面包含/tenantId/,那么就直接前往主页 + if(isOAuth2AppEnv() && to.path.indexOf("/tenantId/") != -1){ + // 代码逻辑说明: 【TV360X-2958】钉钉登录后打开了敲敲云,换其他账号登录后,再打开敲敲云显示的是原来账号的应用--- + if (isOAuth2DingAppEnv()) { + next(OAUTH2_LOGIN_PAGE_PATH); + } else { + next(userStore.getUserInfo.homePath || PageEnum.BASE_HOME); + } + return; + } + //==============================【首次登录并且是企业微信或者钉钉的情况下才会调用】================== + // Jump to the 404 page after processing the login + if (from.path === LOGIN_PATH && to.name === PAGE_NOT_FOUND_NAME_404 && to.fullPath !== (userStore.getUserInfo.homePath || PageEnum.BASE_HOME)) { + next(userStore.getUserInfo.homePath || PageEnum.BASE_HOME); + return; + } + + // // get userinfo while last fetch time is empty + // if (userStore.getLastUpdateTime === 0) { + // try { + // console.log("--LastUpdateTime---getUserInfoAction-----") + // await userStore.getUserInfoAction(); + // } catch (err) { + // console.info(err); + // next(); + // } + // } + // 代码逻辑说明: 【QQYUN-8572】表格行选择卡顿问题(customRender中字典引起的) + if (userStore.getLastUpdateTime === 0) { + userStore.setAllDictItemsByLocal(); + } + if (permissionStore.getIsDynamicAddedRoute) { + next(); + return; + } + + // 构建后台菜单路由 + const routes = await permissionStore.buildRoutesAction(); + routes.forEach((route) => { + router.addRoute(route as unknown as RouteRecordRaw); + }); + + router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw); + permissionStore.setDynamicAddedRoute(true); + // 代码逻辑说明: 【issues/7500】vue-router4.5.0版本路由name:PageNotFound同名导致登录进不去 + if (to.name === PAGE_NOT_FOUND_NAME_404) { + // 动态添加路由后,此处应当重定向到fullPath,否则会加载404页面内容 + next({ path: to.fullPath, replace: true, query: to.query }); + } else { + const redirectPath = (from.query.redirect || to.path) as string; + const redirect = decodeURIComponent(redirectPath); + const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }; + next(nextData); + } + }); +} diff --git a/src/router/guard/stateGuard.ts b/src/router/guard/stateGuard.ts new file mode 100644 index 0000000..c34513c --- /dev/null +++ b/src/router/guard/stateGuard.ts @@ -0,0 +1,24 @@ +import type { Router } from 'vue-router'; +import { useAppStore } from '/@/store/modules/app'; +import { useMultipleTabStore } from '/@/store/modules/multipleTab'; +import { useUserStore } from '/@/store/modules/user'; +import { usePermissionStore } from '/@/store/modules/permission'; +import { PageEnum } from '/@/enums/pageEnum'; +import { removeTabChangeListener } from '/@/logics/mitt/routeChange'; + +export function createStateGuard(router: Router) { + router.afterEach((to) => { + // Just enter the login page and clear the authentication information + if (to.path === PageEnum.BASE_LOGIN) { + const tabStore = useMultipleTabStore(); + const userStore = useUserStore(); + const appStore = useAppStore(); + const permissionStore = usePermissionStore(); + appStore.resetAllState(); + permissionStore.resetState(); + tabStore.resetState(); + userStore.resetState(); + removeTabChangeListener(); + } + }); +} diff --git a/src/router/helper/menuHelper.ts b/src/router/helper/menuHelper.ts new file mode 100644 index 0000000..561c23a --- /dev/null +++ b/src/router/helper/menuHelper.ts @@ -0,0 +1,132 @@ +import { AppRouteModule } from '/@/router/types'; +import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types'; +import { findPath, treeMap } from '/@/utils/helper/treeHelper'; +import { cloneDeep } from 'lodash-es'; +import { isUrl } from '/@/utils/is'; +import { RouteParams } from 'vue-router'; +import { toRaw } from 'vue'; + +export function getAllParentPath(treeData: T[], path: string) { + // 原代码 + // const menuList = findPath(treeData, (n) => n.path === path) as Menu[]; + // 先匹配不包含隐藏菜单的路径 + let menuList = findMenuPath(treeData, path, false); + // 如果没有匹配到,再匹配包含隐藏菜单的路径 + if(!(menuList?.length)) { + menuList = findMenuPath(treeData, path, true) + } + return (menuList || []).map((item) => item.path); +} + +/** + * 查找菜单路径 + * + * @param treeData + * @param path + * @param matchHide 是否匹配隐藏菜单 + */ +function findMenuPath(treeData: T[], path: string, matchHide: boolean) { + return findPath(treeData, (n) => { + // 隐藏菜单不参与匹配 + if(!matchHide && n.hideMenu) { + return false; + } + return n.path === path + }) as Menu[]; +} + +// 路径处理 +function joinParentPath(menus: Menu[], parentPath = '') { + for (let index = 0; index < menus.length; index++) { + const menu = menus[index]; + // https://next.router.vuejs.org/guide/essentials/nested-routes.html + // Note that nested paths that start with / will be treated as a root path. + // 请注意,以 / 开头的嵌套路径将被视为根路径。 + // This allows you to leverage the component nesting without having to use a nested URL. + // 这允许你利用组件嵌套,而无需使用嵌套 URL。 + if (!(menu.path.startsWith('/') || isUrl(menu.path))) { + // path doesn't start with /, nor is it a url, join parent path + // 路径不以 / 开头,也不是 url,加入父路径 + menu.path = `${parentPath}/${menu.path}`; + } + if (menu?.children?.length) { + joinParentPath(menu.children, menu.meta?.hidePathForChildren ? parentPath : menu.path); + } + } +} + +// Parsing the menu module +export function transformMenuModule(menuModule: MenuModule): Menu { + const { menu } = menuModule; + + const menuList = [menu]; + + joinParentPath(menuList); + return menuList[0]; +} + +// 将路由转换成菜单 +export function transformRouteToMenu(routeModList: AppRouteModule[], routerMapping = false) { + // 借助 lodash 深拷贝 + const cloneRouteModList = cloneDeep(routeModList); + const routeList: AppRouteRecordRaw[] = []; + + // 对路由项进行修改 + cloneRouteModList.forEach((item) => { + if (routerMapping && item.meta.hideChildrenInMenu && typeof item.redirect === 'string') { + item.path = item.redirect; + } + + if (item.meta?.single) { + const realItem = item?.children?.[0]; + realItem && routeList.push(realItem); + } else { + routeList.push(item); + } + }); + // 提取树指定结构 + const list = treeMap(routeList, { + conversion: (node: AppRouteRecordRaw) => { + const { meta: { title, hideMenu = false } = {} } = node; + + return { + ...(node.meta || {}), + meta: node.meta, + name: title, + hideMenu, + alwaysShow:node.alwaysShow||false, + path: node.path, + originComponent: node.originComponent, + ...(node.redirect ? { redirect: node.redirect } : {}), + }; + }, + }); + // 路径处理 + joinParentPath(list); + return cloneDeep(list); +} + +/** + * config menu with given params + */ +const menuParamRegex = /(?::)([\s\S]+?)((?=\/)|$)/g; + +export function configureDynamicParamsMenu(menu: Menu, params: RouteParams) { + const { path, paramPath } = toRaw(menu); + let realPath = paramPath ? paramPath : path; + const matchArr = realPath.match(menuParamRegex); + + matchArr?.forEach((it) => { + const realIt = it.substr(1); + if (params[realIt]) { + realPath = realPath.replace(`:${realIt}`, params[realIt] as string); + } + }); + // save original param path. + if (!paramPath && matchArr && matchArr.length > 0) { + menu.paramPath = path; + } + menu.path = realPath; + // children + menu.children?.forEach((item) => configureDynamicParamsMenu(item, params)); +} diff --git a/src/router/helper/routeHelper.ts b/src/router/helper/routeHelper.ts new file mode 100644 index 0000000..b907011 --- /dev/null +++ b/src/router/helper/routeHelper.ts @@ -0,0 +1,247 @@ +import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types'; +import type { Router, RouteRecordNormalized } from 'vue-router'; + +import { getParentLayout, LAYOUT, EXCEPTION_COMPONENT } from '/@/router/constant'; +import { cloneDeep, omit } from 'lodash-es'; +import { warn } from '/@/utils/log'; +import { createRouter, createWebHashHistory } from 'vue-router'; +import { getTenantId, getToken } from "/@/utils/auth"; +import { URL_HASH_TAB, _eval } from '/@/utils'; +//引入online lib路由 +import { packageViews } from '/@/utils/monorepo/dynamicRouter'; +import { loadPackageComponent } from '/@/utils/monorepo/registerPackages'; +import { dynamicPages } from '/@/utils/dynamicPages'; +import {useI18n} from "/@/hooks/web/useI18n"; + +export type LayoutMapKey = 'LAYOUT'; +const IFRAME = () => import('/@/views/sys/iframe/FrameBlank.vue'); +const LayoutContent = () => import('/@/layouts/default/content/index.vue'); + +const LayoutMap = new Map Promise>(); + +LayoutMap.set('LAYOUT', LAYOUT); +LayoutMap.set('IFRAME', IFRAME); +//微前端qiankun +LayoutMap.set('LayoutsContent', LayoutContent); + +let dynamicViewsModules: Record Promise>; + +// Dynamic introduction +function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) { + if (!dynamicViewsModules) { + // update-begin--author:liaozhiyang---date:20260302---for:【QQYUN-14799】动态引入页面会生成两份及引入components下的组件文件 + dynamicViewsModules = dynamicPages as Record Promise>; + // update-end--author:liaozhiyang---date:20260302---for:【QQYUN-14799】动态引入页面会生成两份及引入components下的组件文件 + //合并online lib路由 + dynamicViewsModules = Object.assign({}, dynamicViewsModules, packageViews); + } + if (!routes) return; + routes.forEach((item) => { + + //【jeecg-boot/issues/I5N2PN】左侧动态菜单怎么做国际化处理 2022-10-09 + //菜单支持国际化翻译 + if (item?.meta?.title) { + const { t } = useI18n(); + if(item.meta.title.includes('t(\'') && t){ + // 代码逻辑说明: 【QQYUN-6390】eval替换成new Function,解决build警告 + item.meta.title = new Function('t', `return ${item.meta.title}`)(t); + } + } + // @ts-ignore 适配隐藏路由 + if (item?.hidden) { + item.meta.hideMenu = true; + //是否隐藏面包屑 + item.meta.hideBreadcrumb = true; + } + // @ts-ignore 添加忽略路由配置 + if (item?.route == 0) { + item.meta.ignoreRoute = true; + } + // @ts-ignore 添加是否缓存路由配置 + item.meta.ignoreKeepAlive = !item?.meta.keepAlive; + let token = getToken(); + let tenantId = getTenantId(); + // URL支持{{ window.xxx }}占位符变量 + // 代码逻辑说明: [VUEN-1638]菜单tenantId需要动态生成------------ + item.component = (item.component || '').replace(/{{([^}}]+)?}}/g, (s1, s2) => _eval(s2)).replace('${token}', token).replace('${tenantId}', tenantId); + // 适配 iframe + if (/^\/?http(s)?/.test(item.component as string)) { + item.component = item.component.substring(1, item.component.length); + } + if (/^http(s)?/.test(item.component as string)) { + if (item.meta?.internalOrExternal) { + // @ts-ignore 外部打开 + item.path = item.component; + // 代码逻辑说明: 【VUEN-656】配置外部网址打不开,原因是带了#号,需要替换一下 + item.path = item.path.replace('#', URL_HASH_TAB); + } else { + // @ts-ignore 内部打开 + item.meta.frameSrc = item.component; + } + delete item.component; + } + if (!item.component && item.meta?.frameSrc) { + item.component = 'IFRAME'; + } + let { component, name } = item; + const { children } = item; + if (component) { + const layoutFound = LayoutMap.get(component.toUpperCase()); + if (layoutFound) { + item.component = layoutFound; + } else { + if (component.indexOf('dashboard/') > -1) { + //当数据标sys_permission中component没有拼接index时前端需要拼接 + if (component.indexOf('/index') < 0) { + component = component + '/index'; + } + } + item.component = dynamicImport(dynamicViewsModules, component as string); + } + } else if (name) { + item.component = getParentLayout(); + } + children && asyncImportRoute(children); + }); +} + +function dynamicImport(dynamicViewsModules: Record Promise>, component: string) { + const keys = Object.keys(dynamicViewsModules); + const matchKeys = keys.filter((key) => { + // update-begin--author:liaozhiyang---date:20260302---for:【QQYUN-14799】动态引入页面会生成两份及引入components下的组件文件 + // 兼容两种前缀:dynamicPages 的 ../views 与 packageViews 的 ../../views + const k = key.replace(/^(\.\.\/)+views/, ''); + // update-end--author:liaozhiyang---date:20260302---for:【QQYUN-14799】动态引入页面会生成两份及引入components下的组件文件 + const startFlag = component.startsWith('/'); + const endFlag = component.endsWith('.vue') || component.endsWith('.tsx'); + const startIndex = startFlag ? 0 : 1; + const lastIndex = endFlag ? k.length : k.lastIndexOf('.'); + return k.substring(startIndex, lastIndex) === component; + }); + if (matchKeys?.length === 1) { + const matchKey = matchKeys[0]; + return dynamicViewsModules[matchKey]; + } else if (matchKeys?.length > 1) { + warn( + 'Please do not create `.vue` and `.TSX` files with the same file name in the same hierarchical directory under the views folder. This will cause dynamic introduction failure' + ); + return; + } + // online/aiflow 本地未找到,尝试从懒加载包中按需加载 + if (component.startsWith('/super/airag/aiflow')) { + return () => { + return loadPackageComponent(component).then((factory) => (factory ? factory() : Promise.reject(`组件 ${component} 未找到`))); + }; + } +} + +// Turn background objects into routing objects +export function transformObjToRoute(routeList: AppRouteModule[]): T[] { + routeList.forEach((route) => { + const component = route.component as string; + if (component) { + route.originComponent = component; + if (component.toUpperCase() === 'LAYOUT') { + route.component = LayoutMap.get(component.toUpperCase()); + } else { + route.children = [cloneDeep(route)]; + route.component = LAYOUT; + route.name = `${route.name}Parent`; + route.path = ''; + const meta = route.meta || {}; + meta.single = true; + meta.affix = false; + route.meta = meta; + } + } else { + warn('请正确配置路由:' + route?.name + '的component属性'); + } + route.children && asyncImportRoute(route.children); + }); + return routeList as unknown as T[]; +} + +/** + * 将多级路由转换为二级 + */ +export function flatMultiLevelRoutes(routeModules: AppRouteModule[]) { + const modules: AppRouteModule[] = cloneDeep(routeModules); + for (let index = 0; index < modules.length; index++) { + const routeModule = modules[index]; + if (!isMultipleRoute(routeModule)) { + continue; + } + promoteRouteLevel(routeModule); + } + return modules; +} + +//提升路由级别 +function promoteRouteLevel(routeModule: AppRouteModule) { + // Use vue-router to splice menus + let router: Router | null = createRouter({ + routes: [routeModule as unknown as RouteRecordNormalized], + history: createWebHashHistory(), + }); + + const routes = router.getRoutes(); + addToChildren(routes, routeModule.children || [], routeModule); + router = null; + + routeModule.children = routeModule.children?.map((item) => omit(item, 'children')); +} + +// Add all sub-routes to the secondary route +function addToChildren(routes: RouteRecordNormalized[], children: AppRouteRecordRaw[], routeModule: AppRouteModule) { + for (let index = 0; index < children.length; index++) { + const child = children[index]; + const route = routes.find((item) => item.name === child.name); + if (!route) { + continue; + } + routeModule.children = routeModule.children || []; + if (!routeModule.children.find((item) => item.name === route.name)) { + routeModule.children?.push(route as unknown as AppRouteModule); + } + if (child.children?.length) { + addToChildren(routes, child.children, routeModule); + } + } +} + +// Determine whether the level exceeds 2 levels +function isMultipleRoute(routeModule: AppRouteModule) { + if (!routeModule || !Reflect.has(routeModule, 'children') || !routeModule.children?.length) { + return false; + } + + const children = routeModule.children; + + let flag = false; + for (let index = 0; index < children.length; index++) { + const child = children[index]; + if (child.children?.length) { + flag = true; + break; + } + } + return flag; +} +/** + * 组件地址前加斜杠处理 + * @updateBy:lsq + * @updateDate:2021-09-08 + */ +export function addSlashToRouteComponent(routeList: AppRouteRecordRaw[]) { + routeList.forEach((route) => { + let component = route.component as string; + if (component) { + const layoutFound = LayoutMap.get(component); + if (!layoutFound) { + route.component = component.startsWith('/') ? component : `/${component}`; + } + } + route.children && addSlashToRouteComponent(route.children); + }); + return routeList as unknown as T[]; +} diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..474bcc2 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,59 @@ +import type { RouteRecordRaw } from 'vue-router'; +import type { App } from 'vue'; + +import { $electron } from "@/electron"; +import { basicRoutes } from './routes'; +import {createRouter as createVueRouter, destroyRouter, router} from './router' + +// 白名单应该包含基本静态路由 +const WHITE_NAME_LIST: string[] = []; +const getRouteNames = (array: any[]) => + array.forEach((item) => { + WHITE_NAME_LIST.push(item.name); + getRouteNames(item.children || []); + }); +getRouteNames(basicRoutes); + +/** + * 创建路由实例 + */ +export function createRouter() { + let router = createVueRouter({ + routes: basicRoutes as unknown as RouteRecordRaw[], + strict: true, + scrollBehavior: () => ({left: 0, top: 0}), + }, + // 如果是 Electron 环境,则使用 hash 路由 + $electron.isElectron(), + ) + + // TODO 【QQYUN-4517】【表单设计器】记录分享路由守卫测试 + // @ts-ignore + router.beforeEach(async (to, from, next) => { + //console.group('【QQYUN-4517】beforeEach'); + //console.warn('from', from); + //console.warn('to', to); + //console.groupEnd(); + next(); + }); +} + +// reset router +export function resetRouter() { + router.getRoutes().forEach((route) => { + const { name } = route; + if (name && !WHITE_NAME_LIST.includes(name as string)) { + router.hasRoute(name) && router.removeRoute(name); + } + }); +} + +// config router +export function setupRouter(app: App) { + app.use(router); +} + +export { + router, + destroyRouter, +} diff --git a/src/router/menus/index.ts b/src/router/menus/index.ts new file mode 100644 index 0000000..97b7199 --- /dev/null +++ b/src/router/menus/index.ts @@ -0,0 +1,126 @@ +import type { Menu, MenuModule } from '/@/router/types'; +import type { RouteRecordNormalized } from 'vue-router'; + +import { useAppStoreWithOut } from '/@/store/modules/app'; +import { usePermissionStore } from '/@/store/modules/permission'; +import { transformMenuModule, getAllParentPath } from '/@/router/helper/menuHelper'; +import { filter } from '/@/utils/helper/treeHelper'; +import { isUrl } from '/@/utils/is'; +import { router } from '/@/router'; +import { PermissionModeEnum } from '/@/enums/appEnum'; +import { pathToRegexp } from 'path-to-regexp'; + +const modules = import.meta.glob('./modules/**/*.ts', { eager: true }); + +const menuModules: MenuModule[] = []; + +Object.keys(modules).forEach((key) => { + const mod = (modules as Recordable)[key].default || {}; + const modList = Array.isArray(mod) ? [...mod] : [mod]; + menuModules.push(...modList); +}); + +// =========================== +// ==========Helper=========== +// =========================== + +const getPermissionMode = () => { + const appStore = useAppStoreWithOut(); + return appStore.getProjectConfig.permissionMode; +}; +const isBackMode = () => { + return getPermissionMode() === PermissionModeEnum.BACK; +}; + +const isRouteMappingMode = () => { + return getPermissionMode() === PermissionModeEnum.ROUTE_MAPPING; +}; + +const isRoleMode = () => { + return getPermissionMode() === PermissionModeEnum.ROLE; +}; + +const staticMenus: Menu[] = []; +(() => { + menuModules.sort((a, b) => { + return (a.orderNo || 0) - (b.orderNo || 0); + }); + + for (const menu of menuModules) { + staticMenus.push(transformMenuModule(menu)); + } +})(); + +async function getAsyncMenus() { + const permissionStore = usePermissionStore(); + if (isBackMode()) { + return permissionStore.getBackMenuList.filter((item) => !item.meta?.hideMenu && !item.hideMenu); + } + if (isRouteMappingMode()) { + return permissionStore.getFrontMenuList.filter((item) => !item.hideMenu); + } + return staticMenus; +} + +export const getMenus = async (): Promise => { + const menus = await getAsyncMenus(); + if (isRoleMode()) { + const routes = router.getRoutes(); + return filter(menus, basicFilter(routes)); + } + return menus; +}; + +export async function getCurrentParentPath(currentPath: string) { + const menus = await getAsyncMenus(); + const allParentPath = await getAllParentPath(menus, currentPath); + return allParentPath?.[0]; +} + +// Get the level 1 menu, delete children +export async function getShallowMenus(): Promise { + const menus = await getAsyncMenus(); + const shallowMenuList = menus.map((item) => ({ ...item, children: undefined })); + if (isRoleMode()) { + const routes = router.getRoutes(); + return shallowMenuList.filter(basicFilter(routes)); + } + return shallowMenuList; +} + +// Get the children of the menu +export async function getChildrenMenus(parentPath: string) { + const menus = await getMenus(); + const parent = menus.find((item) => item.path === parentPath); + if (!parent || !parent.children || !!parent?.meta?.hideChildrenInMenu) { + return [] as Menu[]; + } + if (isRoleMode()) { + const routes = router.getRoutes(); + return filter(parent.children, basicFilter(routes)); + } + return parent.children; +} + +function basicFilter(routes: RouteRecordNormalized[]) { + return (menu: Menu) => { + const matchRoute = routes.find((route) => { + if (isUrl(menu.path)) return true; + + if (route.meta?.carryParam) { + return pathToRegexp(route.path).test(menu.path); + } + const isSame = route.path === menu.path; + if (!isSame) return false; + + if (route.meta?.ignoreAuth) return true; + + return isSame || pathToRegexp(route.path).test(menu.path); + }); + + if (!matchRoute) return false; + menu.icon = (menu.icon || matchRoute.meta.icon) as string; + menu.meta = matchRoute.meta; + return true; + }; +} diff --git a/src/router/router.ts b/src/router/router.ts new file mode 100644 index 0000000..7a847b1 --- /dev/null +++ b/src/router/router.ts @@ -0,0 +1,42 @@ +/* + * 路由实例存储文件,请勿轻易添加其他代码,防止出现 HMR 或其他问题 + */ +import type {Router, RouterHistory} from 'vue-router'; +import {createRouter as createVueRouter, createWebHistory, createWebHashHistory, RouterOptions} from 'vue-router'; + +export let router: Router = null as unknown as Router; + +export function setRouter(r: Router) { + router = r +} + +let webHistory: Nullable = null; + +/** + * 创建路由 + * @param options 参数 + * @param useHashHistory 是否使用 hash 路由,true使用,false不使用hash路由 + */ +export function createRouter(options: Partial, useHashHistory = false) { + const createFn = useHashHistory ? createWebHashHistory : createWebHistory; + webHistory = createFn(import.meta.env.VITE_PUBLIC_PATH); + // app router + let router = createVueRouter({ + history: webHistory, + routes: [], + ...options, + }); + + setRouter(router) + + return router +} + +// 销毁路由 +export function destroyRouter() { + setRouter(null as unknown as Router); + if (webHistory) { + webHistory.destroy(); + } + webHistory = null +} diff --git a/src/router/routes/basic.ts b/src/router/routes/basic.ts new file mode 100644 index 0000000..386e75f --- /dev/null +++ b/src/router/routes/basic.ts @@ -0,0 +1,75 @@ +import type { AppRouteRecordRaw } from '/@/router/types'; +import { t } from '/@/hooks/web/useI18n'; +import { REDIRECT_NAME, LAYOUT, EXCEPTION_COMPONENT, PAGE_NOT_FOUND_NAME, PAGE_NOT_FOUND_NAME_404 } from '/@/router/constant'; + +// 404 on a page +export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = { + path: '/:path(.*)*', + + name: PAGE_NOT_FOUND_NAME, + component: LAYOUT, + meta: { + title: 'ErrorPage', + hideBreadcrumb: true, + hideMenu: true, + }, + children: [ + { + path: '/:path(.*)*', + // 代码逻辑说明: 【issues/7500】vue-router4.5.0版本路由name:PageNotFound同名导致登录进不去 + name: PAGE_NOT_FOUND_NAME_404, + component: EXCEPTION_COMPONENT, + meta: { + title: 'ErrorPage', + hideBreadcrumb: true, + hideMenu: true, + }, + }, + ], +}; + +export const REDIRECT_ROUTE: AppRouteRecordRaw = { + path: '/redirect', + component: LAYOUT, + name: 'RedirectTo', + meta: { + title: REDIRECT_NAME, + hideBreadcrumb: true, + hideMenu: true, + }, + children: [ + { + path: '/redirect/:path(.*)', + name: REDIRECT_NAME, + component: () => import('/@/views/sys/redirect/index.vue'), + meta: { + title: REDIRECT_NAME, + hideBreadcrumb: true, + }, + }, + ], +}; + +export const ERROR_LOG_ROUTE: AppRouteRecordRaw = { + path: '/error-log', + name: 'ErrorLog', + component: LAYOUT, + redirect: '/error-log/list', + meta: { + title: 'ErrorLog', + hideBreadcrumb: true, + hideChildrenInMenu: true, + }, + children: [ + { + path: 'list', + name: 'ErrorLogList', + component: () => import('/@/views/sys/error-log/index.vue'), + meta: { + title: t('routes.basic.errorLogList'), + hideBreadcrumb: true, + currentActiveMenu: '/error-log', + }, + }, + ], +}; diff --git a/src/router/routes/index.ts b/src/router/routes/index.ts new file mode 100644 index 0000000..60a4831 --- /dev/null +++ b/src/router/routes/index.ts @@ -0,0 +1,66 @@ +import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types'; + +import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic'; + +import { mainOutRoutes } from './mainOut'; +import { PageEnum } from '/@/enums/pageEnum'; +import { t } from '/@/hooks/web/useI18n'; +import { LAYOUT } from '/@/router/constant'; + +const modules = import.meta.glob('./modules/**/*.ts', { eager: true }); + +const routeModuleList: AppRouteModule[] = []; + +// 加入到路由集合中 +Object.keys(modules).forEach((key) => { + const mod = (modules as Recordable)[key].default || {}; + const modList = Array.isArray(mod) ? [...mod] : [mod]; + routeModuleList.push(...modList); +}); + +export const asyncRoutes = [PAGE_NOT_FOUND_ROUTE, ...routeModuleList]; + +export const RootRoute: AppRouteRecordRaw = { + path: '/', + name: 'Root', + redirect: PageEnum.BASE_HOME, + meta: { + title: 'Root', + }, +}; + +export const LoginRoute: AppRouteRecordRaw = { + path: '/login', + name: 'Login', + component: () => import('/@/views/system/loginmini/MiniLogin.vue'), + meta: { + title: t('routes.basic.login'), + }, +}; + +// 代码逻辑说明: auth2登录页面路由------------ +export const Oauth2LoginRoute: AppRouteRecordRaw = { + path: '/oauth2-app/login', + name: 'oauth2-app-login', + //新版钉钉免登录,如果想要使用旧版放开即可 + // component: () => import('/@/views/sys/login/OAuth2Login.vue'), + component: () => import('/@/views/system/loginmini/OAuth2Login.vue'), + meta: { + title: t('routes.oauth2.login'), + }, +}; + +/** + * 【通过token直接静默登录】流程办理登录页面 中转跳转 + */ +export const TokenLoginRoute: AppRouteRecordRaw = { + path: '/tokenLogin', + name: 'TokenLoginRoute', + component: () => import('/@/views/sys/login/TokenLoginPage.vue'), + meta: { + title: '带token登录页面', + ignoreAuth: true, + }, +}; +// Basic routing without permission +export const basicRoutes = [LoginRoute, RootRoute, ...mainOutRoutes, REDIRECT_ROUTE, PAGE_NOT_FOUND_ROUTE, TokenLoginRoute, Oauth2LoginRoute]; diff --git a/src/router/routes/mainOut.ts b/src/router/routes/mainOut.ts new file mode 100644 index 0000000..99b3cae --- /dev/null +++ b/src/router/routes/mainOut.ts @@ -0,0 +1,11 @@ +/** +The routing of this file will not show the layout. +It is an independent new page. +the contents of the file still need to log in to access + */ +import type { AppRouteModule } from '/@/router/types'; + +// 演示用免登录路由已移除(精简版无 demo 页面) +export const mainOutRoutes: AppRouteModule[] = []; + +export const mainOutRouteNames = mainOutRoutes.map((item) => item.name); diff --git a/src/router/routes/modules/about.ts b/src/router/routes/modules/about.ts new file mode 100644 index 0000000..d32c4f5 --- /dev/null +++ b/src/router/routes/modules/about.ts @@ -0,0 +1,31 @@ +import type { AppRouteModule } from '/@/router/types'; + +import { LAYOUT } from '/@/router/constant'; +import { t } from '/@/hooks/web/useI18n'; + +const dashboard: AppRouteModule = { + path: '/about', + name: 'About', + component: LAYOUT, + redirect: '/about/index', + meta: { + hideChildrenInMenu: true, + icon: 'simple-icons:about-dot-me', + title: t('routes.dashboard.about'), + orderNo: 100000, + }, + children: [ + { + path: 'index', + name: 'AboutPage', + component: () => import('/@/views/sys/about/index.vue'), + meta: { + title: t('routes.dashboard.about'), + icon: 'simple-icons:about-dot-me', + hideMenu: true, + }, + }, + ], +}; + +export default dashboard; diff --git a/src/router/routes/modules/dashboard.ts b/src/router/routes/modules/dashboard.ts new file mode 100644 index 0000000..2a7bebb --- /dev/null +++ b/src/router/routes/modules/dashboard.ts @@ -0,0 +1,37 @@ +import type { AppRouteModule } from '/@/router/types'; +import { PageEnum } from '/@/enums/pageEnum'; +import { LAYOUT } from '/@/router/constant'; +import { t } from '/@/hooks/web/useI18n'; + +const dashboard: AppRouteModule = { + path: '/dashboard', + name: 'Dashboard', + component: LAYOUT, + redirect: PageEnum.BASE_HOME, + meta: { + orderNo: 10, + icon: 'ion:grid-outline', + title: t('routes.dashboard.dashboard'), + }, + children: [ + { + path: 'analysis', + name: 'Analysis', + component: () => import('/@/views/dashboard/Analysis/index.vue'), + meta: { + // affix: true, + title: t('routes.dashboard.analysis'), + }, + }, + { + path: 'workbench', + name: 'Workbench', + component: () => import('/@/views/dashboard/workbench/index.vue'), + meta: { + title: t('routes.dashboard.workbench'), + }, + }, + ], +}; + +export default dashboard; diff --git a/src/router/routes/staticRouter.ts b/src/router/routes/staticRouter.ts new file mode 100644 index 0000000..e7e95e1 --- /dev/null +++ b/src/router/routes/staticRouter.ts @@ -0,0 +1,23 @@ +import type { AppRouteRecordRaw } from '/@/router/types'; +import { LAYOUT } from '/@/router/constant'; + +export const AI_ROUTE: AppRouteRecordRaw = { + path: '', + name: 'ai-parent', + component: LAYOUT, + meta: { + title: 'ai', + }, + children: [ + { + path: '/ai', + name: 'ai', + component: () => import('/@/views/dashboard/ai/index.vue'), + meta: { + title: 'AI助手', + }, + }, + ], +}; + +export const staticRoutesList = [AI_ROUTE]; diff --git a/src/router/types.ts b/src/router/types.ts new file mode 100644 index 0000000..995dd79 --- /dev/null +++ b/src/router/types.ts @@ -0,0 +1,60 @@ +import type { RouteRecordRaw, RouteMeta } from 'vue-router'; +import { RoleEnum } from '/@/enums/roleEnum'; +import { defineComponent } from 'vue'; + +export type Component = ReturnType | (() => Promise) | (() => Promise); + +// @ts-ignore +export interface AppRouteRecordRaw extends Omit { + name: string; + meta: RouteMeta; + component?: Component | string; + originComponent?: string; + components?: Component; + children?: AppRouteRecordRaw[]; + props?: Recordable; + fullPath?: string; + alwaysShow?: boolean; +} + +export interface MenuTag { + type?: 'primary' | 'error' | 'warn' | 'success'; + content?: string; + dot?: boolean; +} + +export interface Menu { + name: string; + + icon?: string; + + path: string; + + // path contains param, auto assignment. + paramPath?: string; + + disabled?: boolean; + + children?: Menu[]; + + orderNo?: number; + + roles?: RoleEnum[]; + + meta?: Partial; + + tag?: MenuTag; + + hideMenu?: boolean; + + alwaysShow?: boolean; + +} + +export interface MenuModule { + orderNo?: number; + menu: Menu; +} + +// export type AppRouteModule = RouteModule | AppRouteRecordRaw; +export type AppRouteModule = AppRouteRecordRaw; diff --git a/src/settings/componentSetting.ts b/src/settings/componentSetting.ts new file mode 100644 index 0000000..d9c2257 --- /dev/null +++ b/src/settings/componentSetting.ts @@ -0,0 +1,91 @@ +// 用于配置某些组件的常规配置,而无需修改组件 + +import type { SorterResult } from '../components/Table'; + +export default { + // 表格配置 + table: { + // 表格接口请求通用配置,可在组件prop覆盖 + // 支持 xxx.xxx.xxx格式 + fetchSetting: { + // 传给后台的当前页字段 + pageField: 'pageNo', + // 传给后台的每页显示多少条的字段 + sizeField: 'pageSize', + // 接口返回表格数据的字段 + listField: 'records', + // 接口返回表格总数的字段 + totalField: 'total', + }, + // 可选的分页选项 + pageSizeOptions: ['10', '50', '80', '100'], + // 表格默认尺寸 + defaultSize: 'middle', + //默认每页显示多少条 + defaultPageSize: 10, + // 默认排序方法 + defaultSortFn: (sortInfo: SorterResult) => { + // 代码逻辑说明: VUEN-2199【表单设计器】多字段排序 + if(sortInfo instanceof Array){ + let sortInfoArray:any[] = [] + for(let item of sortInfo){ + let info = getSort(item); + if(info){ + sortInfoArray.push(info) + } + } + return { + sortInfoString: JSON.stringify(sortInfoArray) + } + }else{ + let info = getSort(sortInfo) + return info || {} + } + }, + // 自定义过滤方法 + defaultFilterFn: (data: Partial>) => { + return data; + }, + // 代码逻辑说明: 【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义 + scrollToFirstRowOnChange: false, + }, + // 滚动组件配置 + scrollbar: { + // 是否使用原生滚动样式 + // 开启后,菜单,弹窗,抽屉会使用原生滚动条组件 + native: false, + }, + //表单配置 + form: { + labelCol: { + xs: { span: 24 }, + sm: { span: 4 }, + xl: { span: 6 }, + xxl: { span: 4 }, + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 18 }, + }, + //表单默认冒号 + colon: true, + }, +}; + +/** + * 获取排序信息 + * @param item + */ +function getSort(item){ + const { field, order } = item; + if (field && order) { + let sortType = 'ascend' == order ? 'asc' : 'desc'; + return { + // 排序字段 + column: field, + // 排序方式 asc/desc + order: sortType, + }; + } + return '' +} diff --git a/src/settings/designSetting.ts b/src/settings/designSetting.ts new file mode 100644 index 0000000..fddd4ff --- /dev/null +++ b/src/settings/designSetting.ts @@ -0,0 +1,71 @@ +import { ThemeEnum } from '../enums/appEnum'; + +export const prefixCls = 'jeecg'; + +export const darkMode = ThemeEnum.LIGHT; + +// app theme preset color +export const APP_PRESET_COLOR_LIST: string[] = [ + '#0960bd', + '#1890ff', + '#009688', + '#536dfe', + '#ff5c93', + '#13c2c2', + '#52c41a', + '#ee4f12', + '#0096c7', + '#9c27b0', + '#ff9800', +]; + +// header preset color +export const HEADER_PRESET_BG_COLOR_LIST: string[] = [ + '#ffffff', + '#151515', + '#009688', + '#5172DC', + '#018ffb', + '#13c2c2', + '#e74c3c', + '#52c41a', + '#394664', + '#faad14', + '#383f45', +]; + +// sider preset color +export const SIDE_BAR_BG_COLOR_LIST: string[] = [ + '#001529', + // '#212121', + '#009688', + '#273352', + '#ffffff', + '#191b24', + // '#191a23', + '#037bd5', + '#304156', + '#001628', + '#28333E', + // '#344058', + '#e74c3c', + '#383f45', +]; + +// sider logo line preset color [logo����ɫ] +export const SIDER_LOGO_BG_COLOR_LIST: string[] = [ + 'linear-gradient(180deg, #000000, #021d37)', + // 'linear-gradient(180deg, #000000, #282828)', + 'linear-gradient(180deg, #078d80, #029184)', + 'linear-gradient(180deg, #1c253e, #2b385c)', + 'linear-gradient(180deg, #ffffff, #ffffff)', + 'linear-gradient(180deg, #000000, #242735)', + // 'linear-gradient(180deg, #000000, #1d1f2a)', + 'linear-gradient(180deg, #1d77bb, #188efa)', + 'linear-gradient(180deg, #304156, #32455d)', + 'linear-gradient(180deg, #000000, #001f39)', + 'linear-gradient(180deg, #000000, #2b3743)', + // 'linear-gradient(180deg, #344058, #374560)', + 'linear-gradient(180deg, #e83723, #e52611)', + 'linear-gradient(180deg, #383f45, #3b434b)', +]; diff --git a/src/settings/encryptionSetting.ts b/src/settings/encryptionSetting.ts new file mode 100644 index 0000000..e9266f7 --- /dev/null +++ b/src/settings/encryptionSetting.ts @@ -0,0 +1,13 @@ +import { isDevMode } from '/@/utils/env'; + +// 缓存默认过期时间 +export const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7; + +// 开启缓存加密后,加密密钥。采用aes加密 +export const cacheCipher = { + key: '_11111000001111@', + iv: '@11111000001111_', +}; + +// 是否加密缓存,默认生产环境加密 +export const enableStorageEncryption = false; diff --git a/src/settings/localeSetting.ts b/src/settings/localeSetting.ts new file mode 100644 index 0000000..304f807 --- /dev/null +++ b/src/settings/localeSetting.ts @@ -0,0 +1,30 @@ +import type { DropMenu } from '../components/Dropdown'; +import type { LocaleSetting, LocaleType } from '/#/config'; + +export const LOCALE: { [key: string]: LocaleType } = { + ZH_CN: 'zh_CN', + EN_US: 'en', +}; + +export const localeSetting: LocaleSetting = { + // 是否显示语言选择器 + showPicker: true, + // 当前语言 + locale: LOCALE.ZH_CN, + // 默认语言 + fallback: LOCALE.ZH_CN, + // 允许的语言 + availableLocales: [LOCALE.ZH_CN, LOCALE.EN_US], +}; + +// 语言列表 +export const localeList: DropMenu[] = [ + { + text: '简体中文', + event: LOCALE.ZH_CN, + }, + { + text: 'English', + event: LOCALE.EN_US, + }, +]; diff --git a/src/settings/projectSetting.ts b/src/settings/projectSetting.ts new file mode 100644 index 0000000..40e1522 --- /dev/null +++ b/src/settings/projectSetting.ts @@ -0,0 +1,203 @@ +import type { ProjectConfig } from '/#/config'; +import { MenuTypeEnum, MenuModeEnum, TriggerEnum, MixSidebarTriggerEnum } from '/@/enums/menuEnum'; +import { CacheTypeEnum } from '/@/enums/cacheEnum'; +import { + ContentEnum, + PermissionModeEnum, + ThemeEnum, + RouterTransitionEnum, + SettingButtonPositionEnum, + SessionTimeoutProcessingEnum, + TabsThemeEnum, +} from '/@/enums/appEnum'; +import { darkMode } from '/@/settings/designSetting'; +import { getConfigByMenuType } from '../utils/getConfigByMenuType'; +// 修改此属性,实现默认的四个系统主题快速切换 +const menuType = MenuTypeEnum.SIDEBAR; + +// update-begin--author:liaozhiyang---date:20251201---for【QQYUN-14176】修改一个配置就能切换默认四个主题,不需要额外修改颜色等 +const { themeColor, headerBgColor, sideBgColor, split, mode } = getConfigByMenuType(menuType); +// update-end--author:liaozhiyang---date:20251201---for【QQYUN-14176】修改一个配置就能切换默认四个主题,不需要额外修改颜色等 +// ! 改动后需要清空浏览器缓存 +const setting: ProjectConfig = { + // 是否显示SettingButton + showSettingButton: true, + + // 是否显示主题切换按钮 + showDarkModeToggle: true, + + // 设置按钮位置 可选项 + // SettingButtonPositionEnum.AUTO: 自动选择 + // SettingButtonPositionEnum.HEADER: 位于头部 + // SettingButtonPositionEnum.FIXED: 固定在右侧 + settingButtonPosition: SettingButtonPositionEnum.AUTO, + + // 权限模式,默认前端角色权限模式 + // ROUTE_MAPPING: 前端模式(菜单由路由生成,默认) + // ROLE:前端模式(菜单路由分开) + // BACK:后台模式 + permissionMode: PermissionModeEnum.BACK, + + // 权限缓存存放位置。默认存放于localStorage + permissionCacheType: CacheTypeEnum.LOCAL, + + // 会话超时处理方案 + // SessionTimeoutProcessingEnum.ROUTE_JUMP: 路由跳转到登录页 + // SessionTimeoutProcessingEnum.PAGE_COVERAGE: 生成登录弹窗,覆盖当前页面 + sessionTimeoutProcessing: SessionTimeoutProcessingEnum.ROUTE_JUMP, + // 项目主题色 - 根据导航栏模式确定主题色动态设置 + themeColor: themeColor, + // 项目主题模式 + themeMode: darkMode, + + // 网站灰色模式,用于可能悼念的日期开启 + grayMode: false, + + // 色弱模式 + colorWeak: false, + + // 是否取消菜单,顶部,多标签页显示, 用于可能内嵌在别的系统内 + fullContent: false, + + // 主题内容宽度 + contentMode: ContentEnum.FULL, + + // 是否显示logo + showLogo: true, + + // 是否显示底部信息 copyright + showFooter: false, + + // ai图标显示 + aiIconShow: false, + + // 头部配置 + headerSetting: { + // 背景色 + bgColor: headerBgColor, + // 固定头部 + fixed: true, + // 是否显示顶部 + show: true, + // 主题 + theme: ThemeEnum.LIGHT, + // 开启锁屏功能 + useLockPage: false, + // 显示全屏按钮 + showFullScreen: false, + // 显示官网按钮 + showDoc: false, + // 显示消息中心按钮 + showNotice: true, + // 显示菜单搜索按钮 + showSearch: true, + }, + + // 菜单配置 + menuSetting: { + // 背景色 + bgColor: sideBgColor, + // 是否固定住左侧菜单 + fixed: true, + // 菜单折叠 + collapsed: false, + // 折叠菜单时候是否显示菜单名 + collapsedShowTitle: false, + // 是否可拖拽 + // Only limited to the opening of the left menu, the mouse has a drag bar on the right side of the menu + canDrag: false, + // Whether to show no dom + show: true, + // Whether to show dom + hidden: false, + // 菜单宽度 + menuWidth: 210, + // 菜单模式 + mode, + // 菜单类型 + type: menuType, + // 菜单主题 + theme: ThemeEnum.LIGHT, + // 左侧导航栏文字颜色调整区分彩色和暗黑 (不对应配置) + isThemeBright: false, + // 分割菜单 + split, + // 顶部菜单布局 + topMenuAlign: 'center', + // 折叠触发器的位置 + trigger: TriggerEnum.HEADER, + // 手风琴模式,只展示一个菜单 + accordion: true, + // 在路由切换的时候关闭左侧混合菜单展开菜单 + closeMixSidebarOnChange: false, + // 左侧混合菜单模块切换触发方式 ‘click’ |'hover' + mixSideTrigger: MixSidebarTriggerEnum.CLICK, + // 是否固定左侧混合菜单 + mixSideFixed: false, + }, + + // 多标签 + multiTabsSetting: { + // 刷新后是否保留已经打开的标签页 + cache: false, + // 开启 + show: true, + // 是否可以拖拽 + canDrag: true, + // 开启快速操作 + showQuick: true, + // 是否显示刷新按钮 + showRedo: true, + // 是否显示折叠按钮 + showFold: true, + // 标签页样式 + theme: TabsThemeEnum.CARD, + }, + + // 动画配置 + transitionSetting: { + // 是否开启切换动画 + // The disabled state will also disable pageLoading + enable: true, + + // 动画名 Route basic switching animation + basicTransition: RouterTransitionEnum.FADE_SIDE, + + // 是否打开页面切换loading + // Only open when enable=true + openPageLoading: true, + + //是否打开页面切换顶部进度条 + openNProgress: true, + }, + + // 是否开启KeepAlive缓存 开发时候最好关闭,不然每次都需要清除缓存 + openKeepAlive: true, + + // 自动锁屏时间,为0不锁屏。 单位分钟 默认1个小时 + lockTime: 0, + + // 显示面包屑 + showBreadCrumb: false, + + // 显示面包屑图标 + showBreadCrumbIcon: true, + + // 是否使用全局错误捕获 + useErrorHandle: false, + + // 是否开启回到顶部 + useOpenBackTop: true, + + // 是否可以嵌入iframe页面 + canEmbedIFramePage: true, + + // 切换界面的时候是否删除未关闭的message及notify + closeMessageOnSwitch: true, + + // 切换界面的时候是否取消已经发送但是未响应的http请求。 + // 如果开启,想对单独接口覆盖。可以在单独接口设置 + removeAllHttpPending: false, +}; + +export default setting; diff --git a/src/settings/registerThirdComp.ts b/src/settings/registerThirdComp.ts new file mode 100644 index 0000000..1e21719 --- /dev/null +++ b/src/settings/registerThirdComp.ts @@ -0,0 +1,68 @@ +import type { App } from 'vue'; +import { registerJVxeTable } from '/@/components/jeecg/JVxeTable'; +import { registerJVxeCustom } from '/@/components/JVxeCustom'; + +// 注册全局dayjs +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; +import customParseFormat from 'dayjs/plugin/customParseFormat'; +import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; + +// JVxeTable 按需加载:仅首次渲染时注册一次 +let jvxeRegistered = false; + +export async function registerThirdComp(app: App) { + //--------------------------------------------------------------------- + // update-begin--author:liaozhiyang---date:20260210---for:【QQYUN-13658】Jvxetable、vxetable按需加载 + // 注册 JVxeTable 组件(按需加载:首次使用 时才加载 vxe-table 与 JVxeTable) + app.component( + 'JVxeTable', + createAsyncComponent( + () => { + return import('/@/components/jeecg/JVxeTable/src/JVxeTable').then(async (m) => { + if (!jvxeRegistered) { + if (app._context.components.VxeTable) { + // 已全局注册 + } else { + const { registerJVxeTable } = await import('/@/components/jeecg/JVxeTable/src/install'); + await registerJVxeTable(app); + const { registerJVxeCustom } = await import('/@/components/JVxeCustom'); + await registerJVxeCustom(); + jvxeRegistered = true; + } + } + return m.default; + }); + }, + { loading: true } + ) + ); + // update-end--author:liaozhiyang---date:20260209---for:【QQYUN-13658】Jvxetable、vxetable按需加载 + //--------------------------------------------------------------------- + // 注册全局聊天表情包 + // 代码逻辑说明: 【QQYUN-8241】emoji-mart-vue-fast库异步加载 + app.component( + 'Picker', + createAsyncComponent(() => { + return new Promise((resolve, rejected) => { + import('emoji-mart-vue-fast/src') + .then((res) => { + const { Picker } = res; + resolve(Picker); + }) + .catch((err) => { + rejected(err); + }); + }); + }) + ); + // update-end--author:liaozhiyang---date:20240308---for:【QQYUN-8241】emoji-mart-vue-fast库异步加载 + //--------------------------------------------------------------------- + // 注册全局dayjs + dayjs.locale('zh-cn'); + dayjs.extend(relativeTime); + dayjs.extend(customParseFormat); + app.config.globalProperties.$dayjs = dayjs + app.provide('$dayjs', dayjs) + //--------------------------------------------------------------------- +} diff --git a/src/settings/siteSetting.ts b/src/settings/siteSetting.ts new file mode 100644 index 0000000..331a4c0 --- /dev/null +++ b/src/settings/siteSetting.ts @@ -0,0 +1,8 @@ +// github repo url +export const GITHUB_URL = 'https://github.com/jeecgboot/JeecgBoot'; + +// vue-Jeecg-admin-next-doc +export const DOC_URL = 'https://help.jeecg.com'; + +// site url +export const SITE_URL = 'http://www.jeecg.com'; diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..9aee017 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,24 @@ +import type { App } from 'vue'; +import type { Pinia } from 'pinia'; +import { createPinia } from 'pinia'; + +let app: Nullable> = null; +let store: Nullable = null; + +export function setupStore($app: App) { + if (store == null) { + store = createPinia(); + } + app = $app; + app.use(store); +} + +// 销毁store +export function destroyStore() { + store = null; +} + +// 获取app实例 +export const getAppContext = () => app?._context; + +export {app, store}; diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts new file mode 100644 index 0000000..ee2f831 --- /dev/null +++ b/src/store/modules/app.ts @@ -0,0 +1,165 @@ +import type { MainAppProps } from "#/main"; +import type { ProjectConfig, HeaderSetting, MenuSetting, TransitionSetting, MultiTabsSetting } from '/#/config'; +import type { BeforeMiniState } from '/#/store'; + +import { defineStore } from 'pinia'; +import { store } from '/@/store'; + +import { ThemeEnum } from '/@/enums/appEnum'; +import { APP_DARK_MODE_KEY_, PROJ_CFG_KEY } from '/@/enums/cacheEnum'; +import { Persistent } from '/@/utils/cache/persistent'; +import { darkMode } from '/@/settings/designSetting'; +import { resetRouter } from '/@/router'; +import { deepMerge } from '/@/utils'; +import { getHideLayoutTypes } from '/@/utils/env'; +import setting from '/@/settings/projectSetting'; + +interface AppState { + darkMode?: ThemeEnum; + // Page loading status + pageLoading: boolean; + // project config + projectConfig: ProjectConfig | null; + // When the window shrinks, remember some states, and restore these states when the window is restored + beforeMiniInfo: BeforeMiniState; + // 页面跳转临时参数存储 + messageHrefParams: any, + // 应用参数 + mainAppProps: MainAppProps, +} +let timeId: TimeoutHandle; +export const useAppStore = defineStore({ + id: 'app', + state: (): AppState => ({ + darkMode: undefined, + pageLoading: false, + projectConfig: Persistent.getLocal(PROJ_CFG_KEY), + beforeMiniInfo: {}, + messageHrefParams: {}, + mainAppProps: {}, + }), + getters: { + getPageLoading(): boolean { + return this.pageLoading; + }, + getDarkMode(): 'light' | 'dark' | string { + // liaozhiyang---date:20250411---for:【QQYUN-11956】修复projectSetting中配置主题模式不生效 + const getSettingTheme = () => { + const theme = setting.themeMode; + if (theme) { + if (theme == ThemeEnum.DARK) { + // 为了index.html页面loading时是暗黑 + localStorage.setItem(APP_DARK_MODE_KEY_, theme); + } + return theme; + } + return ''; + }; + // liaozhiyang---date:20250411---for:【QQYUN-11956】修复projectSetting中配置主题模式不生效 + return this.darkMode || localStorage.getItem(APP_DARK_MODE_KEY_) || getSettingTheme() || darkMode; + }, + + getBeforeMiniInfo(): BeforeMiniState { + return this.beforeMiniInfo; + }, + + getProjectConfig(): ProjectConfig { + return this.projectConfig || ({} as ProjectConfig); + }, + + getHeaderSetting(): HeaderSetting { + return this.getProjectConfig.headerSetting; + }, + getMenuSetting(): MenuSetting { + return this.getProjectConfig.menuSetting; + }, + getTransitionSetting(): TransitionSetting { + return this.getProjectConfig.transitionSetting; + }, + getMultiTabsSetting(): MultiTabsSetting { + return this.getProjectConfig.multiTabsSetting; + }, + getMessageHrefParams():any{ + return this.messageHrefParams; + }, + getMainAppProps(): MainAppProps { + return this.mainAppProps; + }, + + getLayoutHideSider(): boolean { + const hideLayoutTypes = getHideLayoutTypes(); + if (hideLayoutTypes.includes('sider')) { + return true; + } + return !!this.mainAppProps.hideSider; + }, + getLayoutHideHeader(): boolean { + const hideLayoutTypes = getHideLayoutTypes(); + if (hideLayoutTypes.includes('header')) { + return true; + } + return !!this.mainAppProps.hideHeader; + }, + getLayoutHideMultiTabs(): boolean { + const hideLayoutTypes = getHideLayoutTypes(); + if (hideLayoutTypes.includes('multi-tabs')) { + return true; + } + return !!this.mainAppProps.hideMultiTabs; + }, + }, + actions: { + setPageLoading(loading: boolean): void { + this.pageLoading = loading; + }, + + setDarkMode(mode: ThemeEnum): void { + this.darkMode = mode; + localStorage.setItem(APP_DARK_MODE_KEY_, mode); + this.setProjectConfig({ themeMode: mode }); + }, + + setBeforeMiniInfo(state: BeforeMiniState): void { + this.beforeMiniInfo = state; + }, + + setProjectConfig(config: DeepPartial): void { + this.projectConfig = deepMerge(this.projectConfig || {}, config); + // 代码逻辑说明: 【QQYUN-8922】设置导航栏模式没存本地,刷新就还原了 + Persistent.setLocal(PROJ_CFG_KEY, this.projectConfig, true); + }, + + async resetAllState() { + resetRouter(); + Persistent.clearAll(); + }, + async setPageLoadingAction(loading: boolean): Promise { + if (loading) { + clearTimeout(timeId); + // Prevent flicker + timeId = setTimeout(() => { + this.setPageLoading(loading); + }, 50); + } else { + this.setPageLoading(loading); + clearTimeout(timeId); + } + }, + setMessageHrefParams(params: any): void { + this.messageHrefParams = params; + }, + + // 设置主应用参数 + setMainAppProps(args: MainAppProps) { + this.mainAppProps.hideHeader = args.hideHeader ?? false; + this.mainAppProps.hideSider = args.hideSider ?? false; + this.mainAppProps.hideMultiTabs = args.hideMultiTabs ?? false; + }, + + }, +}); + +// Need to be used outside the setup +export function useAppStoreWithOut() { + return useAppStore(store); +} diff --git a/src/store/modules/defIndex.ts b/src/store/modules/defIndex.ts new file mode 100644 index 0000000..5e6e505 --- /dev/null +++ b/src/store/modules/defIndex.ts @@ -0,0 +1,76 @@ +import {store} from '/@/store'; +import {defineStore} from 'pinia'; +import {defHttp} from "@/utils/http/axios"; + +interface DefIndexState { + // 首页url + url: string, + // 首页组件 + component: string +} + +export const useDefIndexStore = defineStore({ + id: 'defIndex', + state: (): DefIndexState => ({ + url: '', + component: '', + }), + getters: {}, + actions: { + /** + * 查询默认主页配置 + */ + async query() { + const config = await defIndexApi.query(); + this.url = config.url; + this.component = config.component; + }, + /** + * 更新默认主页配置 + * @param url 首页url + * @param component 首页组件 + * @param isRoute 是否是路由 + */ + async update(url: string, component: string, isRoute: boolean) { + await defIndexApi.update(url, component, isRoute); + await this.query() + }, + + check(url: string) { + return url === this.url; + } + } +}); + +// Need to be used outside the setup +export function useDefIndexStoreWithOut() { + return useDefIndexStore(store); +} + +/** + * 默认首页配置API + */ +export const defIndexApi = { + /** + * 查询默认首页配置 + */ + async query() { + const url = '/sys/sysRoleIndex/queryDefIndex' + return await defHttp.get({url}); + }, + /** + * 更新默认首页配置 + * @param url 首页url + * @param component 首页组件 + * @param isRoute 是否是路由 + */ + async update(url: string, component: string, isRoute: boolean) { + let apiUrl = '/sys/sysRoleIndex/updateDefIndex' + apiUrl += '?url=' + url + // 代码逻辑说明: 设置默认首页接口传参修改,增加encodeURIComponent,防止{{ window._CONFIG['domianURL'] }}/**保存不上 + apiUrl += '&component=' + encodeURIComponent(component) + apiUrl += '&isRoute=' + isRoute + return await defHttp.put({url: apiUrl}); + }, + +} diff --git a/src/store/modules/errorLog.ts b/src/store/modules/errorLog.ts new file mode 100644 index 0000000..c95edba --- /dev/null +++ b/src/store/modules/errorLog.ts @@ -0,0 +1,74 @@ +import type { ErrorLogInfo } from '/#/store'; + +import { defineStore } from 'pinia'; +import { store } from '/@/store'; + +import { formatToDateTime } from '/@/utils/dateUtil'; +import projectSetting from '/@/settings/projectSetting'; + +import { ErrorTypeEnum } from '/@/enums/exceptionEnum'; + +export interface ErrorLogState { + errorLogInfoList: Nullable; + errorLogListCount: number; +} + +export const useErrorLogStore = defineStore({ + id: 'app-error-log', + state: (): ErrorLogState => ({ + errorLogInfoList: null, + errorLogListCount: 0, + }), + getters: { + getErrorLogInfoList(): ErrorLogInfo[] { + return this.errorLogInfoList || []; + }, + getErrorLogListCount(): number { + return this.errorLogListCount; + }, + }, + actions: { + addErrorLogInfo(info: ErrorLogInfo) { + const item = { + ...info, + time: formatToDateTime(new Date()), + }; + this.errorLogInfoList = [item, ...(this.errorLogInfoList || [])]; + this.errorLogListCount += 1; + }, + + setErrorLogListCount(count: number): void { + this.errorLogListCount = count; + }, + + /** + * Triggered after ajax request error + * @param error + * @returns + */ + addAjaxErrorInfo(error) { + const { useErrorHandle } = projectSetting; + if (!useErrorHandle) { + return; + } + const errInfo: Partial = { + message: error.message, + type: ErrorTypeEnum.AJAX, + }; + if (error.response) { + const { config: { url = '', data: params = '', method = 'get', headers = {} } = {}, data = {} } = error.response; + errInfo.url = url; + errInfo.name = 'Ajax Error!'; + errInfo.file = '-'; + errInfo.stack = JSON.stringify(data); + errInfo.detail = JSON.stringify({ params, method, headers }); + } + this.addErrorLogInfo(errInfo as ErrorLogInfo); + }, + }, +}); + +// Need to be used outside the setup +export function useErrorLogStoreWithOut() { + return useErrorLogStore(store); +} diff --git a/src/store/modules/locale.ts b/src/store/modules/locale.ts new file mode 100644 index 0000000..31030c0 --- /dev/null +++ b/src/store/modules/locale.ts @@ -0,0 +1,83 @@ +import type { LocaleSetting, LocaleType } from '/#/config'; + +import { defineStore } from 'pinia'; +import { store } from '/@/store'; + +import { LOCALE_KEY } from '/@/enums/cacheEnum'; +import { createLocalStorage } from '/@/utils/cache'; +import { localeSetting } from '/@/settings/localeSetting'; + +const ls = createLocalStorage(); + +const lsLocaleSetting = (ls.get(LOCALE_KEY) || localeSetting) as LocaleSetting; + +interface LocaleState { + localInfo: LocaleSetting; + pathTitleMap: object; + // myapps主题色(低代码应用列表首页) + appIndexTheme: string + // myapps - 跳转前路由地址 + appMainPth: string +} + +export const useLocaleStore = defineStore({ + id: 'app-locale', + state: (): LocaleState => ({ + localInfo: lsLocaleSetting, + pathTitleMap: {}, + appIndexTheme: '', + appMainPth: '' + }), + getters: { + getShowPicker(): boolean { + return !!this.localInfo?.showPicker; + }, + getLocale(): LocaleType { + return this.localInfo?.locale ?? 'zh_CN'; + }, + // 代码逻辑说明: VUEN-1144 online 配置成菜单后,打开菜单,显示名称未展示为菜单名称 + getPathTitle: (state) => { + return (path) => state.pathTitleMap[path]; + }, + getAppIndexTheme(): string { + return this.appIndexTheme; + }, + getAppMainPth(): string { + return this.appMainPth; + }, + }, + actions: { + /** + * Set up multilingual information and cache + * @param info multilingual info + */ + setLocaleInfo(info: Partial) { + this.localInfo = { ...this.localInfo, ...info }; + ls.set(LOCALE_KEY, this.localInfo); + }, + /** + * Initialize multilingual information and load the existing configuration from the local cache + */ + initLocale() { + this.setLocaleInfo({ + ...localeSetting, + ...this.localInfo, + }); + }, + // 代码逻辑说明: VUEN-1144 online 配置成菜单后,打开菜单,显示名称未展示为菜单名称 + setPathTitle(path, title) { + this.pathTitleMap[path] = title; + }, + setAppIndexTheme(theme) { + this.appIndexTheme = theme; + }, + setAppMainPth(path) { + this.appMainPth = path; + }, + }, +}); + +// Need to be used outside the setup +export function useLocaleStoreWithOut() { + return useLocaleStore(store); +} diff --git a/src/store/modules/lock.ts b/src/store/modules/lock.ts new file mode 100644 index 0000000..90f8b48 --- /dev/null +++ b/src/store/modules/lock.ts @@ -0,0 +1,40 @@ +import type { LockInfo } from '/#/store'; + +import { defineStore } from 'pinia'; + +import { LOCK_INFO_KEY } from '/@/enums/cacheEnum'; +import { Persistent } from '/@/utils/cache/persistent'; +import { useUserStore } from './user'; + +interface LockState { + lockInfo: Nullable; +} + +export const useLockStore = defineStore({ + id: 'app-lock', + state: (): LockState => ({ + lockInfo: Persistent.getLocal(LOCK_INFO_KEY), + }), + getters: { + getLockInfo(): Nullable { + return this.lockInfo; + }, + }, + actions: { + setLockInfo(info: LockInfo) { + this.lockInfo = Object.assign({}, this.lockInfo, info); + Persistent.setLocal(LOCK_INFO_KEY, this.lockInfo, true); + }, + resetLockInfo() { + Persistent.removeLocal(LOCK_INFO_KEY, true); + this.lockInfo = null; + }, + // Unlock + async unLock(password?: string) { + if (this.lockInfo?.pwd === password) { + this.resetLockInfo(); + return true; + } + }, + }, +}); diff --git a/src/store/modules/multipleTab.ts b/src/store/modules/multipleTab.ts new file mode 100644 index 0000000..68af9f3 --- /dev/null +++ b/src/store/modules/multipleTab.ts @@ -0,0 +1,472 @@ +import type { RouteLocationNormalized, RouteLocationRaw, Router } from 'vue-router'; + +import { toRaw, unref } from 'vue'; +import { defineStore } from 'pinia'; +import { store } from '/@/store'; +import { PAGE_NOT_FOUND_NAME_404 } from '/@/router/constant'; + +import { useGo, useRedo } from '/@/hooks/web/usePage'; +import { Persistent } from '/@/utils/cache/persistent'; + +import { PageEnum } from '/@/enums/pageEnum'; +import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic'; +import { getRawRoute } from '/@/utils'; +import { MULTIPLE_TABS_KEY } from '/@/enums/cacheEnum'; + +import projectSetting from '/@/settings/projectSetting'; +import { useUserStore } from '/@/store/modules/user'; +import type { LocationQueryRaw, RouteParamsRaw } from 'vue-router'; +import { getMenus } from '/@/router/menus'; + +export interface MultipleTabState { + cacheTabList: Set; + tabList: RouteLocationNormalized[]; + lastDragEndIndex: number; + redirectPageParam: null | redirectPageParamType; +} + +interface redirectPageParamType { + redirect_type: string; + name?: string; + path?: string; + query: LocationQueryRaw; + params?: RouteParamsRaw; +} + +function handleGotoPage(router: Router, path?) { + const go = useGo(router); + // 代码逻辑说明: 【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用 + go(path || unref(router.currentRoute).path, true); +} +const getToTarget = (tabItem: RouteLocationNormalized) => { + const { params, path, query } = tabItem; + return { + params: params || {}, + path, + query: query || {}, + }; +}; + +/** + * 2024-06-05 + * liaozhiyang + * 关闭的tab中是否包含当前页面 + */ +const closeTabContainCurrentRoute = (router, pathList) => { + const { currentRoute } = router; + const getCurrentTab = () => { + const route = unref(currentRoute); + const tabStore = useMultipleTabStore(); + return tabStore.getTabList.find((item) => item.path === route.path)!; + }; + const currentTab = getCurrentTab(); + if (currentTab) { + return pathList.includes(currentTab.path); + } + return false; +}; +/** + * 2025-05-08 + * liaozhiyang + * 【issues/8216】online生成的菜单sql 自动带上组件名称 + * */ +function getMatchingRoute(menus, path) { + for (let i = 0, len = menus.length; i < len; i++) { + const item = menus[i]; + if (item.path === path && !item.redirect && !item.paramPath) { + return item; + } else if (item.children?.length) { + const result = getMatchingRoute(item.children, path); + if (result) { + return result; + } + } + } + return null; +} + +const cacheTab = projectSetting.multiTabsSetting.cache; + +export const useMultipleTabStore = defineStore({ + id: 'app-multiple-tab', + state: (): MultipleTabState => ({ + // Tabs that need to be cached + cacheTabList: new Set(), + // multiple tab list + tabList: cacheTab ? Persistent.getLocal(MULTIPLE_TABS_KEY) || [] : [], + // Index of the last moved tab + lastDragEndIndex: 0, + // 重定向时存储的路由参数 + redirectPageParam: null, + }), + getters: { + getTabList(): RouteLocationNormalized[] { + return this.tabList; + }, + getCachedTabList(): string[] { + return Array.from(this.cacheTabList); + }, + getLastDragEndIndex(): number { + return this.lastDragEndIndex; + }, + }, + actions: { + /** + * Update the cache according to the currently opened tabs + */ + async updateCacheTab() { + const cacheMap: Set = new Set(); + const allMenus = await getMenus(); + for (const tab of this.tabList) { + const item = getRawRoute(tab); + // Ignore the cache + const needCache = !item.meta?.ignoreKeepAlive; + if (!needCache) { + continue; + } + // 代码逻辑说明: 【QQYUN-12348】online生成的菜单sql 自动带上组件名称 + if ( + ['OnlineAutoList', 'DefaultOnlineList', 'CgformErpList', 'OnlCgformInnerTableList', 'OnlCgformTabList', 'OnlCgReportList', 'GraphreportAutoChart', 'AutoDesformDataList'].includes(item.name as string) && + allMenus?.length + ) { + const route = getMatchingRoute(allMenus, item.path); + if (route?.meta?.keepAlive) { + // 如果keepAlive为true,则添加到缓存中 + } else { + continue; + } + } + const name = item.name as string; + cacheMap.add(name); + } + this.cacheTabList = cacheMap; + }, + + /** + * Refresh tabs + */ + async refreshPage(router: Router) { + const { currentRoute } = router; + const route = unref(currentRoute); + const name = route.name; + + const findTab = this.getCachedTabList.find((item) => item === name); + if (findTab) { + this.cacheTabList.delete(findTab); + } + const redo = useRedo(router); + await redo(); + }, + /** + * 修改设计模式 + * changeDesign + */ + async changeDesign(router: Router) { + const { currentRoute } = router; + const route = unref(currentRoute); + const name = route.name; + + const findTab = this.getCachedTabList.find((item) => item === name); + if (findTab) { + this.cacheTabList.delete(findTab); + } + const redo = useRedo(router, { isDesign: true }); + await redo(); + }, + clearCacheTabs(): void { + this.cacheTabList = new Set(); + }, + resetState(): void { + this.tabList = []; + this.clearCacheTabs(); + }, + goToPage(router: Router) { + const go = useGo(router); + const len = this.tabList.length; + const { path } = unref(router.currentRoute); + + let toPath: PageEnum | string = PageEnum.BASE_HOME; + + if (len > 0) { + const page = this.tabList[len - 1]; + const p = page.fullPath || page.path; + if (p) { + toPath = p; + } + } + // Jump to the current page and report an error + path !== toPath && go(toPath as PageEnum, true); + }, + + async addTab(route: RouteLocationNormalized) { + const { path, name, fullPath, params, query, meta } = getRawRoute(route); + // 404 The page does not need to add a tab + if ( + path === PageEnum.ERROR_PAGE || + path === PageEnum.BASE_LOGIN || + !name || + [REDIRECT_ROUTE.name, PAGE_NOT_FOUND_NAME_404].includes(name as string) + ) { + return; + } + + let updateIndex = -1; + // Existing pages, do not add tabs repeatedly + const tabHasExits = this.tabList.some((tab, index) => { + updateIndex = index; + return (tab.fullPath || tab.path) === (fullPath || path); + }); + + // If the tab already exists, perform the update operation + if (tabHasExits) { + const curTab = toRaw(this.tabList)[updateIndex]; + if (!curTab) { + return; + } + curTab.params = params || curTab.params; + curTab.query = query || curTab.query; + curTab.fullPath = fullPath || curTab.fullPath; + this.tabList.splice(updateIndex, 1, curTab); + } else { + // 只比较path,忽略query + const findIndex = this.tabList.findIndex((tab) => tab.path === path); + const isTabExist = findIndex !== -1; + if (isTabExist) { + this.tabList.splice(findIndex, 1, route); + return; + } + // Add tab + // 获取动态路由打开数,超过 0 即代表需要控制打开数 + const dynamicLevel = meta?.dynamicLevel ?? -1; + if (dynamicLevel > 0) { + // 如果动态路由层级大于 0 了,那么就要限制该路由的打开数限制了 + // 首先获取到真实的路由,使用配置方式减少计算开销. + // const realName: string = path.match(/(\S*)\//)![1]; + const realPath = meta?.realPath ?? ''; + // 获取到已经打开的动态路由数, 判断是否大于某一个值 + if (this.tabList.filter((e) => e.meta?.realPath ?? '' === realPath).length >= dynamicLevel) { + // 关闭第一个 + const index = this.tabList.findIndex((item) => item.meta.realPath === realPath); + index !== -1 && this.tabList.splice(index, 1); + } + } + this.tabList.push(route); + } + this.updateCacheTab(); + cacheTab && Persistent.setLocal(MULTIPLE_TABS_KEY, this.tabList); + }, + + async closeTab(tab: RouteLocationNormalized, router: Router) { + const close = (route: RouteLocationNormalized) => { + const { fullPath, meta: { affix } = {} } = route; + if (affix) { + return; + } + const index = this.tabList.findIndex((item) => item.fullPath === fullPath); + index !== -1 && this.tabList.splice(index, 1); + }; + + const { currentRoute, replace } = router; + + const { path } = unref(currentRoute); + if (path !== tab.path) { + // Closed is not the activation tab + close(tab); + this.updateCacheTab(); + return; + } + + // Closed is activated atb + let toTarget: RouteLocationRaw = {}; + + const index = this.tabList.findIndex((item) => item.path === path); + + // If the current is the leftmost tab + if (index === 0) { + // There is only one tab, then jump to the homepage, otherwise jump to the right tab + if (this.tabList.length === 1) { + const userStore = useUserStore(); + toTarget = userStore.getUserInfo.homePath || PageEnum.BASE_HOME; + } else { + // Jump to the right tab + const page = this.tabList[index + 1]; + toTarget = getToTarget(page); + } + } else { + // Close the current tab + const page = this.tabList[index - 1]; + toTarget = getToTarget(page); + } + close(currentRoute.value); + await replace(toTarget); + }, + + // Close according to key + async closeTabByKey(key: string, router: Router) { + const index = this.tabList.findIndex((item) => (item.fullPath || item.path) === key); + if (index !== -1) { + await this.closeTab(this.tabList[index], router); + const { currentRoute, replace } = router; + // 检查当前路由是否存在于tabList中 + const isActivated = this.tabList.findIndex((item) => { + return item.fullPath === currentRoute.value.fullPath; + }); + // 如果当前路由不存在于TabList中,尝试切换到其它路由 + if (isActivated === -1) { + let pageIndex; + if (index > 0) { + pageIndex = index - 1; + } else if (index < this.tabList.length - 1) { + pageIndex = index + 1; + } else { + pageIndex = -1; + } + if (pageIndex >= 0) { + const page = this.tabList[index - 1]; + const toTarget = getToTarget(page); + await replace(toTarget); + } + } + } + }, + + // Sort the tabs + async sortTabs(oldIndex: number, newIndex: number) { + const currentTab = this.tabList[oldIndex]; + this.tabList.splice(oldIndex, 1); + this.tabList.splice(newIndex, 0, currentTab); + this.lastDragEndIndex = this.lastDragEndIndex + 1; + }, + + // Close the tab on the right and jump + async closeLeftTabs(route: RouteLocationNormalized, router: Router) { + const index = this.tabList.findIndex((item) => item.path === route.path); + let isCloseCurrentTab = false; + if (index > 0) { + const leftTabs = this.tabList.slice(0, index); + const pathList: string[] = []; + for (const item of leftTabs) { + const affix = item?.meta?.affix ?? false; + if (!affix) { + pathList.push(item.fullPath); + } + } + // 代码逻辑说明: 【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用 + isCloseCurrentTab = closeTabContainCurrentRoute(router, pathList); + this.bulkCloseTabs(pathList); + } + this.updateCacheTab(); + // 代码逻辑说明: 【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用 + if (isCloseCurrentTab) { + handleGotoPage(router, route.path); + } else { + handleGotoPage(router); + } + }, + + // Close the tab on the left and jump + async closeRightTabs(route: RouteLocationNormalized, router: Router) { + const index = this.tabList.findIndex((item) => item.fullPath === route.fullPath); + let isCloseCurrentTab = false; + if (index >= 0 && index < this.tabList.length - 1) { + const rightTabs = this.tabList.slice(index + 1, this.tabList.length); + + const pathList: string[] = []; + for (const item of rightTabs) { + const affix = item?.meta?.affix ?? false; + if (!affix) { + pathList.push(item.fullPath); + } + } + // 代码逻辑说明: 【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用 + isCloseCurrentTab = closeTabContainCurrentRoute(router, pathList); + this.bulkCloseTabs(pathList); + } + this.updateCacheTab(); + // 代码逻辑说明: 【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用 + if (isCloseCurrentTab) { + handleGotoPage(router, route.path); + } else { + handleGotoPage(router); + } + }, + + async closeAllTab(router: Router) { + this.tabList = this.tabList.filter((item) => item?.meta?.affix ?? false); + this.clearCacheTabs(); + this.goToPage(router); + }, + + + /** + * Close other tabs + */ + async closeOtherTabs(route: RouteLocationNormalized, router: Router) { + const closePathList = this.tabList.map((item) => item.fullPath); + let isCloseCurrentTab = false; + const pathList: string[] = []; + + for (const path of closePathList) { + if (path !== route.fullPath) { + const closeItem = this.tabList.find((item) => item.path === path); + if (!closeItem) { + continue; + } + const affix = closeItem?.meta?.affix ?? false; + if (!affix) { + pathList.push(closeItem.fullPath); + } + } + } + isCloseCurrentTab = closeTabContainCurrentRoute(router, pathList); + this.bulkCloseTabs(pathList); + this.updateCacheTab(); + // 代码逻辑说明: 【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用 + if (isCloseCurrentTab) { + handleGotoPage(router, route.path); + } else { + handleGotoPage(router); + } + }, + + /** + * Close tabs in bulk + */ + async bulkCloseTabs(pathList: string[]) { + this.tabList = this.tabList.filter((item) => !pathList.includes(item.fullPath)); + }, + + /** + * Set tab's title + */ + async setTabTitle(title: string, route: RouteLocationNormalized) { + const findTab = this.getTabList.find((item) => item === route); + if (findTab) { + findTab.meta.title = title; + await this.updateCacheTab(); + } + }, + /** + * replace tab's path + * **/ + async updateTabPath(fullPath: string, route: RouteLocationNormalized) { + const findTab = this.getTabList.find((item) => item === route); + if (findTab) { + findTab.fullPath = fullPath; + findTab.path = fullPath; + await this.updateCacheTab(); + } + }, + setRedirectPageParam(data) { + this.redirectPageParam = data; + }, + getRedirectPageParam() { + return this.redirectPageParam; + }, + }, +}); + +// Need to be used outside the setup +export function useMultipleTabWithOutStore() { + return useMultipleTabStore(store); +} diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts new file mode 100644 index 0000000..cdac519 --- /dev/null +++ b/src/store/modules/permission.ts @@ -0,0 +1,307 @@ +import type { AppRouteRecordRaw, Menu } from '/@/router/types'; + +import { defineStore } from 'pinia'; +import { store } from '/@/store'; +import { useI18n } from '/@/hooks/web/useI18n'; +import { useUserStore } from './user'; +import { useAppStoreWithOut } from './app'; +import { toRaw } from 'vue'; +import { transformObjToRoute, flatMultiLevelRoutes, addSlashToRouteComponent } from '/@/router/helper/routeHelper'; +import { transformRouteToMenu } from '/@/router/helper/menuHelper'; + +import projectSetting from '/@/settings/projectSetting'; + +import { PermissionModeEnum } from '/@/enums/appEnum'; + +import { asyncRoutes } from '/@/router/routes'; +import { ERROR_LOG_ROUTE, PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic'; +import { staticRoutesList } from '../../router/routes/staticRouter'; + +import { filter } from '/@/utils/helper/treeHelper'; + +import { getBackMenuAndPerms } from '/@/api/sys/menu'; + +import { useMessage } from '/@/hooks/web/useMessage'; +import { PageEnum } from '/@/enums/pageEnum'; + +// 系统权限 +interface AuthItem { + // 菜单权限编码,例如:“sys:schedule:list,sys:schedule:info”,多个逗号隔开 + action: string; + // 权限策略1显示2禁用 + type: string | number; + // 权限状态(0无效1有效) + status: string | number; + // 权限名称 + describe?: string; + isAuth?: boolean; +} + +interface PermissionState { + // Permission code list + permCodeList: string[] | number[]; + // Whether the route has been dynamically added + isDynamicAddedRoute: boolean; + // To trigger a menu update + lastBuildMenuTime: number; + // Backstage menu list + backMenuList: Menu[]; + frontMenuList: Menu[]; + // 用户所拥有的权限 + authList: AuthItem[]; + // 全部权限配置 + allAuthList: AuthItem[]; + // 系统安全模式 + sysSafeMode: boolean; + // online子表按钮权限 + onlineSubTableAuthMap: object; +} +export const usePermissionStore = defineStore({ + id: 'app-permission', + state: (): PermissionState => ({ + permCodeList: [], + // Whether the route has been dynamically added + isDynamicAddedRoute: false, + // To trigger a menu update + lastBuildMenuTime: 0, + // Backstage menu list + backMenuList: [], + // menu List + frontMenuList: [], + authList: [], + allAuthList: [], + sysSafeMode: false, + onlineSubTableAuthMap: {}, + }), + getters: { + getPermCodeList(): string[] | number[] { + return this.permCodeList; + }, + getBackMenuList(): Menu[] { + return this.backMenuList; + }, + getFrontMenuList(): Menu[] { + return this.frontMenuList; + }, + getLastBuildMenuTime(): number { + return this.lastBuildMenuTime; + }, + getIsDynamicAddedRoute(): boolean { + return this.isDynamicAddedRoute; + }, + + // 代码逻辑说明: VUEN-1162 子表按钮没控制 + getOnlineSubTableAuth: (state) => { + return (code) => state.onlineSubTableAuthMap[code]; + }, + }, + actions: { + setPermCodeList(codeList: string[]) { + this.permCodeList = codeList; + }, + + setBackMenuList(list: Menu[]) { + this.backMenuList = list; + list?.length > 0 && this.setLastBuildMenuTime(); + }, + + setFrontMenuList(list: Menu[]) { + this.frontMenuList = list; + }, + + setLastBuildMenuTime() { + this.lastBuildMenuTime = new Date().getTime(); + }, + + setDynamicAddedRoute(added: boolean) { + this.isDynamicAddedRoute = added; + }, + resetState(): void { + this.isDynamicAddedRoute = false; + this.permCodeList = []; + this.backMenuList = []; + this.lastBuildMenuTime = 0; + }, + async changePermissionCode() { + const systemPermission = await getBackMenuAndPerms(); + const codeList = systemPermission.codeList; + this.setPermCodeList(codeList); + this.setAuthData(systemPermission); + + //菜单路由 + const routeList = systemPermission.menu; + return routeList; + }, + async buildRoutesAction(): Promise { + const { t } = useI18n(); + const userStore = useUserStore(); + const appStore = useAppStoreWithOut(); + + let routes: AppRouteRecordRaw[] = []; + const roleList = toRaw(userStore.getRoleList) || []; + const { permissionMode = projectSetting.permissionMode } = appStore.getProjectConfig; + + const routeFilter = (route: AppRouteRecordRaw) => { + const { meta } = route; + const { roles } = meta || {}; + if (!roles) return true; + return roleList.some((role) => roles.includes(role)); + }; + + const routeRemoveIgnoreFilter = (route: AppRouteRecordRaw) => { + const { meta } = route; + const { ignoreRoute } = meta || {}; + return !ignoreRoute; + }; + + /** + * @description 根据设置的首页path,修正routes中的affix标记(固定首页) + * */ + const patchHomeAffix = (routes: AppRouteRecordRaw[]) => { + if (!routes || routes.length === 0) return; + let homePath: string = userStore.getUserInfo.homePath || PageEnum.BASE_HOME; + function patcher(routes: AppRouteRecordRaw[], parentPath = '') { + if (parentPath) parentPath = parentPath + '/'; + routes.forEach((route: AppRouteRecordRaw) => { + const { path, children, redirect } = route; + const currentPath = path.startsWith('/') ? path : parentPath + path; + if (currentPath === homePath) { + if (redirect) { + homePath = route.redirect! as string; + } else { + route.meta = Object.assign({}, route.meta, { affix: true }); + throw new Error('end'); + } + } + children && children.length > 0 && patcher(children, currentPath); + }); + } + try { + patcher(routes); + } catch (e) { + // 已处理完毕跳出循环 + } + return; + }; + + switch (permissionMode) { + case PermissionModeEnum.ROLE: + routes = filter(asyncRoutes, routeFilter); + routes = routes.filter(routeFilter); + // 将多级路由转换为二级 + routes = flatMultiLevelRoutes(routes); + break; + + case PermissionModeEnum.ROUTE_MAPPING: + routes = filter(asyncRoutes, routeFilter); + routes = routes.filter(routeFilter); + const menuList = transformRouteToMenu(routes, true); + routes = filter(routes, routeRemoveIgnoreFilter); + routes = routes.filter(routeRemoveIgnoreFilter); + menuList.sort((a, b) => { + return (a.meta?.orderNo || 0) - (b.meta?.orderNo || 0); + }); + + this.setFrontMenuList(menuList); + // 将多级路由转换为二级 + routes = flatMultiLevelRoutes(routes); + break; + + // 后台菜单构建 + case PermissionModeEnum.BACK: + const { createMessage, createWarningModal } = useMessage(); + console.log(" --- 构建后台路由菜单 --- ") + // 菜单加载提示 + // createMessage.loading({ + // content: t('sys.app.menuLoading'), + // duration: 1, + // }); + + // 从后台获取权限码, + // 这个函数可能只需要执行一次,并且实际的项目可以在正确的时间被放置 + let routeList: AppRouteRecordRaw[] = []; + try { + routeList = await this.changePermissionCode(); + //routeList = (await getMenuList()) as AppRouteRecordRaw[]; + // let hasIndex: boolean = false; + // let hasIcon: boolean = false; + // for (let menuItem of routeList) { + // // 条件1:判断组件是否是 layouts/default/index + // if (!hasIndex) { + // hasIndex = menuItem.component === 'layouts/default/index'; + // } + // // 条件2:判断图标是否带有 冒号 + // if (!hasIcon) { + // hasIcon = !!menuItem.meta?.icon?.includes(':'); + // } + // // 满足任何一个条件都直接跳出循环 + // if (hasIcon || hasIndex) { + // break; + // } + // } + // // 两个条件都不满足,就弹出提示框 + // if (!hasIcon && !hasIndex) { + // // 延迟1.5秒之后再出现提示,否则提示框出不来 + // setTimeout( + // () => + // createWarningModal({ + // title: '检测提示', + // content: + // '当前菜单表是 Vue2版本,导致菜单加载异常!
点击确认,切换到Vue3版菜单!', + // onOk:function () { + // switchVue3Menu(); + // location.reload(); + // } + // }), + // 100 + // ); + // } + } catch (error) { + console.error(error); + } + // 组件地址前加斜杠处理 author: lsq date:2021-09-08 + routeList = addSlashToRouteComponent(routeList); + // 动态引入组件 + routeList = transformObjToRoute(routeList); + + // 构建后台路由菜单 + const backMenuList = transformRouteToMenu(routeList); + this.setBackMenuList(backMenuList); + + // 删除meta.ignoreRoute项 + routeList = filter(routeList, routeRemoveIgnoreFilter); + routeList = routeList.filter(routeRemoveIgnoreFilter); + + routeList = flatMultiLevelRoutes(routeList); + // 代码逻辑说明: 【TV360X-522】ai助手路由写死在前端 + routes = [PAGE_NOT_FOUND_ROUTE, ...routeList, ...staticRoutesList]; + break; + } + + routes.push(ERROR_LOG_ROUTE); + patchHomeAffix(routes); + return routes; + }, + setAuthData(systemPermission) { + this.authList = systemPermission.auth; + this.allAuthList = systemPermission.allAuth; + this.sysSafeMode = systemPermission.sysSafeMode; + }, + setAuthList(authList: AuthItem[]) { + this.authList = authList; + }, + setAllAuthList(authList: AuthItem[]) { + this.allAuthList = authList; + }, + + // 代码逻辑说明: VUEN-1162 子表按钮没控制 + setOnlineSubTableAuth(code, hideBtnList) { + this.onlineSubTableAuthMap[code] = hideBtnList; + }, + }, +}); + +// 需要在设置之外使用 +export function usePermissionStoreWithOut() { + return usePermissionStore(store); +} diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts new file mode 100644 index 0000000..22bc6f2 --- /dev/null +++ b/src/store/modules/user.ts @@ -0,0 +1,370 @@ +import type { UserInfo, LoginInfo } from '/#/store'; +import type { ErrorMessageMode } from '/#/axios'; +import { defineStore } from 'pinia'; +import { store } from '/@/store'; +import { RoleEnum } from '/@/enums/roleEnum'; +import { PageEnum } from '/@/enums/pageEnum'; +import { ROLES_KEY, TOKEN_KEY, USER_INFO_KEY, LOGIN_INFO_KEY, DB_DICT_DATA_KEY, TENANT_ID, OAUTH2_THIRD_LOGIN_TENANT_ID } from '/@/enums/cacheEnum'; +import { getAuthCache, setAuthCache, removeAuthCache } from '/@/utils/auth'; +import { GetUserInfoModel, LoginParams, ThirdLoginParams } from '/@/api/sys/model/userModel'; +import { doLogout, getUserInfo, loginApi, phoneLoginApi, thirdLogin } from '/@/api/sys/user'; +import { useI18n } from '/@/hooks/web/useI18n'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { router } from '/@/router'; +import { usePermissionStore } from '/@/store/modules/permission'; +import { RouteRecordRaw } from 'vue-router'; +import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic'; +import { isArray } from '/@/utils/is'; +import { useGlobSetting } from '/@/hooks/setting'; +import { JDragConfigEnum } from '/@/enums/jeecgEnum'; +import { useSso } from '/@/hooks/web/useSso'; +import { isOAuth2AppEnv } from "/@/views/sys/login/useLogin"; +import { getUrlParam } from "@/utils"; +interface dictType { + [key: string]: any; +} +interface UserState { + userInfo: Nullable; + token?: string; + roleList: RoleEnum[]; + dictItems?: dictType | null; + sessionTimeout?: boolean; + lastUpdateTime: number; + tenantid?: string | number; + shareTenantId?: Nullable; + loginInfo?: Nullable; +} + +export const useUserStore = defineStore({ + id: 'app-user', + state: (): UserState => ({ + // 用户信息 + userInfo: null, + // token + token: undefined, + // 角色列表 + roleList: [], + // 字典 + dictItems: null, + // session过期时间 + sessionTimeout: false, + // Last fetch time + lastUpdateTime: 0, + //租户id + tenantid: '', + // 分享租户ID + // 用于分享页面所属租户与当前用户登录租户不一致的情况 + shareTenantId: null, + //登录返回信息 + loginInfo: null, + }), + getters: { + getUserInfo(): UserInfo { + if(this.userInfo == null){ + this.userInfo = getAuthCache(USER_INFO_KEY)!=null ? getAuthCache(USER_INFO_KEY) : null; + } + return this.userInfo || getAuthCache(USER_INFO_KEY) || {}; + }, + getLoginInfo(): LoginInfo { + return this.loginInfo || getAuthCache(LOGIN_INFO_KEY) || {}; + }, + getToken(): string { + return this.token || getAuthCache(TOKEN_KEY); + }, + getAllDictItems(): [] { + return this.dictItems || getAuthCache(DB_DICT_DATA_KEY); + }, + getRoleList(): RoleEnum[] { + return this.roleList.length > 0 ? this.roleList : getAuthCache(ROLES_KEY); + }, + getSessionTimeout(): boolean { + return !!this.sessionTimeout; + }, + getLastUpdateTime(): number { + return this.lastUpdateTime; + }, + getTenant(): string | number { + return this.tenantid || getAuthCache(TENANT_ID); + }, + // 是否有分享租户id + hasShareTenantId(): boolean { + return this.shareTenantId != null && this.shareTenantId !== ''; + }, + }, + actions: { + setToken(info: string | undefined) { + this.token = info ? info : ''; // for null or undefined value + setAuthCache(TOKEN_KEY, info); + }, + setRoleList(roleList: RoleEnum[]) { + this.roleList = roleList; + setAuthCache(ROLES_KEY, roleList); + }, + setUserInfo(info: UserInfo | null) { + this.userInfo = info; + this.lastUpdateTime = new Date().getTime(); + setAuthCache(USER_INFO_KEY, info); + }, + setLoginInfo(info: LoginInfo | null) { + this.loginInfo = info; + setAuthCache(LOGIN_INFO_KEY, info); + }, + setAllDictItems(dictItems) { + this.dictItems = dictItems; + setAuthCache(DB_DICT_DATA_KEY, dictItems); + }, + setAllDictItemsByLocal() { + // 代码逻辑说明: 【QQYUN-8572】表格行选择卡顿问题(customRender中字典引起的) + if (!this.dictItems) { + const allDictItems = getAuthCache(DB_DICT_DATA_KEY); + if (allDictItems) { + this.dictItems = allDictItems; + } + } + }, + setTenant(id) { + this.tenantid = id; + setAuthCache(TENANT_ID, id); + }, + setShareTenantId(id: NonNullable) { + this.shareTenantId = id; + }, + setSessionTimeout(flag: boolean) { + this.sessionTimeout = flag; + }, + resetState() { + this.userInfo = null; + this.dictItems = null; + this.token = ''; + this.roleList = []; + this.sessionTimeout = false; + }, + /** + * 登录事件 + */ + async login( + params: LoginParams & { + goHome?: boolean; + mode?: ErrorMessageMode; + } + ): Promise { + try { + const { goHome = true, mode, ...loginParams } = params; + const data = await loginApi(loginParams, mode); + const { token, userInfo } = data; + // save token + this.setToken(token); + this.setTenant(userInfo.loginTenantId); + return this.afterLoginAction(goHome, data); + } catch (error) { + return Promise.reject(error); + } + }, + /** + * 扫码登录事件 + */ + async qrCodeLogin(token): Promise { + try { + // save token + this.setToken(token); + return this.afterLoginAction(true, {}); + } catch (error) { + return Promise.reject(error); + } + }, + /** + * 登录完成处理 + * @param goHome + */ + async afterLoginAction(goHome?: boolean, data?: any): Promise { + if (!this.getToken) return null; + //获取用户信息 + const userInfo = await this.getUserInfoAction(); + const sessionTimeout = this.sessionTimeout; + if (sessionTimeout) { + this.setSessionTimeout(false); + } else { + // // 构建后台菜单路由 + // const permissionStore = usePermissionStore(); + // if (!permissionStore.isDynamicAddedRoute) { + // const routes = await permissionStore.buildRoutesAction(); + // routes.forEach((route) => { + // router.addRoute(route as unknown as RouteRecordRaw); + // }); + // router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw); + // permissionStore.setDynamicAddedRoute(true); + // } + + await this.setLoginInfo({ ...data, isLogin: true }); + // 代码逻辑说明: 登录成功后缓存拖拽模块的接口前缀 + localStorage.setItem(JDragConfigEnum.DRAG_BASE_URL, useGlobSetting().domainUrl); + + // 代码逻辑说明: 修复登录成功后,没有正确重定向的问题 + let redirect = router.currentRoute.value?.query?.redirect as string; + // 判断是否有 redirect 重定向地址 + // 代码逻辑说明: 【QQYUN-5195】登录之后直接刷新页面导致没有进入创建组织页面------------ + if (redirect && goHome) { + // router.options.history.base可替代之前的publicPath + // 当前页面打开 + window.open(`${router.options.history.base}${redirect}`, '_self'); + return data; + } + + // 代码逻辑说明: 【issues/1102】设置单点登录后页面,进入首页提示404,也没有绘制侧边栏 #1102--- + let ticket = getUrlParam('ticket'); + if(ticket){ + goHome && (window.location.replace((userInfo && userInfo.homePath) || PageEnum.BASE_HOME)); + }else{ + goHome && (await router.replace((userInfo && userInfo.homePath) || PageEnum.BASE_HOME)); + } + } + return data; + }, + /** + * 手机号登录 + * @param params + */ + async phoneLogin( + params: LoginParams & { + goHome?: boolean; + mode?: ErrorMessageMode; + } + ): Promise { + try { + const { goHome = true, mode, ...loginParams } = params; + const data = await phoneLoginApi(loginParams, mode); + // 代码逻辑说明: 【issues/7488】手机号码登录,在请求头中无法获取租户id--- + const { token , userInfo } = data; + this.setTenant(userInfo!.loginTenantId); + // save token + this.setToken(token); + return this.afterLoginAction(goHome, data); + } catch (error) { + return Promise.reject(error); + } + }, + /** + * 获取用户信息 + */ + async getUserInfoAction(): Promise { + if (!this.getToken) { + return null; + } + const { userInfo, sysAllDictItems } = await getUserInfo(); + if (userInfo) { + const { roles = [] } = userInfo; + if (isArray(roles)) { + const roleList = roles.map((item) => item.value) as RoleEnum[]; + this.setRoleList(roleList); + } else { + userInfo.roles = []; + this.setRoleList([]); + } + this.setUserInfo(userInfo); + } + /** + * 添加字典信息到缓存 + * @updateBy:lsq + * @updateDate:2021-09-08 + */ + if (sysAllDictItems) { + this.setAllDictItems(sysAllDictItems); + } + return userInfo; + }, + /** + * 退出登录 + */ + async logout(goLogin = false) { + if (this.getToken) { + try { + await doLogout(); + } catch { + console.log('注销Token失败'); + } + } + + // let username:any = this.userInfo && this.userInfo.username; + // if(username){ + // removeAuthCache(username) + // } + + this.setToken(''); + setAuthCache(TOKEN_KEY, null); + this.setSessionTimeout(false); + this.setUserInfo(null); + this.setLoginInfo(null); + this.setTenant(null); + // 代码逻辑说明: 【TV360X-23】退出登录后会提示「Token时效,请重新登录」 + setTimeout(() => { + this.setAllDictItems(null); + }, 1e3); + // 代码逻辑说明: 退出登录后清除拖拽模块的接口前缀 + localStorage.removeItem(JDragConfigEnum.DRAG_BASE_URL); + + //如果开启单点登录,则跳转到单点统一登录中心 + const openSso = useGlobSetting().openSso; + if (openSso == 'true') { + await useSso().ssoLoginOut(); + } + //退出登录的时候需要用的应用id + if(isOAuth2AppEnv()){ + let tenantId = getAuthCache(OAUTH2_THIRD_LOGIN_TENANT_ID); + removeAuthCache(OAUTH2_THIRD_LOGIN_TENANT_ID); + goLogin && await router.push({ name:"Login",query:{ tenantId:tenantId }}) + }else{ + // 代码逻辑说明: 修复登录成功后,没有正确重定向的问题 + goLogin && (await router.push({ + path: PageEnum.BASE_LOGIN, + query: { + // 传入当前的路由,登录成功后跳转到当前路由 + redirect: router.currentRoute.value.fullPath, + } + })); + + } + }, + /** + * 登录事件 + */ + async ThirdLogin( + params: ThirdLoginParams & { + goHome?: boolean; + mode?: ErrorMessageMode; + } + ): Promise { + try { + const { goHome = true, mode, ...ThirdLoginParams } = params; + const data = await thirdLogin(ThirdLoginParams, mode); + // 代码逻辑说明: 【issues/6652】开启租户数据隔离,接入钉钉后登录默认租户为0了--- + const { token, userInfo } = data; + this.setTenant(userInfo?.loginTenantId); + // save token + this.setToken(token); + return this.afterLoginAction(goHome, data); + } catch (error) { + return Promise.reject(error); + } + }, + /** + * 退出询问 + */ + confirmLoginOut() { + const { createConfirm } = useMessage(); + const { t } = useI18n(); + createConfirm({ + iconType: 'warning', + title: t('sys.app.logoutTip'), + content: t('sys.app.logoutMessage'), + onOk: async () => { + await this.logout(true); + }, + }); + }, + }, +}); + +// Need to be used outside the setup +export function useUserStoreWithOut() { + return useUserStore(store); +} diff --git a/src/utils/areaData/pcaUtils.ts b/src/utils/areaData/pcaUtils.ts new file mode 100644 index 0000000..a95f818 --- /dev/null +++ b/src/utils/areaData/pcaUtils.ts @@ -0,0 +1,38 @@ +import {areaList} from '@vant/area-data' +import {freezeDeep} from "@/utils/common/compUtils"; + +// 扁平化的省市区数据 +export const pcaa = freezeDeep(usePlatPcaaData()) + +/** + * 获取扁平化的省市区数据 + */ +function usePlatPcaaData() { + const {city_list: city, county_list: county, province_list: province} = areaList; + const dataMap = new Map() + const flatData: Recordable = {'86': province} + // 省 + Object.keys(province).forEach((code) => { + flatData[code] = {} + dataMap.set(code.slice(0, 2), flatData[code]) + }) + // 市区 + Object.keys(city).forEach((code) => { + flatData[code] = {} + dataMap.set(code.slice(0, 4), flatData[code]) + // 填充上一级 + const getProvince = dataMap.get(code.slice(0, 2)) + if (getProvince) { + getProvince[code] = city[code] + } + }); + // 县 + Object.keys(county).forEach((code) => { + // 填充上一级 + const getCity = dataMap.get(code.slice(0, 4)) + if (getCity) { + getCity[code] = county[code] + } + }); + return flatData +} \ No newline at end of file diff --git a/src/utils/auth/index.ts b/src/utils/auth/index.ts new file mode 100644 index 0000000..95d79b7 --- /dev/null +++ b/src/utils/auth/index.ts @@ -0,0 +1,80 @@ +import { Persistent, BasicKeys } from '/@/utils/cache/persistent'; +import { CacheTypeEnum } from '/@/enums/cacheEnum'; +import projectSetting from '/@/settings/projectSetting'; +import { TOKEN_KEY, LOGIN_INFO_KEY, TENANT_ID } from '/@/enums/cacheEnum'; + +const { permissionCacheType } = projectSetting; +const isLocal = permissionCacheType === CacheTypeEnum.LOCAL; + +/** + * 获取token + */ +export function getToken() { + return getAuthCache(TOKEN_KEY); +} +/** + * 获取登录信息 + */ +export function getLoginBackInfo() { + return getAuthCache(LOGIN_INFO_KEY); +} +/** + * 获取租户id + */ +export function getTenantId() { + return getAuthCache(TENANT_ID); +} + +export function getAuthCache(key: BasicKeys) { + const fn = isLocal ? Persistent.getLocal : Persistent.getSession; + return fn(key) as T; +} + +export function setAuthCache(key: BasicKeys, value) { + const fn = isLocal ? Persistent.setLocal : Persistent.setSession; + return fn(key, value, true); +} + +/** + * 设置动态key + * @param key + * @param value + */ +export function setCacheByDynKey(key, value) { + const fn = isLocal ? Persistent.setLocal : Persistent.setSession; + return fn(key, value, true); +} + +/** + * 获取动态key + * @param key + */ +export function getCacheByDynKey(key) { + const fn = isLocal ? Persistent.getLocal : Persistent.getSession; + return fn(key) as T; +} + +/** + * 移除动态key + * @param key + */ +export function removeCacheByDynKey(key) { + const fn = isLocal ? Persistent.removeLocal : Persistent.removeSession; + return fn(key) as T; +} +/** + * 移除缓存中的某个属性 + * @param key + * @update:移除缓存中的某个属性 + * @updateBy:lsq + * @updateDate:2021-09-07 + */ +export function removeAuthCache(key: BasicKeys) { + const fn = isLocal ? Persistent.removeLocal : Persistent.removeSession; + return fn(key) as T; +} + +export function clearAuthCache(immediate = true) { + const fn = isLocal ? Persistent.clearLocal : Persistent.clearSession; + return fn(immediate); +} diff --git a/src/utils/bem.ts b/src/utils/bem.ts new file mode 100644 index 0000000..7dcadbc --- /dev/null +++ b/src/utils/bem.ts @@ -0,0 +1,52 @@ +import { prefixCls } from '/@/settings/designSetting'; + +type Mod = string | { [key: string]: any }; +type Mods = Mod | Mod[]; + +export type BEM = ReturnType; + +function genBem(name: string, mods?: Mods): string { + if (!mods) { + return ''; + } + + if (typeof mods === 'string') { + return ` ${name}--${mods}`; + } + + if (Array.isArray(mods)) { + return mods.reduce((ret, item) => ret + genBem(name, item), ''); + } + + return Object.keys(mods).reduce((ret, key) => ret + (mods[key] ? genBem(name, key) : ''), ''); +} + +/** + * bem helper + * b() // 'button' + * b('text') // 'button__text' + * b({ disabled }) // 'button button--disabled' + * b('text', { disabled }) // 'button__text button__text--disabled' + * b(['disabled', 'primary']) // 'button button--disabled button--primary' + */ +export function buildBEM(name: string) { + return (el?: Mods, mods?: Mods): Mods => { + if (el && typeof el !== 'string') { + mods = el; + el = ''; + } + + el = el ? `${name}__${el}` : name; + + return `${el}${genBem(el, mods)}`; + }; +} + +export function createBEM(name: string) { + return [buildBEM(`${prefixCls}-${name}`)]; +} + +export function createNamespace(name: string) { + const prefixedName = `${prefixCls}-${name}`; + return [prefixedName, buildBEM(prefixedName)] as const; +} diff --git a/src/utils/browser.js b/src/utils/browser.js new file mode 100644 index 0000000..9765f94 --- /dev/null +++ b/src/utils/browser.js @@ -0,0 +1,37 @@ +//判断是否IE<11浏览器 +export function isIE() { + return navigator.userAgent.indexOf('compatible') > -1 && navigator.userAgent.indexOf('MSIE') > -1; +} + +export function isIE11() { + return navigator.userAgent.indexOf('Trident') > -1 && navigator.userAgent.indexOf('rv:11.0') > -1; +} + +//判断是否IE的Edge浏览器 +export function isEdge() { + return navigator.userAgent.indexOf('Edge') > -1 && !isIE(); +} + +export function getIEVersion() { + let userAgent = navigator.userAgent; //取得浏览器的userAgent字符串 + let isIE = isIE(); + let isIE11 = isIE11(); + let isEdge = isEdge(); + + if (isIE) { + let reIE = new RegExp('MSIE (\\d+\\.\\d+);'); + reIE.test(userAgent); + let fIEVersion = parseFloat(RegExp['$1']); + if (fIEVersion === 7 || fIEVersion === 8 || fIEVersion === 9 || fIEVersion === 10) { + return fIEVersion; + } else { + return 6; //IE版本<7 + } + } else if (isEdge) { + return 'edge'; + } else if (isIE11) { + return 11; + } else { + return -1; + } +} diff --git a/src/utils/cache/index.ts b/src/utils/cache/index.ts new file mode 100644 index 0000000..2004c66 --- /dev/null +++ b/src/utils/cache/index.ts @@ -0,0 +1,32 @@ +import { getStorageShortName } from '/@/utils/env'; +import { createStorage as create, CreateStorageParams } from './storageCache'; +import { enableStorageEncryption } from '/@/settings/encryptionSetting'; +import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting'; + +export type Options = Partial; + +const createOptions = (storage: Storage, options: Options = {}): Options => { + return { + // No encryption in debug mode + hasEncrypt: enableStorageEncryption, + storage, + prefixKey: getStorageShortName(), + ...options, + }; +}; + +export const WebStorage = create(createOptions(sessionStorage)); + +export const createStorage = (storage: Storage = sessionStorage, options: Options = {}) => { + return create(createOptions(storage, options)); +}; + +export const createSessionStorage = (options: Options = {}) => { + return createStorage(sessionStorage, { ...options, timeout: DEFAULT_CACHE_TIME }); +}; + +export const createLocalStorage = (options: Options = {}) => { + return createStorage(localStorage, { ...options, timeout: DEFAULT_CACHE_TIME }); +}; + +export default WebStorage; diff --git a/src/utils/cache/memory.ts b/src/utils/cache/memory.ts new file mode 100644 index 0000000..a4d89f0 --- /dev/null +++ b/src/utils/cache/memory.ts @@ -0,0 +1,109 @@ +import { TOKEN_KEY, ROLES_KEY, USER_INFO_KEY, DB_DICT_DATA_KEY, TENANT_ID, LOGIN_INFO_KEY, PROJ_CFG_KEY } from '/@/enums/cacheEnum'; +import { omit } from 'lodash-es'; + +export interface Cache { + value?: V; + timeoutId?: ReturnType; + time?: number; + alive?: number; +} + +const NOT_ALIVE = 0; + +export class Memory { + private cache: { [key in keyof T]?: Cache } = {}; + private alive: number; + + constructor(alive = NOT_ALIVE) { + // Unit second + this.alive = alive * 1000; + } + + get getCache() { + return this.cache; + } + + setCache(cache) { + this.cache = cache; + } + + // get(key: K) { + // const item = this.getItem(key); + // const time = item?.time; + // if (!isNullOrUnDef(time) && time < new Date().getTime()) { + // this.remove(key); + // } + // return item?.value ?? undefined; + // } + + get(key: K) { + return this.cache[key]; + } + + set(key: K, value: V, expires?: number) { + let item = this.get(key); + + if (!expires || (expires as number) <= 0) { + expires = this.alive; + } + if (item) { + if (item.timeoutId) { + clearTimeout(item.timeoutId); + item.timeoutId = undefined; + } + item.value = value; + } else { + item = { value, alive: expires }; + this.cache[key] = item; + } + + if (!expires) { + return value; + } + const now = new Date().getTime(); + item.time = now + this.alive; + item.timeoutId = setTimeout( + () => { + this.remove(key); + }, + expires > now ? expires - now : expires + ); + + return value; + } + + remove(key: K) { + const item = this.get(key); + Reflect.deleteProperty(this.cache, key); + if (item) { + clearTimeout(item.timeoutId!); + return item.value; + } + } + + resetCache(cache: { [K in keyof T]: Cache }) { + Object.keys(cache).forEach((key) => { + const k = key as any as keyof T; + const item = cache[k]; + if (item && item.time) { + const now = new Date().getTime(); + const expire = item.time; + if (expire > now) { + this.set(k, item.value, expire); + } + } + }); + } + + clear() { + console.log('------clear------进入clear方法'); + Object.keys(this.cache).forEach((key) => { + const item = this.cache[key]; + item.timeoutId && clearTimeout(item.timeoutId); + }); + // 代码逻辑说明: 不删除登录用户的租户id,其他缓存信息都清除---- + this.cache = { + ...omit(this.cache, [TOKEN_KEY, USER_INFO_KEY, ROLES_KEY, DB_DICT_DATA_KEY, TENANT_ID, LOGIN_INFO_KEY, PROJ_CFG_KEY]), + }; + } +} diff --git a/src/utils/cache/persistent.ts b/src/utils/cache/persistent.ts new file mode 100644 index 0000000..a89b4f9 --- /dev/null +++ b/src/utils/cache/persistent.ts @@ -0,0 +1,148 @@ +import type { LockInfo, UserInfo, LoginInfo } from '/#/store'; +import type { ProjectConfig } from '/#/config'; +import type { RouteLocationNormalized } from 'vue-router'; + +import { createLocalStorage, createSessionStorage } from '/@/utils/cache'; +import { Memory } from './memory'; +import { + TOKEN_KEY, + USER_INFO_KEY, + ROLES_KEY, + LOCK_INFO_KEY, + PROJ_CFG_KEY, + APP_LOCAL_CACHE_KEY, + APP_SESSION_CACHE_KEY, + MULTIPLE_TABS_KEY, + DB_DICT_DATA_KEY, + TENANT_ID, + LOGIN_INFO_KEY, + OAUTH2_THIRD_LOGIN_TENANT_ID, +} from '/@/enums/cacheEnum'; +import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting'; +import { toRaw } from 'vue'; +import { pick, omit } from 'lodash-es'; +import { PageEnum } from '/@/enums/pageEnum'; +import { router } from '/@/router'; + +interface BasicStore { + [TOKEN_KEY]: string | number | null | undefined; + [USER_INFO_KEY]: UserInfo; + [ROLES_KEY]: string[]; + [LOCK_INFO_KEY]: LockInfo; + [PROJ_CFG_KEY]: ProjectConfig; + [MULTIPLE_TABS_KEY]: RouteLocationNormalized[]; + [DB_DICT_DATA_KEY]: string; + [TENANT_ID]: string; + [LOGIN_INFO_KEY]: LoginInfo; + [OAUTH2_THIRD_LOGIN_TENANT_ID]: string +} + +type LocalStore = BasicStore; + +type SessionStore = BasicStore; + +export type BasicKeys = keyof BasicStore; +type LocalKeys = keyof LocalStore; +type SessionKeys = keyof SessionStore; + +const ls = createLocalStorage(); +const ss = createSessionStorage(); + +const localMemory = new Memory(DEFAULT_CACHE_TIME); +const sessionMemory = new Memory(DEFAULT_CACHE_TIME); + +function initPersistentMemory() { + const localCache = ls.get(APP_LOCAL_CACHE_KEY); + const sessionCache = ss.get(APP_SESSION_CACHE_KEY); + localCache && localMemory.resetCache(localCache); + sessionCache && sessionMemory.resetCache(sessionCache); +} + +export class Persistent { + static getLocal(key: LocalKeys) { + // 代码逻辑说明: token过期退出重新登录,online菜单还是提示token过期---------- + const globalCache = ls.get(APP_LOCAL_CACHE_KEY); + // 代码逻辑说明: 【issues/7250】自动锁屏无法解锁 + if (globalCache && router?.currentRoute?.value.path !== PageEnum.BASE_LOGIN) { + localMemory.setCache(globalCache); + } + return localMemory.get(key)?.value as Nullable; + } + + static setLocal(key: LocalKeys, value: LocalStore[LocalKeys], immediate = false): void { + localMemory.set(key, toRaw(value)); + immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache); + } + + static removeLocal(key: LocalKeys, immediate = false): void { + localMemory.remove(key); + immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache); + } + + static clearLocal(immediate = false): void { + localMemory.clear(); + immediate && ls.clear(); + } + + static getSession(key: SessionKeys) { + return sessionMemory.get(key)?.value as Nullable; + } + + static setSession(key: SessionKeys, value: SessionStore[SessionKeys], immediate = false): void { + sessionMemory.set(key, toRaw(value)); + immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache); + } + + static removeSession(key: SessionKeys, immediate = false): void { + sessionMemory.remove(key); + immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache); + } + static clearSession(immediate = false): void { + sessionMemory.clear(); + immediate && ss.clear(); + } + + static clearAll(immediate = false) { + sessionMemory.clear(); + localMemory.clear(); + if (immediate) { + ls.clear(); + ss.clear(); + } + } +} + +window.addEventListener('beforeunload', function () { + // TOKEN_KEY 在登录或注销时已经写入到storage了,此处为了解决同时打开多个窗口时token不同步的问题 + // LOCK_INFO_KEY 在锁屏和解锁时写入,此处也不应修改 + ls.set(APP_LOCAL_CACHE_KEY, { + ...omit(localMemory.getCache, LOCK_INFO_KEY), + ...pick(ls.get(APP_LOCAL_CACHE_KEY), [TOKEN_KEY, USER_INFO_KEY, LOCK_INFO_KEY]), + }); + ss.set(APP_SESSION_CACHE_KEY, { + ...omit(sessionMemory.getCache, LOCK_INFO_KEY), + ...pick(ss.get(APP_SESSION_CACHE_KEY), [TOKEN_KEY, USER_INFO_KEY, LOCK_INFO_KEY]), + }); +}); + +function storageChange(e: any) { + const { key, newValue, oldValue } = e; + + if (!key) { + Persistent.clearAll(); + return; + } + + if (!!newValue && !!oldValue) { + if (APP_LOCAL_CACHE_KEY === key) { + Persistent.clearLocal(); + } + if (APP_SESSION_CACHE_KEY === key) { + Persistent.clearSession(); + } + } +} + +window.addEventListener('storage', storageChange); + +initPersistentMemory(); diff --git a/src/utils/cache/storageCache.ts b/src/utils/cache/storageCache.ts new file mode 100644 index 0000000..faa7acd --- /dev/null +++ b/src/utils/cache/storageCache.ts @@ -0,0 +1,112 @@ +import { cacheCipher } from '/@/settings/encryptionSetting'; + +import type { EncryptionParams } from '/@/utils/cipher'; + +import { AesEncryption } from '/@/utils/cipher'; + +import { isNullOrUnDef } from '/@/utils/is'; + +export interface CreateStorageParams extends EncryptionParams { + prefixKey: string; + storage: Storage; + hasEncrypt: boolean; + timeout?: Nullable; +} +export const createStorage = ({ + prefixKey = '', + storage = sessionStorage, + key = cacheCipher.key, + iv = cacheCipher.iv, + timeout = null, + hasEncrypt = true, +}: Partial = {}) => { + if (hasEncrypt && [key.length, iv.length].some((item) => item !== 16)) { + throw new Error('When hasEncrypt is true, the key or iv must be 16 bits!'); + } + + const encryption = new AesEncryption({ key, iv }); + + /** + *Cache class + *Construction parameters can be passed into sessionStorage, localStorage, + * @class Cache + * @example + */ + const WebStorage = class WebStorage { + private storage: Storage; + private prefixKey?: string; + private encryption: AesEncryption; + private hasEncrypt: boolean; + /** + * + * @param {*} storage + */ + constructor() { + this.storage = storage; + this.prefixKey = prefixKey; + this.encryption = encryption; + this.hasEncrypt = hasEncrypt; + } + + private getKey(key: string) { + return `${this.prefixKey}${key}`.toUpperCase(); + } + + /** + * + * Set cache + * @param {string} key + * @param {*} value + * @expire Expiration time in seconds + * @memberof Cache + */ + set(key: string, value: any, expire: number | null = timeout) { + const stringData = JSON.stringify({ + value, + time: Date.now(), + expire: !isNullOrUnDef(expire) ? new Date().getTime() + expire * 1000 : null, + }); + const stringifyValue = this.hasEncrypt ? this.encryption.encryptByAES(stringData) : stringData; + this.storage.setItem(this.getKey(key), stringifyValue); + } + + /** + *Read cache + * @param {string} key + * @memberof Cache + */ + get(key: string, def: any = null): any { + const val = this.storage.getItem(this.getKey(key)); + if (!val) return def; + + try { + const decVal = this.hasEncrypt ? this.encryption.decryptByAES(val) : val; + const data = JSON.parse(decVal); + const { value, expire } = data; + if (isNullOrUnDef(expire) || expire >= new Date().getTime()) { + return value; + } + this.remove(key); + } catch (e) { + return def; + } + } + + /** + * Delete cache based on key + * @param {string} key + * @memberof Cache + */ + remove(key: string) { + this.storage.removeItem(this.getKey(key)); + } + + /** + * Delete all caches of this instance + */ + clear(): void { + this.storage.clear(); + } + }; + return new WebStorage(); +}; diff --git a/src/utils/cipher.ts b/src/utils/cipher.ts new file mode 100644 index 0000000..8d0bb89 --- /dev/null +++ b/src/utils/cipher.ts @@ -0,0 +1,81 @@ +import { encrypt, decrypt } from 'crypto-js/aes'; +import { parse } from 'crypto-js/enc-utf8'; +import pkcs7 from 'crypto-js/pad-pkcs7'; +import ECB from 'crypto-js/mode-ecb'; +import md5 from 'crypto-js/md5'; +import UTF8 from 'crypto-js/enc-utf8'; +import Base64 from 'crypto-js/enc-base64'; +import CryptoJS from 'crypto-js'; + +export interface EncryptionParams { + key: string; + iv: string; +} + +export class AesEncryption { + private key; + private iv; + + constructor(opt: Partial = {}) { + const { key, iv } = opt; + if (key) { + this.key = parse(key); + } + if (iv) { + this.iv = parse(iv); + } + } + + get getOptions() { + return { + mode: ECB, + padding: pkcs7, + iv: this.iv, + }; + } + + encryptByAES(cipherText: string) { + return encrypt(cipherText, this.key, this.getOptions).toString(); + } + + decryptByAES(cipherText: string) { + return decrypt(cipherText, this.key, this.getOptions).toString(UTF8); + } +} + +export function encryptByBase64(cipherText: string) { + return UTF8.parse(cipherText).toString(Base64); +} + +export function decodeByBase64(cipherText: string) { + return Base64.parse(cipherText).toString(UTF8); +} + +export function encryptByMd5(password: string) { + return md5(password).toString(); +} + +// ================== 密码加密相关 ================== +// 密码加密统一使用 AES CBC 模式,前后端 key 和 iv 必须保持一致 +// AES_KEY 和 AES_IV 需与后端配置完全一致,否则加密/解密会失败 +// ================== 密码加密相关 ===========BEGIN======= + +// AES加密key和iv常量 +export const AES_KEY = '1234567890adbcde'; +export const AES_IV = '1234567890hjlkew'; + +/** + * AES CBC 加密,使用全局常量 AES_KEY 和 AES_IV + * @param plainText 明文 + * @returns 加密后的密文 + */ +export function encryptAESCBC(plainText: string): string { + const key = parse(AES_KEY); + const iv = parse(AES_IV); + return encrypt(plainText, key, { + iv: iv, + mode: CryptoJS.mode.CBC, + padding: pkcs7 + }).toString(); +} +// ================== 密码加密相关 =============END===== diff --git a/src/utils/color.ts b/src/utils/color.ts new file mode 100644 index 0000000..5a07dfb --- /dev/null +++ b/src/utils/color.ts @@ -0,0 +1,145 @@ +/** + * 判断是否 十六进制颜色值. + * 输入形式可为 #fff000 #f00 + * + * @param String color 十六进制颜色值 + * @return Boolean + */ +export function isHexColor(color: string) { + const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-f]{6})$/; + return reg.test(color); +} + +/** + * RGB 颜色值转换为 十六进制颜色值. + * r, g, 和 b 需要在 [0, 255] 范围内 + * + * @return String 类似#ff00ff + * @param r + * @param g + * @param b + */ +export function rgbToHex(r: number, g: number, b: number) { + // tslint:disable-next-line:no-bitwise + const hex = ((r << 16) | (g << 8) | b).toString(16); + return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex; +} + +/** + * Transform a HEX color to its RGB representation + * @param {string} hex The color to transform + * @returns The RGB representation of the passed color + */ +export function hexToRGB(hex: string) { + let sHex = hex.toLowerCase(); + if (isHexColor(hex)) { + if (sHex.length === 4) { + let sColorNew = '#'; + for (let i = 1; i < 4; i += 1) { + sColorNew += sHex.slice(i, i + 1).concat(sHex.slice(i, i + 1)); + } + sHex = sColorNew; + } + const sColorChange: number[] = []; + for (let i = 1; i < 7; i += 2) { + sColorChange.push(parseInt('0x' + sHex.slice(i, i + 2))); + } + return 'RGB(' + sColorChange.join(',') + ')'; + } + return sHex; +} + +export function colorIsDark(color: string) { + if (!isHexColor(color)) return; + const [r, g, b] = hexToRGB(color) + .replace(/(?:\(|\)|rgb|RGB)*/g, '') + .split(',') + .map((item) => Number(item)); + return r * 0.299 + g * 0.578 + b * 0.114 < 192; +} + +/** + * Darkens a HEX color given the passed percentage + * @param {string} color The color to process + * @param {number} amount The amount to change the color by + * @returns {string} The HEX representation of the processed color + */ +export function darken(color: string, amount: number) { + color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color; + amount = Math.trunc((255 * amount) / 100); + return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight(color.substring(2, 4), amount)}${subtractLight( + color.substring(4, 6), + amount + )}`; +} + +/** + * Lightens a 6 char HEX color according to the passed percentage + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed color represented as HEX + */ +export function lighten(color: string, amount: number) { + color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color; + amount = Math.trunc((255 * amount) / 100); + return `#${addLight(color.substring(0, 2), amount)}${addLight(color.substring(2, 4), amount)}${addLight(color.substring(4, 6), amount)}`; +} + +/* Suma el porcentaje indicado a un color (RR, GG o BB) hexadecimal para aclararlo */ +/** + * Sums the passed percentage to the R, G or B of a HEX color + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed part of the color + */ +function addLight(color: string, amount: number) { + const cc = parseInt(color, 16) + amount; + const c = cc > 255 ? 255 : cc; + return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`; +} + +/** + * Calculates luminance of an rgb color + * @param {number} r red + * @param {number} g green + * @param {number} b blue + */ +function luminanace(r: number, g: number, b: number) { + const a = [r, g, b].map((v) => { + v /= 255; + return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); + }); + return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; +} + +/** + * Calculates contrast between two rgb colors + * @param {string} rgb1 rgb color 1 + * @param {string} rgb2 rgb color 2 + */ +function contrast(rgb1: string[], rgb2: number[]) { + return (luminanace(~~rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) / (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05); +} + +/** + * Determines what the best text color is (black or white) based con the contrast with the background + * @param hexColor - Last selected color by the user + */ +export function calculateBestTextColor(hexColor: string) { + const rgbColor = hexToRGB(hexColor.substring(1)); + const contrastWithBlack = contrast(rgbColor.split(','), [0, 0, 0]); + + return contrastWithBlack >= 12 ? '#000000' : '#FFFFFF'; +} + +/** + * Subtracts the indicated percentage to the R, G or B of a HEX color + * @param {string} color The color to change + * @param {number} amount The amount to change the color by + * @returns {string} The processed part of the color + */ +function subtractLight(color: string, amount: number) { + const cc = parseInt(color, 16) - amount; + const c = cc < 0 ? 0 : cc; + return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`; +} diff --git a/src/utils/common/compUtils.ts b/src/utils/common/compUtils.ts new file mode 100644 index 0000000..0faab61 --- /dev/null +++ b/src/utils/common/compUtils.ts @@ -0,0 +1,781 @@ +import { useGlobSetting } from '/@/hooks/setting'; +import { merge, random } from 'lodash-es'; +import { isArray } from '/@/utils/is'; +import { FormSchema } from '/@/components/Form'; +import { h, reactive, ref } from "vue"; +import { getTenantId, getToken } from "/@/utils/auth"; +import { useUserStoreWithOut } from "/@/store/modules/user"; +import dayjs from 'dayjs'; +import Big from 'big.js'; + +import { Modal } from "ant-design-vue"; +import { defHttp } from "@/utils/http/axios"; +import { useI18n } from "@/hooks/web/useI18n"; +import {$electron} from "@/electron"; +import {router} from "@/router"; +import {encryptByBase64} from "@/utils/cipher"; +//存放部门路径的数组 +const departNamePath = ref>({}); + +const globSetting = useGlobSetting(); +const baseApiUrl = globSetting.domainUrl; +/** + * 获取文件服务访问路径 + * @param fileUrl 文件路径 + * @param prefix(默认http) 文件路径前缀 http/https + */ +export const getFileAccessHttpUrl = (fileUrl, prefix = 'http') => { + let result = fileUrl; + try { + if (fileUrl && fileUrl.length > 0 && !fileUrl.startsWith(prefix)) { + //判断是否是数组格式 + let isArray = fileUrl.indexOf('[') != -1; + if (!isArray) { + let prefix = `${baseApiUrl}/sys/common/static/`; + // 判断是否已包含前缀 + if (!fileUrl.startsWith(prefix)) { + result = `${prefix}${fileUrl}`; + } + } + } + } catch (err) {} + return result; +}; +/** + * 获取桌面端wps的文件服务访问路径 + * @param fileUrl 文件路径 + */ +export const getElectronFileUrl = (url) => { + let fileUrl: any = url; + if (url && $electron.isElectron()) { + fileUrl = router.resolve({path: '/onlinePreview', query: {url: encryptByBase64(getFileAccessHttpUrl(url))}}).href; + } + return fileUrl; +}; + +/** + * 触发 window.resize + */ +export function triggerWindowResizeEvent() { + let event: any = document.createEvent('HTMLEvents'); + event.initEvent('resize', true, true); + event.eventType = 'message'; + window.dispatchEvent(event); +} + +/** + * 获取随机数 + * @param length 数字位数 + */ +export const getRandom = (length: number = 1) => { + return '-' + parseInt(String(Math.random() * 10000 + 1), length); +}; + +/** + * 随机生成字符串 + * @param length 字符串的长度 + * @param chats 可选字符串区间(只会生成传入的字符串中的字符) + * @return string 生成的字符串 + */ +export function randomString(length: number, chats?: string) { + if (!length) length = 1; + if (!chats) { + // noinspection SpellCheckingInspection + chats = '0123456789qwertyuioplkjhgfdsazxcvbnm'; + } + let str = ''; + for (let i = 0; i < length; i++) { + let num = random(0, chats.length - 1); + str += chats[num]; + } + return str; +} + +/** + * 将普通列表数据转化为tree结构 + * @param array tree数据 + * @param opt 配置参数 + * @param startPid 父节点 + */ +export const listToTree = (array, opt, startPid) => { + const obj = { + primaryKey: opt.primaryKey || 'key', + parentKey: opt.parentKey || 'parentId', + titleKey: opt.titleKey || 'title', + startPid: opt.startPid || '', + currentDept: opt.currentDept || 0, + maxDept: opt.maxDept || 100, + childKey: opt.childKey || 'children', + }; + if (startPid) { + obj.startPid = startPid; + } + return toTree(array, obj.startPid, obj.currentDept, obj); +}; +/** + * 递归构建tree + * @param list + * @param startPid + * @param currentDept + * @param opt + * @returns {Array} + */ +export const toTree = (array, startPid, currentDept, opt) => { + if (opt.maxDept < currentDept) { + return []; + } + let child = []; + if (array && array.length > 0) { + child = array + .map((item) => { + // 筛查符合条件的数据(主键 = startPid) + if (typeof item[opt.parentKey] !== 'undefined' && item[opt.parentKey] === startPid) { + // 满足条件则递归 + const nextChild = toTree(array, item[opt.primaryKey], currentDept + 1, opt); + // 节点信息保存 + if (nextChild.length > 0) { + item['isLeaf'] = false; + item[opt.childKey] = nextChild; + } else { + item['isLeaf'] = true; + } + item['title'] = item[opt.titleKey]; + item['label'] = item[opt.titleKey]; + item['key'] = item[opt.primaryKey]; + item['value'] = item[opt.primaryKey]; + return item; + } + }) + .filter((item) => { + return item !== undefined; + }); + } + return child; +}; + +/** + * 表格底部合计工具方法 + * @param tableData 表格数据 + * @param fieldKeys 要计算合计的列字段 + */ +export function mapTableTotalSummary(tableData: Recordable[], fieldKeys: string[]) { + let totals: any = { _row: '合计', _index: '合计' }; + fieldKeys.forEach((key) => { + totals[key] = tableData.reduce((prev, next) => { + // 代码逻辑说明: 【QQYUN-7891】PR 合计工具方法,转换为Nuber类型再计算 + const value = Number(next[key]); + if (!Number.isNaN(value)) { + // 代码逻辑说明: 【issues/7830】合计小数计算精度 + prev = Big(prev).plus(value).toString(); + } + return prev; + }, 0); + // 代码逻辑说明: 【issues/7830】合计小数计算精度 + totals[key] = +totals[key]; + }); + return totals; +} + +/** + * 简单实现防抖方法 + * + * 防抖(debounce)函数在第一次触发给定的函数时,不立即执行函数,而是给出一个期限值(delay),比如100ms。 + * 如果100ms内再次执行函数,就重新开始计时,直到计时结束后再真正执行函数。 + * 这样做的好处是如果短时间内大量触发同一事件,只会执行一次函数。 + * + * @param fn 要防抖的函数 + * @param delay 防抖的毫秒数 + * @returns {Function} + */ +export function simpleDebounce(fn, delay = 100) { + let timer: any | null = null; + return function () { + let args = arguments; + if (timer) { + clearTimeout(timer); + } + timer = setTimeout(() => { + // @ts-ignore + fn.apply(this, args); + }, delay); + }; +} + +/** + * 日期格式化 + * @param date 日期 + * @param block 格式化字符串 + */ +export function dateFormat(date, block) { + if (!date) { + return ''; + } + let format = block || 'yyyy-MM-dd'; + date = new Date(date); + const map = { + M: date.getMonth() + 1, // 月份 + d: date.getDate(), // 日 + h: date.getHours(), // 小时 + m: date.getMinutes(), // 分 + s: date.getSeconds(), // 秒 + q: Math.floor((date.getMonth() + 3) / 3), // 季度 + S: date.getMilliseconds(), // 毫秒 + }; + format = format.replace(/([yMdhmsqS])+/g, (all, t) => { + let v = map[t]; + if (v !== undefined) { + if (all.length > 1) { + v = `0${v}`; + v = v.substr(v.length - 2); + } + return v; + } else if (t === 'y') { + return date + .getFullYear() + .toString() + .substr(4 - all.length); + } + return all; + }); + return format; +} + +/** + * 获取事件冒泡路径,兼容 IE11,Edge,Chrome,Firefox,Safari + * 目前使用的地方:JVxeTable Span模式 + */ +export function getEventPath(event) { + let target = event.target; + let path = (event.composedPath && event.composedPath()) || event.path; + + if (path != null) { + return path.indexOf(window) < 0 ? path.concat(window) : path; + } + + if (target === window) { + return [window]; + } + + let getParents = (node, memo) => { + const parentNode = node.parentNode; + + if (!parentNode) { + return memo; + } else { + return getParents(parentNode, memo.concat(parentNode)); + } + }; + return [target].concat(getParents(target, []), window); +} + +/** + * 如果值不存在就 push 进数组,反之不处理 + * @param array 要操作的数据 + * @param value 要添加的值 + * @param key 可空,如果比较的是对象,可能存在地址不一样但值实际上是一样的情况,可以传此字段判断对象中唯一的字段,例如 id。不传则直接比较实际值 + * @returns {boolean} 成功 push 返回 true,不处理返回 false + */ +export function pushIfNotExist(array, value, key?) { + for (let item of array) { + if (key && item[key] === value[key]) { + return false; + } else if (item === value) { + return false; + } + } + array.push(value); + return true; +} +/** + * 过滤对象中为空的属性 + * @param obj + * @returns {*} + */ +export function filterObj(obj) { + if (!(typeof obj == 'object')) { + return; + } + + for (let key in obj) { + if (obj.hasOwnProperty(key) && (obj[key] == null || obj[key] == undefined || obj[key] === '')) { + delete obj[key]; + } + } + return obj; +} + +/** + * 下划线转驼峰 + * @param string + */ +export function underLine2CamelCase(string: string) { + return string.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); +} + +/** + * 查找树结构 + * @param treeList + * @param fn 查找方法 + * @param childrenKey + */ +export function findTree(treeList: any[], fn: Fn, childrenKey = 'children') { + for (let i = 0; i < treeList.length; i++) { + let item = treeList[i]; + if (fn(item, i, treeList)) { + return item; + } + let children = item[childrenKey]; + if (isArray(children)) { + let findResult = findTree(children, fn, childrenKey); + if (findResult) { + return findResult; + } + } + } + return null; +} + +/** 获取 mapFormSchema 方法 */ +export function bindMapFormSchema(spanMap, spanTypeDef: T) { + return function (s: FormSchema, spanType: T = spanTypeDef) { + return merge( + { + disabledLabelWidth: true, + } as FormSchema, + spanMap[spanType], + s + ); + }; +} + +/** + * 字符串是否为null或null字符串 + * @param str + * @return {boolean} + */ +export function stringIsNull(str) { + // 两个 == 可以同时判断 null 和 undefined + return str == null || str === 'null' || str === 'undefined'; +} + +/** + * 【组件多了可能存在性能问题】获取弹窗div,将下拉框、日期等组件挂载到modal上,解决弹窗遮盖问题 + * @param node + */ +export function getAutoScrollContainer(node: HTMLElement) { + let element: Nullable = node + while (element != null) { + if (element.classList.contains('scrollbar__view')) { + // 判断是否有滚动条 + if (element.clientHeight < element.scrollHeight) { + // 有滚动条时,挂载到父级,解决滚动问题 + return node.parentElement + } else { + // 无滚动条时,挂载到body上,解决下拉框遮盖问题 + return document.body + } + } else { + element = element.parentElement + } + } + // 不在弹窗内,走默认逻辑 + return node.parentElement +} + +/** + * 判断子菜单是否全部隐藏 + * @param menuTreeItem + */ +export function checkChildrenHidden(menuTreeItem){ + //是否是聚合路由 + let alwaysShow=menuTreeItem.alwaysShow; + if(alwaysShow){ + return false; + } + if(!menuTreeItem.children){ + return false + } + return menuTreeItem.children?.find((item) => item.hideMenu == false) != null; +} + +/** + * 计算文件大小 + * @param fileSize + * @param unit + * @return 返回大小及后缀 + */ +export function calculateFileSize(fileSize, unit?) { + let unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + if (unit && unit.length > 0) { + unitArr = unit; + } + let size = fileSize; + let unitIndex = 0; + while (size >= 1024 && unitIndex < unitArr.length - 1) { + size /= 1024; + unitIndex++; + } + //保留两位小数,四舍五入 + size = Math.round(size * 100) / 100; + return size + unitArr[unitIndex]; +} + +/** + * 获取上传header + */ +export function getHeaders() { + let tenantId = getTenantId(); + return reactive({ + 'X-Access-Token': getToken(), + 'X-Tenant-Id': tenantId ? tenantId : '0', + }); +} + +/** 根据表达式获取相应的用户信息 */ +export function getUserInfoByExpression(expression) { + if (!expression) { + return expression; + } + // 当前日期 + if (expression === 'sys_date' || expression === 'sysDate') { + return dayjs().format('YYYY-MM-DD'); + } + // 当前时间 + if (expression === 'sys_time' || expression === 'sysTime') { + return dayjs().format('HH:mm:ss'); + } + const userStore = useUserStoreWithOut(); + let userInfo = userStore.getUserInfo; + if (userInfo) { + switch (expression) { + case 'sysUserId': + return userInfo.id; + // 当前登录用户登录账号 + case 'sysUserCode': + case 'sys_user_code': + return userInfo.username; + // 当前登录用户真实名称 + case 'sysUserName': + return userInfo.realname; + // 当前登录用户部门编号 + case 'sysOrgCode': + case 'sys_org_code': + return userInfo.orgCode; + } + } + return expression; +} + +/** + * 替换表达式(#{xxx})为用户信息 + * @param expression + */ +export function replaceUserInfoByExpression(expression: string | any[]) { + if (!expression) { + return expression; + } + const isString = typeof expression === 'string'; + const isArray = Array.isArray(expression) + if (!isString && !isArray) { + return expression; + } + const reg = /#{(.*?)}/g; + const replace = (str) => { + if (typeof str !== 'string') { + return str; + } + let result = str.match(reg); + if (result && result.length > 0) { + result.forEach((item) => { + let userInfo = getUserInfoByExpression(item.substring(2, item.length - 1)); + str = str.replace(item, userInfo); + }); + } + return str; + }; + // @ts-ignore + return isString ? replace(expression) : expression.map(replace); +} + +/** + * 设置租户缓存,当租户退出的时候 + * + * @param tenantId + */ +export async function userExitChangeLoginTenantId(tenantId){ + const userStore = useUserStoreWithOut(); + //step 1 获取用户租户 + const url = '/sys/tenant/getCurrentUserTenant' + let currentTenantId = null; + const data = await defHttp.get({ url }); + if(data && data.list){ + let arr = data.list; + if(arr.length>0){ + //step 2.判断当前id是否存在用户租户中 + let filterTenantId = arr.filter((item) => item.id == tenantId); + //存在说明不是退出的不是当前租户,还用用来的租户即可 + if(filterTenantId && filterTenantId.length>0){ + currentTenantId = tenantId; + }else{ + //不存在默认第一个 + currentTenantId = arr[0].id + } + } + } + let loginTenantId = getTenantId(); + userStore.setTenant(currentTenantId); + + //租户为空,说明没有租户了,需要刷新页面。或者当前租户和退出的租户一致则需要刷新浏览器 + if(!currentTenantId || tenantId == loginTenantId){ + window.location.reload(); + } +} + +/** + * 我的租户模块需要开启多租户提示 + * + * @param title 标题 + */ +export function tenantSaasMessage(title){ + let tenantId = getTenantId(); + if(!tenantId){ + Modal.confirm({ + title:title, + content: '此菜单需要在多租户模式下使用,否则数据会出现混乱', + okText: '确认', + okType: 'danger', + // @ts-ignore + cancelButtonProps: { style: { display: 'none' } }, + }) + } +} + +/** + * 判断日期和当前时间是否为同一天 + * @param dateStr + */ +export function sameDay(dateStr) { + if (!dateStr) { + return false; + } + // 获取当前日期 + let currentDate = new Date(); + let currentDay = currentDate.getDate(); + let currentMonth = currentDate.getMonth(); + let currentYear = currentDate.getFullYear(); + + //创建另一个日期进行比较 + let otherDate = new Date(dateStr); + let otherDay = otherDate.getDate(); + let otherMonth = otherDate.getMonth(); + let otherYear = otherDate.getFullYear(); + + //比较日期 + if (currentDay === otherDay && currentMonth === otherMonth && currentYear === otherYear) { + return true; + } else { + return false; + } +} + + +/** + * 翻译菜单名称 + * 2024-02-28 + * liaozhiyang + * @param data + */ +export function translateTitle(data) { + if (data?.length) { + const { t } = useI18n(); + data.forEach((item) => { + if (item.slotTitle) { + if (item.slotTitle.includes("t('") && t) { + item.slotTitle = new Function('t', `return ${item.slotTitle}`)(t); + } + } + if (item.children?.length) { + translateTitle(item.children); + } + }); + } + return data; +} + +/** + * + * 深度冻结对象 + * @param obj Object or Array + */ +export function freezeDeep(obj: Recordable | Recordable[]) { + if (obj != null) { + if (Array.isArray(obj)) { + obj.forEach(item => freezeDeep(item)) + } else if (typeof obj === 'object') { + Object.values(obj).forEach(value => { + freezeDeep(value) + }) + } + Object.freeze(obj) + } + return obj +} + +/** + * 获取父级名称 + * + * @param orgCode 当前部门的code + * @param label 当前默认显示的值 + * @param depId depId + * @return 部门名称 + */ +export async function getDepartPathNameByOrgCode(orgCode, label, depId){ + if (orgCode) { + depId = ""; + } + let result = await defHttp.get({ url: "/sys/sysDepart/getDepartPathNameByOrgCode", params:{ orgCode: orgCode, depId: depId } }, { isTransformResponse: false }); + if (result.success) { + return result.result; + } + return label; +} + +/** + * 获取部门路径名称 + * @param title + * @param key 部门code或者部门id + * @param izOrgCode 是否是机构编码 + */ +export function getDepartPathName(title,key,izOrgCode) { + if (departNamePath.value[key]) { + return departNamePath.value[key]; + } + if(izOrgCode){ + getDepartPathNameByOrgCode(key, title, "").then(result => { + departNamePath.value[key] = result; + }); + }else{ + getDepartPathNameByOrgCode("", title, key).then(result => { + departNamePath.value[key] = result; + }); + } + +} + +/** + * 获取多个部门路径名称 + * @param title + * @param id + */ +export function getMultiDepartPathName(title,id) { + if(!id || id.length === 0){ + return ''; + } + let postIds:any = ""; + if(id instanceof Array){ + postIds = id; + } else { + postIds = id.split(",") + } + let postNames = ""; + postIds.forEach((postId)=>{ + postNames += getDepartPathName(title,postId,false) + ","; + }); + if(postNames.endsWith(",")){ + postNames = postNames.substring(0,postNames.length - 1); + } + return postNames; +} + +/** + * 获取部门名称 返回h + * @param departNamePath 部门路径 + */ +export function getDepartName(departNamePath) { + if(departNamePath){ + let names = departNamePath.split(","); + let textElements:any = []; + for (let i = 0; i < names.length; i++) { + textElements.push(h("p", { style: { marginBottom: '2px'} }, names[i])); + } + // 组合完整内容字符串用于title属性 + const fullContent = names.join('\n'); + return h("div",{ + style: { + overflow: 'hidden', + textOverflow: 'ellipsis', + display: '-webkit-box', + WebkitLineClamp: 3, + WebkitBoxOrient: 'vertical', + lineHeight: '1.5em', + maxHeight: '4.5em', + width: '100%', + whiteSpace: 'normal' + }, + // 鼠标悬停显示全部内容 + title: fullContent + },textElements) + } + return departNamePath; +} + +/** + * 获取文件表 + * @param fileUrl + */ +export function getFileIcon(fileUrl) { + if(!fileUrl) { + return 'ant-design:file-outlined'; + } + const suffix = fileUrl.substring(fileUrl.lastIndexOf('.') + 1).toLowerCase(); + if(['xls','xlsx','csv'].includes(suffix)) { + return 'ant-design:file-excel-filled'; + } + if(['doc','docx'].includes(suffix)) { + return 'ant-design:file-word-filled'; + } + if(['pdf'].includes(suffix)) { + return 'ant-design:file-pdf-filled'; + } + if(['ppt','pptx'].includes(suffix)) { + return 'ant-design:file-ppt-filled'; + } + if(['txt'].includes(suffix)) { + return 'ant-design:file-text-filled'; + } + if(['md'].includes(suffix)) { + return 'ant-design:file-markdown-filled'; + } + return 'ant-design:file-unknown-filled'; +} + +/** + * 获取文件图标颜色 + * + * @param fileUrl + */ +export function getFileIconColor(fileUrl) { + if(!fileUrl) { + return '#999'; + } + const suffix = fileUrl.substring(fileUrl.lastIndexOf('.') + 1).toLowerCase(); + if(['xls','xlsx','csv'].includes(suffix)) { + return '#52c41a'; + } + if(['doc','docx'].includes(suffix)) { + return '#1890ff'; + } + if(['pdf'].includes(suffix)) { + return '#ff4d4f'; + } + if(['ppt','pptx'].includes(suffix)) { + return '#fa8c16'; + } + if(['txt'].includes(suffix)) { + return '#666'; + } + if(['md'].includes(suffix)) { + return '#000'; + } + return '#999'; +} diff --git a/src/utils/common/renderUtils.ts b/src/utils/common/renderUtils.ts new file mode 100644 index 0000000..dbd522a --- /dev/null +++ b/src/utils/common/renderUtils.ts @@ -0,0 +1,179 @@ +import { h } from 'vue'; +import { Avatar, Tag, Tooltip, Image } from 'ant-design-vue'; +import { getFileAccessHttpUrl } from '/@/utils/common/compUtils'; +import { Tinymce } from '/@/components/Tinymce'; +import Icon from '/@/components/Icon'; +import { getDictItemsByCode } from '/@/utils/dict/index'; +import { filterMultiDictText } from '/@/utils/dict/JDictSelectUtil.js'; +import { isEmpty } from '/@/utils/is'; +import { useMessage } from '/@/hooks/web/useMessage'; +const { createMessage } = useMessage(); + +const render = { + /** + * 渲染列表头像 + */ + renderAvatar: ({ record }) => { + if (record.avatar) { + let avatarList = record.avatar.split(','); + return h( + 'span', + avatarList.map((item) => { + return h(Avatar, { + src: getFileAccessHttpUrl(item), + shape: 'square', + size: 'default', + style: { marginRight: '5px' }, + }); + }) + ); + } else { + return h( + Avatar, + { shape: 'square', size: 'default' }, + { + icon: () => h(Icon, { icon: 'ant-design:file-image-outlined', size: 30 }), + } + ); + } + }, + /** + * 根据字典编码 渲染 + * @param v 值 + * @param code 字典编码 + * @param renderTag 是否使用tag渲染 + */ + renderDict: (v, code, renderTag = false) => { + let text = ''; + let color = ''; + let array = getDictItemsByCode(code) || []; + let obj = array.filter((item) => { + return item.value == v; + }); + if (obj.length > 0) { + text = obj[0].text; + color = obj[0].color; + } + //【jeecgboot-vue3/issues/903】render.renderDict使用tag渲染报警告问题 #903 + return isEmpty(text) || !renderTag ? h('span', text) : h(Tag,{ color }, () => text); + }, + /** + * 渲染图片 + * @param text + */ + renderImage: ({ text }) => { + if (!text) { + return h(Image, { + width: 30, + height: 30, + src: '', + fallback: + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==', + }); + } + let avatarList = text.split(','); + return h( + 'span', + avatarList.map((item) => { + return h(Image, { + src: getFileAccessHttpUrl(item), + width: 30, + height: 30, + style: { marginRight: '5px' }, + previewMask: () => { + return h(Icon, { icon: 'ant-design:eye-outlined', size: 20 }); + }, + }); + }) + ); + }, + /** + * 渲染 Tooltip + * @param text + * @param len + */ + renderTip: (text, len = 20) => { + if (text) { + let showText = text + ''; + if (showText.length > len) { + showText = showText.substr(0, len) + '...'; + } + return h(Tooltip, { title: text }, () => showText); + } + return text; + }, + /** + * 渲染a标签 + * @param text + */ + renderHref: ({ text }) => { + if (!text) { + return ''; + } + const len = 20; + if (text.length > len) { + text = text.substr(0, len); + } + return h('a', { href: text, target: '_blank' }, text); + }, + /** + * 根据字典渲染 + * @param v + * @param array + */ + renderDictNative: (v, array, renderTag = false) => { + let text = ''; + let color = ''; + let obj = array.filter((item) => { + return item.value == v; + }); + if (obj.length > 0) { + text = obj[0].label; + color = obj[0].color; + } + return isEmpty(text) || !renderTag ? h('span', text) : h(Tag, { color }, () => text); + }, + /** + * 渲染富文本 + */ + renderTinymce: ({ model, field }) => { + return h(Tinymce, { + showImageUpload: false, + height: 300, + value: model[field], + onChange: (value: string) => { + model[field] = value; + }, + }); + }, + + renderSwitch: (text, arr) => { + return text ? filterMultiDictText(arr, text) : ''; + }, + renderCategoryTree: (text, code) => { + let array = getDictItemsByCode(code); + return filterMultiDictText(array, text); + }, + renderTag(text, color) { + return isEmpty(text) ? h('span', text) : h(Tag, { color }, () => text); + }, +}; + +/** + * 文件下载 + */ +function downloadFile(url) { + if (!url) { + createMessage.warning('未知的文件'); + return; + } + if (url.indexOf(',') > 0) { + url = url.substring(0, url.indexOf(',')); + } + url = getFileAccessHttpUrl(url.split(',')[0]); + if (url) { + window.open(url); + } +} + +export { render, downloadFile }; diff --git a/src/utils/common/vxeUtils.ts b/src/utils/common/vxeUtils.ts new file mode 100644 index 0000000..3c83f27 --- /dev/null +++ b/src/utils/common/vxeUtils.ts @@ -0,0 +1,103 @@ +import { getValueType } from '/@/utils'; + +export const VALIDATE_FAILED = Symbol(); +/** + * 一次性验证主表单和所有的次表单(新版本) + * @param form 主表单 form 对象 + * @param cases 接收一个数组,每项都是一个JEditableTable实例 + * @returns {Promise} + */ +export async function validateFormModelAndTables(validate, formData, cases, props, autoJumpTab?) { + if (!(validate && typeof validate === 'function')) { + throw `validate 参数需要的是一个方法,而传入的却是${typeof validate}`; + } + let dataMap = {}; + let values = await new Promise((resolve, reject) => { + // 验证主表表单 + validate() + .then(() => { + // 代码逻辑说明: [VUEN-912]一对多用户组件(所有风格,单表和树没问题)保存报错------------ + for (let data in formData) { + //如果该数据是数组 + if (formData[data] instanceof Array) { + let valueType = getValueType(props, data); + //如果是字符串类型的需要变成以逗号分割的字符串 + if (valueType === 'string') { + formData[data] = formData[data].join(','); + } + } + } + resolve(formData); + }) + // 代码逻辑说明: 【TV360X-1064】非原生提交表单滚动校验没通过的项--- + .catch(({ errorFields }) => { + reject({ error: VALIDATE_FAILED, index: 0, errorFields: errorFields }); + }); + }); + Object.assign(dataMap, { formValue: values }); + // 验证所有子表的表单 + let subData = await validateTables(cases, autoJumpTab); + // 合并最终数据 + dataMap = Object.assign(dataMap, { tablesValue: subData }); + return dataMap; +} +/** + * 验证并获取一个或多个表格的所有值 + * @param cases 接收一个数组,每项都是一个JEditableTable实例 + * @param autoJumpTab 是否自动跳转到报错的tab + */ +export function validateTables(cases, autoJumpTab = true) { + if (!(cases instanceof Array)) { + throw `'validateTables'函数的'cases'参数需要的是一个数组,而传入的却是${typeof cases}`; + } + return new Promise((resolve, reject) => { + let tablesData: any = []; + let index = 0; + if (!cases || cases.length === 0) { + resolve(tablesData); + } + (function next() { + let vm = cases[index]; + vm.value.validateTable().then((errMap) => { + // 校验通过 + if (!errMap) { + tablesData[index] = { tableData: vm.value.getTableData() }; + // 判断校验是否全部完成,完成返回成功,否则继续进行下一步校验 + if (++index === cases.length) { + resolve(tablesData); + } else next(); + } else { + // 尝试获取tabKey,如果在ATab组件内即可获取 + let paneKey; + let tabPane = getVmParentByName(vm.value, 'ATabPane'); + if (tabPane) { + paneKey = tabPane.$.vnode.key; + // 自动跳转到该表格 + if (autoJumpTab) { + let tabs = getVmParentByName(tabPane, 'Tabs'); + tabs && tabs.setActiveKey && tabs.setActiveKey(paneKey); + } + } + // 出现未验证通过的表单,不再进行下一步校验,直接返回失败 + // 代码逻辑说明: TV360X-478 一对多tab,校验未通过时,tab没有跳转 + reject({ error: VALIDATE_FAILED, index, paneKey, errMap, subIndex: index }); + } + }); + })(); + }); +} + +export function getVmParentByName(vm, name) { + let parent = vm.$parent; + if (parent && parent.$options) { + if (parent.$options.name === name) { + return parent; + } else { + let res = getVmParentByName(parent, name); + if (res) { + return res; + } + } + } + return null; +} diff --git a/src/utils/dateUtil.ts b/src/utils/dateUtil.ts new file mode 100644 index 0000000..687eb25 --- /dev/null +++ b/src/utils/dateUtil.ts @@ -0,0 +1,17 @@ +/** + * Independent time operation tool to facilitate subsequent switch to dayjs + */ +import dayjs from 'dayjs'; + +const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; +const DATE_FORMAT = 'YYYY-MM-DD'; + +export function formatToDateTime(date: dayjs.Dayjs | undefined = undefined, format = DATE_TIME_FORMAT): string { + return dayjs(date).format(format); +} + +export function formatToDate(date: dayjs.Dayjs | undefined = undefined, format = DATE_FORMAT): string { + return dayjs(date).format(format); +} + +export const dateUtil = dayjs; diff --git a/src/utils/desform/customExpression.ts b/src/utils/desform/customExpression.ts new file mode 100644 index 0000000..5359ba6 --- /dev/null +++ b/src/utils/desform/customExpression.ts @@ -0,0 +1,30 @@ +/* + * + * 这里填写用户自定义的表达式 + * 可用在Online表单的默认值表达式中使用 + * 需要外部使用的变量或方法一定要 export,否则无法识别 + * 示例: + * export const name = '张三'; // const 是常量 + * export let age = 17; // 看情况 export const 还是 let ,两者都可正常使用 + * export function content(arg) { // export 方法,可传参数,使用时要加括号,值一定要return回去,可以返回Promise + * return 'content' + arg; + * } + * export const address = (arg) => content(arg) + ' | 北京市'; // export 箭头函数也可以 + * + */ + +/** 字段默认值官方示例:获取地址 */ +export function demoFieldDefVal_getAddress(arg) { + if (!arg) { + arg = '朝阳区'; + } + return `北京市 ${arg}`; +} + +/** 自定义JS函数示例 */ +export function sayHi(name) { + if (!name) { + name = '张三'; + } + return `您好,我叫: ${name}`; +} diff --git a/src/utils/dict/DictColors.js b/src/utils/dict/DictColors.js new file mode 100644 index 0000000..ae91da4 --- /dev/null +++ b/src/utils/dict/DictColors.js @@ -0,0 +1,65 @@ +const whiteColor = '#ffffff' +const blackColor = '#666666' + +export const Colors = [ + // 背景颜色,文字颜色 + ['#2196F3', whiteColor], + ['#08C9C9', whiteColor], + ['#00C345', whiteColor], + ['#FAD714', whiteColor], + ['#FF9300', whiteColor], + ['#F52222', whiteColor], + ['#EB2F96', whiteColor], + ['#7500EA', whiteColor], + ['#2D46C4', whiteColor], + ['#484848', whiteColor], + // -------------------- + ['#C9E6FC', blackColor], + ['#C3F2F2', blackColor], + ['#C2F1D2', blackColor], + ['#FEF6C6', blackColor], + ['#FFE5C2', blackColor], + ['#FDCACA', blackColor], + ['#FACDE6', blackColor], + ['#DEC2FA', blackColor], + ['#CCD2F1', blackColor], + ['#D3D3D3', blackColor], +] + +export const NONE_COLOR = ['#e9e9e9', blackColor] + +/** + * 返回一个颜色迭代器,每次调用返回一个颜色,当颜色用完后,再从头开始 + * @param {number} initIndex 初始颜色索引 + * @returns {{getIndex: function, next: function}} + */ +export function getColorIterator(initIndex = 0) { + let index = initIndex; + if (index < 0 || index >= Colors.length) { + index = 0; + } + return { + getIndex: () => index, + next() { + const color = Colors[index]; + index = (index + 1) % Colors.length; + return color; + }, + } +} + +/** + * 根据颜色获取当前坐标和颜色 + */ +export function getItemColor(color) { + if(!color){ + return NONE_COLOR[1]; + } + let colorIndex = Colors.findIndex((value)=>{ + return value[0] === color; + }) + if(colorIndex === -1){ + return NONE_COLOR[1]; + } + return Colors[colorIndex][1]; +} diff --git a/src/utils/dict/JDictSelectUtil.js b/src/utils/dict/JDictSelectUtil.js new file mode 100644 index 0000000..52ff6a1 --- /dev/null +++ b/src/utils/dict/JDictSelectUtil.js @@ -0,0 +1,186 @@ +/** + * 字典 util + * author: scott + * date: 20190109 + */ + +import { ajaxGetDictItems, getDictItemsByCode } from './index'; + +/** + * 获取字典数组 + * 【目前仅表单设计器页面使用该方法】 + * @param dictCode 字典Code + * @param isTransformResponse 是否转换返回结果 + * @return List + */ +export async function initDictOptions(dictCode, isTransformResponse = true) { + if (!dictCode) { + return '字典Code不能为空!'; + } + //优先从缓存中读取字典配置 + if (getDictItemsByCode(dictCode)) { + let res = {}; + res.result = getDictItemsByCode(dictCode); + res.success = true; + if (isTransformResponse) { + return res.result; + } else { + return res; + } + } + //获取字典数组 + return await ajaxGetDictItems(dictCode, {}, { isTransformResponse }); +} + +/** + * 字典值替换文本通用方法 + * @param dictOptions 字典数组 + * @param text 字典值 + * @return String + */ +export function filterDictText(dictOptions, text) { + // --update-begin----author:sunjianlei---date:20200323------for: 字典翻译 text 允许逗号分隔 --- + if (text != null && Array.isArray(dictOptions)) { + let result = []; + // 允许多个逗号分隔,允许传数组对象 + let splitText; + if (Array.isArray(text)) { + splitText = text; + } else { + splitText = text.toString().trim().split(','); + } + for (let txt of splitText) { + let dictText = txt; + for (let dictItem of dictOptions) { + // 代码逻辑说明: 【TV360X-469】兼容数据null值防止报错 + if (dictItem == null) continue; + if (dictItem.value == null) continue; + if (txt.toString() === dictItem.value.toString()) { + dictText = dictItem.text || dictItem.title || dictItem.label; + break; + } + } + result.push(dictText); + } + return result.join(','); + } + return text; + // --update-end----author:sunjianlei---date:20200323------for: 字典翻译 text 允许逗号分隔 --- +} + +/** + * 字典值替换文本通用方法(多选) + * @param dictOptions 字典数组 + * @param text 字典值 + * @return String + */ +export function filterMultiDictText(dictOptions, text) { + //js “!text” 认为0为空,所以做提前处理 + if (text === 0 || text === '0') { + if (dictOptions) { + for (let dictItem of dictOptions) { + if (text == dictItem.value) { + return dictItem.text; + } + } + } + } + + if (!text || text == 'undefined' || text == 'null' || !dictOptions || dictOptions.length == 0) { + return ''; + } + let re = ''; + text = text.toString(); + let arr = text.split(','); + dictOptions.forEach(function (option) { + if (option) { + for (let i = 0; i < arr.length; i++) { + if (arr[i] === option.value) { + re += option.text + ','; + break; + } + } + } + }); + if (re == '') { + return text; + } + return re.substring(0, re.length - 1); +} + +/** + * 字典值替换文本通用方法(多选) + * @param dictOptions 字典数组 + * @param val 字典值 + * @return {*[]} 返回字典项原对象 + */ +export function filterMultiDictObjs(dictOptions, val) { + val = val?.toString?.() ?? ''; + if (!val || !dictOptions || dictOptions.length === 0) { + return []; + } + const objs = []; + const vals = val.split(','); + for (const item of vals) { + const option = dictOptions.find((option) => option && option.value === item); + if (option) { + objs.push({ + value: item, + text: option.text || option.title || option.label, + color: option.color, + hasColor: !!option.color, + }); + } + } + return objs; +} + +/** + * 翻译字段值对应的文本 + * @param children + * @returns string + */ +export function filterDictTextByCache(dictCode, key) { + if (key == null || key.length == 0) { + return; + } + if (!dictCode) { + return '字典Code不能为空!'; + } + //优先从缓存中读取字典配置 + if (getDictItemsByCode(dictCode)) { + let item = getDictItemsByCode(dictCode).filter((t) => t['value'] == key); + if (item && item.length > 0) { + return item[0]['text']; + } + } +} + +/** 通过code获取字典数组 */ +export async function getDictItems(dictCode, params) { + //优先从缓存中读取字典配置 + if (getDictItemsByCode(dictCode)) { + let desformDictItems = getDictItemsByCode(dictCode).map((item) => ({ + ...item, + label: item.text, + })); + return Promise.resolve(desformDictItems); + } + + //缓存中没有,就请求后台 + return await ajaxGetDictItems(dictCode, params) + .then((result) => { + if (result.length) { + let res = result.map((item) => ({ ...item, label: item.text })); + console.log('------- 从DB中获取到了字典-------dictCode : ', dictCode, res); + return Promise.resolve(res); + } else { + console.error('getDictItems error: : ', res); + return Promise.resolve([]); + } + }) + .catch((res) => { + console.error('getDictItems error: ', res); + return Promise.resolve([]); + }); +} diff --git a/src/utils/dict/index.ts b/src/utils/dict/index.ts new file mode 100644 index 0000000..55024fc --- /dev/null +++ b/src/utils/dict/index.ts @@ -0,0 +1,66 @@ +import { defHttp } from '/@/utils/http/axios'; +import { useUserStore } from '/@/store/modules/user'; +import { getAuthCache } from '/@/utils/auth'; +import { DB_DICT_DATA_KEY } from '/@/enums/cacheEnum'; + +/** + * 从缓存中获取字典配置 + * @param code + */ +export const getDictItemsByCode = (code) => { + // 代码逻辑说明: 【QQYUN-6417】生产环境字典慢的问题 + const userStore = useUserStore(); + const dictItems = userStore.getAllDictItems; + if (null != dictItems && typeof dictItems === 'object' && dictItems[code]) { + return dictItems[code]; + } + //兼容以前的旧写法 + if (getAuthCache(DB_DICT_DATA_KEY) && getAuthCache(DB_DICT_DATA_KEY)[code]) { + return getAuthCache(DB_DICT_DATA_KEY)[code]; + } + + +}; +/** + * 从缓存中获取Pop字典配置 + * @param text + * @param code + */ +export const getPopDictByCode = (text, codeStr) => { + const [code, dictCode, dictText] = codeStr.split(','); + if (!code || !dictCode || !dictText) { + return []; + } + return defHttp.get( + { url: `/online/api/cgreportGetDataPackage`, params: { code, dictText, dictCode, dataList: text } }, + { isTransformResponse: false } + ); +}; +/** + * 获取字典数组 + * @param dictCode 字典Code + * @return List + */ +export const initDictOptions = (code) => { + //1.优先从缓存中读取字典配置 + if (getDictItemsByCode(code)) { + return new Promise((resolve, reject) => { + resolve(getDictItemsByCode(code)); + }); + } + //2.获取字典数组 + // 代码逻辑说明: 字典数据请求前将参数编码处理,但是不能直接编码,因为可能之前已经编码过了 + if (code.indexOf(',') > 0 && code.indexOf(' ') > 0) { + // 编码后类似sys_user%20where%20username%20like%20xxx' 是不包含空格的,这里判断如果有空格和逗号说明需要编码处理 + code = encodeURI(code); + } + return defHttp.get({ url: `/sys/dict/getDictItems/${code}` }); +}; +/** + * 获取字典数组 + * @param code 字典Code + * @param params 查询参数 + * @param options 查询配置 + * @return List + */ +export const ajaxGetDictItems = (code, params, options?) => defHttp.get({ url: `/sys/dict/getDictItems/${code}`, params }, options); diff --git a/src/utils/domUtils.ts b/src/utils/domUtils.ts new file mode 100644 index 0000000..42a33cb --- /dev/null +++ b/src/utils/domUtils.ts @@ -0,0 +1,192 @@ +import type { FunctionArgs } from '@vueuse/core'; +import { upperFirst } from 'lodash-es'; + +export interface ViewportOffsetResult { + left: number; + top: number; + right: number; + bottom: number; + rightIncludeBody: number; + bottomIncludeBody: number; +} + +export function getBoundingClientRect(element: Element): DOMRect | number { + if (!element || !element.getBoundingClientRect) { + return 0; + } + return element.getBoundingClientRect(); +} + +function trim(string: string) { + return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, ''); +} + +/* istanbul ignore next */ +export function hasClass(el: Element, cls: string) { + if (!el || !cls) return false; + if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.'); + if (el.classList) { + return el.classList.contains(cls); + } else { + return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1; + } +} + +/* istanbul ignore next */ +export function addClass(el: Element, cls: string) { + if (!el) return; + let curClass = el.className; + const classes = (cls || '').split(' '); + + for (let i = 0, j = classes.length; i < j; i++) { + const clsName = classes[i]; + if (!clsName) continue; + + if (el.classList) { + el.classList.add(clsName); + } else if (!hasClass(el, clsName)) { + curClass += ' ' + clsName; + } + } + if (!el.classList) { + el.className = curClass; + } +} + +/* istanbul ignore next */ +export function removeClass(el: Element, cls: string) { + if (!el || !cls) return; + const classes = cls.split(' '); + let curClass = ' ' + el.className + ' '; + + for (let i = 0, j = classes.length; i < j; i++) { + const clsName = classes[i]; + if (!clsName) continue; + + if (el.classList) { + el.classList.remove(clsName); + } else if (hasClass(el, clsName)) { + curClass = curClass.replace(' ' + clsName + ' ', ' '); + } + } + if (!el.classList) { + el.className = trim(curClass); + } +} +/** + * Get the left and top offset of the current element + * left: the distance between the leftmost element and the left side of the document + * top: the distance from the top of the element to the top of the document + * right: the distance from the far right of the element to the right of the document + * bottom: the distance from the bottom of the element to the bottom of the document + * rightIncludeBody: the distance between the leftmost element and the right side of the document + * bottomIncludeBody: the distance from the bottom of the element to the bottom of the document + * + * @description: + */ +export function getViewportOffset(element: Element): ViewportOffsetResult { + const doc = document.documentElement; + + const docScrollLeft = doc.scrollLeft; + const docScrollTop = doc.scrollTop; + const docClientLeft = doc.clientLeft; + const docClientTop = doc.clientTop; + + const pageXOffset = window.pageXOffset; + const pageYOffset = window.pageYOffset; + + const box = getBoundingClientRect(element); + + const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect; + + const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0); + const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0); + const offsetLeft = retLeft + pageXOffset; + const offsetTop = rectTop + pageYOffset; + + const left = offsetLeft - scrollLeft; + const top = offsetTop - scrollTop; + + const clientWidth = window.document.documentElement.clientWidth; + const clientHeight = window.document.documentElement.clientHeight; + return { + left: left, + top: top, + right: clientWidth - rectWidth - left, + bottom: clientHeight - rectHeight - top, + rightIncludeBody: clientWidth - left, + bottomIncludeBody: clientHeight - top, + }; +} + +export function hackCss(attr: string, value: string) { + const prefix: string[] = ['webkit', 'Moz', 'ms', 'OT']; + + const styleObj: any = {}; + prefix.forEach((item) => { + styleObj[`${item}${upperFirst(attr)}`] = value; + }); + return { + ...styleObj, + [attr]: value, + }; +} + +/* istanbul ignore next */ +export function on(element: Element | HTMLElement | Document | Window, event: string, handler: EventListenerOrEventListenerObject): void { + if (element && event && handler) { + element.addEventListener(event, handler, false); + } +} + +/* istanbul ignore next */ +export function off(element: Element | HTMLElement | Document | Window, event: string, handler: Fn): void { + if (element && event && handler) { + element.removeEventListener(event, handler, false); + } +} + +/* istanbul ignore next */ +export function once(el: HTMLElement, event: string, fn: EventListener): void { + const listener = function (this: any, ...args: unknown[]) { + if (fn) { + fn.apply(this, args); + } + off(el, event, listener); + }; + on(el, event, listener); +} + +export function useRafThrottle(fn: T): T { + let locked = false; + // @ts-ignore + return function (...args: any[]) { + if (locked) return; + locked = true; + window.requestAnimationFrame(() => { + // @ts-ignore + fn.apply(this, args); + locked = false; + }); + }; +} + +/** + * 查找父级元素,直到找到符合条件的元素 + * @param element 当前元素 + * @param checkFn 判断条件 + */ +export function queryParentElement(element: HTMLElement, checkFn: (node: HTMLElement) => boolean): HTMLElement | null { + let ele: HTMLElement | null = element; + while (ele) { + try { + if (checkFn(ele)) { + return ele; + } + ele = ele.parentElement; + } catch (e) { + return null; + } + } + return null; +} diff --git a/src/utils/dynamicPages.ts b/src/utils/dynamicPages.ts new file mode 100644 index 0000000..88e71d2 --- /dev/null +++ b/src/utils/dynamicPages.ts @@ -0,0 +1,10 @@ +// 获取所有动态页面(views目录下的所有vue文件和tsx文件) +const allFiles = import.meta.glob( + [ + '../views/**/*.{vue,tsx}',// 获取所有vue和tsx文件 + // 排除特定文件夹 + '!../views/system/approvalrole/compoments/**', + ] +); +// 合并所有动态页面 +export const dynamicPages = { ...allFiles }; diff --git a/src/utils/encryption/signMd5Utils.js b/src/utils/encryption/signMd5Utils.js new file mode 100644 index 0000000..08e0a1c --- /dev/null +++ b/src/utils/encryption/signMd5Utils.js @@ -0,0 +1,146 @@ +import md5 from 'md5'; +//签名密钥串(前后端要一致,正式发布请自行修改) +const signatureSecret = 'dd05f1c54d63749eda95f9fa6d49v442a'; + +export default class signMd5Utils { + /** + * json参数升序 + * @param jsonObj 发送参数 + */ + + static sortAsc(jsonObj) { + let arr = new Array(); + let num = 0; + for (let i in jsonObj) { + arr[num] = i; + num++; + } + let sortArr = arr.sort(); + let sortObj = {}; + for (let i in sortArr) { + sortObj[sortArr[i]] = jsonObj[sortArr[i]]; + } + return sortObj; + } + + /** + * @param url 请求的url,应该包含请求参数(url的?后面的参数) + * @param requestParams 请求参数(@RequestParam(get)的JSON参数) + * @param requestBodyParams 请求参数(@RequestBody(post)参数) + * @returns {string} 获取签名 + */ + static getSign(url, requestParams, requestBodyParams) { + let urlParams = this.parseQueryString(url); + let jsonObj = this.mergeObject(urlParams, requestParams); + // 代码逻辑说明: 【QQYUN-9005】发送短信加签--- + if(requestBodyParams){ + jsonObj = this.mergeObject(jsonObj, requestBodyParams) + } + let requestBody = this.sortAsc(jsonObj); + delete requestBody._t; + // console.log('sign requestBody:', requestBody); + return md5(JSON.stringify(requestBody) + signatureSecret).toUpperCase(); + } + + /** + * @param url 请求的url + * @returns {{}} 将url中请求参数组装成json对象(url的?后面的参数) + */ + static parseQueryString(url) { + let urlReg = /^[^\?]+\?([\w\W]+)$/, + paramReg = /([^&=]+)=([\w\W]*?)(&|$|#)/g, + urlArray = urlReg.exec(url), + result = {}; + + // 获取URL上最后带逗号的参数变量 sys/dict/getDictItems/sys_user,realname,username + //【这边条件没有encode】带条件参数例子:/sys/dict/getDictItems/sys_user,realname,id,username!='admin'%20order%20by%20create_time + let lastpathVariable = url.substring(url.lastIndexOf('/') + 1); + if (lastpathVariable.includes(',')) { + if (lastpathVariable.includes('?')) { + lastpathVariable = lastpathVariable.substring(0, lastpathVariable.indexOf('?')); + } + //解决Sign 签名校验失败 #2728 + //decodeURI对特殊字符没有没有编码和解码的能力,需要使用decodeURIComponent + result['x-path-variable'] = decodeURIComponent(lastpathVariable); + } + if (urlArray && urlArray[1]) { + let paramString = urlArray[1], + paramResult; + while ((paramResult = paramReg.exec(paramString)) != null) { + //数字值转为string类型,前后端加密规则保持一致 + if (this.myIsNaN(paramResult[2])) { + paramResult[2] = paramResult[2].toString(); + } + result[paramResult[1]] = paramResult[2]; + } + } + return result; + } + + /** + * @returns {*} 将两个对象合并成一个 + */ + static mergeObject(objectOne, objectTwo) { + if (objectTwo && Object.keys(objectTwo).length > 0) { + for (let key in objectTwo) { + if (objectTwo.hasOwnProperty(key) === true) { + //数字值转为string类型,前后端加密规则保持一致 + if (this.myIsNaN(objectTwo[key])) { + objectTwo[key] = objectTwo[key].toString(); + } + //布尔类型转成string类型,前后端加密规则保持一致 + if (typeof objectTwo[key] === 'boolean') { + objectTwo[key] = objectTwo[key].toString(); + } + objectOne[key] = objectTwo[key]; + } + } + } + return objectOne; + } + + static urlEncode(param, key, encode) { + if (param == null) return ''; + let paramStr = ''; + let t = typeof param; + if (t == 'string' || t == 'number' || t == 'boolean') { + paramStr += '&' + key + '=' + (encode == null || encode ? encodeURIComponent(param) : param); + } else { + for (let i in param) { + let k = key == null ? i : key + (param instanceof Array ? '[' + i + ']' : '.' + i); + paramStr += this.urlEncode(param[i], k, encode); + } + } + return paramStr; + } + + /** + * 接口签名用 生成header中的时间戳 + * @returns {number} + */ + static getTimestamp() { + return new Date().getTime(); + } + + // static getDateTimeToString() { + // const date_ = new Date() + // const year = date_.getFullYear() + // let month = date_.getMonth() + 1 + // let day = date_.getDate() + // if (month < 10) month = '0' + month + // if (day < 10) day = '0' + day + // let hours = date_.getHours() + // let mins = date_.getMinutes() + // let secs = date_.getSeconds() + // const msecs = date_.getMilliseconds() + // if (hours < 10) hours = '0' + hours + // if (mins < 10) mins = '0' + mins + // if (secs < 10) secs = '0' + secs + // if (msecs < 10) secs = '0' + msecs + // return year + '' + month + '' + day + '' + hours + '' + mins + '' + secs + // } + // true:数值型的,false:非数值型 + static myIsNaN(value) { + return typeof value === 'number' && !isNaN(value); + } +} diff --git a/src/utils/env.ts b/src/utils/env.ts new file mode 100644 index 0000000..272c634 --- /dev/null +++ b/src/utils/env.ts @@ -0,0 +1,135 @@ +import type { GlobEnvConfig } from '/#/config'; + +import { warn } from '/@/utils/log'; +import pkg from '../../package.json'; +import { getConfigFileName } from '../../build/getConfigFileName'; +import { getGlobal } from "@/qiankun/micro"; + +export function getCommonStoragePrefix() { + const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig(); + return `${VITE_GLOB_APP_SHORT_NAME}__${getEnv()}`.toUpperCase(); +} + +// Generate cache key according to version +export function getStorageShortName() { + return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase(); +} + +export function getAppEnvConfig() { + const ENV_NAME = getConfigFileName(import.meta.env); + + const global = getGlobal(); + + const ENV = (import.meta.env.DEV + ? // Get the global configuration (the configuration will be extracted independently when packaging) + (import.meta.env as unknown as GlobEnvConfig) + : global[ENV_NAME as any]) as unknown as GlobEnvConfig; + + const { + VITE_GLOB_APP_TITLE, + VITE_GLOB_API_URL, + VITE_USE_MOCK, + VITE_GLOB_APP_SHORT_NAME, + VITE_GLOB_API_URL_PREFIX, + VITE_GLOB_APP_OPEN_SSO, + VITE_GLOB_APP_OPEN_QIANKUN, + VITE_GLOB_APP_CAS_BASE_URL, + VITE_GLOB_DOMAIN_URL, + VITE_GLOB_ONLINE_VIEW_URL, + // 全局隐藏哪些布局,多个用逗号隔开 + VITE_GLOB_HIDE_LAYOUT_TYPES, + // 当前运行在什么平台 + VITE_GLOB_RUN_PLATFORM, + + // 【JEECG作为乾坤子应用】 + VITE_GLOB_QIANKUN_MICRO_APP_NAME, + VITE_GLOB_QIANKUN_MICRO_APP_ENTRY, + + //在线文档编辑版本。可选属性:wps, onlyoffice + VITE_GLOB_ONLINE_DOCUMENT_VERSION, + } = ENV; + + // if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) { + // warn( + // `VITE_GLOB_APP_SHORT_NAME 变量只能是字符/下划线,请在环境变量中修改并重新运行.` + // ); + // } + + return { + VITE_GLOB_APP_TITLE, + VITE_GLOB_API_URL, + VITE_USE_MOCK, + VITE_GLOB_APP_SHORT_NAME, + VITE_GLOB_API_URL_PREFIX, + VITE_GLOB_APP_OPEN_SSO, + VITE_GLOB_APP_OPEN_QIANKUN, + VITE_GLOB_APP_CAS_BASE_URL, + VITE_GLOB_DOMAIN_URL, + VITE_GLOB_ONLINE_VIEW_URL, + VITE_GLOB_HIDE_LAYOUT_TYPES, + VITE_GLOB_RUN_PLATFORM, + + // 【JEECG作为乾坤子应用】 + VITE_GLOB_QIANKUN_MICRO_APP_NAME, + VITE_GLOB_QIANKUN_MICRO_APP_ENTRY, + + //在线文档编辑版本。可选属性:wps, onlyoffice + VITE_GLOB_ONLINE_DOCUMENT_VERSION + }; +} + +/** + * @description: Development mode + */ +export const devMode = 'development'; + +/** + * @description: Production mode + */ +export const prodMode = 'production'; + +/** + * @description: Get environment variables + * @returns: + * @example: + */ +export function getEnv(): string { + return import.meta.env.MODE; +} + +/** + * @description: Is it a development mode + * @returns: + * @example: + */ +export function isDevMode(): boolean { + return import.meta.env.DEV; +} + +/** + * @description: Is it a production mode + * @returns: + * @example: + */ +export function isProdMode(): boolean { + return import.meta.env.PROD; +} + +export function getHideLayoutTypes(): string[] { + const {VITE_GLOB_HIDE_LAYOUT_TYPES} = getAppEnvConfig(); + if (typeof VITE_GLOB_HIDE_LAYOUT_TYPES !== 'string') { + return []; + } + return VITE_GLOB_HIDE_LAYOUT_TYPES.split(','); +} + +/** + * 获取在线文档版本号 + */ +export function getOnlineDocumentVersion(): string { + const { VITE_GLOB_ONLINE_DOCUMENT_VERSION } = getAppEnvConfig(); + if (typeof VITE_GLOB_ONLINE_DOCUMENT_VERSION !== 'string') { + return 'wps'; + } + return VITE_GLOB_ONLINE_DOCUMENT_VERSION; +} diff --git a/src/utils/event/index.ts b/src/utils/event/index.ts new file mode 100644 index 0000000..3a60d7c --- /dev/null +++ b/src/utils/event/index.ts @@ -0,0 +1,42 @@ +import ResizeObserver from 'resize-observer-polyfill'; + +const isServer = typeof window === 'undefined'; + +/* istanbul ignore next */ +function resizeHandler(entries: any[]) { + for (const entry of entries) { + const listeners = entry.target.__resizeListeners__ || []; + if (listeners.length) { + listeners.forEach((fn: () => any) => { + fn(); + }); + } + } +} + +/* istanbul ignore next */ +export function addResizeListener(element: any, fn: () => any) { + if (isServer) return; + if (!element.__resizeListeners__) { + element.__resizeListeners__ = []; + element.__ro__ = new ResizeObserver(resizeHandler); + element.__ro__.observe(element); + } + element.__resizeListeners__.push(fn); +} + +/* istanbul ignore next */ +export function removeResizeListener(element: any, fn: () => any) { + if (!element || !element.__resizeListeners__) return; + element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1); + if (!element.__resizeListeners__.length) { + element.__ro__.disconnect(); + } +} + +export function triggerWindowResize() { + const event = document.createEvent('HTMLEvents'); + event.initEvent('resize', true, true); + (event as any).eventType = 'message'; + window.dispatchEvent(event); +} diff --git a/src/utils/factory/createAsyncComponent.tsx b/src/utils/factory/createAsyncComponent.tsx new file mode 100644 index 0000000..52ffc4c --- /dev/null +++ b/src/utils/factory/createAsyncComponent.tsx @@ -0,0 +1,62 @@ +import { + defineAsyncComponent, + // FunctionalComponent, CSSProperties +} from 'vue'; +import { Spin } from 'ant-design-vue'; +const noop = () => {}; +// const Loading: FunctionalComponent<{ size: 'small' | 'default' | 'large' }> = (props) => { +// const style: CSSProperties = { +// position: 'absolute', +// display: 'flex', +// justifyContent: 'center', +// alignItems: 'center', +// }; +// return ( +//
+// +//
+// ); +// }; + +interface Options { + size?: 'default' | 'small' | 'large'; + delay?: number; + timeout?: number; + loading?: boolean; + retry?: boolean; +} + +export function createAsyncComponent(loader: Fn, options: Options = {}) { + const { size = 'small', delay = 100, timeout = 30000, loading = false, retry = true } = options; + return defineAsyncComponent({ + loader, + loadingComponent: loading ? : undefined, + // The error component will be displayed if a timeout is + // provided and exceeded. Default: Infinity. + // TODO + timeout, + // errorComponent + // Defining if component is suspensible. Default: true. + // suspensible: false, + delay, + /** + * + * @param {*} error Error message object + * @param {*} retry A function that indicating whether the async component should retry when the loader promise rejects + * @param {*} fail End of failure + * @param {*} attempts Maximum allowed retries number + */ + onError: !retry + ? noop + : (error, retry, fail, attempts) => { + if (error.message.match(/fetch/) && attempts <= 3) { + // retry on fetch errors, 3 max attempts + retry(); + } else { + // Note that retry/fail are like resolve/reject of a promise: + // one of them must be called for the error handling to continue. + fail(); + } + }, + }); +} diff --git a/src/utils/file/base64Conver.ts b/src/utils/file/base64Conver.ts new file mode 100644 index 0000000..d77618a --- /dev/null +++ b/src/utils/file/base64Conver.ts @@ -0,0 +1,41 @@ +/** + * @description: base64 to blob + */ +export function dataURLtoBlob(base64Buf: string): Blob { + const arr = base64Buf.split(','); + const typeItem = arr[0]; + const mime = typeItem.match(/:(.*?);/)![1]; + const bstr = atob(arr[1]); + let n = bstr.length; + const u8arr = new Uint8Array(n); + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + return new Blob([u8arr], { type: mime }); +} + +/** + * img url to base64 + * @param url + */ +export function urlToBase64(url: string, mineType?: string): Promise { + return new Promise((resolve, reject) => { + let canvas = document.createElement('CANVAS') as Nullable; + const ctx = canvas!.getContext('2d'); + + const img = new Image(); + img.crossOrigin = ''; + img.onload = function () { + if (!canvas || !ctx) { + return reject(); + } + canvas.height = img.height; + canvas.width = img.width; + ctx.drawImage(img, 0, 0); + const dataURL = canvas.toDataURL(mineType || 'image/png'); + canvas = null; + resolve(dataURL); + }; + img.src = url; + }); +} diff --git a/src/utils/file/download.ts b/src/utils/file/download.ts new file mode 100644 index 0000000..168e235 --- /dev/null +++ b/src/utils/file/download.ts @@ -0,0 +1,91 @@ +import { openWindow } from '..'; +import { dataURLtoBlob, urlToBase64 } from './base64Conver'; + +/** + * Download online pictures + * @param url + * @param filename + * @param mime + * @param bom + */ +export function downloadByOnlineUrl(url: string, filename: string, mime?: string, bom?: BlobPart) { + urlToBase64(url).then((base64) => { + downloadByBase64(base64, filename, mime, bom); + }); +} + +/** + * Download pictures based on base64 + * @param buf + * @param filename + * @param mime + * @param bom + */ +export function downloadByBase64(buf: string, filename: string, mime?: string, bom?: BlobPart) { + const base64Buf = dataURLtoBlob(buf); + downloadByData(base64Buf, filename, mime, bom); +} + +/** + * Download according to the background interface file stream + * @param {*} data + * @param {*} filename + * @param {*} mime + * @param {*} bom + */ +export function downloadByData(data: BlobPart, filename: string, mime?: string, bom?: BlobPart) { + const blobData = typeof bom !== 'undefined' ? [bom, data] : [data]; + const blob = new Blob(blobData, { type: mime || 'application/octet-stream' }); + if (typeof window.navigator.msSaveBlob !== 'undefined') { + window.navigator.msSaveBlob(blob, filename); + } else { + const blobURL = window.URL.createObjectURL(blob); + const tempLink = document.createElement('a'); + tempLink.style.display = 'none'; + tempLink.href = blobURL; + tempLink.setAttribute('download', filename); + if (typeof tempLink.download === 'undefined') { + tempLink.setAttribute('target', '_blank'); + } + document.body.appendChild(tempLink); + tempLink.click(); + document.body.removeChild(tempLink); + window.URL.revokeObjectURL(blobURL); + } +} + +/** + * Download file according to file address + * @param {*} sUrl + */ +export function downloadByUrl({ url, target = '_blank', fileName }: { url: string; target?: TargetContext; fileName?: string }): boolean { + const isChrome = window.navigator.userAgent.toLowerCase().indexOf('chrome') > -1; + const isSafari = window.navigator.userAgent.toLowerCase().indexOf('safari') > -1; + + if (/(iP)/g.test(window.navigator.userAgent)) { + console.error('Your browser does not support download!'); + return false; + } + if (isChrome || isSafari) { + const link = document.createElement('a'); + link.href = url; + link.target = target; + + if (link.download !== undefined) { + link.download = fileName || url.substring(url.lastIndexOf('/') + 1, url.length); + } + + if (document.createEvent) { + const e = document.createEvent('MouseEvents'); + e.initEvent('click', true, true); + link.dispatchEvent(e); + return true; + } + } + if (url.indexOf('?') === -1) { + url += '?download'; + } + + openWindow(url, { target }); + return true; +} diff --git a/src/utils/getConfigByMenuType.ts b/src/utils/getConfigByMenuType.ts new file mode 100644 index 0000000..428bd47 --- /dev/null +++ b/src/utils/getConfigByMenuType.ts @@ -0,0 +1,49 @@ +import { MenuTypeEnum, MenuModeEnum } from '/@/enums/menuEnum'; +import { APP_PRESET_COLOR_LIST, HEADER_PRESET_BG_COLOR_LIST, SIDE_BAR_BG_COLOR_LIST } from '/@/settings/designSetting'; + +/** + * 根据菜单类型和模式获取对应的主题色 + * @param menuType 菜单类型 + */ +export function getConfigByMenuType(menuType: MenuTypeEnum): { + themeColor: string; + headerBgColor: string; + sideBgColor: string; + split: boolean; + mode: MenuModeEnum; +} { + let themeColor; + let headerBgColor; + let sideBgColor; + let split = false; + let mode: MenuModeEnum = MenuModeEnum.INLINE; + if (menuType === MenuTypeEnum.TOP_MENU) { + // 顶部栏导航 + themeColor = APP_PRESET_COLOR_LIST[1]; + headerBgColor = HEADER_PRESET_BG_COLOR_LIST[4]; + mode = MenuModeEnum.HORIZONTAL; + } else if (menuType === MenuTypeEnum.MIX) { + // 顶部混合菜单模式 + themeColor = APP_PRESET_COLOR_LIST[2]; + headerBgColor = HEADER_PRESET_BG_COLOR_LIST[2]; + sideBgColor = SIDE_BAR_BG_COLOR_LIST[3]; + split = true; + } else if (menuType === MenuTypeEnum.MIX_SIDEBAR) { + // 侧边折叠导航模式 + themeColor = APP_PRESET_COLOR_LIST[1]; + headerBgColor = HEADER_PRESET_BG_COLOR_LIST[0]; + sideBgColor = SIDE_BAR_BG_COLOR_LIST[0]; + } else if (menuType === MenuTypeEnum.SIDEBAR) { + // 侧边栏导航 + themeColor = APP_PRESET_COLOR_LIST[1]; + headerBgColor = HEADER_PRESET_BG_COLOR_LIST[4]; + sideBgColor = SIDE_BAR_BG_COLOR_LIST[7]; + } + return { + themeColor, + headerBgColor, + sideBgColor, + split, + mode, + }; +} diff --git a/src/utils/helper/treeHelper.ts b/src/utils/helper/treeHelper.ts new file mode 100644 index 0000000..8d01984 --- /dev/null +++ b/src/utils/helper/treeHelper.ts @@ -0,0 +1,197 @@ +interface TreeHelperConfig { + id: string; + children: string; + pid: string; +} + +// 默认配置 +const DEFAULT_CONFIG: TreeHelperConfig = { + id: 'id', + children: 'children', + pid: 'pid', +}; + +// 获取配置。 Object.assign 从一个或多个源对象复制到目标对象 +const getConfig = (config: Partial) => Object.assign({}, DEFAULT_CONFIG, config); + +// tree from list +// 列表中的树 +export function listToTree(list: any[], config: Partial = {}): T[] { + const conf = getConfig(config) as TreeHelperConfig; + const nodeMap = new Map(); + const result: T[] = []; + const { id, children, pid } = conf; + + for (const node of list) { + node[children] = node[children] || []; + nodeMap.set(node[id], node); + } + for (const node of list) { + const parent = nodeMap.get(node[pid]); + (parent ? parent[children] : result).push(node); + } + return result; +} + +export function treeToList(tree: any, config: Partial = {}): T { + config = getConfig(config); + const { children } = config; + const result: any = [...tree]; + for (let i = 0; i < result.length; i++) { + if (!result[i][children!]) continue; + result.splice(i + 1, 0, ...result[i][children!]); + } + return result; +} + +export function findNode(tree: any, func: Fn, config: Partial = {}): T | null { + config = getConfig(config); + const { children } = config; + const list = [...tree]; + for (const node of list) { + if (func(node)) return node; + node[children!] && list.push(...node[children!]); + } + return null; +} + +export function findNodeAll(tree: any, func: Fn, config: Partial = {}): T[] { + config = getConfig(config); + const { children } = config; + const list = [...tree]; + const result: T[] = []; + for (const node of list) { + func(node) && result.push(node); + node[children!] && list.push(...node[children!]); + } + return result; +} + +export function findPath(tree: any, func: Fn, config: Partial = {}): T | T[] | null { + config = getConfig(config); + const path: T[] = []; + const list = [...tree]; + const visitedSet = new Set(); + const { children } = config; + while (list.length) { + const node = list[0]; + if (visitedSet.has(node)) { + path.pop(); + list.shift(); + } else { + visitedSet.add(node); + node[children!] && list.unshift(...node[children!]); + path.push(node); + if (func(node)) { + return path; + } + } + } + return null; +} + +export function findPathAll(tree: any, func: Fn, config: Partial = {}) { + config = getConfig(config); + const path: any[] = []; + const list = [...tree]; + const result: any[] = []; + const visitedSet = new Set(), + { children } = config; + while (list.length) { + const node = list[0]; + if (visitedSet.has(node)) { + path.pop(); + list.shift(); + } else { + visitedSet.add(node); + node[children!] && list.unshift(...node[children!]); + path.push(node); + func(node) && result.push([...path]); + } + } + return result; +} + +export function filter( + tree: T[], + func: (n: T) => boolean, + // Partial 将 T 中的所有属性设为可选 + config: Partial = {} +): T[] { + // 获取配置 + config = getConfig(config); + const children = config.children as string; + + function listFilter(list: T[]) { + return list + .map((node: any) => ({ ...node })) + .filter((node) => { + // 递归调用 对含有children项 进行再次调用自身函数 listFilter + node[children] = node[children] && listFilter(node[children]); + // 执行传入的回调 func 进行过滤 + return func(node) || (node[children] && node[children].length); + }); + } + + return listFilter(tree); +} + +export function forEach(tree: T[], func: (n: T) => any, config: Partial = {}): void { + config = getConfig(config); + const list: any[] = [...tree]; + const { children } = config; + for (let i = 0; i < list.length; i++) { + //func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿 + if (func(list[i])) { + return; + } + children && list[i][children] && list.splice(i + 1, 0, ...list[i][children]); + } +} + +/** + * @description: Extract tree specified structure + * @description: 提取树指定结构 + */ +export function treeMap(treeData: T[], opt: { children?: string; conversion: Fn }): T[] { + return treeData.map((item) => treeMapEach(item, opt)); +} + +/** + * @description: Extract tree specified structure + * @description: 提取树指定结构 + */ +export function treeMapEach(data: any, { children = 'children', conversion }: { children?: string; conversion: Fn }) { + const haveChildren = Array.isArray(data[children]) && data[children].length > 0; + const conversionData = conversion(data) || {}; + if (haveChildren) { + return { + ...conversionData, + [children]: data[children].map((i: number) => + treeMapEach(i, { + children, + conversion, + }) + ), + }; + } else { + return { + ...conversionData, + }; + } +} + +/** + * 递归遍历树结构 + * @param treeDatas 树 + * @param callBack 回调 + * @param parentNode 父节点 + */ +export function eachTree(treeDatas: any[], callBack: Fn, parentNode = {}) { + treeDatas.forEach((element) => { + const newNode = callBack(element, parentNode) || element; + if (element.children) { + eachTree(element.children, callBack, newNode); + } + }); +} diff --git a/src/utils/helper/tsxHelper.tsx b/src/utils/helper/tsxHelper.tsx new file mode 100644 index 0000000..46e0001 --- /dev/null +++ b/src/utils/helper/tsxHelper.tsx @@ -0,0 +1,35 @@ +import { Slots } from 'vue'; +import { isFunction } from '/@/utils/is'; + +/** + * @description: Get slot to prevent empty error + */ +export function getSlot(slots: Slots, slot = 'default', data?: any) { + if (!slots || !Reflect.has(slots, slot)) { + return null; + } + if (!isFunction(slots[slot])) { + console.error(`${slot} is not a function!`); + return null; + } + const slotFn = slots[slot]; + if (!slotFn) return null; + return slotFn(data); +} + +/** + * extends slots + * @param slots + * @param excludeKeys + */ +export function extendSlots(slots: Slots, excludeKeys: string[] = []) { + const slotKeys = Object.keys(slots); + const ret: any = {}; + slotKeys.map((key) => { + if (excludeKeys.includes(key)) { + return null; + } + ret[key] = () => getSlot(slots, key); + }); + return ret; +} diff --git a/src/utils/helper/validator.ts b/src/utils/helper/validator.ts new file mode 100644 index 0000000..f17ab66 --- /dev/null +++ b/src/utils/helper/validator.ts @@ -0,0 +1,153 @@ +import { dateUtil } from '/@/utils/dateUtil'; +import { duplicateCheck } from '/@/views/system/user/user.api'; + +export const rules = { + rule(type, required) { + if (type === 'email') { + return this.email(required); + } + if (type === 'phone') { + return this.phone(required); + } + }, + email(required) { + return [ + { + required: required ? required : false, + validator: async (_rule, value) => { + if (required == true && !value) { + return Promise.reject('请输入邮箱!'); + } + if ( + value && + !new RegExp( + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + ).test(value) + ) { + return Promise.reject('请输入正确邮箱格式!'); + } + return Promise.resolve(); + }, + trigger: 'change', + }, + ] as ArrayRule; + }, + phone(required) { + return [ + { + required: required, + validator: async (_, value) => { + if (required && !value) { + return Promise.reject('请输入手机号码!'); + } + if (!/^1[3456789]\d{9}$/.test(value)) { + return Promise.reject('手机号码格式有误'); + } + return Promise.resolve(); + }, + trigger: 'change', + }, + ]; + }, + startTime(endTime, required) { + return [ + { + required: required ? required : false, + validator: (_, value) => { + if (required && !value) { + return Promise.reject('请选择开始时间'); + } + if (endTime && value && dateUtil(endTime).isBefore(value)) { + return Promise.reject('开始时间需小于结束时间'); + } + return Promise.resolve(); + }, + trigger: 'change', + }, + ]; + }, + endTime(startTime, required) { + return [ + { + required: required ? required : false, + validator: (_, value) => { + if (required && !value) { + return Promise.reject('请选择结束时间'); + } + if (startTime && value && dateUtil(value).isBefore(startTime)) { + return Promise.reject('结束时间需大于开始时间'); + } + return Promise.resolve(); + }, + trigger: 'change', + }, + ]; + }, + confirmPassword(values, required) { + return [ + { + required: required ? required : false, + validator: (_, value) => { + if (!value) { + return Promise.reject('密码不能为空'); + } + if (value !== values.password) { + return Promise.reject('两次输入的密码不一致!'); + } + return Promise.resolve(); + }, + }, + ]; + }, + duplicateCheckRule(tableName, fieldName, model, schema, required?) { + return [ + { + validator: (_, value) => { + if (!value && required) { + return Promise.reject(`请输入${schema.label}`); + } + return new Promise((resolve, reject) => { + duplicateCheck({ + tableName, + fieldName, + fieldVal: value, + dataId: model.id, + }) + .then((res) => { + res.success ? resolve() : reject(res.message || '校验失败'); + }) + .catch((err) => { + reject(err.message || '验证失败'); + }); + }); + }, + }, + ] as ArrayRule; + }, +}; + +/** + * 唯一校验函数,给原生使用,vben的表单校验建议使用上述rules + * @param tableName 表名 + * @param fieldName 字段名 + * @param fieldVal 字段值 + * @param dataId 数据ID + */ +export async function duplicateValidate(tableName, fieldName, fieldVal, dataId) { + try { + let params = { + tableName, + fieldName, + fieldVal, + dataId: dataId, + }; + const res = await duplicateCheck(params); + if (res.success) { + return Promise.resolve(); + } else { + return Promise.reject(res.message || '校验失败'); + } + } catch (e) { + return Promise.reject('校验失败,可能是断网等问题导致的校验失败'); + } +} diff --git a/src/utils/http/axios/Axios.ts b/src/utils/http/axios/Axios.ts new file mode 100644 index 0000000..4d7c296 --- /dev/null +++ b/src/utils/http/axios/Axios.ts @@ -0,0 +1,270 @@ +import type { AxiosRequestConfig, AxiosInstance, AxiosResponse, AxiosError } from 'axios'; +import type { RequestOptions, Result, UploadFileParams, UploadFileCallBack } from '/#/axios'; +import type { CreateAxiosOptions } from './axiosTransform'; +import axios from 'axios'; +import qs from 'qs'; +import { AxiosCanceler } from './axiosCancel'; +import { isFunction } from '/@/utils/is'; +import { cloneDeep } from 'lodash-es'; +import { ContentTypeEnum } from '/@/enums/httpEnum'; +import { RequestEnum } from '/@/enums/httpEnum'; +import { useGlobSetting } from '/@/hooks/setting'; +import { useMessage } from '/@/hooks/web/useMessage'; + +const { createMessage } = useMessage(); +export * from './axiosTransform'; + +/** + * @description: axios module + */ +export class VAxios { + private axiosInstance: AxiosInstance; + private readonly options: CreateAxiosOptions; + + constructor(options: CreateAxiosOptions) { + this.options = options; + this.axiosInstance = axios.create(options); + this.setupInterceptors(); + } + + /** + * @description: Create axios instance + */ + private createAxios(config: CreateAxiosOptions): void { + this.axiosInstance = axios.create(config); + } + + private getTransform() { + const { transform } = this.options; + return transform; + } + + getAxios(): AxiosInstance { + return this.axiosInstance; + } + + /** + * @description: Reconfigure axios + */ + configAxios(config: CreateAxiosOptions) { + if (!this.axiosInstance) { + return; + } + this.createAxios(config); + } + + /** + * @description: Set general header + */ + setHeader(headers: any): void { + if (!this.axiosInstance) { + return; + } + Object.assign(this.axiosInstance.defaults.headers, headers); + } + + /** + * @description: Interceptor configuration + */ + private setupInterceptors() { + const transform = this.getTransform(); + if (!transform) { + return; + } + const { requestInterceptors, requestInterceptorsCatch, responseInterceptors, responseInterceptorsCatch } = transform; + + const axiosCanceler = new AxiosCanceler(); + + // 请求侦听器配置处理 + this.axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => { + // If cancel repeat request is turned on, then cancel repeat request is prohibited + // @ts-ignore + const { ignoreCancelToken } = config.requestOptions; + + const ignoreCancel = ignoreCancelToken !== undefined ? ignoreCancelToken : this.options.requestOptions?.ignoreCancelToken; + + !ignoreCancel && axiosCanceler.addPending(config); + if (requestInterceptors && isFunction(requestInterceptors)) { + config = requestInterceptors(config, this.options); + } + return config; + }, undefined); + + // 请求拦截器错误捕获 + requestInterceptorsCatch && + isFunction(requestInterceptorsCatch) && + this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch); + + // 响应结果拦截器处理 + this.axiosInstance.interceptors.response.use((res: AxiosResponse) => { + res && axiosCanceler.removePending(res.config); + if (responseInterceptors && isFunction(responseInterceptors)) { + res = responseInterceptors(res); + } + return res; + }, undefined); + + // 响应结果拦截器错误捕获 + responseInterceptorsCatch && + isFunction(responseInterceptorsCatch) && + this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch); + } + + /** + * 文件上传 + */ + //--@updateBy-begin----author:liusq---date:20211117------for:增加上传回调参数callback------ + uploadFile(config: AxiosRequestConfig, params: UploadFileParams, callback?: UploadFileCallBack) { + //--@updateBy-end----author:liusq---date:20211117------for:增加上传回调参数callback------ + const formData = new window.FormData(); + const customFilename = params.name || 'file'; + + if (params.filename) { + formData.append(customFilename, params.file, params.filename); + } else { + formData.append(customFilename, params.file); + } + const glob = useGlobSetting(); + config.baseURL = glob.uploadUrl; + if (params.data) { + Object.keys(params.data).forEach((key) => { + const value = params.data![key]; + if (Array.isArray(value)) { + value.forEach((item) => { + formData.append(`${key}[]`, item); + }); + return; + } + + formData.append(key, params.data[key]); + }); + } + + return this.axiosInstance + .request({ + ...config, + method: 'POST', + data: formData, + headers: { + 'Content-type': ContentTypeEnum.FORM_DATA, + ignoreCancelToken: true, + }, + }) + .then((res: any) => { + //--@updateBy-begin----author:liusq---date:20210914------for:上传判断是否包含回调方法------ + if (callback?.success && isFunction(callback?.success)) { + callback?.success(res?.data); + //--@updateBy-end----author:liusq---date:20210914------for:上传判断是否包含回调方法------ + } else if (callback?.isReturnResponse) { + //--@updateBy-begin----author:liusq---date:20211117------for:上传判断是否返回res信息------ + return Promise.resolve(res?.data); + //--@updateBy-end----author:liusq---date:20211117------for:上传判断是否返回res信息------ + } else { + if (res.data.success == true && res.data.code == 200) { + createMessage.success(res.data.message); + } else { + createMessage.error(res.data.message); + } + } + }); + } + + // 支持表单数据 + supportFormData(config: AxiosRequestConfig) { + const headers = config.headers || this.options.headers; + const contentType = headers?.['Content-Type'] || headers?.['content-type']; + + if (contentType !== ContentTypeEnum.FORM_URLENCODED || !Reflect.has(config, 'data') || config.method?.toUpperCase() === RequestEnum.GET) { + return config; + } + + return { + ...config, + data: qs.stringify(config.data, { arrayFormat: 'brackets' }), + }; + } + + get(config: AxiosRequestConfig, options?: RequestOptions): Promise { + return this.request({ ...config, method: 'GET' }, options); + } + + post(config: AxiosRequestConfig, options?: RequestOptions): Promise { + return this.request({ ...config, method: 'POST' }, options); + } + + put(config: AxiosRequestConfig, options?: RequestOptions): Promise { + return this.request({ ...config, method: 'PUT' }, options); + } + + delete(config: AxiosRequestConfig, options?: RequestOptions): Promise { + return this.request({ ...config, method: 'DELETE' }, options); + } + + request(config: AxiosRequestConfig, options?: RequestOptions): Promise { + let conf: CreateAxiosOptions = cloneDeep(config); + const transform = this.getTransform(); + + const { requestOptions } = this.options; + + const opt: RequestOptions = Object.assign({}, requestOptions, options); + + const { beforeRequestHook, requestCatchHook, transformRequestHook } = transform || {}; + if (beforeRequestHook && isFunction(beforeRequestHook)) { + conf = beforeRequestHook(conf, opt); + } + conf.requestOptions = opt; + + conf = this.supportFormData(conf); + + return new Promise((resolve, reject) => { + this.axiosInstance + .request>(conf) + .then((res: AxiosResponse) => { + if (transformRequestHook && isFunction(transformRequestHook)) { + try { + const ret = transformRequestHook(res, opt); + //zhangyafei---添加回调方法 + config.success && config.success(res.data); + //zhangyafei---添加回调方法 + resolve(ret); + } catch (err) { + reject(err || new Error('request error!')); + } + return; + } + resolve(res as unknown as Promise); + }) + .catch((e: Error | AxiosError) => { + if (requestCatchHook && isFunction(requestCatchHook)) { + reject(requestCatchHook(e, opt)); + return; + } + if (axios.isAxiosError(e)) { + // 在此处重写来自axios的错误消息 + } + reject(e); + }); + }); + } + + + /** + * 【用于评论功能】自定义文件上传-请求 + * @param url + * @param formData + */ + uploadMyFile(url, formData) { + const glob = useGlobSetting(); + return this.axiosInstance + .request({ + url: url, + baseURL: glob.uploadUrl, + method: 'POST', + data: formData, + headers: { + 'Content-type': ContentTypeEnum.FORM_DATA, + ignoreCancelToken: true, + }, + }); + } +} diff --git a/src/utils/http/axios/axiosCancel.ts b/src/utils/http/axios/axiosCancel.ts new file mode 100644 index 0000000..081233e --- /dev/null +++ b/src/utils/http/axios/axiosCancel.ts @@ -0,0 +1,60 @@ +import type { AxiosRequestConfig, Canceler } from 'axios'; +import axios from 'axios'; +import { isFunction } from '/@/utils/is'; + +// Used to store the identification and cancellation function of each request +let pendingMap = new Map(); + +export const getPendingUrl = (config: AxiosRequestConfig) => [config.method, config.url].join('&'); + +export class AxiosCanceler { + /** + * Add request + * @param {Object} config + */ + addPending(config: AxiosRequestConfig) { + this.removePending(config); + const url = getPendingUrl(config); + config.cancelToken = + config.cancelToken || + new axios.CancelToken((cancel) => { + if (!pendingMap.has(url)) { + // If there is no current request in pending, add it + pendingMap.set(url, cancel); + } + }); + } + + /** + * @description: Clear all pending + */ + removeAllPending() { + pendingMap.forEach((cancel) => { + cancel && isFunction(cancel) && cancel(); + }); + pendingMap.clear(); + } + + /** + * Removal request + * @param {Object} config + */ + removePending(config: AxiosRequestConfig) { + const url = getPendingUrl(config); + + if (pendingMap.has(url)) { + // If there is a current request identifier in pending, + // the current request needs to be cancelled and removed + const cancel = pendingMap.get(url); + cancel && cancel(url); + pendingMap.delete(url); + } + } + + /** + * @description: reset + */ + reset(): void { + pendingMap = new Map(); + } +} diff --git a/src/utils/http/axios/axiosTransform.ts b/src/utils/http/axios/axiosTransform.ts new file mode 100644 index 0000000..141ac5a --- /dev/null +++ b/src/utils/http/axios/axiosTransform.ts @@ -0,0 +1,49 @@ +/** + * Data processing class, can be configured according to the project + */ +import type { AxiosRequestConfig, AxiosResponse } from 'axios'; +import type { RequestOptions, Result } from '/#/axios'; + +export interface CreateAxiosOptions extends AxiosRequestConfig { + authenticationScheme?: string; + transform?: AxiosTransform; + requestOptions?: RequestOptions; +} + +export abstract class AxiosTransform { + /** + * @description: Process configuration before request + * @description: Process configuration before request + */ + beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig; + + /** + * @description: Request successfully processed + */ + transformRequestHook?: (res: AxiosResponse, options: RequestOptions) => any; + + /** + * @description: 请求失败处理 + */ + requestCatchHook?: (e: Error, options: RequestOptions) => Promise; + + /** + * @description: 请求之前的拦截器 + */ + requestInterceptors?: (config: AxiosRequestConfig, options: CreateAxiosOptions) => AxiosRequestConfig; + + /** + * @description: 请求之后的拦截器 + */ + responseInterceptors?: (res: AxiosResponse) => AxiosResponse; + + /** + * @description: 请求之前的拦截器错误处理 + */ + requestInterceptorsCatch?: (error: Error) => void; + + /** + * @description: 请求之后的拦截器错误处理 + */ + responseInterceptorsCatch?: (error: Error) => void; +} diff --git a/src/utils/http/axios/checkStatus.ts b/src/utils/http/axios/checkStatus.ts new file mode 100644 index 0000000..fc292e3 --- /dev/null +++ b/src/utils/http/axios/checkStatus.ts @@ -0,0 +1,76 @@ +import type { ErrorMessageMode } from '/#/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { useI18n } from '/@/hooks/web/useI18n'; +// import router from '/@/router'; +// import { PageEnum } from '/@/enums/pageEnum'; +import { useUserStoreWithOut } from '/@/store/modules/user'; +import projectSetting from '/@/settings/projectSetting'; +import { SessionTimeoutProcessingEnum } from '/@/enums/appEnum'; + +const { createMessage, createErrorModal } = useMessage(); +const error = createMessage.error!; +const stp = projectSetting.sessionTimeoutProcessing; + +export function checkStatus(status: number, msg: string, errorMessageMode: ErrorMessageMode = 'message'): void { + const { t } = useI18n(); + const userStore = useUserStoreWithOut(); + let errMessage = ''; + + switch (status) { + case 400: + errMessage = `${msg}`; + break; + // 401: Not logged in + // Jump to the login page if not logged in, and carry the path of the current page + // Return to the current page after successful login. This step needs to be operated on the login page. + case 401: + userStore.setToken(undefined); + errMessage = msg || t('sys.api.errMsg401'); + if (stp === SessionTimeoutProcessingEnum.PAGE_COVERAGE) { + userStore.setSessionTimeout(true); + } else { + userStore.logout(true); + } + break; + case 403: + errMessage = t('sys.api.errMsg403'); + break; + // 404请求不存在 + case 404: + errMessage = t('sys.api.errMsg404'); + break; + case 405: + errMessage = t('sys.api.errMsg405'); + break; + case 408: + errMessage = t('sys.api.errMsg408'); + break; + case 500: + errMessage = t('sys.api.errMsg500'); + break; + case 501: + errMessage = t('sys.api.errMsg501'); + break; + case 502: + errMessage = t('sys.api.errMsg502'); + break; + case 503: + errMessage = t('sys.api.errMsg503'); + break; + case 504: + errMessage = t('sys.api.errMsg504'); + break; + case 505: + errMessage = t('sys.api.errMsg505'); + break; + default: + } + + if (errMessage) { + if (errorMessageMode === 'modal') { + createErrorModal({ title: t('sys.api.errorTip'), content: errMessage }); + } else if (errorMessageMode === 'message') { + error({ content: errMessage, key: `global_error_message_status_${status}` }); + } + } +} diff --git a/src/utils/http/axios/helper.ts b/src/utils/http/axios/helper.ts new file mode 100644 index 0000000..790cc3d --- /dev/null +++ b/src/utils/http/axios/helper.ts @@ -0,0 +1,46 @@ +import { isObject, isString } from '/@/utils/is'; +import dayjs from "dayjs"; +// 代码逻辑说明: 【QQYUN-9138】系统用户保存的时间没有秒 +const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; + +export function joinTimestamp(join: boolean, restful: T): T extends true ? string : object; + +export function joinTimestamp(join: boolean, restful = false): string | object { + if (!join) { + return restful ? '' : {}; + } + const now = new Date().getTime(); + if (restful) { + return `?_t=${now}`; + } + return { _t: now }; +} + +/** + * @description: Format request parameter time + */ +export function formatRequestDate(params: Recordable) { + if (Object.prototype.toString.call(params) !== '[object Object]') { + return; + } + + for (const key in params) { + // 判断是否是dayjs实例 + if (dayjs.isDayjs(params[key])) { + params[key] = params[key].format(DATE_TIME_FORMAT); + } + if (isString(key)) { + const value = params[key]; + if (value) { + try { + params[key] = isString(value) ? value.trim() : value; + } catch (error) { + throw new Error(error); + } + } + } + if (isObject(params[key])) { + formatRequestDate(params[key]); + } + } +} diff --git a/src/utils/http/axios/index.ts b/src/utils/http/axios/index.ts new file mode 100644 index 0000000..e6e922f --- /dev/null +++ b/src/utils/http/axios/index.ts @@ -0,0 +1,313 @@ +// axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动 +// The axios configuration can be changed according to the project, just change the file, other files can be left unchanged + +import type { AxiosResponse } from 'axios'; +import type { RequestOptions, Result } from '/#/axios'; +import type { AxiosTransform, CreateAxiosOptions } from './axiosTransform'; +import { VAxios } from './Axios'; +import { checkStatus } from './checkStatus'; +import { router } from '/@/router'; +import { useGlobSetting } from '/@/hooks/setting'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { RequestEnum, ResultEnum, ContentTypeEnum, ConfigEnum } from '/@/enums/httpEnum'; +import { isString } from '/@/utils/is'; +import { getToken, getTenantId } from '/@/utils/auth'; +import { setObjToUrlParams, deepMerge } from '/@/utils'; +import signMd5Utils from '/@/utils/encryption/signMd5Utils'; +import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog'; +import { useI18n } from '/@/hooks/web/useI18n'; +import { joinTimestamp, formatRequestDate } from './helper'; +import { useUserStoreWithOut } from '/@/store/modules/user'; +import { cloneDeep } from "lodash-es"; +const globSetting = useGlobSetting(); +const urlPrefix = globSetting.urlPrefix; +const { createMessage, createErrorModal } = useMessage(); + +/** + * @description: 数据处理,方便区分多种处理方式 + */ +const transform: AxiosTransform = { + /** + * @description: 处理请求数据。如果数据不是预期格式,可直接抛出错误 + */ + transformRequestHook: (res: AxiosResponse, options: RequestOptions) => { + const { t } = useI18n(); + const { isTransformResponse, isReturnNativeResponse } = options; + // 是否返回原生响应头 比如:需要获取响应头时使用该属性 + if (isReturnNativeResponse) { + return res; + } + // 不进行任何处理,直接返回 + // 用于页面代码可能需要直接获取code,data,message这些信息时开启 + if (!isTransformResponse) { + return res.data; + } + // 错误的时候返回 + + const { data } = res; + if (!data) { + // return '[HTTP] Request has no return value'; + throw new Error(t('sys.api.apiRequestFailed')); + } + // 这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式 + const { code, result, message, success } = data; + // 这里逻辑可以根据项目进行修改 + const hasSuccess = data && Reflect.has(data, 'code') && (code === ResultEnum.SUCCESS || code === 200); + if (hasSuccess) { + if (success && message && options.successMessageMode === 'success') { + //信息成功提示 + createMessage.success(message); + } + return result; + } + + // 在此处根据自己项目的实际情况对不同的code执行不同的操作 + // 如果不希望中断当前请求,请return数据,否则直接抛出异常即可 + let timeoutMsg = ''; + switch (code) { + case ResultEnum.TIMEOUT: + timeoutMsg = t('sys.api.timeoutMessage'); + const userStore = useUserStoreWithOut(); + userStore.setToken(undefined); + userStore.logout(true); + break; + default: + if (message) { + timeoutMsg = message; + } + } + + // errorMessageMode=‘modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误 + // errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示 + if (options.errorMessageMode === 'modal') { + createErrorModal({ title: t('sys.api.errorTip'), content: timeoutMsg }); + } else if (options.errorMessageMode === 'message') { + createMessage.error(timeoutMsg); + } + + throw new Error(timeoutMsg || t('sys.api.apiRequestFailed')); + }, + + // 请求之前处理config + beforeRequestHook: (config, options) => { + const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options; + + // http开头的请求url,不加前缀 + let isStartWithHttp = false; + const requestUrl = config.url; + if(requestUrl!=null && (requestUrl.startsWith("http:") || requestUrl.startsWith("https:"))){ + isStartWithHttp = true; + } + // 代码逻辑说明: 【QQYUN-9685】构建 electron 桌面应用 + if (!isStartWithHttp && requestUrl != null) { + // 由于electron的url是file://开头的,所以需要判断一下 + isStartWithHttp = requestUrl.startsWith('file://'); + } + if (!isStartWithHttp && joinPrefix) { + config.url = `${urlPrefix}${config.url}`; + } + + if (!isStartWithHttp && apiUrl && isString(apiUrl)) { + config.url = `${apiUrl}${config.url}`; + } + + const params = config.params || {}; + const data = config.data || false; + formatDate && data && !isString(data) && formatRequestDate(data); + if (config.method?.toUpperCase() === RequestEnum.GET) { + if (!isString(params)) { + // 给 get 请求加上时间戳参数,避免从缓存中拿数据。 + config.params = Object.assign(params || {}, joinTimestamp(joinTime, false)); + } else { + // 兼容restful风格 + config.url = config.url + params + `${joinTimestamp(joinTime, true)}`; + config.params = undefined; + } + } else { + if (!isString(params)) { + formatDate && formatRequestDate(params); + if (Reflect.has(config, 'data') && config.data && Object.keys(config.data).length > 0) { + config.data = data; + config.params = params; + } else { + // 非GET请求如果没有提供data,则将params视为data + config.data = params; + config.params = undefined; + } + if (joinParamsToUrl) { + config.url = setObjToUrlParams(config.url as string, Object.assign({}, config.params, config.data)); + } + } else { + // 兼容restful风格 + config.url = config.url + params; + config.params = undefined; + } + } + + // 代码逻辑说明: 【JEECG作为乾坤子应用】作为乾坤子应用启动时,拼接请求路径 + if (globSetting.isQiankunMicro) { + if (config.url && config.url.startsWith('/')) { + config.url = globSetting.qiankunMicroAppEntry + config.url + } + } + + return config; + }, + + /** + * @description: 请求拦截器处理 + */ + requestInterceptors: (config: Recordable, options) => { + // 请求之前处理config + const token = getToken(); + let tenantId: string | number = getTenantId(); + + // 将签名和时间戳,添加在请求接口 Header + config.headers[ConfigEnum.TIMESTAMP] = signMd5Utils.getTimestamp(); + config.headers[ConfigEnum.Sign] = signMd5Utils.getSign(config.url, cloneDeep(config.params), cloneDeep(config.data)); + + config.headers[ConfigEnum.VERSION] = 'v3'; + if (token && (config as Recordable)?.requestOptions?.withToken !== false) { + // jwt token + config.headers.Authorization = options.authenticationScheme ? `${options.authenticationScheme} ${token}` : token; + config.headers[ConfigEnum.TOKEN] = token; + + // 将签名和时间戳,添加在请求接口 Header + //config.headers[ConfigEnum.TIMESTAMP] = signMd5Utils.getTimestamp(); + //config.headers[ConfigEnum.Sign] = signMd5Utils.getSign(config.url, config.params); + if (!tenantId) { + tenantId = 0; + } + + // 代码逻辑说明: 【QQYUN-5279】修复分享的应用租户和当前登录租户不一致时,提示404的问题 + const userStore = useUserStoreWithOut(); + // 判断是否有临时租户id + if (userStore.hasShareTenantId && userStore.shareTenantId !== 0) { + // 临时租户id存在,使用临时租户id + tenantId = userStore.shareTenantId!; + } + + config.headers[ConfigEnum.TENANT_ID] = tenantId; + //--update-end--author:liusq---date:20211105---for:将多租户id,添加在请求接口 Header + + // ======================================================================================== + // 代码逻辑说明: 添加低代码应用ID + let routeParams = router.currentRoute.value.params; + if (routeParams.appId) { + config.headers[ConfigEnum.X_LOW_APP_ID] = routeParams.appId; + // lowApp自定义筛选条件 + if (routeParams.lowAppFilter) { + config.params = { ...config.params, ...JSON.parse(routeParams.lowAppFilter as string) }; + delete routeParams.lowAppFilter; + } + } + // ======================================================================================== + + } + return config; + }, + + /** + * @description: 响应拦截器处理 + */ + responseInterceptors: (res: AxiosResponse) => { + return res; + }, + + /** + * @description: 响应错误处理 + */ + responseInterceptorsCatch: (error: any) => { + const { t } = useI18n(); + const errorLogStore = useErrorLogStoreWithOut(); + errorLogStore.addAjaxErrorInfo(error); + const { response, code, message, config } = error || {}; + const errorMessageMode = config?.requestOptions?.errorMessageMode || 'none'; + //scott 20211022 token失效提示信息 + //const msg: string = response?.data?.error?.message ?? ''; + const msg: string = response?.data?.message ?? ''; + const err: string = error?.toString?.() ?? ''; + let errMessage = ''; + + try { + if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) { + errMessage = t('sys.api.apiTimeoutMessage'); + } + if (err?.includes('Network Error')) { + errMessage = t('sys.api.networkExceptionMsg'); + } + + if (errMessage) { + if (errorMessageMode === 'modal') { + createErrorModal({ title: t('sys.api.errorTip'), content: errMessage }); + } else if (errorMessageMode === 'message') { + createMessage.error(errMessage); + } + return Promise.reject(error); + } + } catch (error) { + throw new Error(error); + } + + checkStatus(error?.response?.status, msg, errorMessageMode); + return Promise.reject(error); + }, +}; + +function createAxios(opt?: Partial) { + return new VAxios( + deepMerge( + { + // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes + // authentication schemes,e.g: Bearer + // authenticationScheme: 'Bearer', + authenticationScheme: '', + //接口超时设置 + timeout: 10 * 1000, + // 基础接口地址 + // baseURL: globSetting.apiUrl, + headers: { 'Content-Type': ContentTypeEnum.JSON }, + // 如果是form-data格式 + // headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED }, + // 数据处理方式 + transform, + // 配置项,下面的选项都可以在独立的接口请求中覆盖 + requestOptions: { + // 默认将prefix 添加到url + joinPrefix: true, + // 是否返回原生响应头 比如:需要获取响应头时使用该属性 + isReturnNativeResponse: false, + // 需要对返回数据进行处理 + isTransformResponse: true, + // post请求的时候添加参数到url + joinParamsToUrl: false, + // 格式化提交参数时间 + formatDate: true, + // 异常消息提示类型 + errorMessageMode: 'message', + // 成功消息提示类型 + successMessageMode: 'success', + // 接口地址 + apiUrl: globSetting.apiUrl, + // 接口拼接地址 + urlPrefix: urlPrefix, + // 是否加入时间戳 + joinTime: true, + // 忽略重复请求 + ignoreCancelToken: true, + // 是否携带token + withToken: true, + }, + }, + opt || {} + ) + ); +} +export const defHttp = createAxios(); + +// other api url +// export const otherHttp = createAxios({ +// requestOptions: { +// apiUrl: 'xxx', +// }, +// }); diff --git a/src/utils/iconfont2.ts b/src/utils/iconfont2.ts new file mode 100644 index 0000000..6056c43 --- /dev/null +++ b/src/utils/iconfont2.ts @@ -0,0 +1,4 @@ +import { createFromIconfontCN } from '@ant-design/icons-vue'; +import '/@/assets/icons/js/iconfont2.js'; + +export const IconFont = createFromIconfontCN({}); diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..27835de --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,823 @@ +import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router'; +import type { App, Plugin } from 'vue'; +import type { FormSchema, FormActionType } from "@/components/Form"; + +import { unref } from 'vue'; +import { isObject, isFunction, isString } from '/@/utils/is'; +import { dynamicPages } from './dynamicPages'; +import Big from 'big.js'; +import dayjs from "dayjs"; +// 代码逻辑说明: 【VUEN-656】配置外部网址打不开,原因是带了#号,需要替换一下 +export const URL_HASH_TAB = `__AGWE4H__HASH__TAG__PWHRG__`; + +export const noop = () => {}; + +/** + * @description: Set ui mount node + */ +export function getPopupContainer(node?: HTMLElement): HTMLElement { + return (node?.parentNode as HTMLElement) ?? document.body; +} + +/** + * Add the object as a parameter to the URL + * @param baseUrl url + * @param obj + * @returns {string} + * eg: + * let obj = {a: '3', b: '4'} + * setObjToUrlParams('www.baidu.com', obj) + * ==>www.baidu.com?a=3&b=4 + */ +export function setObjToUrlParams(baseUrl: string, obj: any): string { + let parameters = ''; + for (const key in obj) { + parameters += key + '=' + encodeURIComponent(obj[key]) + '&'; + } + parameters = parameters.replace(/&$/, ''); + return /\?$/.test(baseUrl) ? baseUrl + parameters : baseUrl.replace(/\/?$/, '?') + parameters; +} + +export function deepMerge(src: any = {}, target: any = {}): T { + let key: string; + for (key in target) { + // 代码逻辑说明: 【QQYUN-7872】online表单label较长优化 + if (isObject(src[key]) && isObject(target[key])) { + src[key] = deepMerge(src[key], target[key]); + } else { + src[key] = target[key]; + } + } + return src; +} + +export function openWindow(url: string, opt?: { target?: TargetContext | string; noopener?: boolean; noreferrer?: boolean }) { + const { target = '__blank', noopener = true, noreferrer = true } = opt || {}; + const feature: string[] = []; + + noopener && feature.push('noopener=yes'); + noreferrer && feature.push('noreferrer=yes'); + + window.open(url, target, feature.join(',')); +} + +// dynamic use hook props +export function getDynamicProps(props: T): Partial { + const ret: Recordable = {}; + + // @ts-ignore + Object.keys(props).map((key) => { + ret[key] = unref((props as Recordable)[key]); + }); + + return ret as Partial; +} + +/** + * 获取表单字段值数据类型 + * @param props + * @param field + * @updateBy:zyf + */ +export function getValueType(props, field) { + let formSchema = unref(unref(props)?.schemas) + let valueType = 'string'; + if (formSchema) { + let schema = formSchema.filter((item) => item.field === field)[0]; + // 代码逻辑说明: 【issues/8976】useListPage 查询中componentProps是函数时获取不到valueType + if (schema && schema.componentProps) { + if (isFunction(schema.componentProps)) { + try { + const result = schema.componentProps({ schema, tableAction: {}, formModel: {}, formActionType: {} }); + valueType = result?.valueType ?? valueType; + } catch (err) {} + } else { + valueType = schema.componentProps.valueType ? schema.componentProps.valueType : valueType; + } + } + } + return valueType; +} + +/** + * 获取表单字段值数据类型 + * @param schema + * @param formAction + */ +export function getValueTypeBySchema(schema: FormSchema, formAction: FormActionType) { + let valueType = 'string'; + if (schema) { + const componentProps = formAction.getSchemaComponentProps(schema); + // 代码逻辑说明: 【issues/8738】componentProps是函数时获取不到valueType + if (isFunction(componentProps)) { + try { + const result = componentProps({ schema, tableAction: {}, formModel: {}, formActionType: {} }); + valueType = result?.valueType ?? valueType; + } catch (err) {} + } else { + valueType = componentProps?.valueType ? componentProps?.valueType : valueType; + } + } + return valueType; +} + +/** + * 通过picker属性获取日期数据 + * @param data + * @param picker + */ +export function getDateByPicker(data, picker) { + if (!data || !picker) { + return data; + } + /** + * 需要把年、年月、设置成这段时间内的第一天([年季度]不需要处理antd回传的就是该季度的第一天,[年周]也不处理) + * 例如日期格式是年,传给数据库的时间必须是20240101 + * 例如日期格式是年月(选择了202502),传给数据库的时间必须是20250201 + */ + if (picker === 'year') { + return dayjs(data).set('month', 0).set('date', 1).format('YYYY-MM-DD'); + } else if (picker === 'month') { + return dayjs(data).set('date', 1).format('YYYY-MM-DD'); + } else if (picker === 'week') { + return dayjs(data).startOf('week').format('YYYY-MM-DD'); + } + return data; +} + +export function getRawRoute(route: RouteLocationNormalized): RouteLocationNormalized { + if (!route) return route; + const { matched, ...opt } = route; + return { + ...opt, + matched: (matched + ? matched.map((item) => ({ + meta: item.meta, + name: item.name, + path: item.path, + })) + : undefined) as RouteRecordNormalized[], + }; +} +/** + * 深度克隆对象、数组 + * @param obj 被克隆的对象 + * @return 克隆后的对象 + */ +export function cloneObject(obj) { + return JSON.parse(JSON.stringify(obj)); +} + +export const withInstall = (component: T, alias?: string) => { + //console.log("---初始化---", component) + + const comp = component as any; + comp.install = (app: App) => { + // @ts-ignore + app.component(comp.name || comp.displayName, component); + if (alias) { + app.config.globalProperties[alias] = component; + } + }; + return component as T & Plugin; +}; + +/** + * 获取url地址参数 + * @param paraName + */ +export function getUrlParam(paraName) { + let url = document.location.toString(); + let arrObj = url.split('?'); + + if (arrObj.length > 1) { + let arrPara = arrObj[1].split('&'); + let arr; + + for (let i = 0; i < arrPara.length; i++) { + arr = arrPara[i].split('='); + + if (arr != null && arr[0] == paraName) { + return arr[1]; + } + } + return ''; + } else { + return ''; + } +} + +/** + * 休眠(setTimeout的promise版) + * @param ms 要休眠的时间,单位:毫秒 + * @param fn callback,可空 + * @return Promise + */ +export function sleep(ms: number, fn?: Fn) { + return new Promise((resolve) => + setTimeout(() => { + fn && fn(); + resolve(); + }, ms) + ); +} + +/** + * 不用正则的方式替换所有值 + * @param text 被替换的字符串 + * @param checker 替换前的内容 + * @param replacer 替换后的内容 + * @returns {String} 替换后的字符串 + */ +export function replaceAll(text, checker, replacer) { + let lastText = text; + text = text.replace(checker, replacer); + if (lastText !== text) { + return replaceAll(text, checker, replacer); + } + return text; +} + +/** + * 获取URL上参数 + * @param url + */ +export function getQueryVariable(url) { + if (!url) return; + + var t, + n, + r, + i = url.split('?')[1], + s = {}; + (t = i.split('&')), (r = null), (n = null); + for (var o in t) { + var u = t[o].indexOf('='); + u !== -1 && ((r = t[o].substr(0, u)), (n = t[o].substr(u + 1)), (s[r] = n)); + } + return s; +} +/** + * 判断是否显示办理按钮 + * @param bpmStatus + * @returns {*} + */ +export function showDealBtn(bpmStatus) { + if (bpmStatus != '1' && bpmStatus != '3' && bpmStatus != '4') { + return true; + } + return false; +} +/** + * 数字转大写 + * @param value + * @returns {*} + */ +export function numToUpper(value) { + if (value != '') { + let unit = new Array('仟', '佰', '拾', '', '仟', '佰', '拾', '', '角', '分'); + const toDx = (n) => { + switch (n) { + case '0': + return '零'; + case '1': + return '壹'; + case '2': + return '贰'; + case '3': + return '叁'; + case '4': + return '肆'; + case '5': + return '伍'; + case '6': + return '陆'; + case '7': + return '柒'; + case '8': + return '捌'; + case '9': + return '玖'; + } + }; + let lth = value.toString().length; + // 代码逻辑说明: 【issues/7493】numToUpper方法返回解决错误 + value = new Big(value).times(100); + value += ''; + let length = value.length; + if (lth <= 8) { + let result = ''; + for (let i = 0; i < length; i++) { + if (i == 2) { + result = '元' + result; + } else if (i == 6) { + result = '万' + result; + } + if (value.charAt(length - i - 1) == 0) { + if (i != 0 && i != 1) { + if (result.charAt(0) != '零' && result.charAt(0) != '元' && result.charAt(0) != '万') { + result = '零' + result; + } + } + continue; + } + result = toDx(value.charAt(length - i - 1)) + unit[unit.length - i - 1] + result; + } + result += result.charAt(result.length - 1) == '元' ? '整' : ''; + return result; + } else { + return null; + } + } + return null; +} + +// 代码逻辑说明: 解决老的vue2动态导入文件语法 vite不支持的问题 +export function importViewsFile(path): Promise { + if (path.startsWith('/')) { + path = path.substring(1); + } + let page = ''; + if (path.endsWith('.vue')) { + page = `../views/${path}`; + } else { + page = `../views/${path}.vue`; + } + return new Promise((resolve, reject) => { + let flag = true; + for (const path in dynamicPages) { + if (path == page) { + flag = false; + dynamicPages[path]().then((mod) => { + console.log(path, mod); + resolve(mod); + }); + } + } + if (flag) { + reject('该文件不存在:' + page); + } + }); +} + + +/** + * 跳转至积木报表的 预览页面 + * @param url + * @param id + * @param token + */ +export function goJmReportViewPage(url, id, token) { + // URL支持{{ window.xxx }}占位符变量 + url = url.replace(/{{([^}]+)?}}/g, (_s1, s2) => _eval(s2)) + if (url.includes('?')) { + url += '&' + } else { + url += '?' + } + url += `id=${id}` + url += `&token=${token}` + window.open(url) +} + +/** + * 获取随机颜色 + */ +export function getRandomColor(index?) { + + const colors = [ + 'rgb(100, 181, 246)', + 'rgb(77, 182, 172)', + 'rgb(255, 183, 77)', + 'rgb(229, 115, 115)', + 'rgb(149, 117, 205)', + 'rgb(161, 136, 127)', + 'rgb(144, 164, 174)', + 'rgb(77, 208, 225)', + 'rgb(129, 199, 132)', + 'rgb(255, 138, 101)', + 'rgb(133, 202, 205)', + 'rgb(167, 214, 118)', + 'rgb(254, 225, 89)', + 'rgb(251, 199, 142)', + 'rgb(239, 145, 139)', + 'rgb(169, 181, 255)', + 'rgb(231, 218, 202)', + 'rgb(252, 128, 58)', + 'rgb(254, 161, 172)', + 'rgb(194, 163, 205)', + ]; + return index && index < 19 ? colors[index] : colors[Math.floor((Math.random()*(colors.length-1)))]; +} + +export function getRefPromise(componentRef) { + return new Promise((resolve) => { + (function next() { + const ref = componentRef.value; + if (ref) { + resolve(ref); + } else { + setTimeout(() => { + next(); + }, 100); + } + })(); + }); +} + +/** + * 2023-09-04 + * liaozhiyang + * 用new Function替换eval + */ +export function _eval(str: string) { + return new Function(`return ${str}`)(); +} + +/** + * 2024-04-30 + * liaozhiyang + * 通过时间或者时间戳获取对应antd的年、月、周、季度。 + */ +export function getWeekMonthQuarterYear(date) { + // 获取 ISO 周数的函数 + const getISOWeek = (date) => { + const jan4 = new Date(date.getFullYear(), 0, 4); + const oneDay = 86400000; // 一天的毫秒数 + return Math.ceil(((date - jan4.getTime()) / oneDay + jan4.getDay() + 1) / 7); + }; + // 将时间戳转换为日期对象 + const dateObj = new Date(date); + // 计算周 + const week = getISOWeek(dateObj); + // 计算月 + const month = dateObj.getMonth() + 1; // 月份是从0开始的,所以要加1 + // 计算季度 + const quarter = Math.floor(dateObj.getMonth() / 3) + 1; + // 计算年 + const year = dateObj.getFullYear(); + return { + year: `${year}`, + month: `${year}-${month.toString().padStart(2, '0')}`, + week: `${year}-${week}周`, + quarter: `${year}-Q${quarter}`, + }; +} + +/** + * 2024-05-17 + * liaozhiyang + * 设置挂载的modal元素有可能会有多个,需要找到对应的。 + */ +export const setPopContainer = (node, selector) => { + if (typeof selector === 'string') { + const targetEles = Array.from(document.querySelectorAll(selector)); + if (targetEles.length > 1) { + const retrospect = (node, elems) => { + let ele = node.parentNode; + while (ele) { + const findParentNode = elems.find(item => item === ele); + if (findParentNode) { + ele = null; + return findParentNode; + } else { + ele = ele.parentNode; + } + } + return null; + }; + const elem = retrospect(node, targetEles); + if (elem) { + return elem; + } else { + return document.querySelector(selector); + } + } else { + return document.querySelector(selector); + } + } else { + return selector; + } +}; + +/** + * 2024-06-14 + * liaozhiyang + * 根据控件显示条件 + * label、value通用,title、val给权限管理用的 + */ +export function useConditionFilter() { + + // 通用条件 + const commonConditionOptions = [ + {label: '为空', value: 'empty', val: 'EMPTY'}, + {label: '不为空', value: 'not_empty', val: 'NOT_EMPTY'}, + ] + + // 数值、日期 + const numberConditionOptions = [ + { label: '等于', value: 'eq', val: '=' }, + { label: '在...中', value: 'in', val: 'IN', title: '包含' }, + { label: '不等于', value: 'ne', val: '!=' }, + { label: '大于', value: 'gt', val: '>' }, + { label: '大于等于', value: 'ge', val: '>=' }, + { label: '小于', value: 'lt', val: '<' }, + { label: '小于等于', value: 'le', val: '<=' }, + ...commonConditionOptions, + ]; + + // 文本、密码、多行文本、富文本、markdown + const inputConditionOptions = [ + { label: '等于', value: 'eq', val: '=' }, + { label: '模糊', value: 'like', val: 'LIKE' }, + { label: '以..开始', value: 'right_like', title: '右模糊', val: 'RIGHT_LIKE' }, + { label: '以..结尾', value: 'left_like', title: '左模糊', val: 'LEFT_LIKE' }, + { label: '在...中', value: 'in', val: 'IN', title: '包含' }, + { label: '不等于', value: 'ne', val: '!=' }, + ...commonConditionOptions, + ]; + + // 下拉、单选、多选、开关、用户、部门、关联记录、省市区、popup、popupDict、下拉多选、下拉搜索、分类字典、自定义树 + const selectConditionOptions = [ + { label: '等于', value: 'eq', val: '=' }, + { label: '在...中', value: 'in', val: 'IN', title: '包含' }, + { label: '不等于', value: 'ne', val: '!=' }, + ...commonConditionOptions, + ]; + + const def = [ + { label: '等于', value: 'eq', val: '=' }, + { label: '模糊', value: 'like', val: 'LIKE' }, + { label: '以..开始', value: 'right_like', title: '右模糊', val: 'RIGHT_LIKE' }, + { label: '以..结尾', value: 'left_like', title: '左模糊', val: 'LEFT_LIKE' }, + { label: '在...中', value: 'in', val: 'IN', title: '包含' }, + { label: '不等于', value: 'ne', val: '!=' }, + { label: '大于', value: 'gt', val: '>' }, + { label: '大于等于', value: 'ge', val: '>=' }, + { label: '小于', value: 'lt', val: '<' }, + { label: '小于等于', value: 'le', val: '<=' }, + ...commonConditionOptions, + ]; + + const filterCondition = (data) => { + if (data.view == 'text' && data.fieldType == 'number') { + data.view = 'number'; + } + switch (data.view) { + case 'file': + case 'image': + case 'password': + return commonConditionOptions; + case 'text': + case 'textarea': + case 'umeditor': + case 'markdown': + case 'pca': + case 'popup': + return inputConditionOptions; + case 'list': + case 'radio': + case 'checkbox': + case 'switch': + case 'sel_user': + case 'sel_depart': + case 'link_table': + case 'popup_dict': + case 'list_multi': + case 'sel_search': + case 'cat_tree': + case 'sel_tree': + return selectConditionOptions; + case 'date': + // number是虚拟的 + case 'number': + return numberConditionOptions; + default: + return def; + } + }; + return { filterCondition }; +} +// 获取url中的参数 +export const getUrlParams = (url) => { + const result = { + url: '', + params: {}, + }; + const list = url.split('?'); + result.url = list[0]; + const params = list[1]; + if (params) { + const list = params.split('&'); + list.forEach((ele) => { + const dic = ele.split('='); + const label = dic[0]; + result.params[label] = dic[1]; + }); + } + return result; +}; + +/* 20250325 + * liaozhiyang + * 分割url字符成数组 + * 【issues/7990】图片参数中包含逗号会错误的识别成多张图 + * */ +export const split = (str) => { + if (isString(str)) { + const text = str.trim(); + if (text.startsWith('http')) { + const parts = str.split(','); + const urls: any = []; + let currentUrl = ''; + for (const part of parts) { + if (part.startsWith('http://') || part.startsWith('https://')) { + // 如果遇到新的URL开头,保存当前URL并开始新的URL + if (currentUrl) { + urls.push(currentUrl); + } + currentUrl = part; + } else { + // 否则,是当前URL的一部分(如参数) + currentUrl += ',' + part; + } + } + // 添加最后一个URL + if (currentUrl) { + urls.push(currentUrl); + } + return urls; + } else { + return str.split(','); + } + } + return str; +}; +/** + * 处理word文档中的o:p标签 + * @param html + */ +export const removeSpecialTags = (html: string): string => { + if (!html) return ''; + try { + const BORDER = '1px solid #8c8c8c'; + + // =================================================================== + // 第一步:移除 Office 垃圾标签(纯字符串) + // =================================================================== + // o:p 标签(转义和普通形式) + html = html.replace(/<o:p[^&]*?>.*?<\/o:p>/gis, ''); + html = html.replace(/<o:p[^&]*?\/?>/gis, ''); + html = html.replace(/<\/o:p>/gis, ''); + html = html.replace(/]*>.*?<\/o:p>/gis, ''); + html = html.replace(/]*\/?>/gis, ''); + html = html.replace(/<\/o:p>/gis, ''); + + // style 标签(转义和普通形式) + html = html.replace(/<style[^&]*?>.*?<\/style>/gis, ''); + html = html.replace(/<style[^&]*?\/?>/gis, ''); + html = html.replace(/<\/style>/gis, ''); + html = html.replace(/]*>.*?<\/style>/gis, ''); + + // 条件注释和 Office 命名空间标签 + html = html.replace(//gis, ''); + html = html.replace(/<\/?w:[^>]*>/gis, ''); + html = html.replace(/<\/?xml[^>]*>/gis, ''); + html = html.replace(/<\/?v:[^>]*>/gis, ''); + + // =================================================================== + // 第二步:DOM 清理(仅清理空段落,完全不动表格行结构) + // ★ 关键:放在边框处理之前,防止 DOM 序列化丢失 !important + // =================================================================== + try { + const DOMParserCtor: any = + typeof DOMParser !== 'undefined' ? DOMParser : (globalThis as any).DOMParser; + if (DOMParserCtor) { + const parser = new DOMParserCtor() as DOMParser; + const doc = parser.parseFromString(html, 'text/html') as Document; + + // 仅清理单元格内的空

标签 + const cells = doc.querySelectorAll('td,th'); + for (let ci = 0; ci < cells.length; ci++) { + const cell = cells[ci] as HTMLElement; + if (!cell) continue; + const ps = cell.querySelectorAll('p'); + for (let pi = ps.length - 1; pi >= 0; pi--) { + const p = ps[pi] as HTMLParagraphElement; + if (!p) continue; + const content = (p.innerHTML || '').replace(/ |\u00A0/g, '').trim(); + if (content === '') { + p.remove(); + } else { + const frag = doc.createDocumentFragment(); + while (p.firstChild) frag.appendChild(p.firstChild); + if (p.parentNode) p.parentNode.replaceChild(frag, p); + } + } + } + + html = doc.body.innerHTML; + } + } catch (_) { + // DOM 不可用,跳过段落清理(不影响边框修复) + } + + // =================================================================== + // 第三步:注入全局表格样式 `; + + if (/]*>/i.test(html)) { + html = html.replace(/(]*>)/i, `$1${globalCSS}`); + } else if (/]*>/i.test(html)) { + html = html.replace(/(]*>)/i, `$1${globalCSS}`); + } else { + html = globalCSS + html; + } + + // =================================================================== + // 第四步:纯字符串处理内联样式(核心边框修复) + // ★ 最后执行!确保输出 HTML 中的 !important 不会被任何后续处理丢失 + // + // 策略:按分号拆分 style 值,逐条判断属性名, + // 过滤掉所有 border* 和 mso-* 声明, + // 然后追加统一的边框声明(含 !important) + // =================================================================== + + /** + * 从 CSS style 字符串中移除所有 border* 和 mso-* 声明, + * 保留其余布局相关属性(width, height, padding, text-align 等) + */ + function stripBorderAndMso(styleStr: string): string { + return styleStr + .split(';') + .filter(function (decl) { + const trimmed = decl.trim(); + if (!trimmed) return false; + const colonIdx = trimmed.indexOf(':'); + if (colonIdx === -1) return true; // 保留无效声明(安全起见) + const prop = trimmed.substring(0, colonIdx).trim().toLowerCase(); + // 移除所有 border 开头和 mso- 开头的属性 + if (prop.startsWith('border') || prop.startsWith('mso-')) return false; + return true; + }) + .join(';'); + } + + // 4a. 移除 上的 border="0" HTML 属性 + html = html.replace(/(]*?)\sborder\s*=\s*['"]?0['"]?/gi, '$1'); + + // 4b. 处理
标签的内联样式 + html = html.replace(/]*)>/gi, function (fullMatch, attrs) { + try { + const sm = attrs.match(/\sstyle\s*=\s*(["'])([\s\S]*?)\1/i); + if (sm) { + const q = sm[1]; + const cleaned = stripBorderAndMso(sm[2]); + const newStyle = cleaned + + ';border-collapse:collapse!important' + + ';border-spacing:0!important' + + ';width:100%!important'; + return ''; + } + return ''; + } catch (_) { return fullMatch; } + }); + + // 4c. 处理
/ 标签的内联样式 + const cellBorderCSS = + `border-top:${BORDER}!important;` + + `border-right:${BORDER}!important;` + + `border-bottom:${BORDER}!important;` + + `border-left:${BORDER}!important;` + + 'padding:6px 8px!important;' + + 'vertical-align:middle!important;' + + 'box-sizing:border-box!important'; + + html = html.replace(/<(td|th)(\s[^>]*)>/gi, function (fullMatch, tag, attrs) { + try { + const sm = attrs.match(/\sstyle\s*=\s*(["'])([\s\S]*?)\1/i); + if (sm) { + const q = sm[1]; + const cleaned = stripBorderAndMso(sm[2]); + const newStyle = (cleaned ? cleaned + ';' : '') + cellBorderCSS; + return '<' + tag + attrs.replace(sm[0], ` style=${q}${newStyle}${q}`) + '>'; + } + return '<' + tag + attrs + ` style="${cellBorderCSS}">`; + } catch (_) { return fullMatch; } + }); + + return html; + } catch (_) { + return html; + } +}; + + diff --git a/src/utils/is.ts b/src/utils/is.ts new file mode 100644 index 0000000..612d174 --- /dev/null +++ b/src/utils/is.ts @@ -0,0 +1,107 @@ +const toString = Object.prototype.toString; + +export function is(val: unknown, type: string) { + return toString.call(val) === `[object ${type}]`; +} + +export function isDef(val?: T): val is T { + return typeof val !== 'undefined'; +} + +export function isUnDef(val?: T): val is T { + return !isDef(val); +} + +export function isObject(val: any): val is Record { + return val !== null && is(val, 'Object'); +} + +export function isEmpty(val: T): val is T { + if (isArray(val) || isString(val)) { + return val.length === 0; + } + + if (val instanceof Map || val instanceof Set) { + return val.size === 0; + } + + if (isObject(val)) { + return Object.keys(val).length === 0; + } + + return false; +} + +export function isDate(val: unknown): val is Date { + return is(val, 'Date'); +} + +export function isNull(val: unknown): val is null { + return val === null; +} + +export function isNullAndUnDef(val: unknown): val is null | undefined { + return isUnDef(val) && isNull(val); +} + +export function isNullOrUnDef(val: unknown): val is null | undefined { + return isUnDef(val) || isNull(val); +} + +export function isNumber(val: unknown): val is number { + return is(val, 'Number'); +} + +export function isPromise(val: any): val is Promise { + // 代码逻辑说明: 不能既是 Promise 又是 Object -------- + return is(val, 'Promise') && isFunction(val.then) && isFunction(val.catch); +} + +export function isString(val: unknown): val is string { + return is(val, 'String'); +} + +export function isJsonObjectString(val: string): val is string { + if (!val) { + return false; + } + return val.startsWith('{') && val.endsWith('}'); +} + +export function isFunction(val: unknown): val is Function { + return typeof val === 'function'; +} + +export function isBoolean(val: unknown): val is boolean { + return is(val, 'Boolean'); +} + +export function isRegExp(val: unknown): val is RegExp { + return is(val, 'RegExp'); +} + +export function isArray(val: any): val is Array { + return val && Array.isArray(val); +} + +export function isWindow(val: any): val is Window { + return typeof window !== 'undefined' && is(val, 'Window'); +} + +export function isElement(val: unknown): val is Element { + return isObject(val) && !!val.tagName; +} + +export function isMap(val: unknown): val is Map { + return is(val, 'Map'); +} + +export const isServer = typeof window === 'undefined'; + +export const isClient = !isServer; + +export function isUrl(path: string): boolean { + const reg = + /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/; + return reg.test(path); +} diff --git a/src/utils/lib/echarts.ts b/src/utils/lib/echarts.ts new file mode 100644 index 0000000..9b40020 --- /dev/null +++ b/src/utils/lib/echarts.ts @@ -0,0 +1,51 @@ +import * as echarts from 'echarts/core'; + +import { BarChart, LineChart, PieChart, MapChart, PictorialBarChart, RadarChart } from 'echarts/charts'; + +import { + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + LegendComponent, + RadarComponent, + ToolboxComponent, + DataZoomComponent, + VisualMapComponent, + TimelineComponent, + CalendarComponent, + GraphicComponent, +} from 'echarts/components'; + +// TODO 如果想换成SVG渲染,就导出SVGRenderer, +// 并且放到 echarts.use 里,注释掉 CanvasRenderer +import { /*SVGRenderer*/ CanvasRenderer } from 'echarts/renderers'; + +echarts.use([ + LegendComponent, + TitleComponent, + TooltipComponent, + GridComponent, + PolarComponent, + AriaComponent, + ParallelComponent, + BarChart, + LineChart, + PieChart, + MapChart, + RadarChart, + // TODO 因为要兼容Online图表自适应打印,所以改成 CanvasRenderer,可能会模糊 + CanvasRenderer, + PictorialBarChart, + RadarComponent, + ToolboxComponent, + DataZoomComponent, + VisualMapComponent, + TimelineComponent, + CalendarComponent, + GraphicComponent, +]); + +export default echarts; diff --git a/src/utils/log.ts b/src/utils/log.ts new file mode 100644 index 0000000..8f79800 --- /dev/null +++ b/src/utils/log.ts @@ -0,0 +1,9 @@ +const projectName = import.meta.env.VITE_GLOB_APP_TITLE; + +export function warn(message: string) { + console.warn(`[${projectName} warn]:${message}`); +} + +export function error(message: string) { + throw new Error(`[${projectName} error]:${message}`); +} diff --git a/src/utils/mitt.ts b/src/utils/mitt.ts new file mode 100644 index 0000000..4b15bba --- /dev/null +++ b/src/utils/mitt.ts @@ -0,0 +1,101 @@ +/** + * copy to https://github.com/developit/mitt + * Expand clear method + */ + +export type EventType = string | symbol; + +// An event handler can take an optional event argument +// and should not return a value +export type Handler = (event?: T) => void; +export type WildcardHandler = (type: EventType, event?: any) => void; + +// An array of all currently registered event handlers for a type +export type EventHandlerList = Array; +export type WildCardEventHandlerList = Array; + +// A map of event types and their corresponding event handlers. +export type EventHandlerMap = Map; + +export interface Emitter { + all: EventHandlerMap; + + on(type: EventType, handler: Handler): void; + on(type: '*', handler: WildcardHandler): void; + + off(type: EventType, handler: Handler): void; + off(type: '*', handler: WildcardHandler): void; + + emit(type: EventType, event?: T): void; + emit(type: '*', event?: any): void; + clear(): void; +} + +/** + * Mitt: Tiny (~200b) functional event emitter / pubsub. + * @name mitt + * @returns {Mitt} + */ +export default function mitt(all?: EventHandlerMap): Emitter { + all = all || new Map(); + + return { + /** + * A Map of event names to registered handler functions. + */ + all, + + /** + * Register an event handler for the given type. + * @param {string|symbol} type Type of event to listen for, or `"*"` for all events + * @param {Function} handler Function to call in response to given event + * @memberOf mitt + */ + on(type: EventType, handler: Handler) { + const handlers = all?.get(type); + const added = handlers && handlers.push(handler); + if (!added) { + all?.set(type, [handler]); + } + }, + + /** + * Remove an event handler for the given type. + * @param {string|symbol} type Type of event to unregister `handler` from, or `"*"` + * @param {Function} handler Handler function to remove + * @memberOf mitt + */ + off(type: EventType, handler: Handler) { + const handlers = all?.get(type); + if (handlers) { + handlers.splice(handlers.indexOf(handler) >>> 0, 1); + } + }, + + /** + * Invoke all handlers for the given type. + * If present, `"*"` handlers are invoked after type-matched handlers. + * + * Note: Manually firing "*" handlers is not supported. + * + * @param {string|symbol} type The event type to invoke + * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler + * @memberOf mitt + */ + emit(type: EventType, evt: T) { + ((all?.get(type) || []) as EventHandlerList).slice().map((handler) => { + handler(evt); + }); + ((all?.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => { + handler(type, evt); + }); + }, + + /** + * Clear all + */ + clear() { + this.all.clear(); + }, + }; +} diff --git a/src/utils/monorepo/dynamicRouter.ts b/src/utils/monorepo/dynamicRouter.ts new file mode 100644 index 0000000..11f9ab8 --- /dev/null +++ b/src/utils/monorepo/dynamicRouter.ts @@ -0,0 +1,19 @@ +export type DynamicViewsRecord = Record Promise>; + +/** 已注册模块的动态页面 */ +export const packageViews: DynamicViewsRecord = {}; + +/** + * 注册动态路由页面 + * @param getViews 获取该模块下所有页面的方法 + */ +export function registerDynamicRouter(getViews: () => DynamicViewsRecord) { + if (typeof getViews === 'function') { + let dynamicViews = getViews(); + Object.keys(dynamicViews).forEach((key) => { + // 处理动态页面的key,使其可以让路由识别 + let newKey = key.replace('./src/views', '../../views'); + packageViews[newKey] = dynamicViews[key]; + }); + } +} diff --git a/src/utils/monorepo/registerPackages.ts b/src/utils/monorepo/registerPackages.ts new file mode 100644 index 0000000..4223c7e --- /dev/null +++ b/src/utils/monorepo/registerPackages.ts @@ -0,0 +1,140 @@ +import type { App } from 'vue'; +import { warn } from '/@/utils/log'; +import { registerDynamicRouter } from '/@/utils/monorepo/dynamicRouter'; + +// 懒加载模块配置(按需加载,访问相关路由时才加载对应包) +const lazyPackages = [ + { name: '@jeecg/aiflow', importer: () => import('@jeecg/aiflow') }, +]; + +let appInstance: App | null = null; + +// noinspection JSUnusedGlobalSymbols +const installOptions = { + baseImport, +}; + +export function registerPackages(app: App) { + // 仅保存 app 实例,不立即加载模块 + appInstance = app; + // app.component( + // 'SuperQuery', + // createAsyncComponent(() => import('@jeecg/online').then(mod => mod.SuperQuery)) + // ); + // app.component( + // 'JOnlineSearchSelect', + // createAsyncComponent(() => import('@jeecg/online').then(mod => mod.JOnlineSearchSelect)) + // ); +} + +/** 已加载的包缓存 */ +const loadedPackages = new Map(); +/** 正在加载的包 Promise 缓存(防止重复加载) */ +const loadingPromises = new Map>(); + +/** + * 按需加载包并注册 + */ +async function ensurePackageLoaded(pkgConfig: typeof lazyPackages[number]) { + const { name, importer } = pkgConfig; + if (loadedPackages.has(name)) { + return loadedPackages.get(name); + } + if (!loadingPromises.has(name)) { + const promise = importer().then((pkg) => { + const mod = pkg.default || pkg; + if (appInstance) { + appInstance.use(mod, installOptions); + registerDynamicRouter(mod.getViews); + } + loadedPackages.set(name, mod); + loadingPromises.delete(name); + return mod; + }); + loadingPromises.set(name, promise); + } + return loadingPromises.get(name); +} + +/** + * 根据 component 路径关键字匹配优先加载的包 + */ +function getMatchedPackage(component: string): typeof lazyPackages[number] | null { + const lc = component.toLowerCase(); + for (const pkgConfig of lazyPackages) { + // 从包名中提取关键字,如 @jeecg/online -> online, @jeecg/aiflow -> aiflow + const keyword = pkgConfig.name.split('/').pop()!; + if (lc.includes(keyword)) { + return pkgConfig; + } + } + return null; +} + +/** + * 从指定包中查找组件 + */ +async function findComponentInPackage(pkgConfig: typeof lazyPackages[number], component: string): Promise<(() => Promise) | null> { + try { + const mod = await ensurePackageLoaded(pkgConfig); + const views = mod.getViews(); + for (const key of Object.keys(views)) { + const k = key.replace('./src/views', ''); + const startFlag = component.startsWith('/'); + const endFlag = component.endsWith('.vue') || component.endsWith('.tsx'); + const startIndex = startFlag ? 0 : 1; + const lastIndex = endFlag ? k.length : k.lastIndexOf('.'); + if (k.substring(startIndex, lastIndex) === component) { + return views[key]; + } + } + } catch (e) { + // 包不存在或加载失败,跳过 + } + return null; +} + +/** + * 按需加载包组件:当路由匹配不到本地组件时调用 + * 根据 component 路径中的关键字优先匹配对应包,避免无意义的遍历 + */ +export async function loadPackageComponent(component: string): Promise<(() => Promise) | null> { + // 优先根据关键字精准匹配包 + const matched = getMatchedPackage(component); + if (matched) { + return findComponentInPackage(matched, component); + } + // 未匹配到关键字,依次尝试所有包 + for (const pkgConfig of lazyPackages) { + const result = await findComponentInPackage(pkgConfig, component); + if (result) return result; + } + return null; +} + +// 模块里可使用的import +const importGlobs = [import.meta.glob('../../utils/**/*.{ts,js,tsx}'), import.meta.glob('../../hooks/**/*.{ts,js,tsx}')]; + +/** + * 基础项目导包 + * 目前支持导入如下 + * /@/utils/** + * /@/hooks/** + * + * @param path 文件路径,ts无需输入后缀名。如:/@/utils/common/compUtils + */ +async function baseImport(path: string) { + if (path) { + // 将 /@/ 替换成 ../../ + path = path.replace(/^\/@\//, '../../'); + for (const glob of importGlobs) { + for (const key of Object.keys(glob)) { + if (path === key || `${path}.ts` === key || `${path}.tsx` === key) { + return glob[key](); + } + } + } + warn(`引入失败:${path} 不存在`); + } + return null; +} diff --git a/src/utils/propTypes.ts b/src/utils/propTypes.ts new file mode 100644 index 0000000..16cf9cd --- /dev/null +++ b/src/utils/propTypes.ts @@ -0,0 +1,35 @@ +import { CSSProperties, VNodeChild } from 'vue'; +import { createTypes, VueTypeValidableDef, VueTypesInterface, toValidableType } from 'vue-types'; + +export type VueNode = VNodeChild | JSX.Element; + +type PropTypes = VueTypesInterface & { + readonly style: VueTypeValidableDef; + readonly VNodeChild: VueTypeValidableDef; + // readonly trueBool: VueTypeValidableDef; +}; +const newPropTypes = createTypes({ + func: undefined, + bool: undefined, + string: undefined, + number: undefined, + object: undefined, + integer: undefined, +}) as PropTypes; + +// 从 vue-types v5.0 开始,extend()方法已经废弃,当前已改为官方推荐的ES6+方法 https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method +class propTypes extends newPropTypes { + // a native-like validator that supports the `.validable` method + static get style() { + return toValidableType('style', { + type: [String, Object], + }); + } + + static get VNodeChild() { + return toValidableType('VNodeChild', { + type: undefined, + }); + } +} +export { propTypes }; diff --git a/src/utils/props.ts b/src/utils/props.ts new file mode 100644 index 0000000..7393d58 --- /dev/null +++ b/src/utils/props.ts @@ -0,0 +1,185 @@ +// copy from element-plus + +import { warn } from 'vue'; +import { isObject } from '@vue/shared'; +import { fromPairs } from 'lodash-es'; +import type { ExtractPropTypes, PropType } from 'vue'; +import type { Mutable } from './types'; + +const wrapperKey = Symbol(); +export type PropWrapper = { [wrapperKey]: T }; + +export const propKey = Symbol(); + +type ResolveProp = ExtractPropTypes<{ + key: { type: T; required: true }; +}>['key']; +type ResolvePropType = ResolveProp extends { type: infer V } ? V : ResolveProp; +type ResolvePropTypeWithReadonly = Readonly extends Readonly> + ? ResolvePropType + : ResolvePropType; + +type IfUnknown = [unknown] extends [T] ? V : T; + +export type BuildPropOption, R, V, C> = { + type?: T; + values?: readonly V[]; + required?: R; + default?: R extends true + ? never + : D extends Record | Array + ? () => D + : (() => D) | D; + validator?: ((val: any) => val is C) | ((val: any) => boolean); +}; + +type _BuildPropType = + | (T extends PropWrapper + ? T[typeof wrapperKey] + : [V] extends [never] + ? ResolvePropTypeWithReadonly + : never) + | V + | C; +export type BuildPropType = _BuildPropType< + IfUnknown, + IfUnknown, + IfUnknown +>; + +type _BuildPropDefault = [T] extends [ + // eslint-disable-next-line @typescript-eslint/ban-types + Record | Array | Function, +] + ? D + : D extends () => T + ? ReturnType + : D; + +export type BuildPropDefault = R extends true + ? { readonly default?: undefined } + : { + readonly default: Exclude extends never + ? undefined + : Exclude<_BuildPropDefault, undefined>; + }; +export type BuildPropReturn = { + readonly type: PropType>; + readonly required: IfUnknown; + readonly validator: ((val: unknown) => boolean) | undefined; + [propKey]: true; +} & BuildPropDefault, IfUnknown, IfUnknown>; + +/** + * @description Build prop. It can better optimize prop types + * @description 生成 prop,能更好地优化类型 + * @example + // limited options + // the type will be PropType<'light' | 'dark'> + buildProp({ + type: String, + values: ['light', 'dark'], + } as const) + * @example + // limited options and other types + // the type will be PropType<'small' | 'medium' | number> + buildProp({ + type: [String, Number], + values: ['small', 'medium'], + validator: (val: unknown): val is number => typeof val === 'number', + } as const) + @link see more: https://github.com/element-plus/element-plus/pull/3341 + */ +export function buildProp< + T = never, + D extends BuildPropType = never, + R extends boolean = false, + V = never, + C = never, +>(option: BuildPropOption, key?: string): BuildPropReturn { + // filter native prop type and nested prop, e.g `null`, `undefined` (from `buildProps`) + if (!isObject(option) || !!option[propKey]) return option as any; + + const { values, required, default: defaultValue, type, validator } = option; + + const _validator = + values || validator + ? (val: unknown) => { + let valid = false; + let allowedValues: unknown[] = []; + + if (values) { + allowedValues = [...values, defaultValue]; + valid ||= allowedValues.includes(val); + } + if (validator) valid ||= validator(val); + + if (!valid && allowedValues.length > 0) { + const allowValuesText = [...new Set(allowedValues)] + .map((value) => JSON.stringify(value)) + .join(', '); + warn( + `Invalid prop: validation failed${ + key ? ` for prop "${key}"` : '' + }. Expected one of [${allowValuesText}], got value ${JSON.stringify(val)}.`, + ); + } + return valid; + } + : undefined; + + return { + type: + typeof type === 'object' && Object.getOwnPropertySymbols(type).includes(wrapperKey) + ? type[wrapperKey] + : type, + required: !!required, + default: defaultValue, + validator: _validator, + [propKey]: true, + } as unknown as BuildPropReturn; +} + +type NativePropType = [((...args: any) => any) | { new (...args: any): any } | undefined | null]; + +export const buildProps = < + O extends { + [K in keyof O]: O[K] extends BuildPropReturn + ? O[K] + : [O[K]] extends NativePropType + ? O[K] + : O[K] extends BuildPropOption + ? D extends BuildPropType + ? BuildPropOption + : never + : never; + }, +>( + props: O, +) => + fromPairs( + Object.entries(props).map(([key, option]) => [key, buildProp(option as any, key)]), + ) as unknown as { + [K in keyof O]: O[K] extends { [propKey]: boolean } + ? O[K] + : [O[K]] extends NativePropType + ? O[K] + : O[K] extends BuildPropOption< + infer T, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + infer _D, + infer R, + infer V, + infer C + > + ? BuildPropReturn + : never; + }; + +export const definePropType = (val: any) => ({ [wrapperKey]: val } as PropWrapper); + +export const keyOf = (arr: T) => Object.keys(arr) as Array; +export const mutable = >(val: T) => + val as Mutable; + +export const componentSize = ['large', 'medium', 'small', 'mini'] as const; diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 0000000..4453ec4 --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,42 @@ +// copy from element-plus + +import type { CSSProperties, Plugin } from 'vue'; + +type OptionalKeys> = { + [K in keyof T]: T extends Record ? never : K; +}[keyof T]; + +type RequiredKeys> = Exclude>; + +type MonoArgEmitter = (evt: K, arg?: T[K]) => void; + +type BiArgEmitter = (evt: K, arg: T[K]) => void; + +export type EventEmitter> = MonoArgEmitter> & + BiArgEmitter>; + +export type AnyFunction = (...args: any[]) => T; + +export type PartialReturnType unknown> = Partial>; + +export type SFCWithInstall = T & Plugin; + +export type Nullable = T | null; + +export type RefElement = Nullable; + +export type CustomizedHTMLElement = HTMLElement & T; + +export type Indexable = { + [key: string]: T; +}; + +export type Hash = Indexable; + +export type TimeoutHandle = ReturnType; + +export type ComponentSize = 'large' | 'medium' | 'small' | 'mini'; + +export type StyleValue = string | CSSProperties | Array; + +export type Mutable = { -readonly [P in keyof T]: T[P] }; diff --git a/src/utils/uuid.ts b/src/utils/uuid.ts new file mode 100644 index 0000000..548bcf3 --- /dev/null +++ b/src/utils/uuid.ts @@ -0,0 +1,28 @@ +const hexList: string[] = []; +for (let i = 0; i <= 15; i++) { + hexList[i] = i.toString(16); +} + +export function buildUUID(): string { + let uuid = ''; + for (let i = 1; i <= 36; i++) { + if (i === 9 || i === 14 || i === 19 || i === 24) { + uuid += '-'; + } else if (i === 15) { + uuid += 4; + } else if (i === 20) { + uuid += hexList[(Math.random() * 4) | 8]; + } else { + uuid += hexList[(Math.random() * 16) | 0]; + } + } + return uuid.replace(/-/g, ''); +} + +let unique = 0; +export function buildShortUUID(prefix = ''): string { + const time = Date.now(); + const random = Math.floor(Math.random() * 1000000000); + unique++; + return prefix + '_' + random + unique + String(time); +} diff --git a/src/views/dashboard/Analysis/api.ts b/src/views/dashboard/Analysis/api.ts new file mode 100644 index 0000000..0f40443 --- /dev/null +++ b/src/views/dashboard/Analysis/api.ts @@ -0,0 +1,16 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + loginfo = '/sys/loginfo', + visitInfo = '/sys/visitInfo', +} +/** + * 日志统计信息 + * @param params + */ +export const getLoginfo = (params) => defHttp.get({ url: Api.loginfo, params }, { isTransformResponse: false }); +/** + * 访问量信息 + * @param params + */ +export const getVisitInfo = (params) => defHttp.get({ url: Api.visitInfo, params }, { isTransformResponse: false }); diff --git a/src/views/dashboard/Analysis/components/BdcTabCard.vue b/src/views/dashboard/Analysis/components/BdcTabCard.vue new file mode 100644 index 0000000..b310af4 --- /dev/null +++ b/src/views/dashboard/Analysis/components/BdcTabCard.vue @@ -0,0 +1,128 @@ + + + + diff --git a/src/views/dashboard/Analysis/components/ChartGroupCard.vue b/src/views/dashboard/Analysis/components/ChartGroupCard.vue new file mode 100644 index 0000000..cf51a1e --- /dev/null +++ b/src/views/dashboard/Analysis/components/ChartGroupCard.vue @@ -0,0 +1,109 @@ + + diff --git a/src/views/dashboard/Analysis/components/GrowCard.vue b/src/views/dashboard/Analysis/components/GrowCard.vue new file mode 100644 index 0000000..af3eb60 --- /dev/null +++ b/src/views/dashboard/Analysis/components/GrowCard.vue @@ -0,0 +1,40 @@ + + diff --git a/src/views/dashboard/Analysis/components/QuickNav.vue b/src/views/dashboard/Analysis/components/QuickNav.vue new file mode 100644 index 0000000..149f426 --- /dev/null +++ b/src/views/dashboard/Analysis/components/QuickNav.vue @@ -0,0 +1,56 @@ + + diff --git a/src/views/dashboard/Analysis/components/SaleTabCard.vue b/src/views/dashboard/Analysis/components/SaleTabCard.vue new file mode 100644 index 0000000..ff69bc7 --- /dev/null +++ b/src/views/dashboard/Analysis/components/SaleTabCard.vue @@ -0,0 +1,87 @@ + + + + diff --git a/src/views/dashboard/Analysis/components/SalesProductPie.vue b/src/views/dashboard/Analysis/components/SalesProductPie.vue new file mode 100644 index 0000000..6dc41fb --- /dev/null +++ b/src/views/dashboard/Analysis/components/SalesProductPie.vue @@ -0,0 +1,63 @@ + + diff --git a/src/views/dashboard/Analysis/components/SiteAnalysis.vue b/src/views/dashboard/Analysis/components/SiteAnalysis.vue new file mode 100644 index 0000000..570861a --- /dev/null +++ b/src/views/dashboard/Analysis/components/SiteAnalysis.vue @@ -0,0 +1,33 @@ + + diff --git a/src/views/dashboard/Analysis/components/VisitAnalysis.vue b/src/views/dashboard/Analysis/components/VisitAnalysis.vue new file mode 100644 index 0000000..a694c95 --- /dev/null +++ b/src/views/dashboard/Analysis/components/VisitAnalysis.vue @@ -0,0 +1,104 @@ + + diff --git a/src/views/dashboard/Analysis/components/VisitAnalysisBar.vue b/src/views/dashboard/Analysis/components/VisitAnalysisBar.vue new file mode 100644 index 0000000..4139be3 --- /dev/null +++ b/src/views/dashboard/Analysis/components/VisitAnalysisBar.vue @@ -0,0 +1,51 @@ + + diff --git a/src/views/dashboard/Analysis/components/VisitRadar.vue b/src/views/dashboard/Analysis/components/VisitRadar.vue new file mode 100644 index 0000000..bc3eb5f --- /dev/null +++ b/src/views/dashboard/Analysis/components/VisitRadar.vue @@ -0,0 +1,94 @@ + + diff --git a/src/views/dashboard/Analysis/components/VisitSource.vue b/src/views/dashboard/Analysis/components/VisitSource.vue new file mode 100644 index 0000000..7b8e32a --- /dev/null +++ b/src/views/dashboard/Analysis/components/VisitSource.vue @@ -0,0 +1,80 @@ + + diff --git a/src/views/dashboard/Analysis/components/props.ts b/src/views/dashboard/Analysis/components/props.ts new file mode 100644 index 0000000..8643650 --- /dev/null +++ b/src/views/dashboard/Analysis/components/props.ts @@ -0,0 +1,16 @@ +import { PropType } from 'vue'; + +export interface BasicProps { + width: string; + height: string; +} +export const basicProps = { + width: { + type: String as PropType, + default: '100%', + }, + height: { + type: String as PropType, + default: '280px', + }, +}; diff --git a/src/views/dashboard/Analysis/data.ts b/src/views/dashboard/Analysis/data.ts new file mode 100644 index 0000000..ff5c277 --- /dev/null +++ b/src/views/dashboard/Analysis/data.ts @@ -0,0 +1,219 @@ +export interface GrowCardItem { + icon: string; + title: string; + value?: number; + total: number; + color?: string; + action?: string; + footer?: string; +} + +export const growCardList: GrowCardItem[] = [ + { + title: '访问数', + icon: 'visit-count|svg', + value: 2000, + total: 120000, + color: 'green', + action: '月', + }, + { + title: '成交额', + icon: 'total-sales|svg', + value: 20000, + total: 500000, + color: 'blue', + action: '月', + }, + { + title: '下载数', + icon: 'download-count|svg', + value: 8000, + total: 120000, + color: 'orange', + action: '周', + }, + { + title: '成交数', + icon: 'transaction|svg', + value: 5000, + total: 50000, + color: 'purple', + action: '年', + }, +]; + +export const chartCardList: GrowCardItem[] = [ + { + title: '总销售额', + icon: 'visit-count|svg', + total: 126560, + value: 234.56, + footer: '日均销售额', + }, + { + title: '订单量', + icon: 'total-sales|svg', + value: 1234, + total: 8846, + color: 'blue', + footer: '日订单量', + }, + { + title: '支付笔数', + icon: 'download-count|svg', + value: 60, + total: 6560, + color: 'orange', + footer: '转化率', + }, + { + title: '运营活动效果', + icon: 'transaction|svg', + total: 78, + }, +]; +export const bdcCardList: GrowCardItem[] = [ + { + title: '受理量', + icon: 'ant-design:info-circle-outlined', + total: 100, + value: 60, + footer: '今日受理量', + }, + { + title: '办结量', + icon: 'ant-design:info-circle-outlined', + value: 54, + total: 87, + color: 'blue', + footer: '今日办结量', + }, + { + title: '用户受理量', + icon: 'ant-design:info-circle-outlined', + value: 13, + total: 15, + color: 'orange', + footer: '用户今日受理量', + }, + { + title: '用户办结量', + icon: 'ant-design:info-circle-outlined', + total: 9, + value: 7, + footer: '用户今日办结量', + }, +]; + +export const table = { + dataSource: [ + { reBizCode: '1', type: '转移登记', acceptBy: '张三', acceptDate: '2019-01-22', curNode: '任务分派', flowRate: 60 }, + { reBizCode: '2', type: '抵押登记', acceptBy: '李四', acceptDate: '2019-01-23', curNode: '领导审核', flowRate: 30 }, + { reBizCode: '3', type: '转移登记', acceptBy: '王武', acceptDate: '2019-01-25', curNode: '任务处理', flowRate: 20 }, + { reBizCode: '4', type: '转移登记', acceptBy: '赵楼', acceptDate: '2019-11-22', curNode: '部门审核', flowRate: 80 }, + { reBizCode: '5', type: '转移登记', acceptBy: '钱就', acceptDate: '2019-12-12', curNode: '任务分派', flowRate: 90 }, + { reBizCode: '6', type: '转移登记', acceptBy: '孙吧', acceptDate: '2019-03-06', curNode: '任务处理', flowRate: 10 }, + { reBizCode: '7', type: '抵押登记', acceptBy: '周大', acceptDate: '2019-04-13', curNode: '任务分派', flowRate: 100 }, + { reBizCode: '8', type: '抵押登记', acceptBy: '吴二', acceptDate: '2019-05-09', curNode: '任务上报', flowRate: 50 }, + { reBizCode: '9', type: '抵押登记', acceptBy: '郑爽', acceptDate: '2019-07-12', curNode: '任务处理', flowRate: 63 }, + { reBizCode: '20', type: '抵押登记', acceptBy: '林有', acceptDate: '2019-12-12', curNode: '任务打回', flowRate: 59 }, + { reBizCode: '11', type: '转移登记', acceptBy: '码云', acceptDate: '2019-09-10', curNode: '任务签收', flowRate: 87 }, + ], + columns: [ + { + title: '业务号', + align: 'center', + dataIndex: 'reBizCode', + }, + { + title: '业务类型', + align: 'center', + dataIndex: 'type', + }, + { + title: '受理人', + align: 'center', + dataIndex: 'acceptBy', + }, + { + title: '受理时间', + align: 'center', + dataIndex: 'acceptDate', + }, + { + title: '当前节点', + align: 'center', + dataIndex: 'curNode', + }, + { + title: '办理时长', + align: 'center', + dataIndex: 'flowRate', + }, + ], + ipagination: { + current: 1, + pageSize: 5, + pageSizeOptions: ['10', '20', '30'], + showTotal: (total, range) => { + return range[0] + '-' + range[1] + ' 共' + total + '条'; + }, + showQuickJumper: true, + showSizeChanger: true, + total: 0, + }, +}; +export const table1 = { + dataSource: [ + { reBizCode: 'A001', type: '转移登记', acceptBy: '张四', acceptDate: '2019-01-22', curNode: '任务分派', flowRate: 12 }, + { reBizCode: 'A002', type: '抵押登记', acceptBy: '李吧', acceptDate: '2019-01-23', curNode: '任务签收', flowRate: 3 }, + { reBizCode: 'A003', type: '转移登记', acceptBy: '王三', acceptDate: '2019-01-25', curNode: '任务处理', flowRate: 24 }, + { reBizCode: 'A004', type: '转移登记', acceptBy: '赵二', acceptDate: '2019-11-22', curNode: '部门审核', flowRate: 10 }, + { reBizCode: 'A005', type: '转移登记', acceptBy: '钱大', acceptDate: '2019-12-12', curNode: '任务签收', flowRate: 8 }, + { reBizCode: 'A006', type: '转移登记', acceptBy: '孙就', acceptDate: '2019-03-06', curNode: '任务处理', flowRate: 10 }, + { reBizCode: 'A007', type: '抵押登记', acceptBy: '周晕', acceptDate: '2019-04-13', curNode: '部门审核', flowRate: 24 }, + { reBizCode: 'A008', type: '抵押登记', acceptBy: '吴有', acceptDate: '2019-05-09', curNode: '部门审核', flowRate: 30 }, + { reBizCode: 'A009', type: '抵押登记', acceptBy: '郑武', acceptDate: '2019-07-12', curNode: '任务分派', flowRate: 1 }, + { reBizCode: 'A0010', type: '抵押登记', acceptBy: '林爽', acceptDate: '2019-12-12', curNode: '部门审核', flowRate: 16 }, + { reBizCode: 'A0011', type: '转移登记', acceptBy: '码楼', acceptDate: '2019-09-10', curNode: '部门审核', flowRate: 7 }, + ], + columns: [ + { + title: '业务号', + align: 'center', + dataIndex: 'reBizCode', + }, + { + title: '受理人', + align: 'center', + dataIndex: 'acceptBy', + }, + { + title: '发起时间', + align: 'center', + dataIndex: 'acceptDate', + }, + { + title: '当前节点', + align: 'center', + dataIndex: 'curNode', + }, + { + title: '超时时间', + align: 'center', + dataIndex: 'flowRate', + }, + ], + ipagination: { + current: 1, + pageSize: 5, + pageSizeOptions: ['10', '20', '30'], + showTotal: (total, range) => { + return range[0] + '-' + range[1] + ' 共' + total + '条'; + }, + showQuickJumper: true, + showSizeChanger: true, + total: 0, + }, +}; diff --git a/src/views/dashboard/Analysis/homePage/IndexBdc.vue b/src/views/dashboard/Analysis/homePage/IndexBdc.vue new file mode 100644 index 0000000..0bb8034 --- /dev/null +++ b/src/views/dashboard/Analysis/homePage/IndexBdc.vue @@ -0,0 +1,244 @@ + + + + diff --git a/src/views/dashboard/Analysis/homePage/IndexChart.vue b/src/views/dashboard/Analysis/homePage/IndexChart.vue new file mode 100644 index 0000000..4f4fe77 --- /dev/null +++ b/src/views/dashboard/Analysis/homePage/IndexChart.vue @@ -0,0 +1,149 @@ + + + + diff --git a/src/views/dashboard/Analysis/homePage/IndexDef.vue b/src/views/dashboard/Analysis/homePage/IndexDef.vue new file mode 100644 index 0000000..48dec86 --- /dev/null +++ b/src/views/dashboard/Analysis/homePage/IndexDef.vue @@ -0,0 +1,25 @@ + + diff --git a/src/views/dashboard/Analysis/homePage/IndexTask.vue b/src/views/dashboard/Analysis/homePage/IndexTask.vue new file mode 100644 index 0000000..42bc8b7 --- /dev/null +++ b/src/views/dashboard/Analysis/homePage/IndexTask.vue @@ -0,0 +1,422 @@ + + + + + diff --git a/src/views/dashboard/Analysis/index.vue b/src/views/dashboard/Analysis/index.vue new file mode 100644 index 0000000..85bd3dd --- /dev/null +++ b/src/views/dashboard/Analysis/index.vue @@ -0,0 +1,24 @@ + + diff --git a/src/views/dashboard/ai/components/aide/images/ai.png b/src/views/dashboard/ai/components/aide/images/ai.png new file mode 100644 index 0000000..59448da Binary files /dev/null and b/src/views/dashboard/ai/components/aide/images/ai.png differ diff --git a/src/views/dashboard/ai/components/aide/index.vue b/src/views/dashboard/ai/components/aide/index.vue new file mode 100644 index 0000000..25ebb29 --- /dev/null +++ b/src/views/dashboard/ai/components/aide/index.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/views/dashboard/ai/index.vue b/src/views/dashboard/ai/index.vue new file mode 100644 index 0000000..9562efb --- /dev/null +++ b/src/views/dashboard/ai/index.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/src/views/dashboard/workbench/components/DynamicInfo.vue b/src/views/dashboard/workbench/components/DynamicInfo.vue new file mode 100644 index 0000000..4be8f1f --- /dev/null +++ b/src/views/dashboard/workbench/components/DynamicInfo.vue @@ -0,0 +1,31 @@ + + diff --git a/src/views/dashboard/workbench/components/ProjectCard.vue b/src/views/dashboard/workbench/components/ProjectCard.vue new file mode 100644 index 0000000..0957031 --- /dev/null +++ b/src/views/dashboard/workbench/components/ProjectCard.vue @@ -0,0 +1,34 @@ + + diff --git a/src/views/dashboard/workbench/components/QuickNav.vue b/src/views/dashboard/workbench/components/QuickNav.vue new file mode 100644 index 0000000..4e004d1 --- /dev/null +++ b/src/views/dashboard/workbench/components/QuickNav.vue @@ -0,0 +1,19 @@ + + diff --git a/src/views/dashboard/workbench/components/SaleRadar.vue b/src/views/dashboard/workbench/components/SaleRadar.vue new file mode 100644 index 0000000..d623ea3 --- /dev/null +++ b/src/views/dashboard/workbench/components/SaleRadar.vue @@ -0,0 +1,94 @@ + + diff --git a/src/views/dashboard/workbench/components/WorkbenchHeader.vue b/src/views/dashboard/workbench/components/WorkbenchHeader.vue new file mode 100644 index 0000000..9a75adc --- /dev/null +++ b/src/views/dashboard/workbench/components/WorkbenchHeader.vue @@ -0,0 +1,33 @@ + + diff --git a/src/views/dashboard/workbench/components/data.ts b/src/views/dashboard/workbench/components/data.ts new file mode 100644 index 0000000..50a5756 --- /dev/null +++ b/src/views/dashboard/workbench/components/data.ts @@ -0,0 +1,156 @@ +interface GroupItem { + title: string; + icon: string; + color: string; + desc: string; + date: string; + group: string; +} + +interface NavItem { + title: string; + icon: string; + color: string; +} + +interface DynamicInfoItem { + avatar: string; + name: string; + date: string; + desc: string; +} + +export const navItems: NavItem[] = [ + { + title: '首页', + icon: 'ion:home-outline', + color: '#1fdaca', + }, + { + title: '仪表盘', + icon: 'ion:grid-outline', + color: '#bf0c2c', + }, + { + title: '组件', + icon: 'ion:layers-outline', + color: '#e18525', + }, + { + title: '系统管理', + icon: 'ion:settings-outline', + color: '#3fb27f', + }, + { + title: '权限管理', + icon: 'ion:key-outline', + color: '#4daf1bc9', + }, + { + title: '图表', + icon: 'ion:bar-chart-outline', + color: '#00d8ff', + }, +]; + +export const dynamicInfoItems: DynamicInfoItem[] = [ + { + avatar: 'dynamic-avatar-1|svg', + name: '威廉', + date: '刚刚', + desc: `在 开源组 创建了项目 Vue`, + }, + { + avatar: 'dynamic-avatar-2|svg', + name: '艾文', + date: '1个小时前', + desc: `关注了 威廉 `, + }, + { + avatar: 'dynamic-avatar-3|svg', + name: '克里斯', + date: '1天前', + desc: `发布了 个人动态 `, + }, + { + avatar: 'dynamic-avatar-4|svg', + name: 'Jeecg', + date: '2天前', + desc: `发表文章 如何编写一个Vite插件 `, + }, + { + avatar: 'dynamic-avatar-5|svg', + name: '皮特', + date: '3天前', + desc: `回复了 杰克 的问题 如何进行项目优化?`, + }, + { + avatar: 'dynamic-avatar-6|svg', + name: '杰克', + date: '1周前', + desc: `关闭了问题 如何运行项目 `, + }, + { + avatar: 'dynamic-avatar-1|svg', + name: '威廉', + date: '1周前', + desc: `发布了 个人动态 `, + }, + { + avatar: 'dynamic-avatar-1|svg', + name: '威廉', + date: '2021-04-01 20:00', + desc: `推送了代码到 Github`, + }, +]; + +export const groupItems: GroupItem[] = [ + { + title: 'Github', + icon: 'carbon:logo-github', + color: '', + desc: '不要等待机会,而要创造机会。', + group: '开源组', + date: '2021-04-01', + }, + { + title: 'Vue', + icon: 'ion:logo-vue', + color: '#3fb27f', + desc: '现在的你决定将来的你。', + group: '算法组', + date: '2021-04-01', + }, + { + title: 'Html5', + icon: 'ion:logo-html5', + color: '#e18525', + desc: '没有什么才能比努力更重要。', + group: '上班摸鱼', + date: '2021-04-01', + }, + { + title: 'Angular', + icon: 'ion:logo-angular', + color: '#bf0c2c', + desc: '热情和欲望可以突破一切难关。', + group: 'UI', + date: '2021-04-01', + }, + { + title: 'React', + icon: 'bx:bxl-react', + color: '#00d8ff', + desc: '健康的身体是实目标的基石。', + group: '技术牛', + date: '2021-04-01', + }, + { + title: 'Js', + icon: 'ion:logo-javascript', + color: '#4daf1bc9', + desc: '路是走出来的,而不是空想出来的。', + group: '架构组', + date: '2021-04-01', + }, +]; diff --git a/src/views/dashboard/workbench/index.vue b/src/views/dashboard/workbench/index.vue new file mode 100644 index 0000000..cc17cae --- /dev/null +++ b/src/views/dashboard/workbench/index.vue @@ -0,0 +1,36 @@ + + diff --git a/src/views/monitor/datalog/DataLogCompareModal.vue b/src/views/monitor/datalog/DataLogCompareModal.vue new file mode 100644 index 0000000..fd4878a --- /dev/null +++ b/src/views/monitor/datalog/DataLogCompareModal.vue @@ -0,0 +1,381 @@ + + + diff --git a/src/views/monitor/datalog/DataLogModal.vue b/src/views/monitor/datalog/DataLogModal.vue new file mode 100644 index 0000000..743e8e6 --- /dev/null +++ b/src/views/monitor/datalog/DataLogModal.vue @@ -0,0 +1,111 @@ + + + + diff --git a/src/views/monitor/datalog/datalog.api.ts b/src/views/monitor/datalog/datalog.api.ts new file mode 100644 index 0000000..a50f12b --- /dev/null +++ b/src/views/monitor/datalog/datalog.api.ts @@ -0,0 +1,31 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + list = '/sys/dataLog/list', + queryDataVerList = '/sys/dataLog/queryDataVerList', + queryCompareList = '/sys/dataLog/queryCompareList', +} + +/** + * 查询数据日志列表 + * @param params + */ +export const getDataLogList = (params) => { + return defHttp.get({ url: Api.list, params }); +}; + +/** + * 查询数据日志列表 + * @param params + */ +export const queryDataVerList = (params) => { + return defHttp.get({ url: Api.queryDataVerList, params }); +}; + +/** + * 查询对比数据 + * @param params + */ +export const queryCompareList = (params) => { + return defHttp.get({ url: Api.queryCompareList, params }); +}; diff --git a/src/views/monitor/datalog/datalog.data.ts b/src/views/monitor/datalog/datalog.data.ts new file mode 100644 index 0000000..85fe0cb --- /dev/null +++ b/src/views/monitor/datalog/datalog.data.ts @@ -0,0 +1,109 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { h } from 'vue'; +import { Tag, Tooltip } from 'ant-design-vue'; + +export const columns: BasicColumn[] = [ + { + title: '表名', + dataIndex: 'dataTable', + width: 120, + align: 'left', + customRender: ({ text }) => { + return h(Tag, { color: 'blue' }, () => text); + }, + }, + { + title: '数据ID', + dataIndex: 'dataId', + width: 260, + align: 'left', + ellipsis: true, + customRender: ({ text }) => { + return h( + 'span', + { style: 'font-family: Consolas, Monaco, monospace; font-size: 12px; color: #595959' }, + text + ); + }, + }, + { + title: '版本号', + dataIndex: 'dataVersion', + width: 70, + align: 'center', + customRender: ({ text }) => { + return h(Tag, { color: 'green' }, () => 'V' + text); + }, + }, + { + title: '数据内容', + dataIndex: 'dataContent', + ellipsis: true, + customRender: ({ text }) => { + if (!text) return '--'; + // 尝试格式化 JSON 显示关键字段 + try { + const obj = JSON.parse(text); + const keys = Object.keys(obj); + const preview = keys + .slice(0, 3) + .map((k) => { + const v = obj[k]; + const val = v === null || v === undefined || v === '' ? '--' : String(v); + return `${k}: ${val.length > 20 ? val.substring(0, 20) + '...' : val}`; + }) + .join(' | '); + const suffix = keys.length > 3 ? ` (+${keys.length - 3} 字段)` : ''; + return h( + Tooltip, + { title: JSON.stringify(obj, null, 2), overlayStyle: { maxWidth: '500px', whiteSpace: 'pre-wrap', fontFamily: 'Consolas, monospace', fontSize: '12px' } }, + () => h('span', { style: 'font-size: 12px; color: #595959' }, preview + suffix) + ); + } catch { + return text; + } + }, + }, + { + title: '创建人', + dataIndex: 'createBy', + sorter: true, + width: 90, + }, + { + title: '创建时间', + dataIndex: 'createTime', + width: 120, + sorter: true, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'dataTable', + label: '表名', + component: 'Input', + componentProps: { + placeholder: '请输入表名', + }, + colProps: { span: 6 }, + }, + { + field: 'dataId', + label: '数据ID', + component: 'Input', + componentProps: { + placeholder: '请输入数据ID', + }, + colProps: { span: 6 }, + }, + { + field: 'createBy', + label: '创建人', + component: 'Input', + componentProps: { + placeholder: '请输入创建人', + }, + colProps: { span: 6 }, + }, +]; diff --git a/src/views/monitor/datalog/index.vue b/src/views/monitor/datalog/index.vue new file mode 100644 index 0000000..5089a01 --- /dev/null +++ b/src/views/monitor/datalog/index.vue @@ -0,0 +1,73 @@ + + + diff --git a/src/views/monitor/datasource/DataSourceModal.vue b/src/views/monitor/datasource/DataSourceModal.vue new file mode 100644 index 0000000..e45d9d1 --- /dev/null +++ b/src/views/monitor/datasource/DataSourceModal.vue @@ -0,0 +1,87 @@ + + diff --git a/src/views/monitor/datasource/datasource.api.ts b/src/views/monitor/datasource/datasource.api.ts new file mode 100644 index 0000000..3aa0580 --- /dev/null +++ b/src/views/monitor/datasource/datasource.api.ts @@ -0,0 +1,83 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +enum Api { + list = '/sys/dataSource/list', + save = '/sys/dataSource/add', + edit = '/sys/dataSource/edit', + get = '/sys/dataSource/queryById', + delete = '/sys/dataSource/delete', + testConnection = '/online/cgreport/api/testConnection', + deleteBatch = '/sys/dataSource/deleteBatch', + exportXlsUrl = 'sys/dataSource/exportXls', + importExcelUrl = 'sys/dataSource/importExcel', +} +/** + * 导出api + */ +export const getExportUrl = Api.exportXlsUrl; +/** + * 导入api + */ +export const getImportUrl = Api.importExcelUrl; + +/** + * 查询数据源列表 + * @param params + */ +export const getDataSourceList = (params) => { + return defHttp.get({ url: Api.list, params }); +}; + +/** + * 保存或者更新数据源 + * @param params + */ +export const saveOrUpdateDataSource = (params, isUpdate) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url: url, params }); +}; + +/** + * 查询数据源详情 + * @param params + */ +export const getDataSourceById = (params) => { + return defHttp.get({ url: Api.get, params }); +}; + +/** + * 删除数据源 + * @param params + */ +export const deleteDataSource = (params, handleSuccess) => { + return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +/** + * 测试连接 + * @param params + */ +export const testConnection = (params) => { + return defHttp.post({ url: Api.testConnection, params }); +}; + +/** + * 批量删除数据源 + * @param params + */ +export const batchDeleteDataSource = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; diff --git a/src/views/monitor/datasource/datasource.data.ts b/src/views/monitor/datasource/datasource.data.ts new file mode 100644 index 0000000..26d2629 --- /dev/null +++ b/src/views/monitor/datasource/datasource.data.ts @@ -0,0 +1,185 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; + +const dbDriverMap = { + // MySQL 数据库 + '1': { dbDriver: 'com.mysql.jdbc.Driver' }, + //MySQL5.7+ 数据库 + '4': { dbDriver: 'com.mysql.cj.jdbc.Driver' }, + // Oracle + '2': { dbDriver: 'oracle.jdbc.OracleDriver' }, + // SQLServer 数据库 + '3': { dbDriver: 'com.microsoft.sqlserver.jdbc.SQLServerDriver' }, + // marialDB 数据库 + '5': { dbDriver: 'org.mariadb.jdbc.Driver' }, + // postgresql 数据库 + '6': { dbDriver: 'org.postgresql.Driver' }, + // 达梦 数据库 + '7': { dbDriver: 'dm.jdbc.driver.DmDriver' }, + // 人大金仓 数据库 + '8': { dbDriver: 'com.kingbase8.Driver' }, + // 神通 数据库 + '9': { dbDriver: 'com.oscar.Driver' }, + // SQLite 数据库 + '10': { dbDriver: 'org.sqlite.JDBC' }, + // DB2 数据库 + '11': { dbDriver: 'com.ibm.db2.jcc.DB2Driver' }, + // Hsqldb 数据库 + '12': { dbDriver: 'org.hsqldb.jdbc.JDBCDriver' }, + // Derby 数据库 + '13': { dbDriver: 'org.apache.derby.jdbc.ClientDriver' }, + // H2 数据库 + '14': { dbDriver: 'org.h2.Driver' }, + // 其他数据库 + '15': { dbDriver: '' }, +}; +const dbUrlMap = { + // MySQL 数据库 + '1': { dbUrl: 'jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&useUnicode=true&useSSL=false' }, + //MySQL5.7+ 数据库 + '4': { + dbUrl: + 'jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai', + }, + // Oracle + '2': { dbUrl: 'jdbc:oracle:thin:@127.0.0.1:1521:ORCL' }, + // SQLServer 数据库 + '3': { dbUrl: 'jdbc:sqlserver://127.0.0.1:1433;SelectMethod=cursor;DatabaseName=jeecgboot' }, + // Mariadb 数据库 + '5': { dbUrl: 'jdbc:mariadb://127.0.0.1:3306/test?characterEncoding=UTF-8&useSSL=false' }, + // Postgresql 数据库 + '6': { dbUrl: 'jdbc:postgresql://127.0.0.1:5432/test' }, + // 达梦 数据库 + '7': { dbUrl: 'jdbc:dm://127.0.0.1:5236/?jeecg-boot&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8' }, + // 人大金仓 数据库 + '8': { dbUrl: 'jdbc:kingbase8://127.0.0.1:54321/test' }, + // 神通 数据库 + '9': { dbUrl: 'jdbc:oscar://192.168.1.125:2003/test' }, + // SQLite 数据库 + '10': { dbUrl: 'jdbc:sqlite://opt/test.db' }, + // DB2 数据库 + '11': { dbUrl: 'jdbc:db2://127.0.0.1:50000/test' }, + // Hsqldb 数据库 + '12': { dbUrl: 'jdbc:hsqldb:hsql://127.0.0.1/test' }, + // Derby 数据库 + '13': { dbUrl: 'jdbc:derby://127.0.0.1:1527/test' }, + // H2 数据库 + '14': { dbUrl: 'jdbc:h2:tcp://127.0.0.1:8082/test' }, + // 其他数据库 + '15': { dbUrl: '' }, +}; + +export const columns: BasicColumn[] = [ + { + title: '数据源名称', + dataIndex: 'name', + width: 200, + align: 'left', + }, + { + title: '数据库类型', + dataIndex: 'dbType_dictText', + width: 200, + }, + { + title: '驱动类', + dataIndex: 'dbDriver', + width: 200, + }, + { + title: '数据源地址', + dataIndex: 'dbUrl', + }, + { + title: '用户名', + dataIndex: 'dbUsername', + width: 200, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'name', + label: '数据源名称', + component: 'Input', + colProps: { span: 8 }, + }, + { + field: 'dbType', + label: '数据库类型', + component: 'JDictSelectTag', + colProps: { span: 8 }, + componentProps: () => { + return { + dictCode: 'database_type', + }; + }, + }, +]; + +export const formSchema: FormSchema[] = [ + { + field: 'id', + label: 'id', + component: 'Input', + show: false, + }, + { + field: 'code', + label: '数据源编码', + component: 'Input', + required: true, + dynamicDisabled: ({ values }) => { + return !!values.id; + }, + }, + { + field: 'name', + label: '数据源名称', + component: 'Input', + required: true, + }, + { + field: 'dbType', + label: '数据库类型', + component: 'JDictSelectTag', + required: true, + componentProps: ({ formModel }) => { + return { + dictCode: 'database_type', + onChange: (e: any) => { + formModel = Object.assign(formModel, dbDriverMap[e], dbUrlMap[e]); + }, + }; + }, + }, + { + field: 'dbDriver', + label: '驱动类', + required: true, + component: 'Input', + }, + { + field: 'dbUrl', + label: '数据源地址', + required: true, + component: 'Input', + }, + { + field: 'dbUsername', + label: '用户名', + required: true, + component: 'Input', + }, + { + field: 'dbPassword', + label: '密码', + required: true, + component: 'InputPassword', + slot: 'pwd', + }, + { + field: 'remark', + label: '备注', + component: 'InputTextArea', + }, +]; diff --git a/src/views/monitor/datasource/index.vue b/src/views/monitor/datasource/index.vue new file mode 100644 index 0000000..f12f5af --- /dev/null +++ b/src/views/monitor/datasource/index.vue @@ -0,0 +1,118 @@ + + diff --git a/src/views/monitor/disk/DiskInfo.vue b/src/views/monitor/disk/DiskInfo.vue new file mode 100644 index 0000000..7154678 --- /dev/null +++ b/src/views/monitor/disk/DiskInfo.vue @@ -0,0 +1,37 @@ + + diff --git a/src/views/monitor/disk/disk.api.ts b/src/views/monitor/disk/disk.api.ts new file mode 100644 index 0000000..ce01231 --- /dev/null +++ b/src/views/monitor/disk/disk.api.ts @@ -0,0 +1,12 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + queryDiskInfo = '/sys/actuator/redis/queryDiskInfo', +} + +/** + * 详细信息 + */ +export const queryDiskInfo = () => { + return defHttp.get({ url: Api.queryDiskInfo }, { successMessageMode: 'none' }); +}; diff --git a/src/views/monitor/disk/gauge.vue b/src/views/monitor/disk/gauge.vue new file mode 100644 index 0000000..7711fa7 --- /dev/null +++ b/src/views/monitor/disk/gauge.vue @@ -0,0 +1,82 @@ + + diff --git a/src/views/monitor/log/index.vue b/src/views/monitor/log/index.vue new file mode 100644 index 0000000..7582060 --- /dev/null +++ b/src/views/monitor/log/index.vue @@ -0,0 +1,146 @@ + + + diff --git a/src/views/monitor/log/log.api.ts b/src/views/monitor/log/log.api.ts new file mode 100644 index 0000000..c11bd67 --- /dev/null +++ b/src/views/monitor/log/log.api.ts @@ -0,0 +1,21 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + list = '/sys/log/list', + exportXls = '/sys/log/exportXls', +} + +/** + * 查询日志列表 + * @param params + */ +export const getLogList = (params) => { + return defHttp.get({ url: Api.list, params }); +}; + + +/** + * 导出api + * @param params + */ +export const getExportUrl = Api.exportXls; diff --git a/src/views/monitor/log/log.data.ts b/src/views/monitor/log/log.data.ts new file mode 100644 index 0000000..e497377 --- /dev/null +++ b/src/views/monitor/log/log.data.ts @@ -0,0 +1,140 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; + +export const columns: BasicColumn[] = [ + { + title: '日志内容', + dataIndex: 'logContent', + width: 200, + align: 'left', + ellipsis: true, + }, + { + title: '操作人ID', + dataIndex: 'userid', + width: 80, + }, + { + title: '操作人', + dataIndex: 'username', + width: 80, + }, + { + title: 'IP', + dataIndex: 'ip', + width: 60, + }, + { + title: '耗时(毫秒)', + dataIndex: 'costTime', + width: 50, + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + width: 80, + }, + { + title: '客户端类型', + dataIndex: 'clientType_dictText', + width: 50, + }, +]; + +/** + * 操作日志需要操作类型 + */ +export const operationLogColumn: BasicColumn[] = [ + ...columns, + { + title: '操作类型', + dataIndex: 'operateType_dictText', + width: 40, + }, +]; + +export const exceptionColumns: BasicColumn[] = [ + { + title: '异常标题', + dataIndex: 'logContent', + width: 200, + align: 'left', + ellipsis: true, + }, + { + title: '请求地址', + dataIndex: 'requestUrl', + width: 140, + align: 'left', + ellipsis: true, + }, + { + title: '请求方法', + dataIndex: 'method', + width: 120, + align: 'left', + ellipsis: true, + }, + { + title: '操作人', + dataIndex: 'username', + width: 80, + customRender: ({ record }) => { + const pname = record.username; + const pid = record.userid; + if (!pname && !pid) { + return ''; + } + return pname + ' (' + pid + ')'; + }, + }, + { + title: 'IP', + dataIndex: 'ip', + width: 60, + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + width: 80, + }, + { + title: '客户端类型', + dataIndex: 'clientType_dictText', + width: 50, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'keyWord', + label: '搜索日志', + component: 'Input', + colProps: { span: 8 }, + }, + { + field: 'fieldTime', + component: 'RangePicker', + label: '创建时间', + componentProps: { + valueType: 'Date', + }, + colProps: { + span: 6, + }, + }, +]; + +export const operationSearchFormSchema: FormSchema[] = [ + ...searchFormSchema, + { + field: 'operateType', + label: '操作类型', + component: 'JDictSelectTag', + colProps: { span: 4 }, + componentProps: { + dictCode: 'operate_type', + }, + }, +]; diff --git a/src/views/monitor/mynews/DetailModal.vue b/src/views/monitor/mynews/DetailModal.vue new file mode 100644 index 0000000..fe90853 --- /dev/null +++ b/src/views/monitor/mynews/DetailModal.vue @@ -0,0 +1,410 @@ + + + + diff --git a/src/views/monitor/mynews/DynamicNotice.vue b/src/views/monitor/mynews/DynamicNotice.vue new file mode 100644 index 0000000..83a0c10 --- /dev/null +++ b/src/views/monitor/mynews/DynamicNotice.vue @@ -0,0 +1,35 @@ + + diff --git a/src/views/monitor/mynews/XssWhiteList.ts b/src/views/monitor/mynews/XssWhiteList.ts new file mode 100644 index 0000000..b4b687a --- /dev/null +++ b/src/views/monitor/mynews/XssWhiteList.ts @@ -0,0 +1,41 @@ +//xss攻击白名单列表 +export const options = { + whiteList: { + h1: ['style'], + h2: ['style'], + h3: ['style'], + h4: ['style'], + h5: ['style'], + h6: ['style'], + hr: ['style'], + span: ['style'], + strong: ['style'], + b: ['style'], + i: ['style'], + br: [], + p: ['style'], + pre: ['style'], + code: ['style'], + a: ['style', 'target', 'href', 'title', 'rel'], + img: ['style', 'src', 'title','width','height'], + div: ['style'], + table: ['style', 'width', 'border', 'height', 'cellspacing', 'cellpadding'], + tr: ['style', 'valign', 'align'], + td: ['style', 'width', 'colspan', 'rowspan', 'border', 'valign', 'align'], + th: ['style', 'width', 'colspan', 'rowspan', 'border', 'valign', 'align'], + tbody: ['style'], + ul: ['style'], + li: ['style'], + ol: ['style'], + dl: ['style'], + dt: ['style'], + em: ['style'], + cite: ['style'], + section: ['style'], + header: ['style'], + footer: ['style'], + blockquote: ['style'], + audio: ['autoplay', 'controls', 'loop', 'preload', 'src'], + video: ['autoplay', 'controls', 'loop', 'preload', 'src', 'height', 'width'], + }, +}; diff --git a/src/views/monitor/mynews/index.vue b/src/views/monitor/mynews/index.vue new file mode 100644 index 0000000..5a15c29 --- /dev/null +++ b/src/views/monitor/mynews/index.vue @@ -0,0 +1,210 @@ + + diff --git a/src/views/monitor/mynews/mynews.api.ts b/src/views/monitor/mynews/mynews.api.ts new file mode 100644 index 0000000..57e6ec2 --- /dev/null +++ b/src/views/monitor/mynews/mynews.api.ts @@ -0,0 +1,94 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +enum Api { + list = '/sys/sysAnnouncementSend/getMyAnnouncementSend', + editCementSend = '/sys/sysAnnouncementSend/editByAnntIdAndUserId', + readAllMsg = '/sys/sysAnnouncementSend/readAll', + syncNotic = '/sys/annountCement/syncNotic', + getOne = '/sys/sysAnnouncementSend/getOne', + delete = '/sys/sysAnnouncementSend/delete', + deleteBatch = '/sys/sysAnnouncementSend/deleteBatch', +} + +/** + * 查询消息列表 + * @param params + */ +export const getMyNewsList = (params) => { + return defHttp.get({ url: Api.list, params }); +}; + +/** + * 更新用户系统消息阅读状态 + * @param params + */ +export const editCementSend = (params) => { + return defHttp.put({ url: Api.editCementSend, params }); +}; + +/** + * 一键已读 + * @param params + */ +export const readAllMsg = (params, handleSuccess) => { + Modal.confirm({ + title: '确认操作', + content: '是否全部标注已读?', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.put({ url: Api.readAllMsg, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; + +/** + * 同步消息 + * @param params + */ +export const syncNotic = (params) => { + return defHttp.get({ url: Api.syncNotic, params }); +}; + +/** + * 根据消息发送记录ID获取消息内容 + * @param sendId + */ +export const getOne = (sendId) => { + return defHttp.get({ url: Api.getOne, params:{sendId} }); +}; + +/** + * 删除用户通告阅读标记的数据 + * @param params + * @param handleSuccess + */ +export const deleteAnnSend = (params, handleSuccess) =>{ + return defHttp.delete({ url: Api.delete, params }, { joinParamsToUrl: true }).then(()=>{ + handleSuccess(); + }) +} + +/** + * 批量删除用户通告阅读标记的数据 + * @param params + * @param handleSuccess + */ +export const deleteBatchAnnSend = (params, handleSuccess) =>{ + Modal.confirm({ + iconType: 'warning', + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, params }, { joinParamsToUrl: true }).then(()=>{ + handleSuccess(); + }) + }, + }); +} + diff --git a/src/views/monitor/mynews/mynews.data.ts b/src/views/monitor/mynews/mynews.data.ts new file mode 100644 index 0000000..3af216b --- /dev/null +++ b/src/views/monitor/mynews/mynews.data.ts @@ -0,0 +1,102 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { render } from '/@/utils/common/renderUtils'; + +export const columns: BasicColumn[] = [ + { + title: '标题', + dataIndex: 'titile', + width: 100, + align: 'left', + }, + { + title: '消息类型', + dataIndex: 'msgCategory', + width: 80, + customRender: ({ text }) => { + return render.renderDictNative( + text, + [ + { label: '通知公告', value: '1', color: 'blue' }, + { label: '系统消息', value: '2' }, + ], + true + ); + }, + }, + { + title: '发布人', + dataIndex: 'sender', + width: 80, + }, + { + title: '发布时间', + dataIndex: 'sendTime', + width: 80, + }, + { + title: '优先级', + dataIndex: 'priority', + width: 80, + customRender: ({ text }) => { + const color = text == 'L' ? 'blue' : text == 'M' ? 'yellow' : 'red'; + return render.renderTag(render.renderDict(text, 'priority'), color); + }, + }, + { + title: '阅读状态', + dataIndex: 'readFlag', + width: 80, + customRender: ({ text }) => { + return render.renderDictNative( + text, + [ + { label: '未读', value: '0', color: 'red' }, + { label: '已读', value: '1' }, + ], + true + ); + }, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'titile', + label: '标题', + component: 'Input', + colProps: { span: 6 }, + }, + { + field: 'sender', + label: '发布人', + component: 'Input', + colProps: { span: 6 }, + }, + { + field: 'sendTime', + label: '发布时间', + component: 'RangeDate', + componentProps: { + valueType: 'Date', + }, + colProps: { span: 6 }, + }, + { + field: 'msgCategory', + label: '消息类型', + component: 'Select', + componentProps: { + options: [ + { label: '通知公告', value: '1' }, + { label: '系统消息', value: '2' }, + { label: '日程计划', value: 'plan' }, + { label: '流程消息', value: 'flow' }, + { label: '会议', value: 'meeting' }, + { label: '知识库', value: 'file' }, + { label: '协同通知', value: 'collab' }, + { label: '督办通知', value: 'supe' }, + ], + }, + colProps: { span: 6 }, + }, +]; diff --git a/src/views/monitor/quartz/QuartzModal.vue b/src/views/monitor/quartz/QuartzModal.vue new file mode 100644 index 0000000..ab6c6ff --- /dev/null +++ b/src/views/monitor/quartz/QuartzModal.vue @@ -0,0 +1,61 @@ + + diff --git a/src/views/monitor/quartz/index.vue b/src/views/monitor/quartz/index.vue new file mode 100644 index 0000000..512dae4 --- /dev/null +++ b/src/views/monitor/quartz/index.vue @@ -0,0 +1,183 @@ + + diff --git a/src/views/monitor/quartz/quartz.api.ts b/src/views/monitor/quartz/quartz.api.ts new file mode 100644 index 0000000..1b89b27 --- /dev/null +++ b/src/views/monitor/quartz/quartz.api.ts @@ -0,0 +1,107 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +enum Api { + list = '/sys/quartzJob/list', + save = '/sys/quartzJob/add', + edit = '/sys/quartzJob/edit', + get = '/sys/quartzJob/queryById', + pause = '/sys/quartzJob/pause', + resume = '/sys/quartzJob/resume', + delete = '/sys/quartzJob/delete', + exportXlsUrl = '/sys/quartzJob/exportXls', + importExcelUrl = '/sys/quartzJob/importExcel', + execute = '/sys/quartzJob/execute', + deleteBatch = '/sys/quartzJob/deleteBatch', +} + +/** + * 导出api + */ +export const getExportUrl = Api.exportXlsUrl; +/** + * 导入api + */ +export const getImportUrl = Api.importExcelUrl; +/** + * 查询任务列表 + * @param params + */ +export const getQuartzList = (params) => { + return defHttp.get({ url: Api.list, params }); +}; + +/** + * 保存或者更新任务 + * @param params + */ +export const saveOrUpdateQuartz = (params, isUpdate) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url: url, params }); +}; + +/** + * 查询任务详情 + * @param params + */ +export const getQuartzById = (params) => { + return defHttp.get({ url: Api.get, params }); +}; + +/** + * 删除任务 + * @param params + */ +export const deleteQuartz = (params, handleSuccess) => { + return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +/** + * 启动 + * @param params + */ +export const resumeJob = (params, handleSuccess) => { + return defHttp.get({ url: Api.resume, params }).then(() => { + handleSuccess(); + }); +}; + +/** + * 暂停 + * @param params + */ +export const pauseJob = (params, handleSuccess) => { + return defHttp.get({ url: Api.pause, params }).then(() => { + handleSuccess(); + }); +}; + +/** + * 立即执行 + * @param params + */ +export const executeImmediately = (params, handleSuccess) => { + return defHttp.get({ url: Api.execute, params }).then(() => { + handleSuccess(); + }); +}; + +/** + * 批量删除任务 + * @param params + */ +export const batchDeleteQuartz = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; diff --git a/src/views/monitor/quartz/quartz.data.ts b/src/views/monitor/quartz/quartz.data.ts new file mode 100644 index 0000000..b54ae7f --- /dev/null +++ b/src/views/monitor/quartz/quartz.data.ts @@ -0,0 +1,124 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { render } from '/@/utils/common/renderUtils'; +import JCronValidator from '/@/components/Form/src/jeecg/components/JEasyCron/validator'; + +export const columns: BasicColumn[] = [ + { + title: '任务类名', + dataIndex: 'jobClassName', + width: 200, + align: 'left', + }, + { + title: 'Cron表达式', + dataIndex: 'cronExpression', + width: 200, + }, + { + title: '参数', + dataIndex: 'parameter', + width: 200, + }, + { + title: '描述', + dataIndex: 'description', + width: 200, + }, + { + title: '状态', + dataIndex: 'status', + width: 100, + customRender: ({ text }) => { + const color = text == '0' ? 'green' : text == '-1' ? 'red' : 'gray'; + return render.renderTag(render.renderDict(text, 'quartz_status'), color); + }, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'jobClassName', + label: '任务类名', + component: 'Input', + colProps: { span: 8 }, + }, + { + field: 'status', + label: '任务状态', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'quartz_status', + stringToNumber: true, + }, + colProps: { span: 8 }, + }, +]; + +export const formSchema: FormSchema[] = [ + { + field: 'id', + label: 'id', + component: 'Input', + show: false, + }, + { + field: 'jobClassName', + label: '任务类名', + component: 'Input', + required: true, + }, + { + field: 'cronExpression', + label: 'Cron表达式', + component: 'JEasyCron', + defaultValue: '* * * * * ? *', + rules: [{ required: true, message: '请输入Cron表达式' }, { validator: JCronValidator }], + }, + { + field: 'paramterType', + label: '参数类型', + component: 'Select', + defaultValue: 'string', + componentProps: { + options: [ + { label: '字符串', value: 'string' }, + { label: 'JSON对象', value: 'json' }, + ], + }, + }, + { + field: 'parameter', + label: '参数', + component: 'InputTextArea', + ifShow: ({ values }) => { + return values.paramterType == 'string'; + }, + }, + { + field: 'parameter', + label: '参数', + component: 'JAddInput', + helpMessage: '键值对形式填写', + ifShow: ({ values }) => { + return values.paramterType == 'json'; + }, + }, + { + field: 'status', + label: '状态', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'quartz_status', + type: 'radioButton', + stringToNumber: true, + dropdownStyle: { + maxHeight: '6vh', + }, + }, + }, + { + field: 'description', + label: '描述', + component: 'InputTextArea', + }, +]; diff --git a/src/views/monitor/redis/index.vue b/src/views/monitor/redis/index.vue new file mode 100644 index 0000000..5740826 --- /dev/null +++ b/src/views/monitor/redis/index.vue @@ -0,0 +1,325 @@ + + + diff --git a/src/views/monitor/redis/redis.api.ts b/src/views/monitor/redis/redis.api.ts new file mode 100644 index 0000000..5b959cb --- /dev/null +++ b/src/views/monitor/redis/redis.api.ts @@ -0,0 +1,40 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + keysSize = '/sys/actuator/redis/keysSize', + memoryInfo = '/sys/actuator/redis/memoryInfo', + info = '/sys/actuator/redis/info', + metricsHistory = '/sys/actuator/redis/metrics/history', +} + +/** + * key个数 + */ +export const getKeysSize = () => { + return defHttp.get({ url: Api.keysSize }, { isTransformResponse: false }); +}; + +/** + * 内存信息 + */ +export const getMemoryInfo = () => { + return defHttp.get({ url: Api.memoryInfo }, { isTransformResponse: false }); +}; + +/** + * 详细信息 + */ +export const getInfo = () => { + return defHttp.get({ url: Api.info }); +}; + +/** + * 历史监控记录 + */ +export const getMetricsHistory = () => { + return defHttp.get({ url: Api.metricsHistory }); +}; + +export const getRedisInfo = () => { + return Promise.all([getKeysSize(), getMemoryInfo()]); +}; diff --git a/src/views/monitor/redis/redis.data.ts b/src/views/monitor/redis/redis.data.ts new file mode 100644 index 0000000..6966c4c --- /dev/null +++ b/src/views/monitor/redis/redis.data.ts @@ -0,0 +1,26 @@ +import { BasicColumn } from '/@/components/Table'; + +export const columns: BasicColumn[] = [ + { + title: '配置项', + dataIndex: 'key', + width: 120, + align: 'left', + customRender: ({ text }) => { + return text; + }, + }, + { + title: '说明', + dataIndex: 'description', + width: 200, + align: 'left', + ellipsis: true, + }, + { + title: '值', + dataIndex: 'value', + width: 80, + align: 'right', + }, +]; diff --git a/src/views/monitor/route/RouteModal.vue b/src/views/monitor/route/RouteModal.vue new file mode 100644 index 0000000..9250fab --- /dev/null +++ b/src/views/monitor/route/RouteModal.vue @@ -0,0 +1,422 @@ + + diff --git a/src/views/monitor/route/components/RouteRecycleBinModal.vue b/src/views/monitor/route/components/RouteRecycleBinModal.vue new file mode 100644 index 0000000..3d14218 --- /dev/null +++ b/src/views/monitor/route/components/RouteRecycleBinModal.vue @@ -0,0 +1,84 @@ + + diff --git a/src/views/monitor/route/index.vue b/src/views/monitor/route/index.vue new file mode 100644 index 0000000..2945cbc --- /dev/null +++ b/src/views/monitor/route/index.vue @@ -0,0 +1,124 @@ + + diff --git a/src/views/monitor/route/route.api.ts b/src/views/monitor/route/route.api.ts new file mode 100644 index 0000000..3224cb3 --- /dev/null +++ b/src/views/monitor/route/route.api.ts @@ -0,0 +1,73 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + list = '/sys/gatewayRoute/list', + deleteList = '/sys/gatewayRoute/deleteList', + save = '/sys/gatewayRoute/add', + edit = '/sys/gatewayRoute/updateAll', + delete = '/sys/gatewayRoute/delete', + + copyRoute = '/sys/gatewayRoute/copyRoute', + batchPutRecycleBin = '/sys/gatewayRoute/putRecycleBin', + batchDeleteRecycleBin = '/sys/gatewayRoute/deleteRecycleBin', +} + +/** + * 查询路由列表 + * @param params + */ +export const getRouteList = (params) => { + return defHttp.get({ url: Api.list, params }); +}; +/** + * 查询逻辑删除的路由列表 + * @param params + */ +export const deleteRouteList = (params) => { + return defHttp.get({ url: Api.deleteList, params }); +}; + +/** + * 保存或者更新路由 + * @param params + */ +export const saveOrUpdateRoute = (params) => { + return defHttp.post({ url: Api.edit, params }); +}; + +/** + * 删除路由 + * @param params + */ +export const deleteRoute = (params, handleSuccess) => { + return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +/** + * 回收站还原 + * @param params + */ +export const putRecycleBin = (params, handleSuccess) => { + return defHttp.put({ url: Api.batchPutRecycleBin, params }).then(() => { + handleSuccess(); + }); +}; +/** + * 回收站删除 + * @param params + */ +export const deleteRecycleBin = (params, handleSuccess) => { + return defHttp.delete({ url: `${Api.batchDeleteRecycleBin}?ids=${params.ids}` }).then(() => { + handleSuccess(); + }); +}; +/** + * 复制 + */ +export const copyRoute = (params, handleSuccess) => { + return defHttp.get({ url: Api.copyRoute, params }).then(() => { + handleSuccess(); + }); +}; diff --git a/src/views/monitor/route/route.data.ts b/src/views/monitor/route/route.data.ts new file mode 100644 index 0000000..26ce7fe --- /dev/null +++ b/src/views/monitor/route/route.data.ts @@ -0,0 +1,52 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; + +export const columns: BasicColumn[] = [ + { + title: '路由ID', + dataIndex: 'routerId', + width: 200, + align: 'left', + }, + { + title: '路由名称', + dataIndex: 'name', + width: 200, + }, + { + title: '路由URI', + dataIndex: 'uri', + width: 200, + }, + { + title: '状态', + dataIndex: 'status', + slots: { customRender: 'status' }, + width: 150, + }, +]; + +export const formSchema: FormSchema[] = [ + { + field: 'name', + label: '路由ID', + component: 'Input', + required: true, + }, + { + field: 'name', + label: '路由名称', + component: 'InputNumber', + required: true, + }, + { + field: 'uri', + label: '路由URI', + component: 'Input', + }, + { + field: 'predicates', + label: '路由条件', + slot: 'predicates', + component: 'Input', + }, +]; diff --git a/src/views/monitor/server/index.vue b/src/views/monitor/server/index.vue new file mode 100644 index 0000000..ee5aada --- /dev/null +++ b/src/views/monitor/server/index.vue @@ -0,0 +1,117 @@ + + diff --git a/src/views/monitor/server/server.api.ts b/src/views/monitor/server/server.api.ts new file mode 100644 index 0000000..8184191 --- /dev/null +++ b/src/views/monitor/server/server.api.ts @@ -0,0 +1,392 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + cpuCount = '/actuator/metrics/system.cpu.count', + cpuUsage = '/actuator/metrics/system.cpu.usage', + processStartTime = '/actuator/metrics/process.start.time', + processUptime = '/actuator/metrics/process.uptime', + processCpuUsage = '/actuator/metrics/process.cpu.usage', + + jvmMemoryMax = '/actuator/metrics/jvm.memory.max', + jvmMemoryCommitted = '/actuator/metrics/jvm.memory.committed', + jvmMemoryUsed = '/actuator/metrics/jvm.memory.used', + jvmBufferMemoryUsed = '/actuator/metrics/jvm.buffer.memory.used', + jvmBufferCount = '/actuator/metrics/jvm.buffer.count', + jvmThreadsDaemon = '/actuator/metrics/jvm.threads.daemon', + jvmThreadsLive = '/actuator/metrics/jvm.threads.live', + jvmThreadsPeak = '/actuator/metrics/jvm.threads.peak', + jvmClassesLoaded = '/actuator/metrics/jvm.classes.loaded', + jvmClassesUnloaded = '/actuator/metrics/jvm.classes.unloaded', + jvmGcMemoryAllocated = '/actuator/metrics/jvm.gc.memory.allocated', + jvmGcMemoryPromoted = '/actuator/metrics/jvm.gc.memory.promoted', + jvmGcMaxDataSize = '/actuator/metrics/jvm.gc.max.data.size', + jvmGcLiveDataSize = '/actuator/metrics/jvm.gc.live.data.size', + jvmGcPause = '/actuator/metrics/jvm.gc.pause', + + tomcatSessionsCreated = '/actuator/metrics/tomcat.sessions.created', + tomcatSessionsExpired = '/actuator/metrics/tomcat.sessions.expired', + tomcatSessionsActiveCurrent = '/actuator/metrics/tomcat.sessions.active.current', + tomcatSessionsActiveMax = '/actuator/metrics/tomcat.sessions.active.max', + tomcatSessionsRejected = '/actuator/metrics/tomcat.sessions.rejected', + + memoryInfo = '/sys/actuator/memory/info', + // undertow 监控 + undertowSessionsCreated = '/actuator/metrics/undertow.sessions.created', + undertowSessionsExpired = '/actuator/metrics/undertow.sessions.expired', + undertowSessionsActiveCurrent = '/actuator/metrics/undertow.sessions.active.current', + undertowSessionsActiveMax = '/actuator/metrics/undertow.sessions.active.max', +} + +/** + * 查询cpu数量 + */ +export const getCpuCount = () => { + return defHttp.get({ url: Api.cpuCount }, { isTransformResponse: false }); +}; + +/** + * 查询系统 CPU 使用率 + */ +export const getCpuUsage = () => { + return defHttp.get({ url: Api.cpuUsage }, { isTransformResponse: false }); +}; + +/** + * 查询应用启动时间点 + */ +export const getProcessStartTime = () => { + return defHttp.get({ url: Api.processStartTime }, { isTransformResponse: false }); +}; + +/** + * 查询应用已运行时间 + */ +export const getProcessUptime = () => { + return defHttp.get({ url: Api.processUptime }, { isTransformResponse: false }); +}; + +/** + * 查询当前应用 CPU 使用率 + */ +export const getProcessCpuUsage = () => { + return defHttp.get({ url: Api.processCpuUsage }, { isTransformResponse: false }); +}; + +/** + * 查询JVM 最大内存 + */ +export const getJvmMemoryMax = () => { + return defHttp.get({ url: Api.jvmMemoryMax }, { isTransformResponse: false }); +}; + +/** + * JVM 可用内存 + */ +export const getJvmMemoryCommitted = () => { + return defHttp.get({ url: Api.jvmMemoryCommitted }, { isTransformResponse: false }); +}; + +/** + * JVM 已用内存 + */ +export const getJvmMemoryUsed = () => { + return defHttp.get({ url: Api.jvmMemoryUsed }, { isTransformResponse: false }); +}; + +/** + * JVM 缓冲区已用内存 + */ +export const getJvmBufferMemoryUsed = () => { + return defHttp.get({ url: Api.jvmBufferMemoryUsed }, { isTransformResponse: false }); +}; + +/** + *JVM 当前缓冲区数量 + */ +export const getJvmBufferCount = () => { + return defHttp.get({ url: Api.jvmBufferCount }, { isTransformResponse: false }); +}; + +/** + **JVM 守护线程数量 + */ +export const getJvmThreadsDaemon = () => { + return defHttp.get({ url: Api.jvmThreadsDaemon }, { isTransformResponse: false }); +}; + +/** + *JVM 当前活跃线程数量 + */ +export const getJvmThreadsLive = () => { + return defHttp.get({ url: Api.jvmThreadsLive }, { isTransformResponse: false }); +}; + +/** + *JVM 峰值线程数量 + */ +export const getJvmThreadsPeak = () => { + return defHttp.get({ url: Api.jvmThreadsPeak }, { isTransformResponse: false }); +}; + +/** + *JVM 已加载 Class 数量 + */ +export const getJvmClassesLoaded = () => { + return defHttp.get({ url: Api.jvmClassesLoaded }, { isTransformResponse: false }); +}; + +/** + *JVM 未加载 Class 数量 + */ +export const getJvmClassesUnloaded = () => { + return defHttp.get({ url: Api.jvmClassesUnloaded }, { isTransformResponse: false }); +}; + +/** + **GC 时, 年轻代分配的内存空间 + */ +export const getJvmGcMemoryAllocated = () => { + return defHttp.get({ url: Api.jvmGcMemoryAllocated }, { isTransformResponse: false }); +}; + +/** + *GC 时, 老年代分配的内存空间 + */ +export const getJvmGcMemoryPromoted = () => { + return defHttp.get({ url: Api.jvmGcMemoryPromoted }, { isTransformResponse: false }); +}; + +/** + *GC 时, 老年代的最大内存空间 + */ +export const getJvmGcMaxDataSize = () => { + return defHttp.get({ url: Api.jvmGcMaxDataSize }, { isTransformResponse: false }); +}; + +/** + *FullGC 时, 老年代的内存空间 + */ +export const getJvmGcLiveDataSize = () => { + return defHttp.get({ url: Api.jvmGcLiveDataSize }, { isTransformResponse: false }); +}; + +/** + *系统启动以来GC 次数 + */ +export const getJvmGcPause = () => { + return defHttp.get({ url: Api.jvmGcPause }, { isTransformResponse: false }); +}; + +/** + *tomcat 已创建 session 数 + */ +export const getTomcatSessionsCreated = () => { + return defHttp.get({ url: Api.tomcatSessionsCreated }, { isTransformResponse: false }); +}; + +/** + *tomcat 已过期 session 数 + */ +export const getTomcatSessionsExpired = () => { + return defHttp.get({ url: Api.tomcatSessionsExpired }, { isTransformResponse: false }); +}; + +/** + *tomcat 当前活跃 session 数 + */ +export const getTomcatSessionsActiveCurrent = () => { + return defHttp.get({ url: Api.tomcatSessionsActiveCurrent }, { isTransformResponse: false }); +}; + +/** + *tomcat 活跃 session 数峰值 + */ +export const getTomcatSessionsActiveMax = () => { + return defHttp.get({ url: Api.tomcatSessionsActiveMax }, { isTransformResponse: false }); +}; + +/** + *超过session 最大配置后,拒绝的 session 个数 + */ +export const getTomcatSessionsRejected = () => { + return defHttp.get({ url: Api.tomcatSessionsRejected }, { isTransformResponse: false }); +}; + +/** + *undertow 已创建 session 数 + */ +export const getUndertowSessionsCreated = () => { + return defHttp.get({ url: Api.undertowSessionsCreated }, { isTransformResponse: false }); +}; + +/** + *undertow 已过期 session 数 + */ +export const getUndertowSessionsExpired = () => { + return defHttp.get({ url: Api.undertowSessionsExpired }, { isTransformResponse: false }); +}; + +/** + *undertow 当前活跃 session 数 + */ +export const getUndertowSessionsActiveCurrent = () => { + return defHttp.get({ url: Api.undertowSessionsActiveCurrent }, { isTransformResponse: false }); +}; + +/** + *undertow 活跃 session 数峰值 + */ +export const getUndertowSessionsActiveMax = () => { + return defHttp.get({ url: Api.undertowSessionsActiveMax }, { isTransformResponse: false }); +}; + +/** + * 内存信息 + */ +export const getMemoryInfo = () => { + return defHttp.get({ url: Api.memoryInfo }, { isTransformResponse: false }); +}; + +export const getMoreInfo = (infoType) => { + if (infoType == '1') { + return {}; + } + if (infoType == '2') { + return { 'jvm.gc.pause': ['.count', '.totalTime'] }; + } + if (infoType == '3') { + return { + 'tomcat.global.request': ['.count', '.totalTime'], + 'tomcat.servlet.request': ['.count', '.totalTime'], + }; + } + if (infoType == '5') { + return {}; + } + if (infoType == '6') { + return {}; + } +}; + +export const getTextInfo = (infoType) => { + if (infoType == '1') { + return { + 'system.cpu.count': { color: 'green', text: 'CPU 数量', unit: '核' }, + 'system.cpu.usage': { color: 'green', text: '系统 CPU 使用率', unit: '%', valueType: 'Number' }, + 'process.start.time': { color: 'purple', text: '应用启动时间点', unit: '', valueType: 'Date' }, + 'process.uptime': { color: 'purple', text: '应用已运行时间', unit: '秒' }, + 'process.cpu.usage': { color: 'purple', text: '当前应用 CPU 使用率', unit: '%', valueType: 'Number' }, + }; + } + if (infoType == '2') { + return { + 'jvm.memory.max': { color: 'purple', text: 'JVM 最大内存', unit: 'MB', valueType: 'RAM' }, + 'jvm.memory.committed': { color: 'purple', text: 'JVM 可用内存', unit: 'MB', valueType: 'RAM' }, + 'jvm.memory.used': { color: 'purple', text: 'JVM 已用内存', unit: 'MB', valueType: 'RAM' }, + 'jvm.buffer.memory.used': { color: 'cyan', text: 'JVM 缓冲区已用内存', unit: 'MB', valueType: 'RAM' }, + 'jvm.buffer.count': { color: 'cyan', text: '当前缓冲区数量', unit: '个' }, + 'jvm.threads.daemon': { color: 'green', text: 'JVM 守护线程数量', unit: '个' }, + 'jvm.threads.live': { color: 'green', text: 'JVM 当前活跃线程数量', unit: '个' }, + 'jvm.threads.peak': { color: 'green', text: 'JVM 峰值线程数量', unit: '个' }, + 'jvm.classes.loaded': { color: 'orange', text: 'JVM 已加载 Class 数量', unit: '个' }, + 'jvm.classes.unloaded': { color: 'orange', text: 'JVM 未加载 Class 数量', unit: '个' }, + 'jvm.gc.memory.allocated': { color: 'pink', text: 'GC 时, 年轻代分配的内存空间', unit: 'MB', valueType: 'RAM' }, + 'jvm.gc.memory.promoted': { color: 'pink', text: 'GC 时, 老年代分配的内存空间', unit: 'MB', valueType: 'RAM' }, + 'jvm.gc.max.data.size': { color: 'pink', text: 'GC 时, 老年代的最大内存空间', unit: 'MB', valueType: 'RAM' }, + 'jvm.gc.live.data.size': { color: 'pink', text: 'FullGC 时, 老年代的内存空间', unit: 'MB', valueType: 'RAM' }, + 'jvm.gc.pause.count': { color: 'blue', text: '系统启动以来GC 次数', unit: '次' }, + 'jvm.gc.pause.totalTime': { color: 'blue', text: '系统启动以来GC 总耗时', unit: '秒' }, + }; + } + if (infoType == '3') { + return { + 'tomcat.sessions.created': { color: 'green', text: 'tomcat 已创建 session 数', unit: '个' }, + 'tomcat.sessions.expired': { color: 'green', text: 'tomcat 已过期 session 数', unit: '个' }, + 'tomcat.sessions.active.current': { color: 'green', text: 'tomcat 当前活跃 session 数', unit: '个' }, + 'tomcat.sessions.active.max': { color: 'green', text: 'tomcat 活跃 session 数峰值', unit: '个' }, + 'tomcat.sessions.rejected': { color: 'green', text: '超过session 最大配置后,拒绝的 session 个数', unit: '个' }, + 'tomcat.global.sent': { color: 'purple', text: '发送的字节数', unit: 'bytes' }, + 'tomcat.global.request.max': { color: 'purple', text: 'request 请求最长耗时', unit: '秒' }, + 'tomcat.global.request.count': { color: 'purple', text: '全局 request 请求次数', unit: '次' }, + 'tomcat.global.request.totalTime': { color: 'purple', text: '全局 request 请求总耗时', unit: '秒' }, + 'tomcat.servlet.request.max': { color: 'cyan', text: 'servlet 请求最长耗时', unit: '秒' }, + 'tomcat.servlet.request.count': { color: 'cyan', text: 'servlet 总请求次数', unit: '次' }, + 'tomcat.servlet.request.totalTime': { color: 'cyan', text: 'servlet 请求总耗时', unit: '秒' }, + 'tomcat.threads.current': { color: 'pink', text: 'tomcat 当前线程数(包括守护线程)', unit: '个' }, + 'tomcat.threads.config.max': { color: 'pink', text: 'tomcat 配置的线程最大数', unit: '个' }, + }; + } + if (infoType == '5') { + return { + 'memory.physical.total': { color: 'green', text: '总物理内存', unit: 'MB', valueType: 'RAM' }, + 'memory.physical.used': { color: 'green', text: '已使用物理内存', unit: 'MB', valueType: 'RAM' }, + 'memory.physical.free': { color: 'green', text: '可用物理内存', unit: 'MB', valueType: 'RAM' }, + 'memory.physical.usage': { color: 'green', text: '物理内存使用率', unit: '%', valueType: 'Number' }, + 'memory.runtime.total': { color: 'purple', text: 'JVM总内存', unit: 'MB', valueType: 'RAM' }, + 'memory.runtime.used': { color: 'purple', text: 'JVM已使用内存', unit: 'MB', valueType: 'RAM' }, + 'memory.runtime.max': { color: 'purple', text: 'JVM最大内存', unit: 'MB', valueType: 'RAM' }, + 'memory.runtime.free': { color: 'purple', text: 'JVM可用内存', unit: 'MB', valueType: 'RAM' }, + 'memory.runtime.usage': { color: 'purple', text: 'JVM内存使用率', unit: '%', valueType: 'Number' }, + }; + } + if (infoType == '6') { + // undertow 监控 + return { + 'undertow.sessions.created': { color: 'green', text: 'undertow 已创建 session 数', unit: '个' }, + 'undertow.sessions.expired': { color: 'green', text: 'undertow 已过期 session 数', unit: '个' }, + 'undertow.sessions.active.current': { color: 'green', text: 'undertow 当前活跃 session 数', unit: '个' }, + 'undertow.sessions.active.max': { color: 'green', text: 'undertow 活跃 session 数峰值', unit: '个' }, + 'undertow.sessions.rejected': { color: 'green', text: '超过session 最大配置后,拒绝的 session 个数', unit: '个' }, + }; + } +}; + +/** + * 查询cpu数量 + * @param params + */ +export const getServerInfo = (infoType) => { + if (infoType == '1') { + return Promise.all([getCpuCount(), getCpuUsage(), getProcessStartTime(), getProcessUptime(), getProcessCpuUsage()]); + } + if (infoType == '2') { + return Promise.all([ + getJvmMemoryMax(), + getJvmMemoryCommitted(), + getJvmMemoryUsed(), + getJvmBufferCount(), + getJvmBufferMemoryUsed(), + getJvmThreadsDaemon(), + getJvmThreadsLive(), + getJvmThreadsPeak(), + getJvmClassesLoaded(), + getJvmClassesUnloaded(), + getJvmGcLiveDataSize(), + getJvmGcMaxDataSize(), + getJvmGcMemoryAllocated(), + getJvmGcMemoryPromoted(), + getJvmGcPause(), + ]); + } + if (infoType == '3') { + return Promise.all([ + getTomcatSessionsActiveCurrent(), + getTomcatSessionsActiveMax(), + getTomcatSessionsCreated(), + getTomcatSessionsExpired(), + getTomcatSessionsRejected(), + ]); + } + if (infoType == '5') { + return Promise.all([getMemoryInfo()]); + } + // undertow监控 + if (infoType == '6') { + return Promise.all([ + getUndertowSessionsActiveCurrent(), + getUndertowSessionsActiveMax(), + getUndertowSessionsCreated(), + getUndertowSessionsExpired(), + ]); + } +}; diff --git a/src/views/monitor/server/server.data.ts b/src/views/monitor/server/server.data.ts new file mode 100644 index 0000000..8b9fa54 --- /dev/null +++ b/src/views/monitor/server/server.data.ts @@ -0,0 +1,23 @@ +import { BasicColumn } from '/@/components/Table'; + +export const columns: BasicColumn[] = [ + { + title: '参数', + dataIndex: 'param', + width: 80, + align: 'left', + slots: { customRender: 'param' }, + }, + { + title: '描述', + dataIndex: 'text', + slots: { customRender: 'text' }, + width: 80, + }, + { + title: '当前值', + dataIndex: 'value', + slots: { customRender: 'value' }, + width: 80, + }, +]; diff --git a/src/views/monitor/trace/index.vue b/src/views/monitor/trace/index.vue new file mode 100644 index 0000000..ce1e601 --- /dev/null +++ b/src/views/monitor/trace/index.vue @@ -0,0 +1,72 @@ + + + diff --git a/src/views/monitor/trace/trace.api.ts b/src/views/monitor/trace/trace.api.ts new file mode 100644 index 0000000..cb69b52 --- /dev/null +++ b/src/views/monitor/trace/trace.api.ts @@ -0,0 +1,12 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + actuatorList = '/actuator/jeecghttptrace/', +} + +/** + * 追踪信息 + */ +export const getActuatorList = (query: String, order: String) => { + return defHttp.get({ url: Api.actuatorList + query + '/' + order }, { isTransformResponse: false }); +}; diff --git a/src/views/monitor/trace/trace.data.ts b/src/views/monitor/trace/trace.data.ts new file mode 100644 index 0000000..ef195be --- /dev/null +++ b/src/views/monitor/trace/trace.data.ts @@ -0,0 +1,85 @@ +import { BasicColumn } from '/@/components/Table'; +import dayjs from 'dayjs'; +import _get from 'lodash.get'; +import { h } from 'vue'; +import { Tag } from 'ant-design-vue'; + +export const columns: BasicColumn[] = [ + { + title: '请求时间', + dataIndex: 'timestamp', + width: 50, + customRender({ text }) { + return dayjs(text).format('YYYY-MM-DD HH:mm:ss'); + }, + }, + { + title: '请求方法', + dataIndex: 'request.method', + width: 20, + customRender({ record, column }) { + let value = _get(record, column.dataIndex!); + let color = ''; + if (value === 'GET') { + color = '#87d068'; + } + if (value === 'POST') { + color = '#2db7f5'; + } + if (value === 'PUT') { + color = '#ffba5a'; + } + if (value === 'DELETE') { + color = '#ff5500'; + } + return h(Tag, { color }, () => value); + }, + }, + { + title: '请求URL', + dataIndex: 'request.uri', + width: 200, + customRender({ record, column }) { + return _get(record, column.dataIndex!); + }, + }, + { + title: '响应状态', + dataIndex: 'response.status', + width: 50, + customRender({ record, column }) { + let value = _get(record, column.dataIndex!); + let color = ''; + if (value < 200) { + color = 'pink'; + } else if (value < 201) { + color = 'green'; + } else if (value < 399) { + color = 'cyan'; + } else if (value < 403) { + color = 'orange'; + } else if (value < 501) { + color = 'red'; + } + return h(Tag, { color }, () => value); + }, + }, + { + title: '请求耗时', + dataIndex: 'timeTaken', + width: 50, + customRender({ record, column }) { + let value = _get(record, column.dataIndex!); + let color = 'red'; + if (value < 500) { + color = 'green'; + } else if (value < 1000) { + color = 'cyan'; + } else if (value < 1500) { + color = 'orange'; + } + return h(Tag, { color }, () => `${value} ms`); + }, + sorter: true, + }, +]; diff --git a/src/views/openapi/OpenApi.api.ts b/src/views/openapi/OpenApi.api.ts new file mode 100644 index 0000000..32c5f36 --- /dev/null +++ b/src/views/openapi/OpenApi.api.ts @@ -0,0 +1,118 @@ +import {defHttp} from '/@/utils/http/axios'; +import { useMessage } from "/@/hooks/web/useMessage"; + +const { createConfirm } = useMessage(); + +enum Api { + list = '/openapi/list', + save='/openapi/add', + edit='/openapi/edit', + deleteOne = '/openapi/delete', + deleteBatch = '/openapi/deleteBatch', + genPath = '/openapi/genPath', + importExcel = '/openapi/importExcel', + exportXls = '/openapi/exportXls', + openApiHeaderList = '/openapi/list', + openApiParamList = '/openapi/list', + openApiJson = '/openapi/json', +} + +/** + * 子表单查询接口 + * @param params + */ +export const genPath = Api.genPath +/** + * swagger文档json + * @param params + */ +export const openApiJson = Api.openApiJson +/** + * 导出api + * @param params + */ +export const getExportUrl = Api.exportXls; + +/** + * 导入api + */ +export const getImportUrl = Api.importExcel; +/** + * 子表单查询接口 + * @param params + */ +export const queryOpenApiHeader = Api.openApiHeaderList +/** + * 子表单查询接口 + * @param params + */ +export const queryOpenApiParam = Api.openApiParamList + +/** + * 列表接口 + * @param params + */ +export const list = (params) => + defHttp.get({url: Api.list, params}); + +/** + * 删除单个 + */ +export const deleteOne = (params,handleSuccess) => { + return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => { + handleSuccess(); + }); +} +/** + * 批量删除 + * @param params + */ +export const batchDelete = (params, handleSuccess) => { + createConfirm({ + iconType: 'warning', + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => { + handleSuccess(); + }); + } + }); +} +/** + * 保存或者更新 + * @param params + */ +export const saveOrUpdate = (params, isUpdate) => { + if (isUpdate) { + return defHttp.put({url: Api.edit, params}); + } else { + return defHttp.post({url: Api.save, params}); + } +} +/** + * 获取接口地址 + * @param params + */ +export const getGenPath = (params) => + defHttp.get({url: Api.genPath, params},{isTransformResponse:false}); +/** + * 子表列表接口 + * @param params + */ +export const openApiHeaderList = (params) => + defHttp.get({url: Api.openApiHeaderList, params},{isTransformResponse:false}); +/** + * 子表列表接口 + * @param params + */ +export const openApiParamList = (params) => + defHttp.get({url: Api.openApiParamList, params},{isTransformResponse:false}); +/** + * swagger文档json + * @param params + */ +export const getOpenApiJson = (params) => + defHttp.get({url: Api.openApiJson, params},{isTransformResponse:false}); diff --git a/src/views/openapi/OpenApi.data.ts b/src/views/openapi/OpenApi.data.ts new file mode 100644 index 0000000..bcb29be --- /dev/null +++ b/src/views/openapi/OpenApi.data.ts @@ -0,0 +1,468 @@ +import {BasicColumn} from '/@/components/Table'; +import {FormSchema} from '/@/components/Table'; +import { rules} from '/@/utils/helper/validator'; +import { render } from '/@/utils/common/renderUtils'; +import {JVxeTypes,JVxeColumn} from '/@/components/jeecg/JVxeTable/types' +import { getWeekMonthQuarterYear } from '/@/utils'; +//列表数据 +export const columns: BasicColumn[] = [ + { + title: '接口名称', + align:"center", + dataIndex: 'name' + }, + { + title: '接口地址', + align:"center", + dataIndex: 'requestUrl', + width: 120, + }, + { + title: '请求方式', + align:"center", + dataIndex: 'requestMethod', + width: 100, + }, + { + title: '原始接口', + align:"center", + dataIndex: 'originUrl', + ellipsis: true, + }, + { + title: 'IP 白名单', + align:"center", + dataIndex: 'whiteList', + ellipsis: true, + customRender: ({ text }) => { + if (!text) return '不限制'; + const count = text.split(/[,\n]/).filter(item => item.trim()).length; + return count + ' 条规则'; + } + }, +]; +//查询数据 +export const searchFormSchema: FormSchema[] = [ + { + label: "接口名称", + field: "name", + component: 'JInput', + }, + { + label: "接口地址", + field: "requestUrl", + component: 'JInput', + }, +]; +//表单数据 +export const formSchema: FormSchema[] = [ + { + label: '接口名称', + field: 'name', + component: 'Input', + dynamicRules: ({model,schema}) => { + return [ + { required: true, message: '请输入接口名称!'}, + ]; + }, + }, + { + label: '原始接口', + field: 'originUrl', + component: 'Input', + componentProps: { + placeholder: '当前系统的原始接口地址,如 /sys/user/list', + }, + helpMessage: '当前系统中被代理的原始接口路径', + dynamicRules: () => { + return [ + { required: true, message: '请输入原始接口路径!' }, + { + validator: (_, value) => { + if (value && !value.startsWith('/')) { + return Promise.reject('原始接口路径必须以 / 开头'); + } + if (value && value.includes('//')) { + return Promise.reject('原始接口路径不能包含 //'); + } + if (value && value.includes('..')) { + return Promise.reject('原始接口路径不能包含 ..'); + } + return Promise.resolve(); + }, + }, + ]; + }, + }, + { + label: '请求方式', + field: 'requestMethod', + component: 'JSearchSelect', + componentProps:{ + dictOptions: [ + { + text: 'POST', + value: 'POST', + }, + { + text: 'GET', + value: 'GET', + }, + { + text: 'HEAD', + value: 'HEAD', + }, + { + text: 'PUT', + value: 'PUT', + }, + { + text: 'PATCH', + value: 'PATCH', + }, + { + text: 'DELETE', + value: 'DELETE', + },{ + text: 'OPTIONS', + value: 'OPTIONS', + },{ + text: 'TRACE', + value: 'TRACE', + }, + ] + }, + dynamicRules: ({model,schema}) => { + return [ + { required: true, message: '请输入请求方式!'}, + ]; + }, + }, + { + label: '接口地址', + field: 'requestUrl', + component: 'Input', + dynamicDisabled:true + }, + { + label: 'IP 白名单', + field: 'whiteList', + helpMessage: '支持精确IP、CIDR网段(如192.168.1.0/24)、通配符(如10.2.3.*),每行一个或逗号分隔,为空则不限制', + component: 'InputTextArea', + slot: 'whiteListSlot', + componentProps: { + rows: 5, + placeholder: '示例:\n192.168.1.100\n10.0.0.0/8\n172.16.*.*', + }, + colProps: { span: 24 }, + }, + { + label: '备注', + field: 'comment', + component: 'InputTextArea', + componentProps: { + rows: 2, + placeholder: '请输入白名单备注说明', + }, + colProps: { span: 24 }, + }, + { + label: '接口描述', + field: 'description', + component: 'InputTextArea', + componentProps: { + rows: 3, + placeholder: '请输入接口描述', + }, + colProps: { span: 24 }, + }, + { + label: '删除标识', + field: 'delFlag', + component: 'Input', + defaultValue:0, + show:false + }, + { + label: '状态', + field: 'status', + component: 'Input', + defaultValue:"1", + show:false + }, + // TODO 主键隐藏字段,目前写死为ID + { + label: '', + field: 'id', + component: 'Input', + show: false + }, +]; +//子表单数据 +//子表列表数据 +export const openApiHeaderColumns: BasicColumn[] = [ + // { + // title: 'apiId', + // align:"center", + // dataIndex: 'apiId' + // }, + { + title: '请求头Key', + align:"center", + dataIndex: 'headerKey' + }, + { + title: '是否必填', + align:"center", + dataIndex: 'required_dictText' + }, + { + title: '默认值', + align:"center", + dataIndex: 'defaultValue' + }, + { + title: '备注', + align:"center", + dataIndex: 'note' + }, +]; +//子表列表数据 +export const openApiParamColumns: BasicColumn[] = [ + // { + // title: 'apiId', + // align:"center", + // dataIndex: 'apiId' + // }, + { + title: '参数Key', + align:"center", + dataIndex: 'paramKey' + }, + { + title: '是否必填', + align:"center", + dataIndex: 'required_dictText' + }, + { + title: '默认值', + align:"center", + dataIndex: 'defaultValue' + }, + { + title: '备注', + align:"center", + dataIndex: 'note' + }, +]; +//子表表格配置 +export const openApiHeaderJVxeColumns: JVxeColumn[] = [ + // { + // title: 'apiId', + // key: 'apiId', + // type: JVxeTypes.input, + // width:"200px", + // placeholder: '请输入${title}', + // defaultValue:'', + // }, + { + title: '请求头Key', + key: 'headerKey', + type: JVxeTypes.input, + width:"200px", + placeholder: '请输入${title}', + defaultValue:'', + }, + { + title: '是否必填', + key: 'required', + type: JVxeTypes.checkbox, + options:[], + // dictCode:"yn", + width:"100px", + placeholder: '请输入${title}', + defaultValue:'', + customValue: ['1','0'] + }, + { + title: '参数类型', + key: 'paramType', + type: JVxeTypes.select, + width: '120px', + options: [ + { title: 'string', value: 'string' }, + { title: 'integer', value: 'integer' }, + { title: 'number', value: 'number' }, + { title: 'boolean', value: 'boolean' }, + { title: 'array', value: 'array' }, + { title: 'object', value: 'object' }, + ], + defaultValue: 'string', + }, + { + title: '默认值', + key: 'defaultValue', + type: JVxeTypes.input, + width:"200px", + placeholder: '请输入${title}', + defaultValue:'', + }, + { + title: '示例值', + key: 'example', + type: JVxeTypes.input, + width: '200px', + placeholder: '请输入${title}', + defaultValue: '', + }, + { + title: '备注', + key: 'note', + type: JVxeTypes.input, + placeholder: '请输入${title}', + defaultValue:'', + }, + ] +export const openApiParamJVxeColumns: JVxeColumn[] = [ + // { + // title: 'apiId', + // key: 'apiId', + // type: JVxeTypes.input, + // width:"200px", + // placeholder: '请输入${title}', + // defaultValue:'', + // }, + { + title: '参数Key', + key: 'paramKey', + type: JVxeTypes.input, + width:"200px", + placeholder: '请输入${title}', + defaultValue:'', + }, + { + title: '是否必填', + key: 'required', + type: JVxeTypes.checkbox, + options:[], + // dictCode:"yn", + width:"100px", + placeholder: '请输入${title}', + defaultValue:'', + customValue: ['1','0'] + }, + { + title: '参数类型', + key: 'paramType', + type: JVxeTypes.select, + width: '120px', + options: [ + { title: 'string', value: 'string' }, + { title: 'integer', value: 'integer' }, + { title: 'number', value: 'number' }, + { title: 'boolean', value: 'boolean' }, + { title: 'array', value: 'array' }, + { title: 'object', value: 'object' }, + ], + defaultValue: 'string', + }, + { + title: '默认值', + key: 'defaultValue', + type: JVxeTypes.input, + width:"200px", + placeholder: '请输入${title}', + defaultValue:'', + }, + { + title: '示例值', + key: 'example', + type: JVxeTypes.input, + width: '200px', + placeholder: '请输入${title}', + defaultValue: '', + }, + { + title: '备注', + key: 'note', + type: JVxeTypes.input, + placeholder: '请输入${title}', + defaultValue:'', + }, + ] + +export const responseFieldJVxeColumns: JVxeColumn[] = [ + { + title: '字段名', + key: 'fieldName', + type: JVxeTypes.input, + width: '200px', + placeholder: '请输入${title}', + defaultValue: '', + }, + { + title: '类型', + key: 'fieldType', + type: JVxeTypes.select, + width: '120px', + options: [ + { title: 'string', value: 'string' }, + { title: 'integer', value: 'integer' }, + { title: 'number', value: 'number' }, + { title: 'boolean', value: 'boolean' }, + { title: 'array', value: 'array' }, + { title: 'object', value: 'object' }, + ], + defaultValue: 'string', + }, + { + title: '说明', + key: 'fieldDesc', + type: JVxeTypes.input, + placeholder: '请输入${title}', + defaultValue: '', + }, +]; + +// 高级查询数据 +export const superQuerySchema = { + name: {title: '接口名称',order: 0,view: 'text', type: 'string',}, + requestMethod: {title: '请求方式',order: 1,view: 'list', type: 'string',dictCode: '',}, + requestUrl: {title: '接口地址',order: 2,view: 'text', type: 'string',}, + whiteList: {title: 'IP 白名单',order: 3,view: 'text', type: 'string',}, + status: {title: '状态',order: 5,view: 'number', type: 'number',}, + createBy: {title: '创建人',order: 6,view: 'text', type: 'string',}, + createTime: {title: '创建时间',order: 7,view: 'datetime', type: 'string',}, + //子表高级查询 + openApiHeader: { + title: '请求头表', + view: 'table', + fields: { + // apiId: {title: 'apiId',order: 0,view: 'text', type: 'string',}, + headerKey: {title: '请求头Key',order: 1,view: 'text', type: 'string',}, + required: {title: '是否必填',order: 2,view: 'number', type: 'number',dictCode: 'yn',}, + defaultValue: {title: '默认值',order: 3,view: 'text', type: 'string',}, + note: {title: '备注',order: 4,view: 'text', type: 'string',}, + } + }, + openApiParam: { + title: '请求参数部分', + view: 'table', + fields: { + // apiId: {title: 'apiId',order: 0,view: 'text', type: 'string',}, + paramKey: {title: '参数Key',order: 1,view: 'text', type: 'string',}, + required: {title: '是否必填',order: 2,view: 'number', type: 'number',dictCode: 'yn',}, + defaultValue: {title: '默认值',order: 3,view: 'text', type: 'string',}, + note: {title: '备注',order: 4,view: 'text', type: 'string',}, + } + }, +}; + +/** +* 流程表单调用这个方法获取formSchema +* @param param +*/ +export function getBpmFormSchema(_formData): FormSchema[]{ + // 默认和原始表单保持一致 如果流程中配置了权限数据,这里需要单独处理formSchema + return formSchema; +} diff --git a/src/views/openapi/OpenApiAuth.api.ts b/src/views/openapi/OpenApiAuth.api.ts new file mode 100644 index 0000000..c2792c9 --- /dev/null +++ b/src/views/openapi/OpenApiAuth.api.ts @@ -0,0 +1,122 @@ +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from "/@/hooks/web/useMessage"; + +const { createConfirm } = useMessage(); + +enum Api { + list = '/openapi/auth/list', + save='/openapi/auth/add', + edit='/openapi/auth/edit', + apiList= '/openapi/list', + genAKSK = '/openapi/auth/genAKSK', + permissionList='/openapi/permission/getOpenApi', + permissionAdd='/openapi/permission/add', + deleteOne = '/openapi/auth/delete', + deleteBatch = '/openapi/auth/deleteBatch', + importExcel = '/openapi/auth/importExcel', + exportXls = '/openapi/auth/exportXls', +} + +/** + * 获取API + * @param params + */ +export const apiList = Api.apiList; +/** + * 权限添加 + * @param params + */ +export const permissionAdd = Api.permissionAdd; +/** + * 生成AKSK + * @param params + */ +export const genAKSK = Api.genAKSK; + +/** + * 导出api + * @param params + */ +export const getExportUrl = Api.exportXls; + +/** + * 导入api + */ +export const getImportUrl = Api.importExcel; + +/** + * 列表接口 + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); + +/** + * 删除单个 + * @param params + * @param handleSuccess + */ +export const deleteOne = (params,handleSuccess) => { + return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => { + handleSuccess(); + }); +} + +/** + * 批量删除 + * @param params + * @param handleSuccess + */ +export const batchDelete = (params, handleSuccess) => { + createConfirm({ + iconType: 'warning', + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => { + handleSuccess(); + }); + } + }); +} + +/** + * 保存或者更新 + * @param params + * @param isUpdate + */ +export const saveOrUpdate = (params, isUpdate) => { + if (isUpdate) { + return defHttp.put({ url: Api.edit, params }, { isTransformResponse: false }); + } + return defHttp.post({ url: Api.save, params }, { isTransformResponse: false }); +} + +/** + * 全部权限列表接口 + * @param params + */ +export const getApiList = (params) => defHttp.get({ url: Api.apiList, params }, { isTransformResponse: false }); + +/** + * 获取已授权项目的接口 + * @param params + */ +export const getPermissionList = (params) => defHttp.get({ url: Api.permissionList, params }); +/** + * 授权保存方法 + * @param params + * @param isUpdate + */ +export const permissionAddFunction = (params) => { + return defHttp.post({ url: Api.permissionAdd, params }, { isTransformResponse: false }); +} +/** + * 授权保存方法 + * @param params + * @param isUpdate + */ +export const getGenAKSK = (params) => { + return defHttp.get({ url: Api.genAKSK, params }); +} diff --git a/src/views/openapi/OpenApiAuth.data.ts b/src/views/openapi/OpenApiAuth.data.ts new file mode 100644 index 0000000..e11945a --- /dev/null +++ b/src/views/openapi/OpenApiAuth.data.ts @@ -0,0 +1,84 @@ +import { BasicColumn } from '/@/components/Table'; +import { FormSchema } from '/@/components/Table'; + +//列表数据 +export const columns: BasicColumn[] = [ + { + title: '授权对象', + align: 'center', + dataIndex: 'name', + }, + { + title: '访问密钥(AK)', + align: 'center', + dataIndex: 'ak', + ellipsis: true, + }, + { + title: '创建人', + align: 'center', + dataIndex: 'createBy', + }, + { + title: '创建时间', + align: 'center', + dataIndex: 'createTime', + }, +]; + +//查询数据 +export const searchFormSchema: FormSchema[] = [ + { + label: '授权对象', + field: 'name', + component: 'JInput', + }, + { + label: '访问密钥', + field: 'ak', + component: 'JInput', + }, +]; + +//授权表单数据 +export const authFormSchema: FormSchema[] = [ + { + label: '授权对象', + field: 'name', + component: 'Input', + required: true, + }, + { + label: '', + field: 'ak', + component: 'Input', + show: false, + }, + { + label: '', + field: 'sk', + component: 'Input', + show: false, + }, + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '', + field: 'systemUserId', + component: 'Input', + show: false, + }, +]; + +// 高级查询数据 +export const superQuerySchema = { + name: { title: '授权对象', order: 0, view: 'text', type: 'string' }, + ak: { title: '访问密钥(AK)', order: 1, view: 'text', type: 'string' }, + sk: { title: '签名密钥(SK)', order: 2, view: 'text', type: 'string' }, + createBy: { title: '创建人', order: 3, view: 'text', type: 'string' }, + createTime: { title: '创建时间', order: 4, view: 'datetime', type: 'string' }, +}; diff --git a/src/views/openapi/OpenApiAuthList.vue b/src/views/openapi/OpenApiAuthList.vue new file mode 100644 index 0000000..85c26c7 --- /dev/null +++ b/src/views/openapi/OpenApiAuthList.vue @@ -0,0 +1,240 @@ + + + + + diff --git a/src/views/openapi/OpenApiList.vue b/src/views/openapi/OpenApiList.vue new file mode 100644 index 0000000..17d0ed4 --- /dev/null +++ b/src/views/openapi/OpenApiList.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/src/views/openapi/SwaggerUI.vue b/src/views/openapi/SwaggerUI.vue new file mode 100644 index 0000000..d99e2de --- /dev/null +++ b/src/views/openapi/SwaggerUI.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/src/views/openapi/components/AuthDrawer.vue b/src/views/openapi/components/AuthDrawer.vue new file mode 100644 index 0000000..4bbb2a9 --- /dev/null +++ b/src/views/openapi/components/AuthDrawer.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/src/views/openapi/components/AuthForm.vue b/src/views/openapi/components/AuthForm.vue new file mode 100644 index 0000000..fdbaeb1 --- /dev/null +++ b/src/views/openapi/components/AuthForm.vue @@ -0,0 +1,271 @@ + + + + + diff --git a/src/views/openapi/components/AuthModal.vue b/src/views/openapi/components/AuthModal.vue new file mode 100644 index 0000000..fefc81f --- /dev/null +++ b/src/views/openapi/components/AuthModal.vue @@ -0,0 +1,100 @@ + + + + + + diff --git a/src/views/openapi/components/OpenApiAuthDrawer.vue b/src/views/openapi/components/OpenApiAuthDrawer.vue new file mode 100644 index 0000000..8d99582 --- /dev/null +++ b/src/views/openapi/components/OpenApiAuthDrawer.vue @@ -0,0 +1,75 @@ + + + diff --git a/src/views/openapi/components/OpenApiAuthForm.vue b/src/views/openapi/components/OpenApiAuthForm.vue new file mode 100644 index 0000000..0291d43 --- /dev/null +++ b/src/views/openapi/components/OpenApiAuthForm.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/src/views/openapi/components/OpenApiAuthModal.vue b/src/views/openapi/components/OpenApiAuthModal.vue new file mode 100644 index 0000000..f81c1d9 --- /dev/null +++ b/src/views/openapi/components/OpenApiAuthModal.vue @@ -0,0 +1,77 @@ + + + + + + diff --git a/src/views/openapi/components/OpenApiDrawer.vue b/src/views/openapi/components/OpenApiDrawer.vue new file mode 100644 index 0000000..eea4630 --- /dev/null +++ b/src/views/openapi/components/OpenApiDrawer.vue @@ -0,0 +1,269 @@ + + + + + diff --git a/src/views/openapi/components/OpenApiModal.vue b/src/views/openapi/components/OpenApiModal.vue new file mode 100644 index 0000000..0e88e75 --- /dev/null +++ b/src/views/openapi/components/OpenApiModal.vue @@ -0,0 +1,177 @@ + + + + + diff --git a/src/views/openapi/subTables/OpenApiHeaderSubTable.vue b/src/views/openapi/subTables/OpenApiHeaderSubTable.vue new file mode 100644 index 0000000..5856754 --- /dev/null +++ b/src/views/openapi/subTables/OpenApiHeaderSubTable.vue @@ -0,0 +1,44 @@ + + + diff --git a/src/views/openapi/subTables/OpenApiParamSubTable.vue b/src/views/openapi/subTables/OpenApiParamSubTable.vue new file mode 100644 index 0000000..b7e4ad6 --- /dev/null +++ b/src/views/openapi/subTables/OpenApiParamSubTable.vue @@ -0,0 +1,44 @@ + + + diff --git a/src/views/report/chartdemo/chartdemo.data.ts b/src/views/report/chartdemo/chartdemo.data.ts new file mode 100644 index 0000000..23d3db5 --- /dev/null +++ b/src/views/report/chartdemo/chartdemo.data.ts @@ -0,0 +1,52 @@ +const colors = ['#4db6ac', '#ffb74d', '#64b5f6', '#e57373', '#9575cd', '#a1887f', '#90a4ae', '#4dd0e1', '#81c784', '#ff8a65']; +export const getData = (() => { + let dottedBase = +new Date(); + const barDataSource: any[] = []; + const barMultiData: any[] = []; + const barLineData: any[] = []; + const barLineColors: any[] = []; + + for (let i = 0; i < 20; i++) { + let obj = { name: '', value: 0 }; + const date = new Date((dottedBase += 1000 * 3600 * 24)); + obj.name = [date.getFullYear(), date.getMonth() + 1, date.getDate()].join('-'); + obj.value = Math.random() * 200; + barDataSource.push(obj); + } + + for (let j = 0; j < 2; j++) { + for (let i = 0; i < 20; i++) { + let obj = { name: '', value: 0, type: 2010 + j + '' }; + const date = new Date(dottedBase + 1000 * 3600 * 24 * i); + obj.name = [date.getFullYear(), date.getMonth() + 1, date.getDate()].join('-'); + obj.value = Math.random() * 200; + barMultiData.push(obj); + } + } + const pieData = [ + { value: 335, name: '客服电话' }, + { value: 310, name: '奥迪官网' }, + { value: 234, name: '媒体曝光' }, + { value: 135, name: '质检总局' }, + { value: 105, name: '其他' }, + ]; + const radarData = [ + { value: 75, name: '政治', type: '文综', max: 100 }, + { value: 65, name: '历史', type: '文综', max: 100 }, + { value: 55, name: '地理', type: '文综', max: 100 }, + { value: 74, name: '化学', type: '文综', max: 100 }, + { value: 38, name: '物理', type: '文综', max: 100 }, + { value: 88, name: '生物', type: '文综', max: 100 }, + ]; + for (let j = 0; j < 2; j++) { + for (let i = 0; i < 15; i++) { + let obj = { name: '', value: 0, type: 2010 + j + '', seriesType: j >= 1 ? 'line' : 'bar' }; + const date = new Date(dottedBase + 1000 * 3600 * 24 * i); + obj.name = [date.getFullYear(), date.getMonth() + 1, date.getDate()].join('-'); + obj.value = Math.random() * 200; + barLineData.push(obj); + } + barLineColors.push(colors[j]); + } + return { barDataSource, barMultiData, pieData, barLineData, barLineColors,radarData }; +})(); diff --git a/src/views/report/chartdemo/index.vue b/src/views/report/chartdemo/index.vue new file mode 100644 index 0000000..df304ef --- /dev/null +++ b/src/views/report/chartdemo/index.vue @@ -0,0 +1,93 @@ + + + diff --git a/src/views/report/statisticst/index.vue b/src/views/report/statisticst/index.vue new file mode 100644 index 0000000..673a5d6 --- /dev/null +++ b/src/views/report/statisticst/index.vue @@ -0,0 +1,135 @@ + + + diff --git a/src/views/super/airag/aiapp/AiApp.api.ts b/src/views/super/airag/aiapp/AiApp.api.ts new file mode 100644 index 0000000..4cd6b80 --- /dev/null +++ b/src/views/super/airag/aiapp/AiApp.api.ts @@ -0,0 +1,140 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +export enum Api { + //知识库管理 + list = '/airag/app/list', + save = '/airag/app/edit', + release = '/airag/app/release', + delete = '/airag/app/delete', + queryById = '/airag/app/queryById', + queryBathById = '/airag/knowledge/query/batch/byId', + queryKnowledgeById = '/airag/knowledge/queryById', + queryFlowById = '/airag/flow/queryById', + queryFlowByIds = '/airag/flow/list', + promptGenerate = '/airag/app/prompt/generate', + generateMemoryByAppId = '/airag/app/prompt/generateMemoryByAppId', +} + +/** + * 查询应用 + * @param params + */ +export const appList = (params) => { + return defHttp.get({ url: Api.list, params }, { isTransformResponse: false }); +}; + +/** + * 查询知识库 + * @param params + */ +export const queryKnowledgeBathById = (params) => { + return defHttp.get({ url: Api.queryBathById, params }, { isTransformResponse: false }); +}; + +/** + * 查询知识库(单条) + * @param params + */ +export const queryKnowledgeById = (params) => { + return defHttp.get({ url: Api.queryKnowledgeById, params }, { isTransformResponse: false }); +}; + +/** + * 根据应用id查询应用 + * @param params + */ +export const queryById = (params) => { + return defHttp.get({ url: Api.queryById, params }, { isTransformResponse: false }); +}; + +/** + * 新增应用 + * @param params + */ +export const saveApp = (params) => { + return defHttp.put({ url: Api.save, params }); +}; + +// 发布应用 +export function releaseApp(appId: string, release = false) { + return defHttp.post({ + url: Api.release, + params: { + id: appId, + release: release, + } + }, {joinParamsToUrl: true}); +} + +/** + * 删除应用 + * @param params + * @param handleSuccess + */ +export const deleteApp = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除名称为'+params.name+'的应用吗?', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.delete, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; + + +/** + * 根据应用id查询流程 + * @param params + */ +export const queryFlowById = (params) => { + return defHttp.get({ url: Api.queryFlowById, params }, { isTransformResponse: false }); +}; + +/** + * 根据应用id查询流程 + * @param params + */ +export const queryFlowByIds = (params) => { + return defHttp.get({ url: Api.queryFlowByIds, params }, { isTransformResponse: false }); +}; + +/** + * 应用编排 + * @param params + */ +export const promptGenerate = (params) => { + return defHttp.post( + { + url: Api.promptGenerate+'?prompt='+ params.prompt, + adapter: 'fetch', + responseType: 'stream', + timeout: 5 * 60 * 1000, + }, + { + isTransformResponse: false, + } + ); +}; + +/** + * 应用编排 + * @param params + */ +export const generateMemoryByAppId = (params) => { + return defHttp.post( + { + url: Api.generateMemoryByAppId+'?variables='+ params.variables + '&memoryId='+ params.memoryId, + adapter: 'fetch', + responseType: 'stream', + timeout: 60 * 60 * 1000, + }, + { + isTransformResponse: false, + } + ); +}; diff --git a/src/views/super/airag/aiapp/AiApp.data.ts b/src/views/super/airag/aiapp/AiApp.data.ts new file mode 100644 index 0000000..c4843f4 --- /dev/null +++ b/src/views/super/airag/aiapp/AiApp.data.ts @@ -0,0 +1,88 @@ +import { FormSchema } from '@/components/Form'; + +/** + * 表单 + */ +export const formSchema: FormSchema[] = [ + { + label: 'id', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '应用名称', + field: 'name', + required: true, + componentProps: { + //是否展示字数 + showCount: true, + maxlength: 64, + }, + component: 'Input', + }, + { + label: '应用描述', + field: 'descr', + component: 'InputTextArea', + componentProps: { + placeholder: '描述该应用的应用场景及用途', + rows: 4, + //是否展示字数 + showCount: true, + maxlength: 256, + }, + }, + { + label: '应用图标', + field: 'icon', + component: 'JImageUpload', + }, + { + label: '选择应用类型', + field: 'type', + component: 'Input', + show:({ values })=>{ + return !values.id; + }, + slot: 'typeSlot', + }, +]; + +/** + * 快捷指令表单 + */ +export const quickCommandFormSchema: FormSchema[] = [ + { + label: 'key', + field: 'key', + component: 'Input', + show: false, + }, + { + label: '按钮名称', + field: 'name', + required: true, + component: 'Input', + componentProps: { + showCount: true, + maxLength: 10, + }, + }, + { + label: '按钮图标', + field: 'icon', + component: 'IconPicker', + }, + { + label: '指令内容', + field: 'descr', + required: true, + component: 'InputTextArea', + componentProps: { + autosize: { minRows: 4, maxRows: 4 }, + showCount: true, + maxLength: 100, + } + }, +]; diff --git a/src/views/super/airag/aiapp/AiAppList.vue b/src/views/super/airag/aiapp/AiAppList.vue new file mode 100644 index 0000000..a20594b --- /dev/null +++ b/src/views/super/airag/aiapp/AiAppList.vue @@ -0,0 +1,600 @@ + + + + + + + diff --git a/src/views/super/airag/aiapp/chat/AiChat.vue b/src/views/super/airag/aiapp/chat/AiChat.vue new file mode 100644 index 0000000..c48a838 --- /dev/null +++ b/src/views/super/airag/aiapp/chat/AiChat.vue @@ -0,0 +1,551 @@ + + + + + diff --git a/src/views/super/airag/aiapp/chat/AiChatIcon.vue b/src/views/super/airag/aiapp/chat/AiChatIcon.vue new file mode 100644 index 0000000..d71047e --- /dev/null +++ b/src/views/super/airag/aiapp/chat/AiChatIcon.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/src/views/super/airag/aiapp/chat/ThinkText.vue b/src/views/super/airag/aiapp/chat/ThinkText.vue new file mode 100644 index 0000000..e57c937 --- /dev/null +++ b/src/views/super/airag/aiapp/chat/ThinkText.vue @@ -0,0 +1,326 @@ + + + + diff --git a/src/views/super/airag/aiapp/chat/chat.vue b/src/views/super/airag/aiapp/chat/chat.vue new file mode 100644 index 0000000..e3716a0 --- /dev/null +++ b/src/views/super/airag/aiapp/chat/chat.vue @@ -0,0 +1,1715 @@ + + + + + + diff --git a/src/views/super/airag/aiapp/chat/chatMessage.vue b/src/views/super/airag/aiapp/chat/chatMessage.vue new file mode 100644 index 0000000..c423442 --- /dev/null +++ b/src/views/super/airag/aiapp/chat/chatMessage.vue @@ -0,0 +1,436 @@ + + + + + diff --git a/src/views/super/airag/aiapp/chat/chatText.vue b/src/views/super/airag/aiapp/chat/chatText.vue new file mode 100644 index 0000000..9510d4f --- /dev/null +++ b/src/views/super/airag/aiapp/chat/chatText.vue @@ -0,0 +1,466 @@ + + + + diff --git a/src/views/super/airag/aiapp/chat/components/CardTemplate.vue b/src/views/super/airag/aiapp/chat/components/CardTemplate.vue new file mode 100644 index 0000000..2416e5c --- /dev/null +++ b/src/views/super/airag/aiapp/chat/components/CardTemplate.vue @@ -0,0 +1,308 @@ + + + + + + diff --git a/src/views/super/airag/aiapp/chat/components/ConversationSettingsModal.vue b/src/views/super/airag/aiapp/chat/components/ConversationSettingsModal.vue new file mode 100644 index 0000000..cc3aba6 --- /dev/null +++ b/src/views/super/airag/aiapp/chat/components/ConversationSettingsModal.vue @@ -0,0 +1,382 @@ + + + + + + diff --git a/src/views/super/airag/aiapp/chat/components/ImageViewer.vue b/src/views/super/airag/aiapp/chat/components/ImageViewer.vue new file mode 100644 index 0000000..b089f34 --- /dev/null +++ b/src/views/super/airag/aiapp/chat/components/ImageViewer.vue @@ -0,0 +1,71 @@ + + + + + + diff --git a/src/views/super/airag/aiapp/chat/hooks/useChat.ts b/src/views/super/airag/aiapp/chat/hooks/useChat.ts new file mode 100644 index 0000000..d6a2369 --- /dev/null +++ b/src/views/super/airag/aiapp/chat/hooks/useChat.ts @@ -0,0 +1,28 @@ +import { useChatStore } from '@/store'; + +export function useChat() { + const chatStore = useChatStore(); + + const getChatByUuidAndIndex = (uuid: number, index: number) => { + return chatStore.getChatByUuidAndIndex(uuid, index); + }; + + const addChat = (uuid: number, chat: Chat.Chat) => { + chatStore.addChatByUuid(uuid, chat); + }; + + const updateChat = (uuid: number, index: number, chat: Chat.Chat) => { + chatStore.updateChatByUuid(uuid, index, chat); + }; + + const updateChatSome = (uuid: number, index: number, chat: Partial) => { + chatStore.updateChatSomeByUuid(uuid, index, chat); + }; + + return { + addChat, + updateChat, + updateChatSome, + getChatByUuidAndIndex, + }; +} diff --git a/src/views/super/airag/aiapp/chat/hooks/useScroll.ts b/src/views/super/airag/aiapp/chat/hooks/useScroll.ts new file mode 100644 index 0000000..c71b6bb --- /dev/null +++ b/src/views/super/airag/aiapp/chat/hooks/useScroll.ts @@ -0,0 +1,41 @@ +import type { Ref } from 'vue'; +import { nextTick, ref } from 'vue'; + +type ScrollElement = HTMLDivElement | null; + +interface ScrollReturn { + scrollRef: Ref; + scrollToBottom: () => Promise; + scrollToTop: () => Promise; + scrollToBottomIfAtBottom: () => Promise; +} + +export function useScroll(): ScrollReturn { + const scrollRef = ref(null); + + const scrollToBottom = async () => { + await nextTick(); + if (scrollRef.value) scrollRef.value.scrollTop = scrollRef.value.scrollHeight; + }; + + const scrollToTop = async () => { + await nextTick(); + if (scrollRef.value) scrollRef.value.scrollTop = 0; + }; + + const scrollToBottomIfAtBottom = async () => { + await nextTick(); + if (scrollRef.value) { + const threshold = 100; // Threshold, indicating the distance threshold to the bottom of the scroll bar. + const distanceToBottom = scrollRef.value.scrollHeight - scrollRef.value.scrollTop - scrollRef.value.clientHeight; + if (distanceToBottom <= threshold) scrollRef.value.scrollTop = scrollRef.value.scrollHeight; + } + }; + + return { + scrollRef, + scrollToBottom, + scrollToTop, + scrollToBottomIfAtBottom, + }; +} diff --git a/src/views/super/airag/aiapp/chat/jeecg-tags/index.ts b/src/views/super/airag/aiapp/chat/jeecg-tags/index.ts new file mode 100644 index 0000000..1090f50 --- /dev/null +++ b/src/views/super/airag/aiapp/chat/jeecg-tags/index.ts @@ -0,0 +1,71 @@ +import type { JeecgTag } from './types'; +import { shallowRef } from 'vue'; + +import ToolExecTag from './tool-exec'; +import JeecgChart from "./jeecg-chart"; + +export const jeecgTagMap: Map = new Map(); +// 所有 jeecg 标签名称列表 +export const tagNames: string[] = []; + +// 注册 工具调用 标签 +useJeecgTag(ToolExecTag); +// 注册 图表渲染 标签 +useJeecgTag(JeecgChart); + +// jeecg 标签统一的 class 名称 +export const JEECG_TAG_CLASS = 'jeecg-tag'; + +/** + * 忽略 jeecg 自定义标签的解析 + * @param md + */ +export function mdPluginJeecgTag(md: any) { + // 保存原始的 html_block 渲染规则 + const htmlBlockOrigin = + md.renderer.rules.html_block || + function (tokens, idx) { + return tokens[idx].content; + }; + + // 覆盖 html_block 渲染规则 + md.renderer.rules.html_block = function (tokens, idx) { + const token = tokens[idx]; + const content = token.content; + + let isJeecgTag = false; + let tagName = ''; + + for (const name of tagNames) { + // 检查内容是否包含自定义标签的起始或结束标签 + const startTag = new RegExp(`<${name}(\\s|>)`, 'i'); + const endTag = new RegExp(``, 'i'); + if (startTag.test(content) || endTag.test(content)) { + isJeecgTag = true; + tagName = name; + break; + } + } + + // jeecg 自定义标签 + if (isJeecgTag) { + const box = document.createElement('div'); + box.innerHTML = content; + const tag = box.firstElementChild!; + tag.classList.add(JEECG_TAG_CLASS); + return tag.outerHTML; + } + + // 其他 HTML 标签按默认方式渲染 + return htmlBlockOrigin(tokens, idx); + }; +} + +export function useJeecgTag(tag: JeecgTag) { + if (jeecgTagMap.has(tag.name)) { + return; + } + tag.component = shallowRef(tag.component); + jeecgTagMap.set(tag.name, tag); + tagNames.push(tag.name); +} diff --git a/src/views/super/airag/aiapp/chat/jeecg-tags/jeecg-chart/ChartRender.api.ts b/src/views/super/airag/aiapp/chat/jeecg-tags/jeecg-chart/ChartRender.api.ts new file mode 100644 index 0000000..cf6c0ea --- /dev/null +++ b/src/views/super/airag/aiapp/chat/jeecg-tags/jeecg-chart/ChartRender.api.ts @@ -0,0 +1,43 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + sqlPageExecute = '/airag/mcp/database/sqlPageExecute', + sqlExportXls = '/airag/mcp/database/sqlExportXls', +} + +/** + * 分页执行 SQL 查询 + */ +export function sqlPageExecute(params: { sql: string; dbSource?: string; pageNo: number; pageSize: number }) { + return defHttp.post( + { + url: Api.sqlPageExecute, + params: { + sql: params.sql, + dbSourceKey: params.dbSource || '', + pageNo: params.pageNo, + pageSize: params.pageSize, + }, + }, + { isTransformResponse: false } + ); +} + +/** + * 导出图表原始数据为 Excel + */ +export function sqlExportXls(params: { sql: string; dbSource?: string; columns?: Recordable }) { + return defHttp.post( + { + url: Api.sqlExportXls, + params: { + sql: params.sql, + dbSourceKey: params.dbSource || '', + columns: params.columns || {}, + }, + responseType: 'blob', + timeout: 5 * 60 * 1000, + }, + { isTransformResponse: false, isReturnNativeResponse: true } + ); +} diff --git a/src/views/super/airag/aiapp/chat/jeecg-tags/jeecg-chart/ChartRender.vue b/src/views/super/airag/aiapp/chat/jeecg-tags/jeecg-chart/ChartRender.vue new file mode 100644 index 0000000..f843079 --- /dev/null +++ b/src/views/super/airag/aiapp/chat/jeecg-tags/jeecg-chart/ChartRender.vue @@ -0,0 +1,642 @@ + + + + + diff --git a/src/views/super/airag/aiapp/chat/jeecg-tags/jeecg-chart/index.ts b/src/views/super/airag/aiapp/chat/jeecg-tags/jeecg-chart/index.ts new file mode 100644 index 0000000..297fc19 --- /dev/null +++ b/src/views/super/airag/aiapp/chat/jeecg-tags/jeecg-chart/index.ts @@ -0,0 +1,10 @@ +import { JeecgTag } from '../types'; + +import ChartRender from './ChartRender.vue'; + +const Tag: JeecgTag = { + name: 'jeecg-chart', + component: ChartRender, +}; + +export default Tag; diff --git a/src/views/super/airag/aiapp/chat/jeecg-tags/jeecg-chart/types.ts b/src/views/super/airag/aiapp/chat/jeecg-tags/jeecg-chart/types.ts new file mode 100644 index 0000000..ec35e6f --- /dev/null +++ b/src/views/super/airag/aiapp/chat/jeecg-tags/jeecg-chart/types.ts @@ -0,0 +1,4 @@ +/** + * 支持的图表类型 + */ +export type ChartType = 'bar' | 'line' | 'pie' | 'radar' | 'gauge' | 'barline' | 'multibar' | 'multiline' | 'area' | ''; diff --git a/src/views/super/airag/aiapp/chat/jeecg-tags/tool-exec/JeecgToolExec.vue b/src/views/super/airag/aiapp/chat/jeecg-tags/tool-exec/JeecgToolExec.vue new file mode 100644 index 0000000..5e8f24d --- /dev/null +++ b/src/views/super/airag/aiapp/chat/jeecg-tags/tool-exec/JeecgToolExec.vue @@ -0,0 +1,395 @@ + + + + + diff --git a/src/views/super/airag/aiapp/chat/jeecg-tags/tool-exec/index.ts b/src/views/super/airag/aiapp/chat/jeecg-tags/tool-exec/index.ts new file mode 100644 index 0000000..e07907e --- /dev/null +++ b/src/views/super/airag/aiapp/chat/jeecg-tags/tool-exec/index.ts @@ -0,0 +1,10 @@ +import { JeecgTag } from '../types'; + +import JeecgToolExec from './JeecgToolExec.vue'; + +const Tag: JeecgTag = { + name: 'jeecg-tool-exec', + component: JeecgToolExec, +}; + +export default Tag; diff --git a/src/views/super/airag/aiapp/chat/jeecg-tags/types.ts b/src/views/super/airag/aiapp/chat/jeecg-tags/types.ts new file mode 100644 index 0000000..0b6e96a --- /dev/null +++ b/src/views/super/airag/aiapp/chat/jeecg-tags/types.ts @@ -0,0 +1,9 @@ +import { Component } from 'vue'; + +/** + * JeecgTag类型 + */ +export type JeecgTag = { + name: string; + component: Component; +}; diff --git a/src/views/super/airag/aiapp/chat/js/chat.js b/src/views/super/airag/aiapp/chat/js/chat.js new file mode 100644 index 0000000..d9fa1de --- /dev/null +++ b/src/views/super/airag/aiapp/chat/js/chat.js @@ -0,0 +1,188 @@ +// iframe-widget.js +(function () { + let widgetInstance = null; + const defaultConfig = { + // 支持'top-left'左上, 'top-right'右上, 'bottom-left'左下, 'bottom-right'右下 + iconPosition: 'bottom-right', + //图标的大小 + iconSize: '45px', + //图标的颜色 + iconColor: '#155eef', + //必填不允许修改 + appId: '', + //聊天弹窗的宽度 + chatWidth: '800px', + //聊天弹窗的高度 + chatHeight: '700px', + }; + + /** + * 创建ai图标 + * @param config + */ + function createAiChat(config) { + // 单例模式,确保只存在一个实例 + if (widgetInstance) { + return; + } + + // 合并配置 + const finalConfig = { ...defaultConfig, ...config }; + + if (!finalConfig.appId) { + console.error('appId为空!'); + return; + } + let body = document.body; + body.style.margin = "0"; + // 创建容器 + const container = document.createElement('div'); + container.style.cssText = ` + position: fixed; + z-index: 998; + ${getPositionStyles(finalConfig.iconPosition)} + cursor: pointer; + `; + // 创建图标 + const icon = document.createElement('div'); + icon.style.cssText = ` + width: ${finalConfig.iconSize}; + height: ${finalConfig.iconSize}; + background-color: ${finalConfig.iconColor}; + border-radius: 50%; + box-shadow: #cccccc 0 4px 8px 0; + padding: 10px; + display: flex; + align-items: center; + justify-content: center; + color: white; + box-sizing: border-box; + `; + icon.innerHTML = + ''; + + // 创建iframe容器 + const iframeContainer = document.createElement('div'); + let right = finalConfig.chatWidth === '100%' ? '0' : '10px'; + let bottom = finalConfig.chatHeight === '100%' ? '0' : '10px'; + let chatWidth = finalConfig.chatWidth; + let chatHeight = finalConfig.chatHeight; + if(isMobileDevice()){ + chatWidth = "100%"; + chatHeight = "100%"; + right = '0'; + bottom = '0'; + } + iframeContainer.style.cssText = ` + position: fixed; + right: ${right}; + bottom: ${bottom}; + width: ${chatWidth} !important; + height: ${chatHeight} !important; + background: white; + border-radius: 8px; + box-shadow: 0 0 20px #cccccc; + display: none; + z-index: 10000; + `; + + // 创建iframe + const iframe = document.createElement('iframe'); + iframe.style.cssText = ` + width: 100%; + height: 100%; + border: none; + border-radius: 8px; + `; + + iframe.id = 'ai-app-chat-document'; + //update-begin---author:wangshuai---date:2025-04-25---for:【QQYUN-12159】【AI 广告位】让需要自建AI知识库的用户知道如何通过敲敲云搭建自己的AI知识库--- + iframe.src = getIframeSrc(finalConfig) + '/ai/app/chat/' + finalConfig.appId + "?source=chatJs"; + //update-end---author:wangshuai---date:2025-04-25---for:【QQYUN-12159】【AI 广告位】让需要自建AI知识库的用户知道如何通过敲敲云搭建自己的AI知识库--- + let iconRight = finalConfig.chatWidth === '100%'?'0':'-6px'; + let iconTop = finalConfig.chatWidth === '100%'?'0':'-9px'; + if(isMobileDevice()){ + iconRight = '2px'; + iconTop = '2px'; + } + // 创建关闭按钮 + const closeBtn = document.createElement('div'); + closeBtn.innerHTML = + ''; + closeBtn.style.cssText = ` + position: absolute; + margin-top: ${iconTop}; + right: ${iconRight}; + cursor: pointer; + background: white; + width: 25px; + height: 25px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 5px #cccccc; + `; + + // 组装元素 + iframeContainer.appendChild(closeBtn); + iframeContainer.appendChild(iframe); + document.body.appendChild(iframeContainer); + container.appendChild(icon); + document.body.appendChild(container); + + // 事件监听 + icon.addEventListener('click', () => { + iframeContainer.style.display = 'block'; + }); + + closeBtn.addEventListener('click', () => { + iframeContainer.style.display = 'none'; + }); + + // 保存实例引用 + widgetInstance = { + remove: () => { + container.remove(); + iframeContainer.remove(); + }, + }; + } + + /** + * 获取位置信息 + * + * @param position + * @returns {*|string} + */ + function getPositionStyles(position) { + const positions = { + 'top-left': 'top: 20px; left: 20px;', + 'top-right': 'top: 20px; right: 20px;', + 'bottom-left': 'bottom: 20px; left: 20px;', + 'bottom-right': 'bottom: 20px; right: 20px;', + }; + return positions[position] || positions['bottom-right']; + } + + /** + * 获取src地址 + */ + function getIframeSrc(finalConfig) { + const specificScript = document.getElementById("e7e007dd52f67fe36365eff636bbffbd"); + if (specificScript) { + return specificScript.src.substring(0, specificScript.src.indexOf('/', specificScript.src.indexOf('://') + 3)); + } + } + + /** + * 判断是否为手机 + * @returns {boolean} + */ + function isMobileDevice() { + return /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); + } + + // 暴露全局方法 + window.createAiChat = createAiChat; +})(); diff --git a/src/views/super/airag/aiapp/chat/js/useScroll.ts b/src/views/super/airag/aiapp/chat/js/useScroll.ts new file mode 100644 index 0000000..c71b6bb --- /dev/null +++ b/src/views/super/airag/aiapp/chat/js/useScroll.ts @@ -0,0 +1,41 @@ +import type { Ref } from 'vue'; +import { nextTick, ref } from 'vue'; + +type ScrollElement = HTMLDivElement | null; + +interface ScrollReturn { + scrollRef: Ref; + scrollToBottom: () => Promise; + scrollToTop: () => Promise; + scrollToBottomIfAtBottom: () => Promise; +} + +export function useScroll(): ScrollReturn { + const scrollRef = ref(null); + + const scrollToBottom = async () => { + await nextTick(); + if (scrollRef.value) scrollRef.value.scrollTop = scrollRef.value.scrollHeight; + }; + + const scrollToTop = async () => { + await nextTick(); + if (scrollRef.value) scrollRef.value.scrollTop = 0; + }; + + const scrollToBottomIfAtBottom = async () => { + await nextTick(); + if (scrollRef.value) { + const threshold = 100; // Threshold, indicating the distance threshold to the bottom of the scroll bar. + const distanceToBottom = scrollRef.value.scrollHeight - scrollRef.value.scrollTop - scrollRef.value.clientHeight; + if (distanceToBottom <= threshold) scrollRef.value.scrollTop = scrollRef.value.scrollHeight; + } + }; + + return { + scrollRef, + scrollToBottom, + scrollToTop, + scrollToBottomIfAtBottom, + }; +} diff --git a/src/views/super/airag/aiapp/chat/portal/AppPortal.vue b/src/views/super/airag/aiapp/chat/portal/AppPortal.vue new file mode 100644 index 0000000..1caa2e1 --- /dev/null +++ b/src/views/super/airag/aiapp/chat/portal/AppPortal.vue @@ -0,0 +1,297 @@ + + + + + + diff --git a/src/views/super/airag/aiapp/chat/portal/LeftPortalSession.vue b/src/views/super/airag/aiapp/chat/portal/LeftPortalSession.vue new file mode 100644 index 0000000..abb5077 --- /dev/null +++ b/src/views/super/airag/aiapp/chat/portal/LeftPortalSession.vue @@ -0,0 +1,485 @@ + + + + + + diff --git a/src/views/super/airag/aiapp/chat/presetQuestion.vue b/src/views/super/airag/aiapp/chat/presetQuestion.vue new file mode 100644 index 0000000..d6aedda --- /dev/null +++ b/src/views/super/airag/aiapp/chat/presetQuestion.vue @@ -0,0 +1,180 @@ + + + + + diff --git a/src/views/super/airag/aiapp/chat/route/register.ts b/src/views/super/airag/aiapp/chat/route/register.ts new file mode 100644 index 0000000..4b9581f --- /dev/null +++ b/src/views/super/airag/aiapp/chat/route/register.ts @@ -0,0 +1,74 @@ +import type { App } from 'vue'; +import { router } from "/@/router"; +import type { RouteRecordRaw } from "vue-router"; +import { LAYOUT } from "@/router/constant"; + +const ChatRoutes: RouteRecordRaw[] = [ + { + path: "/ai/app/chat/:appId", + name: "ai-chat-@appId-@modeType", + component: () => import("/@/views/super/airag/aiapp/chat/AiChat.vue"), + meta: { + title: 'AI聊天', + ignoreAuth: true, + }, + }, + { + path: "/ai/app/chatIcon/:appId", + name: "ai-chatIcon-@appId", + component: () => import("/@/views/super/airag/aiapp/chat/AiChatIcon.vue"), + meta: { + title: 'AI聊天', + ignoreAuth: true, + }, + }, + { + path: '/ai/chat', + name: 'aiChat', + component: LAYOUT, + meta: { + title: 'ai聊天', + }, + children: [ + { + path: "/ai/chat/:appId", + name: "ai-chat-@appId", + component: () => import("/@/views/super/airag/aiapp/chat/AiChat.vue"), + meta: { + title:'AI助手', + ignoreAuth: false, + }, + }, + { + path: "/ai/chat", + name: "ai-chat", + component: () => import("/@/views/super/airag/aiapp/chat/AiChat.vue"), + meta: { + title:'AI助手', + ignoreAuth: false, + }, + } + ], + }, + { + path: '/ai/chat/portal', + name: 'ai-chat-portal', + component: () => import('/@/views/super/airag/aiapp/chat/portal/AppPortal.vue'), + meta: { + title: 'AI聊天', + ignoreAuth: false, + }, + }, +] + +/** 注册路由 */ +export async function register(app: App) { + await registerMyAppRouter(app); + console.log('[聊天路由] 注册完成!'); +} + +async function registerMyAppRouter(_: App) { + for(let appRoute of ChatRoutes){ + await router.addRoute(appRoute); + } +} diff --git a/src/views/super/airag/aiapp/chat/slide.vue b/src/views/super/airag/aiapp/chat/slide.vue new file mode 100644 index 0000000..958b595 --- /dev/null +++ b/src/views/super/airag/aiapp/chat/slide.vue @@ -0,0 +1,338 @@ + + + + + diff --git a/src/views/super/airag/aiapp/chat/style/github-markdown.less b/src/views/super/airag/aiapp/chat/style/github-markdown.less new file mode 100644 index 0000000..c6091ab --- /dev/null +++ b/src/views/super/airag/aiapp/chat/style/github-markdown.less @@ -0,0 +1,1123 @@ +html.dark { + .markdown-body { + color-scheme: dark; + --color-prettylights-syntax-comment: #8b949e; + --color-prettylights-syntax-constant: #79c0ff; + --color-prettylights-syntax-entity: #d2a8ff; + --color-prettylights-syntax-storage-modifier-import: #c9d1d9; + --color-prettylights-syntax-entity-tag: #7ee787; + --color-prettylights-syntax-keyword: #ff7b72; + --color-prettylights-syntax-string: #a5d6ff; + --color-prettylights-syntax-variable: #ffa657; + --color-prettylights-syntax-brackethighlighter-unmatched: #f85149; + --color-prettylights-syntax-invalid-illegal-text: #f0f6fc; + --color-prettylights-syntax-invalid-illegal-bg: #8e1519; + --color-prettylights-syntax-carriage-return-text: #f0f6fc; + --color-prettylights-syntax-carriage-return-bg: #b62324; + --color-prettylights-syntax-string-regexp: #7ee787; + --color-prettylights-syntax-markup-list: #f2cc60; + --color-prettylights-syntax-markup-heading: #1f6feb; + --color-prettylights-syntax-markup-italic: #c9d1d9; + --color-prettylights-syntax-markup-bold: #c9d1d9; + --color-prettylights-syntax-markup-deleted-text: #ffdcd7; + --color-prettylights-syntax-markup-deleted-bg: #67060c; + --color-prettylights-syntax-markup-inserted-text: #aff5b4; + --color-prettylights-syntax-markup-inserted-bg: #033a16; + --color-prettylights-syntax-markup-changed-text: #ffdfb6; + --color-prettylights-syntax-markup-changed-bg: #5a1e02; + --color-prettylights-syntax-markup-ignored-text: #c9d1d9; + --color-prettylights-syntax-markup-ignored-bg: #1158c7; + --color-prettylights-syntax-meta-diff-range: #d2a8ff; + --color-prettylights-syntax-brackethighlighter-angle: #8b949e; + --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58; + --color-prettylights-syntax-constant-other-reference-link: #a5d6ff; + --color-fg-default: #c9d1d9; + --color-fg-muted: #8b949e; + --color-fg-subtle: #6e7681; + --color-canvas-default: #0d1117; + --color-canvas-subtle: #161b22; + --color-border-default: #30363d; + --color-border-muted: #21262d; + --color-neutral-muted: #bfc4cc; + --color-accent-fg: #58a6ff; + --color-accent-emphasis: #1f6feb; + --color-attention-subtle: #ece6d9; + --color-danger-fg: #f85149; + } +} + +html { + .markdown-body { + color-scheme: light; + --color-prettylights-syntax-comment: #6e7781; + --color-prettylights-syntax-constant: #0550ae; + --color-prettylights-syntax-entity: #8250df; + --color-prettylights-syntax-storage-modifier-import: #24292f; + --color-prettylights-syntax-entity-tag: #116329; + --color-prettylights-syntax-keyword: #cf222e; + --color-prettylights-syntax-string: #0a3069; + --color-prettylights-syntax-variable: #953800; + --color-prettylights-syntax-brackethighlighter-unmatched: #82071e; + --color-prettylights-syntax-invalid-illegal-text: #f6f8fa; + --color-prettylights-syntax-invalid-illegal-bg: #82071e; + --color-prettylights-syntax-carriage-return-text: #f6f8fa; + --color-prettylights-syntax-carriage-return-bg: #cf222e; + --color-prettylights-syntax-string-regexp: #116329; + --color-prettylights-syntax-markup-list: #3b2300; + --color-prettylights-syntax-markup-heading: #0550ae; + --color-prettylights-syntax-markup-italic: #24292f; + --color-prettylights-syntax-markup-bold: #24292f; + --color-prettylights-syntax-markup-deleted-text: #82071e; + --color-prettylights-syntax-markup-deleted-bg: #ffebe9; + --color-prettylights-syntax-markup-inserted-text: #116329; + --color-prettylights-syntax-markup-inserted-bg: #dafbe1; + --color-prettylights-syntax-markup-changed-text: #953800; + --color-prettylights-syntax-markup-changed-bg: #ffd8b5; + --color-prettylights-syntax-markup-ignored-text: #eaeef2; + --color-prettylights-syntax-markup-ignored-bg: #0550ae; + --color-prettylights-syntax-meta-diff-range: #8250df; + --color-prettylights-syntax-brackethighlighter-angle: #57606a; + --color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f; + --color-prettylights-syntax-constant-other-reference-link: #0a3069; + --color-fg-default: #24292f; + --color-fg-muted: #57606a; + --color-fg-subtle: #6e7781; + --color-canvas-default: #ffffff; + --color-canvas-subtle: #f6f8fa; + --color-border-default: #d0d7de; + --color-border-muted: hsla(210, 18%, 87%, 1); + --color-neutral-muted: #e7ebf2; + --color-accent-fg: #0969da; + --color-accent-emphasis: #0969da; + --color-attention-subtle: #fff8c5; + --color-danger-fg: #cf222e; + } +} + +.markdown-body { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + margin: 0; + color: var(--color-fg-default); + background-color: var(--color-canvas-default); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; +} + +.markdown-body .octicon { + display: inline-block; + fill: currentColor; + vertical-align: text-bottom; +} + +.markdown-body h1:hover .anchor .octicon-link:before, +.markdown-body h2:hover .anchor .octicon-link:before, +.markdown-body h3:hover .anchor .octicon-link:before, +.markdown-body h4:hover .anchor .octicon-link:before, +.markdown-body h5:hover .anchor .octicon-link:before, +.markdown-body h6:hover .anchor .octicon-link:before { + width: 16px; + height: 16px; + content: ' '; + display: inline-block; + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,"); + mask-image: url("data:image/svg+xml,"); +} + +.markdown-body details, +.markdown-body figcaption, +.markdown-body figure { + display: block; +} + +.markdown-body summary { + display: list-item; +} + +.markdown-body [hidden] { + display: none !important; +} + +.markdown-body a { + background-color: transparent; + color: var(--color-accent-fg); + text-decoration: none; +} + +.markdown-body abbr[title] { + border-bottom: none; + text-decoration: underline dotted; +} + +.markdown-body b, +.markdown-body strong { + font-weight: var(--base-text-weight-semibold, 600); +} + +.markdown-body dfn { + font-style: italic; +} + +.markdown-body h1 { + margin: 0.67em 0; + font-weight: var(--base-text-weight-semibold, 600); + padding-bottom: 0.3em; + font-size: 2em; + border-bottom: 1px solid var(--color-border-muted); +} + +.markdown-body mark { + background-color: var(--color-attention-subtle); + color: var(--color-fg-default); +} + +.markdown-body small { + font-size: 90%; +} + +.markdown-body sub, +.markdown-body sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +.markdown-body sub { + bottom: -0.25em; +} + +.markdown-body sup { + top: -0.5em; +} + +.markdown-body img { + border-style: none; + max-width: 100%; + box-sizing: content-box; + background-color: var(--color-canvas-default); +} + +.markdown-body code, +.markdown-body kbd, +.markdown-body pre, +.markdown-body samp { + font-family: monospace; + font-size: 1em; +} + +.markdown-body figure { + margin: 1em 40px; +} + +.markdown-body hr { + box-sizing: content-box; + overflow: hidden; + background: transparent; + border-bottom: 1px solid var(--color-border-muted); + height: 0.25em; + padding: 0; + margin: 24px 0; + background-color: var(--color-border-default); + border: 0; +} + +.markdown-body input { + font: inherit; + margin: 0; + overflow: visible; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.markdown-body [type='button'], +.markdown-body [type='reset'], +.markdown-body [type='submit'] { + -webkit-appearance: button; +} + +.markdown-body [type='checkbox'], +.markdown-body [type='radio'] { + box-sizing: border-box; + padding: 0; +} + +.markdown-body [type='number']::-webkit-inner-spin-button, +.markdown-body [type='number']::-webkit-outer-spin-button { + height: auto; +} + +.markdown-body [type='search']::-webkit-search-cancel-button, +.markdown-body [type='search']::-webkit-search-decoration { + -webkit-appearance: none; +} + +.markdown-body ::-webkit-input-placeholder { + color: inherit; + opacity: 0.54; +} + +.markdown-body ::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit; +} + +.markdown-body a:hover { + text-decoration: underline; +} + +.markdown-body ::placeholder { + color: var(--color-fg-subtle); + opacity: 1; +} + +.markdown-body hr::before { + display: table; + content: ''; +} + +.markdown-body hr::after { + display: table; + clear: both; + content: ''; +} + +.markdown-body table { + border-spacing: 0; + border-collapse: collapse; + display: block; + width: max-content; + max-width: 100%; + overflow: auto; +} + +.markdown-body td, +.markdown-body th { + padding: 0; +} + +.markdown-body details summary { + cursor: pointer; +} + +.markdown-body details:not([open]) > *:not(summary) { + display: none !important; +} + +.markdown-body a:focus, +.markdown-body [role='button']:focus, +.markdown-body input[type='radio']:focus, +.markdown-body input[type='checkbox']:focus { + outline: 2px solid var(--color-accent-fg); + outline-offset: -2px; + box-shadow: none; +} + +.markdown-body a:focus:not(:focus-visible), +.markdown-body [role='button']:focus:not(:focus-visible), +.markdown-body input[type='radio']:focus:not(:focus-visible), +.markdown-body input[type='checkbox']:focus:not(:focus-visible) { + outline: solid 1px transparent; +} + +.markdown-body a:focus-visible, +.markdown-body [role='button']:focus-visible, +.markdown-body input[type='radio']:focus-visible, +.markdown-body input[type='checkbox']:focus-visible { + outline: 2px solid var(--color-accent-fg); + outline-offset: -2px; + box-shadow: none; +} + +.markdown-body a:not([class]):focus, +.markdown-body a:not([class]):focus-visible, +.markdown-body input[type='radio']:focus, +.markdown-body input[type='radio']:focus-visible, +.markdown-body input[type='checkbox']:focus, +.markdown-body input[type='checkbox']:focus-visible { + outline-offset: 0; +} + +.markdown-body kbd { + display: inline-block; + padding: 3px 5px; + font: + 11px ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + line-height: 10px; + color: var(--color-fg-default); + vertical-align: middle; + background-color: var(--color-canvas-subtle); + border: solid 1px var(--color-neutral-muted); + border-bottom-color: var(--color-neutral-muted); + border-radius: 6px; + box-shadow: inset 0 -1px 0 var(--color-neutral-muted); +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: var(--base-text-weight-semibold, 600); + line-height: 1.25; +} + +.markdown-body h2 { + font-weight: var(--base-text-weight-semibold, 600); + padding-bottom: 0.3em; + font-size: 1.5em; + border-bottom: 1px solid var(--color-border-muted); +} + +.markdown-body h3 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: 1.25em; +} + +.markdown-body h4 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: 1em; +} + +.markdown-body h5 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: 0.875em; +} + +.markdown-body h6 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: 0.85em; + color: var(--color-fg-muted); +} + +.markdown-body p { + margin-top: 0; + margin-bottom: 10px; +} + +.markdown-body blockquote { + margin: 0; + padding: 0 1em; + color: var(--color-fg-muted); + border-left: 0.25em solid var(--color-border-default); +} + +.markdown-body ul, +.markdown-body ol { + margin-top: 0; + margin-bottom: 0; + padding-left: 2em; +} + +.markdown-body ol ol, +.markdown-body ul ol { + list-style-type: lower-roman; +} + +.markdown-body ul ul ol, +.markdown-body ul ol ol, +.markdown-body ol ul ol, +.markdown-body ol ol ol { + list-style-type: lower-alpha; +} + +.markdown-body dd { + margin-left: 0; +} + +.markdown-body tt, +.markdown-body code, +.markdown-body samp { + font-family: + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + font-size: 12px; +} + +.markdown-body pre { + margin-top: 0; + margin-bottom: 0; + font-family: + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + font-size: 12px; + word-wrap: normal; +} + +.markdown-body .octicon { + display: inline-block; + overflow: visible !important; + vertical-align: text-bottom; + fill: currentColor; +} + +.markdown-body input::-webkit-outer-spin-button, +.markdown-body input::-webkit-inner-spin-button { + margin: 0; + -webkit-appearance: none; + appearance: none; +} + +.markdown-body::before { + display: table; + content: ''; +} + +.markdown-body::after { + display: table; + clear: both; + content: ''; +} + +.markdown-body > *:first-child { + margin-top: 0 !important; +} + +.markdown-body > *:last-child { + margin-bottom: 0 !important; +} + +.markdown-body a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-body .absent { + color: var(--color-danger-fg); +} + +.markdown-body .anchor { + float: left; + padding-right: 4px; + margin-left: -20px; + line-height: 1; +} + +.markdown-body .anchor:focus { + outline: none; +} + +.markdown-body p, +.markdown-body blockquote, +.markdown-body ul, +.markdown-body ol, +.markdown-body dl, +.markdown-body table, +.markdown-body pre, +.markdown-body details { + margin-top: 0; + margin-bottom: 16px; +} + +.markdown-body blockquote > :first-child { + margin-top: 0; +} + +.markdown-body blockquote > :last-child { + margin-bottom: 0; +} + +.markdown-body h1 .octicon-link, +.markdown-body h2 .octicon-link, +.markdown-body h3 .octicon-link, +.markdown-body h4 .octicon-link, +.markdown-body h5 .octicon-link, +.markdown-body h6 .octicon-link { + color: var(--color-fg-default); + vertical-align: middle; + visibility: hidden; +} + +.markdown-body h1:hover .anchor, +.markdown-body h2:hover .anchor, +.markdown-body h3:hover .anchor, +.markdown-body h4:hover .anchor, +.markdown-body h5:hover .anchor, +.markdown-body h6:hover .anchor { + text-decoration: none; +} + +.markdown-body h1:hover .anchor .octicon-link, +.markdown-body h2:hover .anchor .octicon-link, +.markdown-body h3:hover .anchor .octicon-link, +.markdown-body h4:hover .anchor .octicon-link, +.markdown-body h5:hover .anchor .octicon-link, +.markdown-body h6:hover .anchor .octicon-link { + visibility: visible; +} + +.markdown-body h1 tt, +.markdown-body h1 code, +.markdown-body h2 tt, +.markdown-body h2 code, +.markdown-body h3 tt, +.markdown-body h3 code, +.markdown-body h4 tt, +.markdown-body h4 code, +.markdown-body h5 tt, +.markdown-body h5 code, +.markdown-body h6 tt, +.markdown-body h6 code { + padding: 0 0.2em; + font-size: inherit; +} + +.markdown-body summary h1, +.markdown-body summary h2, +.markdown-body summary h3, +.markdown-body summary h4, +.markdown-body summary h5, +.markdown-body summary h6 { + display: inline-block; +} + +.markdown-body summary h1 .anchor, +.markdown-body summary h2 .anchor, +.markdown-body summary h3 .anchor, +.markdown-body summary h4 .anchor, +.markdown-body summary h5 .anchor, +.markdown-body summary h6 .anchor { + margin-left: -40px; +} + +.markdown-body summary h1, +.markdown-body summary h2 { + padding-bottom: 0; + border-bottom: 0; +} + +.markdown-body ul.no-list, +.markdown-body ol.no-list { + padding: 0; + list-style-type: none; +} + +.markdown-body ol[type='a'] { + list-style-type: lower-alpha; +} + +.markdown-body ol[type='A'] { + list-style-type: upper-alpha; +} + +.markdown-body ol[type='i'] { + list-style-type: lower-roman; +} + +.markdown-body ol[type='I'] { + list-style-type: upper-roman; +} + +.markdown-body ol[type='1'] { + list-style-type: decimal; +} + +.markdown-body div > ol:not([type]) { + list-style-type: decimal; +} + +.markdown-body ul ul, +.markdown-body ul ol, +.markdown-body ol ol, +.markdown-body ol ul { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body li > p { + margin-top: 16px; +} + +.markdown-body li + li { + margin-top: 0.25em; +} + +.markdown-body dl { + padding: 0; +} + +.markdown-body dl dt { + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: var(--base-text-weight-semibold, 600); +} + +.markdown-body dl dd { + padding: 0 16px; + margin-bottom: 16px; +} + +.markdown-body table th { + font-weight: var(--base-text-weight-semibold, 600); +} + +.markdown-body table th, +.markdown-body table td { + padding: 6px 13px; + border: 1px solid var(--color-border-default); +} + +.markdown-body table tr { + background-color: var(--color-canvas-default); + border-top: 1px solid var(--color-border-muted); +} + +.markdown-body table tr:nth-child(2n) { + background-color: var(--color-canvas-subtle); +} + +.markdown-body table img { + background-color: transparent; +} + +.markdown-body img[align='right'] { + padding-left: 20px; +} + +.markdown-body img[align='left'] { + padding-right: 20px; +} + +.markdown-body .emoji { + max-width: none; + vertical-align: text-top; + background-color: transparent; +} + +.markdown-body span.frame { + display: block; + overflow: hidden; +} + +.markdown-body span.frame > span { + display: block; + float: left; + width: auto; + padding: 7px; + margin: 13px 0 0; + overflow: hidden; + border: 1px solid var(--color-border-default); +} + +.markdown-body span.frame span img { + display: block; + float: left; +} + +.markdown-body span.frame span span { + display: block; + padding: 5px 0 0; + clear: both; + color: var(--color-fg-default); +} + +.markdown-body span.align-center { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body span.align-center > span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: center; +} + +.markdown-body span.align-center span img { + margin: 0 auto; + text-align: center; +} + +.markdown-body span.align-right { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body span.align-right > span { + display: block; + margin: 13px 0 0; + overflow: hidden; + text-align: right; +} + +.markdown-body span.align-right span img { + margin: 0; + text-align: right; +} + +.markdown-body span.float-left { + display: block; + float: left; + margin-right: 13px; + overflow: hidden; +} + +.markdown-body span.float-left span { + margin: 13px 0 0; +} + +.markdown-body span.float-right { + display: block; + float: right; + margin-left: 13px; + overflow: hidden; +} + +.markdown-body span.float-right > span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: right; +} + +.markdown-body code, +.markdown-body tt { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + white-space: break-spaces; + background-color: var(--color-neutral-muted); + border-radius: 6px; +} + +.markdown-body code br, +.markdown-body tt br { + display: none; +} + +.markdown-body del code { + text-decoration: inherit; +} + +.markdown-body samp { + font-size: 85%; +} + +.markdown-body pre code { + font-size: 100%; +} + +.markdown-body pre > code { + padding: 0; + margin: 0; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; +} + +.markdown-body .highlight { + margin-bottom: 16px; +} + +.markdown-body .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.markdown-body .highlight pre, +.markdown-body pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: var(--color-canvas-subtle); + border-radius: 6px; +} + +.markdown-body pre code, +.markdown-body pre tt { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} + +.markdown-body .csv-data td, +.markdown-body .csv-data th { + padding: 5px; + overflow: hidden; + font-size: 12px; + line-height: 1; + text-align: left; + white-space: nowrap; +} + +.markdown-body .csv-data .blob-num { + padding: 10px 8px 9px; + text-align: right; + background: var(--color-canvas-default); + border: 0; +} + +.markdown-body .csv-data tr { + border-top: 0; +} + +.markdown-body .csv-data th { + font-weight: var(--base-text-weight-semibold, 600); + background: var(--color-canvas-subtle); + border-top: 0; +} + +.markdown-body [data-footnote-ref]::before { + content: '['; +} + +.markdown-body [data-footnote-ref]::after { + content: ']'; +} + +.markdown-body .footnotes { + font-size: 12px; + color: var(--color-fg-muted); + border-top: 1px solid var(--color-border-default); +} + +.markdown-body .footnotes ol { + padding-left: 16px; +} + +.markdown-body .footnotes ol ul { + display: inline-block; + padding-left: 16px; + margin-top: 16px; +} + +.markdown-body .footnotes li { + position: relative; +} + +.markdown-body .footnotes li:target::before { + position: absolute; + top: -8px; + right: -8px; + bottom: -8px; + left: -24px; + pointer-events: none; + content: ''; + border: 2px solid var(--color-accent-emphasis); + border-radius: 6px; +} + +.markdown-body .footnotes li:target { + color: var(--color-fg-default); +} + +.markdown-body .footnotes .data-footnote-backref g-emoji { + font-family: monospace; +} + +.markdown-body .pl-c { + color: var(--color-prettylights-syntax-comment); +} + +.markdown-body .pl-c1, +.markdown-body .pl-s .pl-v { + color: var(--color-prettylights-syntax-constant); +} + +.markdown-body .pl-e, +.markdown-body .pl-en { + color: var(--color-prettylights-syntax-entity); +} + +.markdown-body .pl-smi, +.markdown-body .pl-s .pl-s1 { + color: var(--color-prettylights-syntax-storage-modifier-import); +} + +.markdown-body .pl-ent { + color: var(--color-prettylights-syntax-entity-tag); +} + +.markdown-body .pl-k { + color: var(--color-prettylights-syntax-keyword); +} + +.markdown-body .pl-s, +.markdown-body .pl-pds, +.markdown-body .pl-s .pl-pse .pl-s1, +.markdown-body .pl-sr, +.markdown-body .pl-sr .pl-cce, +.markdown-body .pl-sr .pl-sre, +.markdown-body .pl-sr .pl-sra { + color: var(--color-prettylights-syntax-string); +} + +.markdown-body .pl-v, +.markdown-body .pl-smw { + color: var(--color-prettylights-syntax-variable); +} + +.markdown-body .pl-bu { + color: var(--color-prettylights-syntax-brackethighlighter-unmatched); +} + +.markdown-body .pl-ii { + color: var(--color-prettylights-syntax-invalid-illegal-text); + background-color: var(--color-prettylights-syntax-invalid-illegal-bg); +} + +.markdown-body .pl-c2 { + color: var(--color-prettylights-syntax-carriage-return-text); + background-color: var(--color-prettylights-syntax-carriage-return-bg); +} + +.markdown-body .pl-sr .pl-cce { + font-weight: bold; + color: var(--color-prettylights-syntax-string-regexp); +} + +.markdown-body .pl-ml { + color: var(--color-prettylights-syntax-markup-list); +} + +.markdown-body .pl-mh, +.markdown-body .pl-mh .pl-en, +.markdown-body .pl-ms { + font-weight: bold; + color: var(--color-prettylights-syntax-markup-heading); +} + +.markdown-body .pl-mi { + font-style: italic; + color: var(--color-prettylights-syntax-markup-italic); +} + +.markdown-body .pl-mb { + font-weight: bold; + color: var(--color-prettylights-syntax-markup-bold); +} + +.markdown-body .pl-md { + color: var(--color-prettylights-syntax-markup-deleted-text); + background-color: var(--color-prettylights-syntax-markup-deleted-bg); +} + +.markdown-body .pl-mi1 { + color: var(--color-prettylights-syntax-markup-inserted-text); + background-color: var(--color-prettylights-syntax-markup-inserted-bg); +} + +.markdown-body .pl-mc { + color: var(--color-prettylights-syntax-markup-changed-text); + background-color: var(--color-prettylights-syntax-markup-changed-bg); +} + +.markdown-body .pl-mi2 { + color: var(--color-prettylights-syntax-markup-ignored-text); + background-color: var(--color-prettylights-syntax-markup-ignored-bg); +} + +.markdown-body .pl-mdr { + font-weight: bold; + color: var(--color-prettylights-syntax-meta-diff-range); +} + +.markdown-body .pl-ba { + color: var(--color-prettylights-syntax-brackethighlighter-angle); +} + +.markdown-body .pl-sg { + color: var(--color-prettylights-syntax-sublimelinter-gutter-mark); +} + +.markdown-body .pl-corl { + text-decoration: underline; + color: var(--color-prettylights-syntax-constant-other-reference-link); +} + +.markdown-body g-emoji { + display: inline-block; + min-width: 1ch; + font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + font-size: 1em; + font-style: normal !important; + font-weight: var(--base-text-weight-normal, 400); + line-height: 1; + vertical-align: -0.075em; +} + +.markdown-body g-emoji img { + width: 1em; + height: 1em; +} + +.markdown-body .task-list-item { + list-style-type: none; +} + +.markdown-body .task-list-item label { + font-weight: var(--base-text-weight-normal, 400); +} + +.markdown-body .task-list-item.enabled label { + cursor: pointer; +} + +.markdown-body .task-list-item + .task-list-item { + margin-top: 4px; +} + +.markdown-body .task-list-item .handle { + display: none; +} + +.markdown-body .task-list-item-checkbox { + margin: 0 0.2em 0.25em -1.4em; + vertical-align: middle; +} + +.markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox { + margin: 0 -1.6em 0.25em 0.2em; +} + +.markdown-body .contains-task-list { + position: relative; +} + +.markdown-body .contains-task-list:hover .task-list-item-convert-container, +.markdown-body .contains-task-list:focus-within .task-list-item-convert-container { + display: block; + width: auto; + height: 24px; + overflow: visible; + clip: auto; +} + +.markdown-body ::-webkit-calendar-picker-indicator { + filter: invert(50%); +} diff --git a/src/views/super/airag/aiapp/chat/style/highlight.less b/src/views/super/airag/aiapp/chat/style/highlight.less new file mode 100644 index 0000000..2dd51cc --- /dev/null +++ b/src/views/super/airag/aiapp/chat/style/highlight.less @@ -0,0 +1,206 @@ +html.dark { + pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em; + } + + code.hljs { + padding: 3px 5px; + } + + .hljs { + color: #abb2bf; + background: #282c34; + } + + .hljs-keyword, + .hljs-operator, + .hljs-pattern-match { + color: #f92672; + } + + .hljs-function, + .hljs-pattern-match .hljs-constructor { + color: #61aeee; + } + + .hljs-function .hljs-params { + color: #a6e22e; + } + + .hljs-function .hljs-params .hljs-typing { + color: #fd971f; + } + + .hljs-module-access .hljs-module { + color: #7e57c2; + } + + .hljs-constructor { + color: #e2b93d; + } + + .hljs-constructor .hljs-string { + color: #9ccc65; + } + + .hljs-comment, + .hljs-quote { + color: #b18eb1; + font-style: italic; + } + + .hljs-doctag, + .hljs-formula { + color: #c678dd; + } + + .hljs-deletion, + .hljs-name, + .hljs-section, + .hljs-selector-tag, + .hljs-subst { + color: #e06c75; + } + + .hljs-literal { + color: #56b6c2; + } + + .hljs-addition, + .hljs-attribute, + .hljs-meta .hljs-string, + .hljs-regexp, + .hljs-string { + color: #98c379; + } + + .hljs-built_in, + .hljs-class .hljs-title, + .hljs-title.class_ { + color: #e6c07b; + } + + .hljs-attr, + .hljs-number, + .hljs-selector-attr, + .hljs-selector-class, + .hljs-selector-pseudo, + .hljs-template-variable, + .hljs-type, + .hljs-variable { + color: #d19a66; + } + + .hljs-bullet, + .hljs-link, + .hljs-meta, + .hljs-selector-id, + .hljs-symbol, + .hljs-title { + color: #61aeee; + } + + .hljs-emphasis { + font-style: italic; + } + + .hljs-strong { + font-weight: 700; + } + + .hljs-link { + text-decoration: underline; + } +} + +html { + pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em; + } + + code.hljs { + padding: 3px 5px; + &::-webkit-scrollbar { + height: 4px; + } + } + + .hljs { + color: #383a42; + background: #fafafa; + } + + .hljs-comment, + .hljs-quote { + color: #a0a1a7; + font-style: italic; + } + + .hljs-doctag, + .hljs-formula, + .hljs-keyword { + color: #a626a4; + } + + .hljs-deletion, + .hljs-name, + .hljs-section, + .hljs-selector-tag, + .hljs-subst { + color: #e45649; + } + + .hljs-literal { + color: #0184bb; + } + + .hljs-addition, + .hljs-attribute, + .hljs-meta .hljs-string, + .hljs-regexp, + .hljs-string { + color: #50a14f; + } + + .hljs-attr, + .hljs-number, + .hljs-selector-attr, + .hljs-selector-class, + .hljs-selector-pseudo, + .hljs-template-variable, + .hljs-type, + .hljs-variable { + color: #986801; + } + + .hljs-bullet, + .hljs-link, + .hljs-meta, + .hljs-selector-id, + .hljs-symbol, + .hljs-title { + color: #4078f2; + } + + .hljs-built_in, + .hljs-class .hljs-title, + .hljs-title.class_ { + color: #c18401; + } + + .hljs-emphasis { + font-style: italic; + } + + .hljs-strong { + font-weight: 700; + } + + .hljs-link { + text-decoration: underline; + } +} diff --git a/src/views/super/airag/aiapp/chat/style/style.less b/src/views/super/airag/aiapp/chat/style/style.less new file mode 100644 index 0000000..758453f --- /dev/null +++ b/src/views/super/airag/aiapp/chat/style/style.less @@ -0,0 +1,132 @@ +.markdown-body { + background-color: transparent; + font-size: 14px; + + p { + white-space: pre-wrap; + } + + ol { + list-style-type: decimal; + } + + ul { + list-style-type: disc; + } + + pre code, + pre tt { + line-height: 1.65; + } + + .highlight pre, + pre { + background-color: #fff; + } + + code.hljs { + padding: 0; + } + + .code-block { + &-wrapper { + position: relative; + padding-top: 24px; + } + + &-header { + position: absolute; + top: 5px; + right: 0; + width: 100%; + padding: 0 1rem; + display: flex; + justify-content: flex-end; + align-items: center; + color: #b3b3b3; + + &__copy { + cursor: pointer; + margin-left: 0.5rem; + user-select: none; + + &:hover { + color: #65a665; + } + } + } + } + + &.markdown-body-generate > dd:last-child:after, + &.markdown-body-generate > dl:last-child:after, + &.markdown-body-generate > dt:last-child:after, + &.markdown-body-generate > h1:last-child:after, + &.markdown-body-generate > h2:last-child:after, + &.markdown-body-generate > h3:last-child:after, + &.markdown-body-generate > h4:last-child:after, + &.markdown-body-generate > h5:last-child:after, + &.markdown-body-generate > h6:last-child:after, + &.markdown-body-generate > li:last-child:after, + &.markdown-body-generate > ol:last-child li:last-child:after, + &.markdown-body-generate > p:last-child:after, + &.markdown-body-generate > pre:last-child code:after, + &.markdown-body-generate > td:last-child:after, + &.markdown-body-generate > ul:last-child li:last-child:after { + animation: blink 1s steps(5, start) infinite; + color: #000; + content: '_'; + font-weight: 700; + margin-left: 3px; + vertical-align: baseline; + } + + @keyframes blink { + to { + visibility: hidden; + } + } +} + +html.dark { + .markdown-body { + &.markdown-body-generate > dd:last-child:after, + &.markdown-body-generate > dl:last-child:after, + &.markdown-body-generate > dt:last-child:after, + &.markdown-body-generate > h1:last-child:after, + &.markdown-body-generate > h2:last-child:after, + &.markdown-body-generate > h3:last-child:after, + &.markdown-body-generate > h4:last-child:after, + &.markdown-body-generate > h5:last-child:after, + &.markdown-body-generate > h6:last-child:after, + &.markdown-body-generate > li:last-child:after, + &.markdown-body-generate > ol:last-child li:last-child:after, + &.markdown-body-generate > p:last-child:after, + &.markdown-body-generate > pre:last-child code:after, + &.markdown-body-generate > td:last-child:after, + &.markdown-body-generate > ul:last-child li:last-child:after { + color: #65a665; + } + } + + .message-reply { + .whitespace-pre-wrap { + white-space: pre-wrap; + color: var(--n-text-color); + } + } + + .highlight pre, + pre { + background-color: #282c34; + } +} + +@media screen and (max-width: 533px) { + .markdown-body .code-block-wrapper { + padding: unset; + + code { + padding: 24px 16px 16px 16px; + } + } +} diff --git a/src/views/super/airag/aiapp/components/AiApp.json b/src/views/super/airag/aiapp/components/AiApp.json new file mode 100644 index 0000000..e5b3793 --- /dev/null +++ b/src/views/super/airag/aiapp/components/AiApp.json @@ -0,0 +1,5 @@ +{ + "prompt": "# 角色\n你是一个犀利的电影解说员,可以使用尖锐幽默的语言,向用户讲解电影剧情、介绍最新上映的电影,还可以用普通人都可以理解的语言讲解电影相关知识。\n\n## 技能\n### 技能 1: 推荐最新上映的电影\n1. 当用户请你推荐最新电影时,需要先了解用户喜欢哪种类型片。如果你已经知道了,请跳过这一步,在询问时可以用“请问您喜欢什么类型的电影呢亲”。\n2. 如果你并不知道用户所说的电影,可以使用 工具搜索电影,了解电影类型。\n3. 根据用户的电影偏好,推荐几部正在上映和即将上映的电影,在推荐开头可以说“好的亲,以下是为您推荐的电影”。\n===回复示例===\n - \uD83C\uDFAC 电影名: <电影名>\n - \uD83D\uDD50 上映时间: <电影在中国大陆的上映的日期>\n - \uD83D\uDCA1 电影简介: <100字总结这部电影的剧情摘要>\n===示例结束===\n\n### 技能 2: 介绍电影\n1. 当用户说介绍某一部电影,请使用工具 搜索电影介绍的链接,在收到需求时可以回应“好嘞亲,马上为您查找相关电影介绍”。\n2. 如果此时获取的信息不够全面,可以继续使用 工具 打开搜索结果中的相关链接,以了解电影详情。\n3. 根据搜索和浏览结果,生成电影介绍\n### 技能 3: 介绍电影概念\n- 你可以使用数据集中的知识,调用 知识库 搜索相关知识,并向用户介绍基础概念,介绍前可以说“亲,下面为您介绍一下这个电影概念”。\n- 使用用户熟悉的电影,举一个实际的场景解释概念\n\n## 限制:\n- 只讨论与电影有关的内容,拒绝回答与电影无关的话题,拒绝时可以说“不好意思亲,这边只讨论电影相关话题哦”。\n- 所输出的内容必须按照给定的格式进行组织,不能偏离框架要求,在表述中合理运用常用语。\n- 总结部分不能超过 100 字。\n- 只会输出知识库中已有内容, 不在知识库中的书籍, 通过 工具去了解。\n- 请使用 Markdown 的 ^^ 形式说明引用来源。”", + "prologue": "嘿,亲!我对电影那可是门儿清,能给你带来超棒的电影体验。", + "presetQuestion": [{"key": 1,"descr": "有啥好看的动作片推荐不?"},{"key": 2,"descr":"介绍下《流浪地球 3》呗。"},{"key": 3,"descr":"啥是电影蒙太奇呀?"}] +} \ No newline at end of file diff --git a/src/views/super/airag/aiapp/components/AiAppAddFlowModal.vue b/src/views/super/airag/aiapp/components/AiAppAddFlowModal.vue new file mode 100644 index 0000000..a514a14 --- /dev/null +++ b/src/views/super/airag/aiapp/components/AiAppAddFlowModal.vue @@ -0,0 +1,436 @@ + + + + + diff --git a/src/views/super/airag/aiapp/components/AiAppAddKnowledgeModal.vue b/src/views/super/airag/aiapp/components/AiAppAddKnowledgeModal.vue new file mode 100644 index 0000000..9105a8a --- /dev/null +++ b/src/views/super/airag/aiapp/components/AiAppAddKnowledgeModal.vue @@ -0,0 +1,327 @@ + + + + + diff --git a/src/views/super/airag/aiapp/components/AiAppAddMcpModal.vue b/src/views/super/airag/aiapp/components/AiAppAddMcpModal.vue new file mode 100644 index 0000000..346bdb0 --- /dev/null +++ b/src/views/super/airag/aiapp/components/AiAppAddMcpModal.vue @@ -0,0 +1,348 @@ + + + + + diff --git a/src/views/super/airag/aiapp/components/AiAppGeneratedPromptModal.vue b/src/views/super/airag/aiapp/components/AiAppGeneratedPromptModal.vue new file mode 100644 index 0000000..09da4ca --- /dev/null +++ b/src/views/super/airag/aiapp/components/AiAppGeneratedPromptModal.vue @@ -0,0 +1,344 @@ + + + + + + diff --git a/src/views/super/airag/aiapp/components/AiAppModal.vue b/src/views/super/airag/aiapp/components/AiAppModal.vue new file mode 100644 index 0000000..d4bc2a9 --- /dev/null +++ b/src/views/super/airag/aiapp/components/AiAppModal.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/src/views/super/airag/aiapp/components/AiAppParamsSettingModal.vue b/src/views/super/airag/aiapp/components/AiAppParamsSettingModal.vue new file mode 100644 index 0000000..6a0bd1d --- /dev/null +++ b/src/views/super/airag/aiapp/components/AiAppParamsSettingModal.vue @@ -0,0 +1,101 @@ + + + + + + diff --git a/src/views/super/airag/aiapp/components/AiAppPromptMarketModal.vue b/src/views/super/airag/aiapp/components/AiAppPromptMarketModal.vue new file mode 100644 index 0000000..d3255c8 --- /dev/null +++ b/src/views/super/airag/aiapp/components/AiAppPromptMarketModal.vue @@ -0,0 +1,367 @@ + + + + + diff --git a/src/views/super/airag/aiapp/components/AiAppQuickCommandModal.vue b/src/views/super/airag/aiapp/components/AiAppQuickCommandModal.vue new file mode 100644 index 0000000..e48338f --- /dev/null +++ b/src/views/super/airag/aiapp/components/AiAppQuickCommandModal.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/views/super/airag/aiapp/components/AiAppSendModal.vue b/src/views/super/airag/aiapp/components/AiAppSendModal.vue new file mode 100644 index 0000000..a16a248 --- /dev/null +++ b/src/views/super/airag/aiapp/components/AiAppSendModal.vue @@ -0,0 +1,274 @@ + + + + + diff --git a/src/views/super/airag/aiapp/components/AiAppSettingModal.vue b/src/views/super/airag/aiapp/components/AiAppSettingModal.vue new file mode 100644 index 0000000..ff5d860 --- /dev/null +++ b/src/views/super/airag/aiapp/components/AiAppSettingModal.vue @@ -0,0 +1,2021 @@ + + + + + + diff --git a/src/views/super/airag/aiapp/components/AiUserVariablesModal.vue b/src/views/super/airag/aiapp/components/AiUserVariablesModal.vue new file mode 100644 index 0000000..8409298 --- /dev/null +++ b/src/views/super/airag/aiapp/components/AiUserVariablesModal.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/src/views/super/airag/aiapp/img/ailogo.png b/src/views/super/airag/aiapp/img/ailogo.png new file mode 100644 index 0000000..f63c9d3 Binary files /dev/null and b/src/views/super/airag/aiapp/img/ailogo.png differ diff --git a/src/views/super/airag/aiapp/img/iconWebEmbedded.png b/src/views/super/airag/aiapp/img/iconWebEmbedded.png new file mode 100644 index 0000000..187c602 Binary files /dev/null and b/src/views/super/airag/aiapp/img/iconWebEmbedded.png differ diff --git a/src/views/super/airag/aiapp/img/webEmbedded.png b/src/views/super/airag/aiapp/img/webEmbedded.png new file mode 100644 index 0000000..53038f0 Binary files /dev/null and b/src/views/super/airag/aiapp/img/webEmbedded.png differ diff --git a/src/views/super/airag/aicloth/AiClothChange.data.ts b/src/views/super/airag/aicloth/AiClothChange.data.ts new file mode 100644 index 0000000..3451af2 --- /dev/null +++ b/src/views/super/airag/aicloth/AiClothChange.data.ts @@ -0,0 +1,91 @@ +import { FormSchema } from '@/components/Form'; + +/** + * AI换衣 - 生成图片表单 + */ +export const clothImageFormSchema: FormSchema[] = [ + { + field: 'drawModelId', + label: '模型', + component: 'JDictSelectTag', + required: true, + helpMessage: ['1、需要选择已激活的图像模型', '2、当前推荐通义万象模型 (wan2.5-i2i-preview)', '3、建议上传清晰的模特图和服装图以获得最佳效果'], + componentProps: { + dictCode: "airag_model where model_type = 'IMAGE' and activate_flag = 1,name,id", + placeholder: '请选择图像模型', + }, + }, + { + field: 'modelImage', + label: '模特图片', + component: 'JImageUpload', + required: true, + componentProps: { + fileMax: 1, + text: '上传模特', + }, + helpMessage: ['上传模特图片,建议使用全身照,正面清晰'], + }, + + { + field: 'clothUpload', + label: '服装', + slot: 'clothUpload', + component: 'Input', + required: false, + }, + { + field: 'userPrompt', + label: '提示词', + component: 'InputTextArea', + componentProps: { + rows: 4, + placeholder: '在此输入你的提示词,或使用示例快速填充', + }, + required: true, + }, +]; + +/** + * AI换衣 - 生成视频表单 + */ +export const clothVideoFormSchema: FormSchema[] = [ + { + field: 'drawModelId', + label: '模型', + component: 'JDictSelectTag', + required: true, + helpMessage: ['1、需要选择已激活的视频模型', '2、建议选择支持图生视频的模型'], + componentProps: { + dictCode: "airag_model where model_type = 'VIDEO' and activate_flag = 1,name,id", + placeholder: '请选择视频模型', + }, + }, + { + field: 'modelImage', + label: '模特图片', + component: 'JImageUpload', + componentProps: { + fileMax: 1, + text: '上传模特', + }, + helpMessage: ['上传模特图片,建议使用全身照,正面清晰'], + }, + { + field: 'clothUpload', + label: '', + slot: 'clothUpload', + component: 'Input', + required: false, + }, + { + field: 'userPrompt', + label: '自定义提示词', + component: 'InputTextArea', + componentProps: { + rows: 4, + placeholder: '在此输入你的提示词,或使用下方示例快速填充', + }, + required: false, + }, +]; diff --git a/src/views/super/airag/aicloth/AiClothChange.less b/src/views/super/airag/aicloth/AiClothChange.less new file mode 100644 index 0000000..7a0f8ca --- /dev/null +++ b/src/views/super/airag/aicloth/AiClothChange.less @@ -0,0 +1,372 @@ +.ai-cloth-change-page { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + padding: 12px 16px 16px; + background-color: #f0f2f5; + display: flex; + flex-direction: column; + box-sizing: border-box; + overflow: hidden; + + // 示例按钮行(单行显示,pill 样式) + .examples-row { + display: flex; + gap: 12px; + align-items: center; + flex-wrap: nowrap; + padding: 8px 0 12px 0; + overflow-x: auto; /* 小屏幕时可以横向滚动 */ + -webkit-overflow-scrolling: touch; + } + + .example-btn { + border-radius: 20px !important; + border: 1px solid #2b8fff !important; + color: #2b8fff !important; + background: #fff !important; + padding: 6px 14px !important; + height: 36px !important; + line-height: 22px !important; + box-shadow: none !important; + white-space: nowrap; + } + + .example-btn:hover { + background: rgba(43, 143, 255, 0.06) !important; + border-color: #1a6fe6 !important; + color: #1a6fe6 !important; + } + + //顶部标题区 + .page-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; + border-bottom: 1px solid #f0f0f0; + background: #fff; + padding: 16px 16px; + + .page-title { + font-size: 20px; + font-weight: 600; + color: #1f2329; + margin: 0; + letter-spacing: 0.5px; + } + + .header-desc { + font-size: 13px; + color: #8f959e; + margin: 0; + flex: 1; + text-align: right; + } + } + + .cloth-change-wrapper { + flex: 1; + display: flex; + gap: 16px; + overflow: hidden; + height: 100%; + } + + //左侧配置面板 + .config-panel { + width: 420px; + min-width: 360px; + background: #fff; + border-radius: 8px; + display: flex; + flex-direction: column; + overflow: hidden; + + .config-tabs { + padding: 16px 20px 0; + + :deep(.ant-tabs-nav::before) { + border-bottom: none; + } + :deep(.ant-tabs-tab) { + padding: 8px 0; + margin: 0 24px 0 0; + font-size: 15px; + &.ant-tabs-tab-active .ant-tabs-tab-btn { + color: #00b96b; + font-weight: 600; + } + } + :deep(.ant-tabs-ink-bar) { + background: #00b96b; + } + } + + .form-scroll { + flex: 1; + overflow-y: auto; + padding: 0 20px 8px; + } + + .action-bar { + display: flex; + align-items: center; + justify-content: center; + padding: 12px 20px 16px; + border-top: 1px solid #f0f0f0; + + .gen-btn { + height: 40px; + padding: 0 40px; + font-size: 15px; + background: #00b96b; + border-color: #00b96b; + border-radius: 20px; + &:hover { + background: #00d97e; + border-color: #00d97e; + } + } + } + + //单件服装上传 + .cloth-upload-area { + width: 100%; + height: 200px; + border: 1.5px dashed #d9d9d9; + border-radius: 8px; + cursor: pointer; + overflow: hidden; + transition: border-color 0.2s; + margin-bottom: 16px; + + &:hover { + border-color: #1890ff; + } + + .upload-placeholder { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + background: #fafafa; + + .upload-text { + font-size: 13px; + color: #aaa; + } + } + + .uploaded-img-box { + position: relative; + width: 100%; + height: 100%; + + .uploaded-img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } + + .img-mask { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.4); + display: none; + align-items: center; + justify-content: center; + cursor: pointer; + } + + &:hover .img-mask { + display: flex; + } + } + } + + //多件服装上传 + .multi-cloth-container { + display: flex; + gap: 12px; + margin-bottom: 16px; + + .cloth-item { + flex: 1; + display: flex; + flex-direction: column; + gap: 8px; + + .cloth-label { + font-size: 12px; + font-weight: 600; + color: #1f2329; + } + + .upload-placeholder { + flex: 1; + border: 1.5px dashed #d9d9d9; + border-radius: 8px; + cursor: pointer; + overflow: hidden; + transition: border-color 0.2s; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + background: #fafafa; + + &:hover { + border-color: #1890ff; + } + + .upload-text { + font-size: 12px; + color: #aaa; + } + } + + .uploaded-img-box { + position: relative; + flex: 1; + border-radius: 8px; + overflow: hidden; + + .uploaded-img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } + + .img-mask { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.4); + display: none; + align-items: center; + justify-content: center; + cursor: pointer; + } + + &:hover .img-mask { + display: flex; + } + } + } + } + + //视频提示 + .ai-notice { + margin-top: 10px; + + :deep(.ant-alert) { + font-size: 12px; + padding: 8px 12px; + border-radius: 6px; + } + + :deep(.ant-alert-message) { + font-size: 12px; + } + } + + //区块 + .section-block { + margin-top: 16px; + + .section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; + + .section-title { + font-size: 14px; + font-weight: 600; + color: #1f2329; + } + } + } + } + + // 状态 + .empty-state { + text-align: center; + color: #8f959e; + p { + margin-top: 16px; + } + } + + .loading-state { + display: flex; + flex-direction: column; + align-items: center; + } + + // 结果展示 + .result-image-wrapper, + .result-video-wrapper { + position: relative; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + + .result-image, + .result-video { + width: 100%; + height: 100%; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + object-fit: contain; + } + + .hover-actions { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.4); + display: none; + align-items: center; + justify-content: center; + gap: 16px; + border-radius: 8px; + backdrop-filter: blur(2px); + } + + &:hover .hover-actions { + display: flex; + } + } + + //右侧预览面板 + .preview-panel { + flex: 1; + background: #fff; + border-radius: 8px; + display: flex; + flex-direction: column; + overflow: hidden; + + .preview-content { + flex: 1; + background: #f7f8fc; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + position: relative; + margin: 16px; + } + } +} diff --git a/src/views/super/airag/aicloth/AiClothChange.vue b/src/views/super/airag/aicloth/AiClothChange.vue new file mode 100644 index 0000000..d4cc0a0 --- /dev/null +++ b/src/views/super/airag/aicloth/AiClothChange.vue @@ -0,0 +1,284 @@ + + + + + diff --git a/src/views/super/airag/aiknowledge/AiKnowledgeBase.api.ts b/src/views/super/airag/aiknowledge/AiKnowledgeBase.api.ts new file mode 100644 index 0000000..3a7e7a0 --- /dev/null +++ b/src/views/super/airag/aiknowledge/AiKnowledgeBase.api.ts @@ -0,0 +1,137 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +enum Api { + //知识库管理 + list = '/airag/knowledge/list', + save = '/airag/knowledge/add', + delete = '/airag/knowledge/delete', + queryById = '/airag/knowledge/queryById', + edit = '/airag/knowledge/edit', + rebuild = '/airag/knowledge/rebuild', + //知识库文档 + knowledgeDocList = '/airag/knowledge/doc/list', + knowledgeEditDoc = '/airag/knowledge/doc/edit', + knowledgeDeleteBatchDoc = '/airag/knowledge/doc/deleteBatch', + knowledgeDeleteAllDoc = '/airag/knowledge/doc/deleteAll', + knowledgeRebuildDoc = '/airag/knowledge/doc/rebuild', + knowledgeEmbeddingHitTest = '/airag/knowledge/embedding/hitTest', +} + +/** + * 查询知识库 + * @param params + */ +export const list = (params) => { + return defHttp.get({ url: Api.list, params }, { isTransformResponse: false }); +}; + +/** + * 根据id查询知识库 + * @param params + */ +export const queryById = (params) => { + return defHttp.get({ url: Api.queryById, params }, { isTransformResponse: false }); +}; + +/** + * 新增知识库 + * @param params + */ +export const saveKnowledge = (params) => { + return defHttp.post({ url: Api.save, params }); +}; + +/** + * 编辑知识库 + * + * @param params + */ +export const editKnowledge = (params) => { + return defHttp.put({ url: Api.edit, params }); +}; + +/** + * 删除知识库 + */ +export const deleteModel = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除名称为'+params.name+'的知识库吗?', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.delete, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; + +/** + * 查询知识库详情 + * @param params + */ +export const knowledgeDocList = (params) => { + return defHttp.get({ url: Api.knowledgeDocList, params }, { isTransformResponse: false }); +}; + +/** + * 知识库向量化 + * + * @param params + */ +export const rebuild = (params) => { + return defHttp.put({ url: Api.rebuild, params,timeout: 2 * 60 * 1000 }, { joinParamsToUrl: true, isTransformResponse: false }); +}; + +/** + * 新增知识库 + * @param params + */ +export const knowledgeSaveDoc = (params) => { + return defHttp.post({ url: Api.knowledgeEditDoc, params }); +}; + +/** + * 文档向量化 + * @param params + */ +export const knowledgeRebuildDoc = (params, handleSuccess) => { + return defHttp.put({ url: Api.knowledgeRebuildDoc, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +/** + * 批量删除文档 + * + * @param params + * @param handleSuccess + */ +export const knowledgeDeleteBatchDoc = (params, handleSuccess) => { + return defHttp.delete({ url: Api.knowledgeDeleteBatchDoc, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +/** + * 批量删除文档 + * + * @param params + * @param handleSuccess + */ +export const knowledgeDeleteAllDoc = (knowId: string, handleSuccess) => { + return defHttp.delete({ url: Api.knowledgeDeleteAllDoc, params: {knowId} }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +/** + * 命中测试 + * @param params + */ +export const knowledgeEmbeddingHitTest = (params) => { + let url = Api.knowledgeEmbeddingHitTest + '/' + params.knowId; + return defHttp.get({ url: url, params }, { isTransformResponse: false }); +}; diff --git a/src/views/super/airag/aiknowledge/AiKnowledgeBase.api.util.tsx b/src/views/super/airag/aiknowledge/AiKnowledgeBase.api.util.tsx new file mode 100644 index 0000000..29aabb7 --- /dev/null +++ b/src/views/super/airag/aiknowledge/AiKnowledgeBase.api.util.tsx @@ -0,0 +1,24 @@ +import {knowledgeDeleteAllDoc} from "./AiKnowledgeBase.api"; +import {useMessage} from "@/hooks/web/useMessage"; + +const {createConfirmSync} = useMessage(); + +// 清空文档 +export async function doDeleteAllDoc(knowledgeId: string, reload: () => void) { + const flag = await createConfirmSync({ + title: '清空文档', + content: () => ( +

+ 确定要清空所有文档吗? +
+ + 此操作会删除所有已录入的文档,并且不能恢复,请谨慎操作 + +

+ ), + }); + if (!flag) { + return; + } + knowledgeDeleteAllDoc(knowledgeId, reload) +} diff --git a/src/views/super/airag/aiknowledge/AiKnowledgeBase.data.ts b/src/views/super/airag/aiknowledge/AiKnowledgeBase.data.ts new file mode 100644 index 0000000..0000347 --- /dev/null +++ b/src/views/super/airag/aiknowledge/AiKnowledgeBase.data.ts @@ -0,0 +1,330 @@ +import { FormSchema } from '@/components/Form'; +import { BasicColumn } from '@/components/Table'; + +/** + * 表单 + */ +export const formSchema: FormSchema[] = [ + { + label: 'id', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '知识库名称', + field: 'name', + required: true, + componentProps: { + placeholder: '请输入知识库名称', + //是否展示字数 + showCount: true, + maxlength: 64, + }, + component: 'Input', + }, + { + label: '知识库描述', + field: 'descr', + component: 'InputTextArea', + componentProps: { + placeholder: '描述知识库的内容,详尽的描述将帮助AI能深入理解该知识库的内容,能更准确的检索到内容,提高该知识库的命中率。', + //是否展示字数 + showCount: true, + maxlength: 256, + }, + }, + { + label: '向量模型', + field: 'embedId', + required: true, + component: 'JDictSelectTag', + componentProps: { + dictCode: "airag_model where model_type = 'EMBED' and activate_flag = 1,name,id", + }, + }, + { + label: '状态', + field: 'status', + required: true, + component: 'JDictSelectTag', + componentProps: { + options: [ + { label: '启用', value: 'enable' }, + { label: '禁用', value: 'disable' }, + ], + type: 'radioButton', + }, + defaultValue: 'enable', + }, + { + label: '类型', + field: 'type', + required: true, + component: 'JDictSelectTag', + componentProps: { + options: [ + { label: '知识库', value: 'knowledge' }, + { label: '记忆库', value: 'memory' }, + ], + type: 'radioButton', + }, + defaultValue: 'knowledge', + }, + { + label: '分段策略', + field: 'enableSegment', + component: 'Switch', + defaultValue: false, + ifShow: ({ values }) => values.type !== 'memory', + componentProps: { + checkedChildren: '开启', + unCheckedChildren: '关闭', + }, + helpMessage: '开启后,知识库里面的文档默认使用该分段策略;文档也可单独配置自己的分段策略', + }, + { + label: '分段模式', + field: 'segmentStrategy', + component: 'RadioGroup', + defaultValue: 'auto', + ifShow: ({ values }) => values.type !== 'memory' && values.enableSegment === true, + componentProps: { + options: [ + { label: '自动分段与清洗', value: 'auto' }, + { label: '自定义', value: 'custom' }, + ], + }, + }, + { + label: '分段标识符', + field: 'separator', + component: 'Select', + defaultValue: '\\n', + required: true, + ifShow: ({ values }) => values.type !== 'memory' && values.enableSegment === true && values.segmentStrategy === 'custom', + componentProps: { + getPopupContainer: () => document.body, + options: [ + { label: '换行', value: '\\n' }, + { label: '2个换行', value: '\\n\\n' }, + { label: '中文句号', value: '。' }, + { label: '中文叹号', value: '!' }, + { label: '中文问号', value: '?' }, + { label: '英文句号', value: '.' }, + { label: '英文叹号', value: '!' }, + { label: '英文问号', value: '?' }, + { label: '自定义', value: 'custom' }, + ], + }, + }, + { + label: '自定义分隔符', + field: 'customSeparator', + component: 'Input', + required: true, + ifShow: ({ values }) => values.type !== 'memory' && values.enableSegment === true && values.separator === 'custom' && values.segmentStrategy !== 'auto', + }, + { + label: '分段最大长度', + field: 'maxSegment', + component: 'InputNumber', + defaultValue: 800, + required: true, + ifShow: ({ values }) => values.type !== 'memory' && values.enableSegment === true, + componentProps: { + min: 100, + max: 5000, + }, + }, + { + label: '分段重叠度%', + field: 'overlap', + component: 'InputNumber', + defaultValue: 10, + required: true, + ifShow: ({ values }) => values.type !== 'memory' && values.enableSegment === true, + componentProps: { + min: 0, + max: 90, + }, + }, + { + label: '文本预处理规则', + field: 'textRules', + component: 'CheckboxGroup', + defaultValue: [], + ifShow: ({ values }) => values.type !== 'memory' && values.enableSegment === true && values.segmentStrategy === 'custom', + componentProps: { + options: [ + { label: '替换掉连续的空格、换行符和制表符', value: 'cleanSpaces' }, + { label: '删除所有 URL 和电子邮箱地址', value: 'removeUrlsEmails' }, + ], + }, + }, +]; + +//文档文本表单 +export const docTextSchema: FormSchema[] = [ + { + label: 'id', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '知识库id', + field: 'knowledgeId', + show: false, + component: 'Input', + }, + { + label: '标题', + field: 'title', + required: true, + component: 'Input', + }, + { + label: '类型', + field: 'type', + required: true, + component: 'Input', + show: false + }, + { + label: '内容', + field: 'content', + rules: [{ required: true, message: '请输入内容' }], + component: 'JMarkdownEditor', + componentProps: { + placeholder: "请输入内容", + preview:{ mode: 'view', action: [] } + }, + ifShow:({ values})=>{ + if(values.type === 'text'){ + return true; + } + return false; + } + }, + { + label: '文件', + field: 'filePath', + rules: [{ required: true, message: '请上传文件' }], + component: 'JUpload', + helpMessage:'支持txt、markdown、pdf、docx、xlsx、pptx', + componentProps:{ + fileType: 'file', + maxCount: 1, + multiple: false, + text: '上传文档' + }, + ifShow:({ values })=>{ + if(values.type === 'file'){ + return true; + } + return false; + } + }, + { + label: '网页地址', + field: 'website', + rules: [ + { required: true, message: '请输入网页URL' }, + { pattern: /^https?:\/\//, message: '请输入正确的网页地址,以http://或https://开头' }, + ], + component: 'Input', + componentProps: { + placeholder: '请输入网页URL,例如:https://help.jeecg.com/', + }, + ifShow:({ values })=>{ + if(values.type === 'web'){ + return true; + } + return false; + } + }, +]; + +/** + * 分段策略表单 + */ +export const docSegmentSchema: FormSchema[] = [ + { + label: '分段策略', + field: 'segmentStrategy', + component: 'RadioGroup', + defaultValue: 'auto', + componentProps: { + options: [ + { label: '自动分段与清洗', value: 'auto' }, + { label: '自定义', value: 'custom' }, + ], + }, + }, + { + label: '分段标识符', + field: 'separator', + component: 'Select', + defaultValue: '\\n', + required: true, + ifShow: ({ values }) => values.segmentStrategy === 'custom', + componentProps: { + getPopupContainer: () => document.body, + options: [ + { label: '换行', value: '\\n' }, + { label: '2个换行', value: '\\n\\n' }, + { label: '中文句号', value: '。' }, + { label: '中文叹号', value: '!' }, + { label: '中文问号', value: '?' }, + { label: '英文句号', value: '.' }, + { label: '英文叹号', value: '!' }, + { label: '英文问号', value: '?' }, + { label: '自定义', value: 'custom' }, + ], + }, + }, + { + label: '', + field: 'customSeparator', + component: 'Input', + required: true, + ifShow: ({ values }) => values.separator === 'custom' && values.segmentStrategy !== 'auto', + }, + { + label: '分段最大长度', + field: 'maxSegment', + component: 'InputNumber', + defaultValue: 800, + required: true, + componentProps: { + min: 100, + max: 5000, + }, + }, + { + label: '分段重叠度%', + field: 'overlap', + component: 'InputNumber', + defaultValue: 10, + componentProps: { + min: 0, + max: 90, + }, + required: true, + }, + { + label: '文本预处理规则', + field: 'textRules', + component: 'CheckboxGroup', + defaultValue: [], + ifShow: ({ values }) => values.segmentStrategy === 'custom', + componentProps: { + options: [ + { label: '替换掉连续的空格、换行符和制表符', value: 'cleanSpaces' }, + { label: '删除所有 URL 和电子邮箱地址', value: 'removeUrlsEmails' }, + ], + }, + }, +]; + diff --git a/src/views/super/airag/aiknowledge/AiKnowledgeBaseList.vue b/src/views/super/airag/aiknowledge/AiKnowledgeBaseList.vue new file mode 100644 index 0000000..538c77b --- /dev/null +++ b/src/views/super/airag/aiknowledge/AiKnowledgeBaseList.vue @@ -0,0 +1,514 @@ + + + + + + diff --git a/src/views/super/airag/aiknowledge/components/AiKnowledgeBaseModal.vue b/src/views/super/airag/aiknowledge/components/AiKnowledgeBaseModal.vue new file mode 100644 index 0000000..81b85b1 --- /dev/null +++ b/src/views/super/airag/aiknowledge/components/AiKnowledgeBaseModal.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/src/views/super/airag/aiknowledge/components/AiTextDescModal.vue b/src/views/super/airag/aiknowledge/components/AiTextDescModal.vue new file mode 100644 index 0000000..dc3ca5f --- /dev/null +++ b/src/views/super/airag/aiknowledge/components/AiTextDescModal.vue @@ -0,0 +1,93 @@ + + + + + + diff --git a/src/views/super/airag/aiknowledge/components/AiragKnowledgeDocListModal.vue b/src/views/super/airag/aiknowledge/components/AiragKnowledgeDocListModal.vue new file mode 100644 index 0000000..7701639 --- /dev/null +++ b/src/views/super/airag/aiknowledge/components/AiragKnowledgeDocListModal.vue @@ -0,0 +1,1085 @@ + + + + + + + diff --git a/src/views/super/airag/aiknowledge/components/AiragKnowledgeDocTextModal.vue b/src/views/super/airag/aiknowledge/components/AiragKnowledgeDocTextModal.vue new file mode 100644 index 0000000..31fdc91 --- /dev/null +++ b/src/views/super/airag/aiknowledge/components/AiragKnowledgeDocTextModal.vue @@ -0,0 +1,302 @@ + + + + + + diff --git a/src/views/super/airag/aiknowledge/icon/draft.png b/src/views/super/airag/aiknowledge/icon/draft.png new file mode 100644 index 0000000..028e583 Binary files /dev/null and b/src/views/super/airag/aiknowledge/icon/draft.png differ diff --git a/src/views/super/airag/aiknowledge/icon/knowledge.png b/src/views/super/airag/aiknowledge/icon/knowledge.png new file mode 100644 index 0000000..b622775 Binary files /dev/null and b/src/views/super/airag/aiknowledge/icon/knowledge.png differ diff --git a/src/views/super/airag/aimcp/AiragMcp.api.ts b/src/views/super/airag/aimcp/AiragMcp.api.ts new file mode 100644 index 0000000..25c33fc --- /dev/null +++ b/src/views/super/airag/aimcp/AiragMcp.api.ts @@ -0,0 +1,87 @@ +import { defHttp } from '/@/utils/http/axios'; +// import { useMessage } from "/@/hooks/web/useMessage"; // 需要确认弹窗再启用 + +enum Api { + list = '/airag/airagMcp/list', + save='/airag/airagMcp/save', + deleteOne = '/airag/airagMcp/delete', + importExcel = '/airag/airagMcp/importExcel', + exportXls = '/airag/airagMcp/exportXls', + sync = '/airag/airagMcp/sync', + toggleStatus = '/airag/airagMcp/status', + saveAndSync = '/airag/airagMcp/saveAndSync', + queryById = '/airag/airagMcp/queryById', + saveTools = '/airag/airagMcp/saveTools', +} + +/** + * 导出api + * @param params + */ +export const getExportUrl = Api.exportXls; + +/** + * 导入api + */ +export const getImportUrl = Api.importExcel; + +/** + * 列表接口 + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); + +/** + * 删除单个 + * @param params + * @param handleSuccess + */ +export const deleteOne = (params,handleSuccess) => { + return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => { + handleSuccess(); + }); +} + + +/** + * 保存或者更新 + * @param params + * @param isUpdate + */ +export const saveOrUpdate = (params) => { + return defHttp.post({url: Api.save, data: params}, { isTransformResponse: false }); +} + +/** + * 保存并同步 + * @param params + * @param isUpdate + */ +export const saveAndSync = (params) => { + return defHttp.post({url: Api.saveAndSync, data: params}, { isTransformResponse: false }); +} + +/** + * 同步接口 + * @param id + */ +export const syncMcp = (id) => defHttp.post({ url: Api.sync+"/"+id }); + +/** + * 修改状态 + * @param id + */ +export const toggleStatus = (id,status) => defHttp.post({ url: Api.toggleStatus+"/"+id + "/"+ status }); + +/** + * 详情查询 + * @param id + */ +export const queryById = (id) => defHttp.get({ url: Api.queryById ,params: { id:id }}, { isTransformResponse: false }); + +/** + * 保存插件工具(仅更新tools字段) + * @param id 插件ID + * @param tools 工具列表JSON字符串 + */ +export const saveTools = (id, tools) => defHttp.post({ url: Api.saveTools, data: { id, tools } }, { isTransformResponse: false }); diff --git a/src/views/super/airag/aimcp/AiragMcp.data.ts b/src/views/super/airag/aimcp/AiragMcp.data.ts new file mode 100644 index 0000000..a733690 --- /dev/null +++ b/src/views/super/airag/aimcp/AiragMcp.data.ts @@ -0,0 +1,78 @@ +import {BasicColumn} from '/@/components/Table'; +import {FormSchema} from '/@/components/Table'; +import { rules} from '/@/utils/helper/validator'; +import { render } from '/@/utils/common/renderUtils'; +import { getWeekMonthQuarterYear } from '/@/utils'; +//列表数据 +export const columns: BasicColumn[] = [ + { + title: '图标', + align: "center", + dataIndex: 'icon' + }, + { + title: '名称', + align: "center", + dataIndex: 'name' + }, + { + title: '描述', + align: "center", + dataIndex: 'descr' + }, + { + title: 'mcp类型(sse:sse类型;stdio:标准类型)', + align: "center", + dataIndex: 'type' + }, + { + title: '服务端点(SSE类型为URL,stdio类型为命令)', + align: "center", + dataIndex: 'endpoint' + }, + { + title: '请求头(sse类型)、环境变量(stdio类型)', + align: "center", + dataIndex: 'headers' + }, + { + title: '工具列表', + align: "center", + dataIndex: 'tools' + }, + { + title: '状态(enable=启用、disable=禁用)', + align: "center", + dataIndex: 'status' + }, + { + title: '是否同步', + align: "center", + dataIndex: 'synced' + }, + { + title: '元数据', + align: "center", + dataIndex: 'metadata' + }, + { + title: '租户id', + align: "center", + dataIndex: 'tenantId' + }, +]; + +// 高级查询数据 +export const superQuerySchema = { + icon: {title: '应用图标',order: 0,view: 'text', type: 'string',}, + name: {title: '名称',order: 1,view: 'text', type: 'string',}, + descr: {title: '描述',order: 2,view: 'text', type: 'string',}, + type: {title: 'mcp类型(sse:sse类型;stdio:标准类型)',order: 3,view: 'text', type: 'string',}, + endpoint: {title: '服务端点(SSE类型为URL,stdio类型为命令)',order: 4,view: 'textarea', type: 'string',}, + headers: {title: '请求头(sse类型)、环境变量(stdio类型)',order: 5,view: 'textarea', type: 'string',}, + tools: {title: '工具列表',order: 6,view: 'textarea', type: 'string',}, + status: {title: '状态(enable=启用、disable=禁用)',order: 7,view: 'text', type: 'string',}, + synced: {title: '是否同步',order: 8,view: 'number', type: 'number',}, + metadata: {title: '元数据',order: 9,view: 'textarea', type: 'string',}, + tenantId: {title: '租户id',order: 10,view: 'text', type: 'string',}, +}; diff --git a/src/views/super/airag/aimcp/AiragMcpList.vue b/src/views/super/airag/aimcp/AiragMcpList.vue new file mode 100644 index 0000000..0232acd --- /dev/null +++ b/src/views/super/airag/aimcp/AiragMcpList.vue @@ -0,0 +1,544 @@ + + + + diff --git a/src/views/super/airag/aimcp/components/AiragMcpAddModal.vue b/src/views/super/airag/aimcp/components/AiragMcpAddModal.vue new file mode 100644 index 0000000..ccaef5e --- /dev/null +++ b/src/views/super/airag/aimcp/components/AiragMcpAddModal.vue @@ -0,0 +1,562 @@ + + + + diff --git a/src/views/super/airag/aimcp/components/AiragMcpDetailModal.vue b/src/views/super/airag/aimcp/components/AiragMcpDetailModal.vue new file mode 100644 index 0000000..023b55e --- /dev/null +++ b/src/views/super/airag/aimcp/components/AiragMcpDetailModal.vue @@ -0,0 +1,614 @@ + + + + diff --git a/src/views/super/airag/aimcp/components/PluginToolEditModal.vue b/src/views/super/airag/aimcp/components/PluginToolEditModal.vue new file mode 100644 index 0000000..1c6ef55 --- /dev/null +++ b/src/views/super/airag/aimcp/components/PluginToolEditModal.vue @@ -0,0 +1,483 @@ + + + + + + + + diff --git a/src/views/super/airag/aimcp/imgs/mcpLogo.png b/src/views/super/airag/aimcp/imgs/mcpLogo.png new file mode 100644 index 0000000..e8e35aa Binary files /dev/null and b/src/views/super/airag/aimcp/imgs/mcpLogo.png differ diff --git a/src/views/super/airag/aimodel/AiModelList.vue b/src/views/super/airag/aimodel/AiModelList.vue new file mode 100644 index 0000000..0c48de6 --- /dev/null +++ b/src/views/super/airag/aimodel/AiModelList.vue @@ -0,0 +1,471 @@ + + + + + diff --git a/src/views/super/airag/aimodel/components/AiModelModal.vue b/src/views/super/airag/aimodel/components/AiModelModal.vue new file mode 100644 index 0000000..a3f16db --- /dev/null +++ b/src/views/super/airag/aimodel/components/AiModelModal.vue @@ -0,0 +1,625 @@ + + + + + + diff --git a/src/views/super/airag/aimodel/components/AiModelSeniorForm.vue b/src/views/super/airag/aimodel/components/AiModelSeniorForm.vue new file mode 100644 index 0000000..5a05aac --- /dev/null +++ b/src/views/super/airag/aimodel/components/AiModelSeniorForm.vue @@ -0,0 +1,393 @@ + + + + + diff --git a/src/views/super/airag/aimodel/components/model.json b/src/views/super/airag/aimodel/components/model.json new file mode 100644 index 0000000..b2bfae9 --- /dev/null +++ b/src/views/super/airag/aimodel/components/model.json @@ -0,0 +1,231 @@ +{ + "data": [ + { + "title": "Anthropic", + "value": "ANTHROPIC", + "LLM": [ + {"label": "claude-sonnet-4-20250514", "value": "claude-sonnet-4-20250514","descr": "【Claude 4系列】Claude Sonnet 4具有卓越推理能力的高性能模型。\n\n支持文本和图像输入,文本输出,拥有200k上下文窗口(1M上下文测试版可用)。","type": "text,image"}, + {"label": "claude-opus-4-20250514", "value": "claude-opus-4-20250514","descr": "【Claude 4系列】Claude Opus 4是最强大、最有能力的模型。\n\n支持文本和图像输入,文本输出,拥有200k上下文窗口,卓越的推理能力。","type": "text,image"}, + {"label": "claude-3-7-sonnet-20250219", "value": "claude-3-7-sonnet-20250219","descr": "【Claude 3.7系列】Claude Sonnet 3.7中型模型,具备卓越的推理能力和效率。\n\n适合企业工作负载和大规模AI部署。","type": "text,image"}, + {"label": "claude-3-5-sonnet-20241022", "value": "claude-3-5-sonnet-20241022","descr": "Claude 3.5 Sonnet是Anthropic最强大的AI模型。\n\n在编程、多步骤工作流、图表解释等复杂任务中表现出色。支持200k上下文长度,支持8k最大输出。","type": "text,image"}, + {"label": "claude-3-5-haiku-20241022", "value": "claude-3-5-haiku-20241022","descr": "【快速模型】Claude 3.5 Haiku是Anthropic最快的AI模型。\n\n响应速度快,成本较低,适合高频交互场景。支持200k上下文长度,支持8k最大输出。","type": "text,image"}, + {"label": "claude-3-opus-20240229", "value": "claude-3-opus-20240229","descr": "Claude 3 Opus是Claude 3系列中性能最强的模型。\n\n在高度复杂的任务上表现出色,如编写高质量代码、数学推理等。支持200k上下文长度,支持4k最大输出。","type": "text,image"}, + {"label": "claude-3-sonnet-20240229", "value": "claude-3-sonnet-20240229","descr": "Claude 3 Sonnet在智能和速度之间取得了良好平衡。\n\n适合企业工作负载和大规模AI部署。支持200k上下文长度,支持4k最大输出。","type": "text,image"}, + {"label": "claude-3-haiku-20240307", "value": "claude-3-haiku-20240307","descr": "Claude 3 Haiku是Claude 3系列中最快的模型。\n\n提供接近即时的响应,适合无缝AI体验。支持200k上下文长度,支持4k最大输出。","type": "text,image"} + ], + "type": ["LLM"], + "baseUrl": "https://api.anthropic.com/v1", + "LLMDefaultValue": "claude-3-5-sonnet-20241022" + }, + { + "title": "DeepSeek", + "value": "DEEPSEEK", + "LLM": [ + {"label": "deepseek-v4-pro", "value": "deepseek-v4-pro","descr": "【官方模型】深度求索 新推出的推理模型R1满血版\n火便全球。\n支持64k上下文,其中支持8k最大回复。","type": "text"}, + {"label": "deepseek-v4-flash", "value": "deepseek-v4-flash","descr": "【官方模型】深度求索 新推出的推理模型R1满血版\n火便全球。\n支持64k上下文,其中支持8k最大回复。","type": "text"}, + {"label": "deepseek-reasoner", "value": "deepseek-reasoner","descr": " 2026/07/24下线,【官方模型】深度求索 新推出的推理模型R1满血版\n火便全球。\n支持64k上下文,其中支持8k最大回复。","type": "text"}, + {"label":"deepseek-chat", "value": "deepseek-chat","descr": "2026/07/24下线,最强开源 MoE 模型 DeepSeek-V3,全球首个在代码、数学能力上与GPT-4-Turbo争锋的模型,在代码、数学的多个榜单上位居全球第二;","type": "text"} + ], + "type": ["LLM"], + "baseUrl": "https://api.deepseek.com/v1", + "LLMDefaultValue": "deepseek-v4-pro" + }, + { + "title": "Ollama", + "value": "OLLAMA", + "LLM": [ + {"label": "llama2", "value": "llama2"}, + {"label": "llama2:13b", "value": "llama2:13b"}, + {"label": "llama2:70b", "value": "llama2:70b"}, + {"label": "llama2-chinese:13b", "value": "llama2-chinese:13b"}, + {"label": "llama3:8b", "value": "llama3:8b"}, + {"label": "llama3:70b", "value": "llama3:70b"}, + {"label": "qwen:0.5b", "value": "qwen:0.5b"}, + {"label": "qwen:1.8b", "value": "qwen:1.8b"}, + {"label": "qwen:4b", "value": "qwen:4b"}, + {"label": "qwen:7b", "value": "qwen:7b"}, + {"label": "qwen:14b", "value": "qwen:14b"}, + {"label": "qwen:32b", "value": "qwen:32b"}, + {"label": "qwen:72b", "value": "qwen:72b"}, + {"label": "qwen:110b", "value": "qwen:110b"}, + {"label": "qwen2:72b-instruct", "value": "qwen2:72b-instruct"}, + {"label": "qwen2:57b-a14b-instruct", "value": "qwen2:57b-a14b-instruct"}, + {"label": "qwen2:7b-instruct", "value": "qwen2:7b-instruct"}, + {"label": "qwen2.5:72b-instruct", "value": "qwen2.5:72b-instruct"}, + {"label": "qwen2.5:32b-instruct", "value": "qwen2.5:32b-instruct"}, + {"label": "qwen2.5:14b-instruct", "value": "qwen2.5:14b-instruct"}, + {"label": "qwen2.5:7b-instruct", "value": "qwen2.5:7b-instruct"}, + {"label": "qwen2.5:1.5b-instruct", "value": "qwen2.5:1.5b-instruct"}, + {"label": "qwen2.5:0.5b-instruct", "value": "qwen2.5:0.5b-instruct"}, + {"label": "qwen2.5:3b-instruct", "value": "qwen2.5:3b-instruct"}, + {"label": "phi3", "value": "phi3"} + ], + "EMBED": [ + {"label": "nomic-embed-text", "value": "nomic-embed-text"} + ], + "type": ["LLM", "EMBED"], + "baseUrl": "http://localhost:11434", + "LLMDefaultValue": "llama2", + "EMBEDDefaultValue": "nomic-embed-text" + }, + { + "title": "OpenAI", + "value": "OPENAI", + "LLM": [ + {"label": "gpt-3.5-turbo", "value": "gpt-3.5-turbo","descr": "纯官方高速GPT3.5系列,目前指向gpt-35-turbo-0125模型,最大回复小于4k。\n综合能力强,过去使用最广泛的文本模型。", "type": "text" + }, + {"label": "gpt-4", "value": "gpt-4","descr": "纯官方GPT4系列。知识库截止于2021年,价格适中,具有中等参数,比gpt-4turbo系列略强。","type": "text"}, + {"label": "gpt-4o", "value": "gpt-4o","descr": "GPT-4o,是openai的新旗舰型号,支持文本和图片分析。\n\n是迈向更自然的人机交互的一步——它接受文本和图像的任意组合作为输入,并生成文本和图像输出的任意组合。","type": "text,image"}, + {"label": "gpt-4o-mini", "value": "gpt-4o-mini","descr": "GPT-4o mini是目前性价比最高的小参数模型,性能介于GPT3.5~GPT4o之间。\n\n成本相比GPT-3.5 Turbo便宜60%以上,支持50种不同语言,用于替代GPT-3.5版本的模型。\n\n4o-mini的图像分析价格和4o差不多,如果有图像分析需求还是4o更好一些。\n\n当前指向 gpt-4o-mini-2024-07-18","type": "text,image"}, + {"label": "gpt-4-turbo", "value": "gpt-4-turbo","descr": "纯官方GPT4系列,支持文本和图片分析,最大回复4k,openai于2024-4-9新增的模型,知识库更新于2023年12月。提高了写作、数学、逻辑推理和编码能力。当前指向gpt-4-turbo-2024-04-09","type": "text,image"}, + {"label": "gpt-4-turbo-preview", "value": "gpt-4-turbo-preview","descr": "纯官方GPT4系列,最大回复4k,知识库更新于2023年4月。当前指向gpt-4-0125-preview","type": "text"}, + {"label": "gpt-3.5-turbo-0125", "value": "gpt-3.5-turbo-0125","descr": "openai于2024年1月25号更新的gpt-3.5模型,最大回复4k。\n\n综合能力强,过去使用最广泛的文本模型。","type": "text"}, + {"label": "gpt-3.5-turbo-1106", "value": "gpt-3.5-turbo-1106","descr": "openai于2023年11月6号更新的gpt-3.5模型,最大回复4k。属于即将被淘汰的模型。\n\n建议使用gpt-3.5-turbo或gpt-4o-mini","type": "text"}, + {"label": "gpt-3.5-turbo-0613", "value": "gpt-3.5-turbo-0613","descr": "通过微调后可以更准确地按照用户的指示进行操作,生成更简洁和针对性的输出。它不仅可以用于文本生成,还可以通过函数调用功能与其他系统和API进行集成,实现更复杂的任务自动化","type": "text"}, + {"label": "gpt-4o-2024-05-13", "value": "gpt-4o-2024-05-13","descr": "GPT-4o,是openai的新旗舰型号,支持文本和图片分析。\n\n是迈向更自然的人机交互的一步——它接受文本和图像的任意组合作为输入,并生成文本和图像输出的任意组合。\n\n该模型为初代的4o模型","type": "text,image"}, + {"label": "gpt-4-turbo-2024-04-09", "value": "gpt-4-turbo-2024-04-09","descr": "纯官方GPT4系列,支持文本和图片分析,最大回复4k,openai于2024-4-9新增的模型,提高了写作、数学、逻辑推理和编码能力。知识库更新于2023年12月。","type": "text,image"}, + {"label": "gpt-4-0125-preview", "value": "gpt-4-0125-preview","descr": "纯官方GPT4系列,最大回复4k,知识库更新于2023年4月。当前与gpt-4-turbo-preview属于同一模型","type": "text"}, + {"label": "gpt-4-1106-preview", "value": "gpt-4-1106-preview","descr": "纯官方GPT4系列,最大回复4k,知识库更新于2023年4月。正在逐渐被新的模型gpt-4-turbo和gpt-4-turbo-preview取代。","type": "text"} + ], + "EMBED": [ + {"label": "text-embedding-ada-002", "value": "text-embedding-ada-002","descr": "用于生成文本嵌入的模型。文本嵌入是将文本转换为数值形式(通常是向量),以便可以用于机器学习模型。","type": "vector,embeddings"}, + {"label": "text-embedding-3-small", "value": "text-embedding-3-small","descr": "用于生成文本的嵌入表示,网络结构较小,计算资源需求较低。虽然可能不如\"large\"版本那样精准,但它更适合于资源受限的环境或需要更快速处理的任务。","type": "vector,embeddings"}, + {"label": "text-embedding-3-large", "value": "text-embedding-3-large","descr": "用于生成文本的嵌入表示,即将文本转换为高维空间中的点,这些点的距离可以表示文本之间的相似度。有较大的网络结构,能够捕捉更丰富的语言特征,适用于需要高质量文本相似度或分类任务的场景。","type": "vector,embeddings"} + ], + "IMAGE": [ + {"label": "gpt-image-1.5", "value": "gpt-image-1.5","descr": "openAI强大的图片生成模型,支持图像生成与编辑功能,生图图片支持中文文字。相比上一版本,拥有更好的指令跟踪和对提示词的遵循。","type": "imageGen"}, + {"label": "gpt-image-1", "value": "gpt-image-1","descr": "是一个图片生成模型,支持图像生成与编辑功能,生图图片支持中文文字。","type": "imageGen"}, + {"label": "dall-e-2", "value": "dall-e-2","descr": "是OpenAI推出的文本生成图像系统","type": "imageGen"}, + {"label": "dall-e-3", "value": "dall-e-3","descr": "是OpenAI开发的第三代文本到图像生成模型,提升了图像细节处理和艺术风格迁移能力","type": "imageGen"} + ], + "type": ["LLM", "EMBED","IMAGE"], + "baseUrl": "https://api.openai.com/v1/", + "LLMDefaultValue": "gpt-4o-mini", + "EMBEDDefaultValue": "text-embedding-ada-002", + "IMAGEDefaultValue": "gpt-image-1.5" + }, + { + "title": "通义千问", + "value": "QWEN", + "LLM": [ + {"label": "qwen-turbo", "value": "qwen-turbo","descr": "通义千问超大规模语言模型,支持中文、英文等不同语言输入。适合文本创作、文本处理、编程辅助、翻译服务、对话模拟。","type": "text"}, + {"label": "qwen-plus", "value": "qwen-plus","descr": "通义千问超大规模语言模型,支持中文、英文等不同语言输入。适合文本创作、文本处理、编程辅助、翻译服务、对话模拟。","type": "text"}, + {"label": "qwen-max", "value": "qwen-max","descr": "暂无描述内容!","type": "text"} + ], + "EMBED": [ + {"label": "text-embedding-v2", "value": "text-embedding-v2","descr": "是一种将文本数据转换为向量的技术,通过深度学习模型将文本的语义信息嵌入到高维向量空间中。这些向量不仅能表达文本内容,还能捕捉文本之间的相似性和关系,从而让计算机高效地进行文本检索、分类、聚类等任务。","type": "vector"} + ], + "IMAGE": [ + { "label": "wan2.2-t2i-plus", "value": "wan2.2-t2i-plus","descr": "全新升级的通义万相2.2文生图,更快的生成速度。在生成图像创意性、稳定性、写实质感方面全面升级,指令遵循更强,原生支持多种风格。支持最大200万像素生成,支持智能提示词改写等。","type": "imageGen"}, + { "label": "wan2.2-t2i-flash", "value": "wan2.2-t2i-flash","descr": "全新升级的通义万相2.2文生图,更快的生成速度。在生成图像创意性、稳定性、写实质感方面全面升级,指令遵循更强,原生支持多种风格。支持最大200万像素生成,支持智能提示词改写等。","type": "imageGen"}, + { "label": "wan2.5-i2i-preview", "value": "wan2.5-i2i-preview","descr": "是万相2.5系列中的图像编辑模型,专注于通用图像编辑任务,支持单图编辑和多图融合功能","type": "imageGen"}, + { "label": "wanx2.1-imageedit", "value": "wanx2.1-imageedit","descr": "是阿里云百炼平台提供的通用图像编辑模型,适用于多种图像处理任务,如风格迁移、内容编辑、尺寸优化等","type": "imageGen"}, + { "label": "wanx-v1", "value": "wanx-v1","descr": "是通义万相系列中的文本生成图像模型,支持中英文双语输入和多种图像风格生成","type": "imageGen"} + ], + "type": ["LLM", "EMBED","IMAGE"], + "baseUrl": "https://dashscope.aliyuncs.com/api/v1/services/", + "baseImageUrl": "https://dashscope.aliyuncs.com/api/v1/", + "LLMDefaultValue": "qwen-plus", + "EMBEDDefaultValue": "text-embedding-v2", + "IMAGEDefaultValue": "wan2.2-t2i-plus" + }, + { + "title": "千帆大模型", + "value": "QIANFAN", + "LLM": [ + {"label": "ERNIE-Bot", "value": "ERNIE-Bot","descr": "是百度推出的一款知识增强大语言模型,主要用于与人对话互动、回答问题、协助创作,帮助人们高效便捷地获取信息、知识和灵感","type": "text"}, + {"label": "ERNIE-Bot 4.0", "value": "ERNIE-Bot 4.0","descr": "百度自行研发的文心产业级知识增强大语言模型4.0版本\n\n实现了基础模型的全面升级,在理解、生成、逻辑和记忆能力上相对ERNIE 3.5都有着显著提升,支持5K输入+2K输出。","type": "text"}, + {"label": "ERNIE-Bot-8K", "value": "ERNIE-Bot-8K","descr": "主要用于数据分析场景,特别是在企业数据分析中表现出色。ERNIE-Bot-8K是百度文心大模型的一个版本,具有模型效果优、生成能力强、应用门槛低等独特优势。","type": "text"}, + {"label": "ERNIE-Bot-turbo", "value": "ERNIE-Bot-turbo","descr": "是一个大语言模型,主要用于对话问答、内容创作生成等任务。它是百度自行研发的大语言模型,覆盖了海量中文数据,具有更强的对话问答和内容创作生成能力","type": "text"}, + {"label": "ERNIE-Speed-128K", "value": "ERNIE-Speed-128K","descr": "是一款基于Transformer结构的轻量级语言模型,旨在满足实时数据处理的需求。它具有高效、低延迟和高准确性的特点,广泛应用于自然语言处理、信息检索和文本分类等领域","type": "text"}, + {"label": "EB-turbo-AppBuilder", "value": "EB-turbo-AppBuilder","descr": "主要用于企业级应用场景,如智能客服、内容创作和知识问答等任务。它是基于文心高性能大语言模型ERNIE-Bot-turbo构建的,针对企业特定需求进行了深度的场景效果优化和输出格式定制,因此在满足企业特定需求方面具有更高的灵活性和实用性","type": "text"}, + {"label": "Yi-34B-Chat", "value": "Yi-34B-Chat","descr": "Yi-34B-Chat是一款基于Transformer架构的生成式预训练语言模型,它拥有340亿个参数,使其在处理自然语言任务时表现出了强大的能力。","type": "text"}, + {"label": "BLOOMZ-7B", "value": "BLOOMZ-7B","descr": "是一个用于生成文本序列的自回归模型,它可以进行多语言处理,支持46种语言和13种编程语言。BLOOMZ-7B是BLOOM模型的一个调优版本,具有更出色的泛化和零样本学习能力,适用于多种任务和场景","type": "text"}, + {"label": "Qianfan-BLOOMZ-7B-compressed", "value": "Qianfan-BLOOMZ-7B-compressed","descr": "是千帆团队在BLOOMZ-7B基础上的压缩版本,融合量化、稀疏化等技术,显存占用降低30%以上。","type": "text"}, + {"label": "Mixtral-8x7B-Instruct", "value": "Mixtral-8x7B-Instruct","descr": "由Mistral AI发布的首个高质量稀疏专家混合模型 (MOE),模型由8个70亿参数专家模型组成,在多个基准测试中表现优于Llama-2-70B及GPT3.5,能够处理32K上下文,在代码生成任务中表现尤为优异。","type": "text"}, + {"label": "Llama-2-7b-chat", "value": "Llama-2-7b-chat","descr": "由Meta AI研发并开源,在编码、推理及知识应用等场景表现优秀,Llama-2-7b-chat是高性能原生开源版本,适用于对话场景。","type": "text"}, + {"label": "Llama-2-13b-chat", "value": "Llama-2-13b-chat","descr": "由Meta AI研发并开源,在编码、推理及知识应用等场景表现优秀,Llama-2-13b-chat是性能与效果均衡的原生开源版本,适用于对话场景。","type": "text"}, + {"label": "Llama-2-70b-chat", "value": "Llama-2-70b-chat","descr": "由Meta AI研发并开源,在编码、推理及知识应用等场景表现优秀,Llama-2-70b-chat是高精度效果的原生开源版本。","type": "text"}, + {"label": "Qianfan-Chinese-Llama-2-7B", "value": "Qianfan-Chinese-Llama-2-7B","descr": "是千帆团队在Llama-2-7b基础上的中文增强版本,在CMMLU、C-EVAL等中文数据集上表现优异。","type": "text"}, + {"label": "ChatGLM2-6B-32K", "value": "ChatGLM2-6B-32K","descr": "是在ChatGLM2-6B的基础上进一步强化了对于长文本的理解能力,能够更好的处理最多32K长度的上下文。","type": "text"}, + {"label": "AquilaChat-7B", "value": "AquilaChat-7B","descr": "是由智源研究院研发,基于Aquila-7B训练的对话模型,支持流畅的文本对话及多种语言类生成任务,通过定义可扩展的特殊指令规范,实现 AquilaChat对其它模型和工具的调用,且易于扩展。","type": "text"} + ], + "EMBED": [ + {"label": "Embedding-V1", "value": "Embedding-V1","descr": "主要用于将离散对象(如文本、图像等)映射为连续的数值向量,以便于计算机处理和机器学习模型的训练和使用","type": "vector,embeddings"}, + {"label": "tao-8k", "value": "tao-8k","descr": "是由Huggingface开发者amu研发并开源的长文本向量表示模型,支持8k上下文长度,模型效果在C-MTEB上居前列,是当前最优的中文长文本embeddings模型之一","type": "vector"}, + {"label": "bge-large-zh", "value": "bge-large-zh","descr": "是由智源研究院研发的中文版文本表示模型,可将任意文本映射为低维稠密向量,以用于检索、分类、聚类或语义匹配等任务,并可支持为大模型调用外部知识。","type": "vector"}, + {"label": "bge-large-en", "value": "bge-large-en","descr": "是由智源研究院研发的英文版文本表示模型,可将任意文本映射为低维稠密向量,以用于检索、分类、聚类或语义匹配等任务,并可支持为大模型调用外部知识。","type": "vector"} + ], + "type": ["LLM", "EMBED"], + "baseUrl": "https://aip.baidubce.com", + "LLMDefaultValue": "Yi-34B-Chat", + "EMBEDDefaultValue": "Embedding-V1" + }, + { + "title": "智谱AI", + "value": "ZHIPU", + "LLM": [ + {"label": "glm-4", "value": "glm-4","descr": "是一个多模态大语言模型,主要用于处理复杂的指令和任务,支持长文本处理、多模态理解和文生图等功能","type": "text,image"}, + {"label": "glm-4v", "value": "glm-4v","descr": "智谱:多模态模型\n\n更懂中文的视觉理解、文生图等多模态模型能力。准确理解各任务场景语言描述及指令,更精确的完成多模态理解类任务,或生成高质量的图片、视频等多模态内容。","type": "text,image"}, + {"label": "glm-4-flash", "value": "glm-4-flash","descr": "该模型官方免费,主要用于处理多种自然语言处理任务,包括智能对话助手、辅助论文翻译、ppt及会议内容生产、网页智能搜索、数据生成和抽取、网页解析、智能规划和决策、辅助科研等场景","type": "text"}, + {"label": "glm-3-turbo", "value": "glm-3-turbo","descr": "是一种基于transformer结构的语言模型,由智谱AI推出。其主要特点包括使用三层transformer结构、采用Turbo机制以实时生成文本、处理长文本输入并具有强大的语言理解能力","type": "text"} + ], + "EMBED": [ + {"label": "Embedding-3", "value": "Embedding-3","descr": "主要用于文本搜索、聚类、推荐等任务。它通过将文本映射到低维向量空间,使得文本之间的语义关系可以通过向量之间的距离或相似度来衡量,从而支持各种基于向量的应用。","type": "vector"}, + {"label": "Embedding-2", "value": "Embedding-2","descr": "用于将高维离散数据映射到低维连续数值向量中,以便机器学习模型能够更好地处理和理解这些数据","type": "vector"} + ], + "IMAGE": [ + {"label": "CogView-4", "value": "CogView-4","descr": "智谱首个支持生成汉字的开源文生图模型,在语义理解、图像生成质量、中英文字生成能力等方面全面提升,支持任意长度的中英双语输入,能够生成在给定范围内的任意分辨率图像。","type": "imageGen"}, + {"label": "Cogview-3-Flash", "value": "Cogview-3-Flash","descr": "是智谱推出的免费图像生成模型,能够根据用户指令生成符合要求且美学评分更高的图像。CogView-3-Flash 主要应用于艺术创作、设计参考、游戏开发、虚拟现实等领域,帮助用户快速实现从文本到图像的转换需求。","type": "imageGen"} + ], + "type": ["LLM", "EMBED", "IMAGE"], + "baseUrl": "https://open.bigmodel.cn", + "LLMDefaultValue": "glm-4-flash", + "EMBEDDefaultValue": "Embedding-2", + "IMAGEDefaultValue": "CogView-4" + }, + { + "title": "Google Gemini", + "value": "GOOGLE", + "LLM": [ + {"label": "gemini-2.5-pro", "value": "gemini-2.5-pro","descr": "【Gemini 2.5系列】Google最新旗舰思维模型,具备强大的推理能力。\n\n支持文本和图像输入,文本输出,拥有1M上下文窗口,在编码、数学和科学推理方面表现卓越。","type": "text,image"}, + {"label": "gemini-2.5-flash", "value": "gemini-2.5-flash","descr": "【Gemini 2.5系列】Google最新的高效思维模型,速度与性能的最佳平衡。\n\n支持文本和图像输入,文本输出,拥有1M上下文窗口,适合高频交互和大规模部署。","type": "text,image"} + ], + "IMAGE": [ + {"label": "gemini-3-pro-image-preview", "value": "gemini-3-pro-image-preview","descr": "Google最新Gemini 3 Pro图像生成预览模型,具备卓越的图像生成质量。\n\n支持文本到图像生成,在细节表现、风格多样性和文字渲染方面表现出色。","type": "imageGen"}, + {"label": "gemini-2.5-flash-image", "value": "gemini-2.5-flash-image","descr": "基于Gemini 2.5 Flash的图像生成模型,支持文本到图像生成。\n\n速度快、成本低,适合高频图像生成场景,支持多种风格。","type": "imageGen"} + ], + "type": ["LLM", "IMAGE"], + "baseUrl": "https://generativelanguage.googleapis.com/v1beta", + "LLMDefaultValue": "gemini-2.5-flash", + "IMAGEDefaultValue": "gemini-2.5-flash-image" + }, + { + "title": "vLLM", + "value": "VLLM", + "LLM": [], + "EMBED": [], + "IMAGE": [], + "type": ["LLM", "EMBED", "IMAGE"], + "baseUrl": "http://localhost:8000/v1" + }, + { + "title": "LM stdio", + "value": "LMSTDIO", + "LLM": [], + "EMBED": [], + "IMAGE": [], + "type": ["LLM", "EMBED", "IMAGE"], + "baseUrl": "http://localhost:1234/v1" + }, + { + "title": "Xinference", + "value": "XINFERENCE", + "LLM": [], + "EMBED": [], + "IMAGE": [], + "type": ["LLM", "EMBED", "IMAGE"], + "baseUrl": "http://localhost:9997/v1" + } + ] +} diff --git a/src/views/super/airag/aimodel/icon/OpenAi.png b/src/views/super/airag/aimodel/icon/OpenAi.png new file mode 100644 index 0000000..83a7e5f Binary files /dev/null and b/src/views/super/airag/aimodel/icon/OpenAi.png differ diff --git a/src/views/super/airag/aimodel/icon/anthropic.png b/src/views/super/airag/aimodel/icon/anthropic.png new file mode 100644 index 0000000..64b6a21 Binary files /dev/null and b/src/views/super/airag/aimodel/icon/anthropic.png differ diff --git a/src/views/super/airag/aimodel/icon/deepspeek.png b/src/views/super/airag/aimodel/icon/deepspeek.png new file mode 100644 index 0000000..6dd9e8e Binary files /dev/null and b/src/views/super/airag/aimodel/icon/deepspeek.png differ diff --git a/src/views/super/airag/aimodel/icon/gemini.png b/src/views/super/airag/aimodel/icon/gemini.png new file mode 100644 index 0000000..043168c Binary files /dev/null and b/src/views/super/airag/aimodel/icon/gemini.png differ diff --git a/src/views/super/airag/aimodel/icon/imstdio.png b/src/views/super/airag/aimodel/icon/imstdio.png new file mode 100644 index 0000000..ccf19a1 Binary files /dev/null and b/src/views/super/airag/aimodel/icon/imstdio.png differ diff --git a/src/views/super/airag/aimodel/icon/ollama.png b/src/views/super/airag/aimodel/icon/ollama.png new file mode 100644 index 0000000..8cd2cf1 Binary files /dev/null and b/src/views/super/airag/aimodel/icon/ollama.png differ diff --git a/src/views/super/airag/aimodel/icon/qianfan.png b/src/views/super/airag/aimodel/icon/qianfan.png new file mode 100644 index 0000000..ace23e8 Binary files /dev/null and b/src/views/super/airag/aimodel/icon/qianfan.png differ diff --git a/src/views/super/airag/aimodel/icon/qianwen.png b/src/views/super/airag/aimodel/icon/qianwen.png new file mode 100644 index 0000000..477b1b6 Binary files /dev/null and b/src/views/super/airag/aimodel/icon/qianwen.png differ diff --git a/src/views/super/airag/aimodel/icon/vllm.png b/src/views/super/airag/aimodel/icon/vllm.png new file mode 100644 index 0000000..1ead997 Binary files /dev/null and b/src/views/super/airag/aimodel/icon/vllm.png differ diff --git a/src/views/super/airag/aimodel/icon/xinference.svg b/src/views/super/airag/aimodel/icon/xinference.svg new file mode 100644 index 0000000..86cdc20 --- /dev/null +++ b/src/views/super/airag/aimodel/icon/xinference.svg @@ -0,0 +1,25 @@ + + + Created with Pixso. + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/views/super/airag/aimodel/icon/zhipuai.png b/src/views/super/airag/aimodel/icon/zhipuai.png new file mode 100644 index 0000000..4e1000c Binary files /dev/null and b/src/views/super/airag/aimodel/icon/zhipuai.png differ diff --git a/src/views/super/airag/aimodel/model.api.ts b/src/views/super/airag/aimodel/model.api.ts new file mode 100644 index 0000000..f1dc2c2 --- /dev/null +++ b/src/views/super/airag/aimodel/model.api.ts @@ -0,0 +1,71 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +enum Api { + list = '/airag/airagModel/list', + save = '/airag/airagModel/add', + testConn = '/airag/airagModel/test', + delete = '/airag/airagModel/delete', + queryById = '/airag/airagModel/queryById', + edit = '/airag/airagModel/edit', +} + +/** + * 查询AI模型 + * @param params + */ +export const list = (params) => { + return defHttp.get({ url: Api.list, params }, { isTransformResponse: false }); +}; + +/** + * 根据id查询AI模型 + * @param params + */ +export const queryById = (params) => { + return defHttp.get({ url: Api.queryById, params }, { isTransformResponse: false }); +}; + +/** + * 新增AI模型 + * + * @param params + */ +export const saveModel = (params) => { + return defHttp.post({ url: Api.save, params }); +}; + +/** + * 编辑AI模型 + * + * @param params + */ +export const editModel = (params) => { + return defHttp.put({ url: Api.edit, params }); +}; + +/** + * 测试链接 + * + * @param params + */ +export const testConn = (params) => { + return defHttp.post({ url: Api.testConn, params, timeout: 2*60*1000 }); +}; + +/** + * 删除数据权限 + */ +export const deleteModel = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除名称为' + params.name + '的模型吗?', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.delete, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; diff --git a/src/views/super/airag/aimodel/model.data.ts b/src/views/super/airag/aimodel/model.data.ts new file mode 100644 index 0000000..cb64692 --- /dev/null +++ b/src/views/super/airag/aimodel/model.data.ts @@ -0,0 +1,128 @@ +import { FormSchema } from '@/components/Form'; + +import anthropic from './icon/anthropic.png'; +import deepspeek from './icon/deepspeek.png'; +import ollama from './icon/ollama.png'; +import OpenAi from './icon/OpenAi.png'; +import qianfan from './icon/qianfan.png'; +import qianwen from './icon/qianwen.png'; +import zhipuai from './icon/zhipuai.png'; +import xinference from './icon/xinference.svg'; +import vllm from './icon/vllm.png'; +import imstdio from './icon/imstdio.png'; +import gemini from './icon/gemini.png'; +import { ref } from 'vue'; + +/** + * 表单 + */ +export const formSchema: FormSchema[] = [ + { + label: 'id', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '模型名称', + field: 'name', + required: true, + component: 'Input', + }, + { + label: '模型类型', + field: 'modelType', + slot: 'modelType', + required: true, + component: 'Select', + }, + { + label: '基础模型', + field: 'modelName', + required: true, + slot: 'modelName', + component: 'Select', + }, + { + label: 'API域名', + field: 'baseUrl', + required: true, + component: 'Input' + }, + { + label: 'API Key', + field: 'apiKey', + required: ({ values }) => values.provider !== 'XINFERENCE', + component: 'InputPassword', + componentProps: { + autocomplete: 'new-password', + }, + ifShow: ({ values }) => { + if(values.provider==="OLLAMA"){ + return false; + } + return true; + }, + }, + { + label: 'Secret Key', + field: 'secretKey', + required: true, + component: 'InputPassword', + ifShow: ({ values }) => { + if(values.provider==='DEEPSEEK' || values.provider==="OLLAMA" || values.provider==="OPENAI" + || values.provider==="ZHIPU" || values.provider==="QWEN" || values.provider==="ANTHROPIC" + || values.provider==="XINFERENCE" || values.provider==="VLLM" || values.provider === 'LMSTDIO' + || values.provider === "GOOGLE"){ + return false; + } + return true; + }, + }, + { + label: 'HTTP1.1协议', + field: 'httpVersionOne', + component: 'Switch', + defaultValue: 1, + helpMessage: '是否使用HTTP1.1协议,在长时间无响应的情况下,可以尝试关闭此开关', + componentProps: { + checkedValue: 1, + unCheckedValue: 0, + }, + ifShow: ({ values }) => { + return values.provider === 'VLLM' || values.provider === 'LMSTDIO' || values.provider === 'XINFERENCE'; + }, + }, + { + label: '额外参数', + field: 'extraParams', + slot: 'extraParams', + component: 'Input', + ifShow: ({ values }) => values.modelType === 'LLM', + }, + { + label: '供应者', + field: 'provider', + component: 'Input', + show: false, + }, +]; + +/** + * 图片路径映射 + * + * @param name + */ +export const imageList = ref({ + ANTHROPIC: anthropic, + DEEPSEEK: deepspeek, + OLLAMA: ollama, + OPENAI: OpenAi, + QIANFAN: qianfan, + QWEN: qianwen, + ZHIPU: zhipuai, + XINFERENCE: xinference, + VLLM: vllm, + LMSTDIO: imstdio, + GOOGLE: gemini, +}); diff --git a/src/views/super/airag/aiposter/AiPainting.vue b/src/views/super/airag/aiposter/AiPainting.vue new file mode 100644 index 0000000..930ad8a --- /dev/null +++ b/src/views/super/airag/aiposter/AiPainting.vue @@ -0,0 +1,414 @@ + + + + + diff --git a/src/views/super/airag/aiposter/AiPoster.data.ts b/src/views/super/airag/aiposter/AiPoster.data.ts new file mode 100644 index 0000000..d47b180 --- /dev/null +++ b/src/views/super/airag/aiposter/AiPoster.data.ts @@ -0,0 +1,311 @@ +import { FormSchema } from '@/components/Form'; + +export const formSchema: FormSchema[] = [ + { + field: 'drawModelId', + label: '模型', + component: 'JDictSelectTag', + required: true, + helpMessage: [ + '1、需要选择在模型中已有的图像模型', + '2、智普语言模型不支持尺寸设置', + "3、openAi旧版模型如(dall-e-2)需要选择尺寸,新版模型直接输入'竖版: 9:16即可'", + '4、当前只有千问万象模型(wanx2.1-imageedit,wan2.5-i2i-preview)支持图生图', + '5、wan2.5-i2i-preview支持多张图片', + '6、当前文生图openAi效果最佳', + ], + componentProps: { + dictCode: "airag_model where model_type = 'IMAGE' and activate_flag = 1,name,id", + }, + }, + { + field: 'content', + label: '提示词', + component: 'InputTextArea', + required: true, + componentProps: { + rows: 10, + placeholder: '请输入提示词,例如:一只可爱的猫咪,赛博朋克风格', + }, + }, + { + field: 'imageUrl', + label: '参考图', + component: 'JImageUpload', + componentProps: { + fileMax: 2, + }, + }, + { + field: 'imageSize', + label: '图片尺寸', + component: 'Select', + defaultValue: '720*1280', + componentProps: { + options: [ + { label: '1:1 (1024x1024)', value: '1024*1024' }, + { label: '16:9 (1280x720)', value: '1280*720' }, + { label: '9:16 (720x1280)', value: '720*1280' }, + { label: '4:3 (1024x768)', value: '1024*768' }, + { label: '3:4 (768x1024)', value: '768*1024' }, + ], + }, + }, +]; + +/** + * 混图表单 + */ +export const mixFormSchema: FormSchema[] = [ + { + field: 'drawModelId', + label: '模型', + component: 'JDictSelectTag', + required: true, + helpMessage: [ + '1、需要选择在模型中已有的图像模型', + '2、当前支持通义万象模型wan2.5-i2i-preview', + ], + componentProps: { + dictCode: "airag_model where model_type = 'IMAGE' and activate_flag = 1,name,id", + }, + }, + { + field: 'imageSize', + label: '尺寸', + component: 'Select', + defaultValue: '720*1280', + componentProps: { + options: [ + { label: '1:1 (1024x1024)', value: '1024*1024' }, + { label: '16:9 (1280x720)', value: '1280*720' }, + { label: '9:16 (720x1280)', value: '720*1280' }, + { label: '4:3 (1024x768)', value: '1024*768' }, + { label: '3:4 (768x1024)', value: '768*1024' }, + ], + }, + }, + { + field: 'imageUrl', + label: '上传图像', + component: 'JImageUpload', + required: true, + componentProps: { + fileMax: 3, + text: '上传图像', + }, + rules: [ + { + required: true, + validator: async (_, value) => { + if (!value) { + return Promise.reject('请上传图像'); + } + const images = value.split(','); + if (images.length < 2) { + return Promise.reject('合成至少2张图片'); + } + return Promise.resolve(); + }, + }, + ], + }, + { + field: 'content', + label: '提示词', + component: 'InputTextArea', + componentProps: { + rows: 4, + placeholder: '如将图一的话花瓶放到图二中', + }, + }, +]; + +/** + * 绘画的表单 + */ +export const drawFormSchema: FormSchema[] = [ + { + field: 'drawModelId', + label: '模型', + component: 'JDictSelectTag', + required: true, + helpMessage: [ + '1、需要选择在模型中已有的图像模型', + '2、智普语言模型不支持尺寸设置', + "3、openAi旧版模型如(dall-e-2)需要选择尺寸,新版模型直接输入'竖版: 9:16即可'", + '4、当前只有千问万象模型(wanx2.1-imageedit,wan2.5-i2i-preview)支持图生图', + '5、wan2.5-i2i-preview支持多张图片', + '6、当前文生图openAi效果最佳', + ], + componentProps: { + dictCode: "airag_model where model_type = 'IMAGE' and activate_flag = 1,name,id", + }, + }, + { + field: 'content', + label: '提示词', + component: 'InputTextArea', + required: true, + componentProps: { + rows: 5, + placeholder: '请输入提示词,例如:一只可爱的猫咪', + }, + }, + { + field: 'imageSize', + label: '图片尺寸', + component: 'Select', + defaultValue: '720*1280', + componentProps: { + options: [ + { label: '1:1 (1024x1024)', value: '1024*1024' }, + { label: '16:9 (1280x720)', value: '1280*720' }, + { label: '9:16 (720x1280)', value: '720*1280' }, + { label: '4:3 (1024x768)', value: '1024*768' }, + { label: '3:4 (768x1024)', value: '768*1024' }, + ], + }, + }, + { + field: 'style', + label: '风格', + component: 'Select', + defaultValue: 'modernOrganic', + componentProps: { + options: [ + { label: '赛博朋克', value: 'cyberpunk' }, + { label: '星际', value: 'star' }, + { label: '动漫', value: 'anime' }, + { label: '日本漫画', value: 'japaneseComicsManga' }, + { label: '水墨画风格', value: 'inkWashPaintingStyle' }, + { label: '原创', value: 'original' }, + { label: '风景画', value: 'landscape' }, + { label: '插画', value: 'illustration' }, + { label: '漫画', value: 'manga' }, + { label: '现代自然', value: 'modernOrganic' }, + { label: '创世纪', value: 'genesis' }, + { label: '海报风格', value: 'posterstyle' }, + { label: '超现实主义', value: 'surrealism' }, + { label: '素描', value: 'sketch' }, + { label: '写实', value: 'realism' }, + { label: '水彩画', value: 'watercolorPainting' }, + { label: '立体主义', value: 'cubism' }, + { label: '黑白', value: 'blackAndWhite' }, + { label: '胶片摄影风格', value: 'fmPhotography' }, + { label: '电影化', value: 'cinematic' }, + { label: '清晰的面部特征', value: 'clearFacialFeatures' }, + ], + }, + }, + { + field: 'visualAngle', + label: '视角', + component: 'Select', + defaultValue: 'frontView', + componentProps: { + options: [ + { label: '宽视角', value: 'wideView' }, + { label: '鸟瞰视角', value: 'birdView' }, + { label: '顶视角', value: 'topView' }, + { label: '仰视角', value: 'upview' }, + { label: '正面视角', value: 'frontView' }, + { label: '头部特写', value: 'headshot' }, + { label: '超广角视角', value: 'ultrawideshot' }, + { label: '中景', value: 'mediumShot' }, + { label: '远景', value: 'longShot' }, + { label: '景深', value: 'depthOfField' }, + ], + }, + }, + { + field: 'characterShot', + label: '人物镜头', + component: 'Select', + defaultValue: 'fullLengthShot', + componentProps: { + options: [ + { label: '脸部特写', value: 'faceShot' }, + { label: '大特写', value: 'bigCloseUp' }, + { label: '特写', value: 'closeUp' }, + { label: '腰部以上', value: 'waistShot' }, + { label: '膝盖以上', value: 'kneeShot' }, + { label: '全身照', value: 'fullLengthShot' }, + { label: '极远景', value: 'extraLongShot' }, + ], + }, + }, + { + field: 'lighting', + label: '灯光', + component: 'Select', + defaultValue: 'naturalLight', + componentProps: { + options: [ + { label: '冷光', value: 'coldLight' }, + { label: '暖光', value: 'warmLight' }, + { label: '硬光', value: 'hardLighting' }, + { label: '戏剧性光线', value: 'dramaticLight' }, + { label: '反射光', value: 'reflectionLight' }, + { label: '薄雾', value: 'mistyFoggy' }, + { label: '自然光', value: 'naturalLight' }, + { label: '阳光', value: 'sunLight' }, + { label: '情绪化', value: 'moody' }, + ], + }, + }, +]; + +/** + * 换脸表单 + */ +export const faceSwapFormSchema: FormSchema[] = [ + { + field: 'drawModelId', + label: '模型', + component: 'JDictSelectTag', + required: true, + helpMessage: [ + '1、需要选择在模型中已有的图像模型', + '2、当前只支持通义万象模型(wan2.5-i2i-preview)' + ], + componentProps: { + dictCode: "airag_model where model_type = 'IMAGE' and activate_flag = 1,name,id", + }, + }, + { + field: 'sourceImage', + label: '你的头像', + component: 'JImageUpload', + required: true, + componentProps: { + fileMax: 1, + text: '上传头像', + }, + }, + { + field: 'targetImage', + label: '明星图', + component: 'JImageUpload', + required: true, + componentProps: { + fileMax: 1, + text: '上传明星图', + }, + }, + { + field: 'imageSize', + label: '图片尺寸', + component: 'Select', + defaultValue: '720*1280', + componentProps: { + options: [ + { label: '1:1 (1024x1024)', value: '1024*1024' }, + { label: '16:9 (1280x720)', value: '1280*720' }, + { label: '9:16 (720x1280)', value: '720*1280' }, + { label: '4:3 (1024x768)', value: '1024*768' }, + { label: '3:4 (768x1024)', value: '768*1024' }, + ], + }, + }, +]; diff --git a/src/views/super/airag/aiposter/AiPoster.vue b/src/views/super/airag/aiposter/AiPoster.vue new file mode 100644 index 0000000..73f2f6d --- /dev/null +++ b/src/views/super/airag/aiposter/AiPoster.vue @@ -0,0 +1,478 @@ + + + + + diff --git a/src/views/super/airag/aiprompts/AiragExtData.api.ts b/src/views/super/airag/aiprompts/AiragExtData.api.ts new file mode 100644 index 0000000..50cd406 --- /dev/null +++ b/src/views/super/airag/aiprompts/AiragExtData.api.ts @@ -0,0 +1,94 @@ +import {defHttp} from '/@/utils/http/axios'; +import { useMessage } from "/@/hooks/web/useMessage"; + +const { createConfirm } = useMessage(); + +enum Api { + list = '/airag/extData/list', + queryById = '/airag/extData/queryById', + save='/airag/extData/add', + edit='/airag/extData/edit', + deleteOne = '/airag/extData/delete', + deleteBatch = '/airag/extData/deleteBatch', + importExcel = '/airag/extData/importExcel', + exportXls = '/airag/extData/exportXls', + debugEvaluator = '/airag/extData/evaluator/debug', + + queryTrackById = '/airag/extData/queryTrackById', + getTrackList = '/airag/extData/getTrackList', +} +/** + * 导出api + * @param params + */ +export const getExportUrl = Api.exportXls; +/** + * 导入api + */ +export const getImportUrl = Api.importExcel; +/** + * 列表接口 + * @param params + */ +export const list = (params) => + defHttp.get({url: Api.list, params}); +/** + * 调用轨迹接口 + * @param params + */ +export const getTrackList = (params) => + defHttp.get({url: Api.getTrackList, params}); +/** + * 根据数据集id查询数据 + * @param params + */ +export const queryById = (params) => + defHttp.get({url: Api.queryById, params},{isTransformResponse: false}); +/** + * 根据数据集id查询数据 + * @param params + */ +export const queryTrackById = (params) => + defHttp.get({url: Api.queryTrackById, params},{isTransformResponse: false}); + +/** + * 删除单个 + */ +export const deleteOne = (params,handleSuccess) => { + return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => { + handleSuccess(); + }); +} +/** + * 批量删除 + * @param params + */ +export const batchDelete = (params, handleSuccess) => { + createConfirm({ + iconType: 'warning', + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => { + handleSuccess(); + }); + } + }); +} +/** + * 保存或者更新 + * @param params + */ +export const saveOrUpdate = (params, isUpdate,showSuccessMsg=true) => { + const url = isUpdate ? Api.edit : Api.save; + return defHttp.post({url: url, params},{successMessageMode:showSuccessMsg?'success':'none'}); +} +/** + * 調試 + * @param params + */ +export const debugEvaluator = (params) => { + return defHttp.post({url: Api.debugEvaluator, params},{ isTransformResponse: false }); +} diff --git a/src/views/super/airag/aiprompts/AiragExtData.data.ts b/src/views/super/airag/aiprompts/AiragExtData.data.ts new file mode 100644 index 0000000..85a7221 --- /dev/null +++ b/src/views/super/airag/aiprompts/AiragExtData.data.ts @@ -0,0 +1,61 @@ +import { BasicColumn } from '/@/components/Table'; +import { FormSchema } from '/@/components/Table'; +//列表数据 +export const columns: BasicColumn[] = [ + { + title: '名称', + align: 'center', + dataIndex: 'name', + }, + { + title: '描述信息', + align: 'center', + dataIndex: 'descr', + }, +]; +//查询数据 +export const searchFormSchema: FormSchema[] = [ + { + label: '名称', + field: 'name', + component: 'Input', + colProps: { + span: 6, + }, + }, +]; +//表单数据 +export const formSchema: FormSchema[] = [ + { + label: '业务类型标识', + field: 'bizType', + component: 'Input', + defaultValue: 'evaluator', + show: false, + }, + { + label: '名称', + field: 'name', + component: 'Input', + }, + { + label: '描述信息', + field: 'descr', + component: 'InputTextArea', + }, + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, +]; + +/** + * 流程表单调用这个方法获取formSchema + * @param param + */ +export function getBpmFormSchema(_formData): FormSchema[] { + // 默认和原始表单保持一致 如果流程中配置了权限数据,这里需要单独处理formSchema + return formSchema; +} diff --git a/src/views/super/airag/aiprompts/AiragExtDataExperiment.vue b/src/views/super/airag/aiprompts/AiragExtDataExperiment.vue new file mode 100644 index 0000000..c96cfe9 --- /dev/null +++ b/src/views/super/airag/aiprompts/AiragExtDataExperiment.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/src/views/super/airag/aiprompts/AiragExtDataList.vue b/src/views/super/airag/aiprompts/AiragExtDataList.vue new file mode 100644 index 0000000..026acb6 --- /dev/null +++ b/src/views/super/airag/aiprompts/AiragExtDataList.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/src/views/super/airag/aiprompts/AiragPrompts.api.ts b/src/views/super/airag/aiprompts/AiragPrompts.api.ts new file mode 100644 index 0000000..53fe4f2 --- /dev/null +++ b/src/views/super/airag/aiprompts/AiragPrompts.api.ts @@ -0,0 +1,81 @@ +import {defHttp} from '/@/utils/http/axios'; +import { useMessage } from "/@/hooks/web/useMessage"; + +const { createConfirm } = useMessage(); + +enum Api { + list = '/airag/prompts/list', + queryById = '/airag/prompts/queryById', + save='/airag/prompts/add', + edit='/airag/prompts/edit', + deleteOne = '/airag/prompts/delete', + deleteBatch = '/airag/prompts/deleteBatch', + importExcel = '/airag/prompts/importExcel', + exportXls = '/airag/prompts/exportXls', + + promptExperiment = '/airag/prompts/experiment', +} +/** + * 导出api + * @param params + */ +export const getExportUrl = Api.exportXls; +/** + * 导入api + */ +export const getImportUrl = Api.importExcel; +/** + * 列表接口 + * @param params + */ +export const list = (params) => + defHttp.get({url: Api.list, params}); + +/** + * 根据ID查询提示词详情 + * @param id 提示词ID + */ +export const queryById = (id: string) => + defHttp.get({url: Api.queryById, params: {id}}); + +/** + * 删除单个 + */ +export const deleteOne = (params,handleSuccess) => { + return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => { + handleSuccess(); + }); +} +/** + * 批量删除 + * @param params + */ +export const batchDelete = (params, handleSuccess) => { + createConfirm({ + iconType: 'warning', + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => { + handleSuccess(); + }); + } + }); +} +/** + * 保存或者更新 + * @param params + */ +export const saveOrUpdate = (params, isUpdate) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({url: url, params}); +} +/** + * 实验 + * @param params + */ +export const promptExperiment = (params) => { + return defHttp.post({url: Api.promptExperiment, params},{ isTransformResponse: false }); +} diff --git a/src/views/super/airag/aiprompts/AiragPrompts.data.ts b/src/views/super/airag/aiprompts/AiragPrompts.data.ts new file mode 100644 index 0000000..7c74b22 --- /dev/null +++ b/src/views/super/airag/aiprompts/AiragPrompts.data.ts @@ -0,0 +1,143 @@ +import { BasicColumn } from '/@/components/Table'; +import { FormSchema } from '/@/components/Table'; +import {duplicateCheckDelay} from "@/views/system/user/user.api"; +import {pinyin} from "pinyin-pro"; +//列表数据 +export const columns: BasicColumn[] = [ + { + title: '名称', + align: 'center', + dataIndex: 'name', + }, + { + title: '功能描述', + align: 'center', + dataIndex: 'description', + }, + // { + // title: '状态', + // align: 'center', + // dataIndex: 'status', + // }, + { + title: '最近提交人', + align: 'center', + dataIndex: 'updateBy', + }, + { + title: '最近提交时间', + align: 'center', + dataIndex: 'updateTime', + }, + { + title: '创建人', + align: 'center', + dataIndex: 'createBy', + }, + { + title: '创建时间', + align: 'center', + dataIndex: 'createTime', + } +]; +//查询数据 +export const searchFormSchema: FormSchema[] = [ + { + label: '名称', + field: 'name', + component: 'Input', + }, +]; +// 名称最大长度 +export const NAME_MAX_LENGTH = 40; +// 编码最大长度 +export const CODE_MAX_LENGTH = 50; +//表单数据 +export const formSchema: FormSchema[] = [ + { + label: '名称', + field: 'name', + component: 'Input', + componentProps: ({ formModel }) => { + return { + placeholder: '例如:SQL转换', + maxlength: 40, + showCount: true, + onChange: (e: ChangeEvent) => { + if(formModel.id){ + return + } + let code = pinyin(e.target.value, { + toneType: 'none', + type: 'array', + nonZh: 'consecutive', + }).join('_'); + code = code.replace(/[^a-zA-Z0-9_\-]/g, ''); + formModel.promptKey = code; + }, + }; + }, + dynamicRules() { + return [ + {required: true, message: '请输入提示词名称'}, + { + max: NAME_MAX_LENGTH, + message: `名称长度不能超过${NAME_MAX_LENGTH}个字符`, + }, + ]; + } + }, + { + label: '提示词编码', + field: 'promptKey', + component: 'Input', + dynamicRules({ model }) { + return [ + { required: true, message: '提示词编码' }, + { + async validator(_, value) { + if (value?.length > CODE_MAX_LENGTH) { + throw `编码长度不能超过${CODE_MAX_LENGTH}个字符`; + } + const pattern = /^[a-z|A-Z][a-z|A-Z\d_-]*$/; + if (!pattern.test(value)) { + throw '编码必须以字母开头,可包含数字、下划线、横杠'; + } else if (/[A-Z]/.test(value)) { + throw '不支持大写字母'; + } else { + const res = await duplicateCheckDelay({ + tableName: 'airag_prompts', + fieldName: 'prompt_key', + fieldVal: value, + dataId: model.id, + }) as any; + if (!res.success) { + throw '表单编码已存在!'; + } + } + }, + }, + ]; + } + }, + { + label: '提示词功能描述', + field: 'description', + component: 'InputTextArea', + }, + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, +]; + +/** + * 流程表单调用这个方法获取formSchema + * @param param + */ +export function getBpmFormSchema(_formData): FormSchema[] { + // 默认和原始表单保持一致 如果流程中配置了权限数据,这里需要单独处理formSchema + return formSchema; +} diff --git a/src/views/super/airag/aiprompts/AiragPromptsList.vue b/src/views/super/airag/aiprompts/AiragPromptsList.vue new file mode 100644 index 0000000..a79518a --- /dev/null +++ b/src/views/super/airag/aiprompts/AiragPromptsList.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/src/views/super/airag/aiprompts/components/AiEvaluatorDebugModal.vue b/src/views/super/airag/aiprompts/components/AiEvaluatorDebugModal.vue new file mode 100644 index 0000000..f18d5fd --- /dev/null +++ b/src/views/super/airag/aiprompts/components/AiEvaluatorDebugModal.vue @@ -0,0 +1,894 @@ + + + + + diff --git a/src/views/super/airag/aiprompts/components/AiEvaluatorSettingModal.vue b/src/views/super/airag/aiprompts/components/AiEvaluatorSettingModal.vue new file mode 100644 index 0000000..02cd660 --- /dev/null +++ b/src/views/super/airag/aiprompts/components/AiEvaluatorSettingModal.vue @@ -0,0 +1,917 @@ + + + + + + diff --git a/src/views/super/airag/aiprompts/components/AiPromptSettingModal.vue b/src/views/super/airag/aiprompts/components/AiPromptSettingModal.vue new file mode 100644 index 0000000..94aba87 --- /dev/null +++ b/src/views/super/airag/aiprompts/components/AiPromptSettingModal.vue @@ -0,0 +1,955 @@ + + + + + + diff --git a/src/views/super/airag/aiprompts/components/AiragDataSetColumnModal.vue b/src/views/super/airag/aiprompts/components/AiragDataSetColumnModal.vue new file mode 100644 index 0000000..9fdc273 --- /dev/null +++ b/src/views/super/airag/aiprompts/components/AiragDataSetColumnModal.vue @@ -0,0 +1,364 @@ + + + + + diff --git a/src/views/super/airag/aiprompts/components/AiragDataSetDataDrawer.vue b/src/views/super/airag/aiprompts/components/AiragDataSetDataDrawer.vue new file mode 100644 index 0000000..3abf511 --- /dev/null +++ b/src/views/super/airag/aiprompts/components/AiragDataSetDataDrawer.vue @@ -0,0 +1,312 @@ + + + + + diff --git a/src/views/super/airag/aiprompts/components/AiragDataSetModal.vue b/src/views/super/airag/aiprompts/components/AiragDataSetModal.vue new file mode 100644 index 0000000..1242ee0 --- /dev/null +++ b/src/views/super/airag/aiprompts/components/AiragDataSetModal.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/src/views/super/airag/aiprompts/components/AiragExtDataModal.vue b/src/views/super/airag/aiprompts/components/AiragExtDataModal.vue new file mode 100644 index 0000000..9468530 --- /dev/null +++ b/src/views/super/airag/aiprompts/components/AiragExtDataModal.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/views/super/airag/aiprompts/components/AiragInvokeRecordsDrawer.vue b/src/views/super/airag/aiprompts/components/AiragInvokeRecordsDrawer.vue new file mode 100644 index 0000000..f45ebfc --- /dev/null +++ b/src/views/super/airag/aiprompts/components/AiragInvokeRecordsDrawer.vue @@ -0,0 +1,275 @@ + + + + + diff --git a/src/views/super/airag/aiprompts/components/AiragPromptsModal.vue b/src/views/super/airag/aiprompts/components/AiragPromptsModal.vue new file mode 100644 index 0000000..1e08d7c --- /dev/null +++ b/src/views/super/airag/aiprompts/components/AiragPromptsModal.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/src/views/super/airag/aiprompts/components/AiragTrackDetailModal.vue b/src/views/super/airag/aiprompts/components/AiragTrackDetailModal.vue new file mode 100644 index 0000000..aebfacb --- /dev/null +++ b/src/views/super/airag/aiprompts/components/AiragTrackDetailModal.vue @@ -0,0 +1,276 @@ + + + + + + + diff --git a/src/views/super/airag/aiprompts/components/EvaluatorDebug.vue b/src/views/super/airag/aiprompts/components/EvaluatorDebug.vue new file mode 100644 index 0000000..1db980c --- /dev/null +++ b/src/views/super/airag/aiprompts/components/EvaluatorDebug.vue @@ -0,0 +1,458 @@ + + + + + diff --git a/src/views/super/airag/aivideo/AiVideo.api.ts b/src/views/super/airag/aivideo/AiVideo.api.ts new file mode 100644 index 0000000..3aa3d39 --- /dev/null +++ b/src/views/super/airag/aivideo/AiVideo.api.ts @@ -0,0 +1,37 @@ +// AI Video API 接口配置 +import { defHttp } from '@/utils/http/axios'; + +enum Api { + submit = '/airag/video/submit', + query = '/airag/video/query', + listByUser = '/airag/video/listByUser', + deleteRecord = '/airag/video/deleteVideoRecord', +} + +/** + * 提交视频生成任务 + */ +export const submitVideoTask = (params: any) => { + return defHttp.post({ url: Api.submit, params }, { isTransformResponse: false }); +}; + +/** + * 查询视频生成任务状态 + */ +export const queryVideoTask = (taskId: string) => { + return defHttp.get({ url: `${Api.query}/${taskId}` }, { isTransformResponse: false }); +}; + +/** + * 根据用户id查询视频列表 + */ +export const getVideoListByUser = (params: { userId: string }) => { + return defHttp.get({ url: Api.listByUser, params }, { isTransformResponse: false }); +}; + +/** + * 删除视频记录 + */ +export const deleteVideoRecord = (params) => { + return defHttp.delete({ url: Api.deleteRecord, params }, { isTransformResponse: false, joinParamsToUrl: true }); +}; diff --git a/src/views/super/airag/aivideo/AiVideo.data.ts b/src/views/super/airag/aivideo/AiVideo.data.ts new file mode 100644 index 0000000..b1329ce --- /dev/null +++ b/src/views/super/airag/aivideo/AiVideo.data.ts @@ -0,0 +1,76 @@ +import { FormSchema } from '@/components/Form'; +import { h } from 'vue'; +import { Button } from 'ant-design-vue'; + +/** + * 视频生成表单配置 + */ +export const videoFormSchemas: FormSchema[] = [ + // { + // label: '模型', + // field: 'model', + // component: 'JDictSelectTag', + // required: true, + // defaultValue: 'video-generation-1', + // componentProps: { + // dictCode: "airag_model where model_type = 'VIDEO' and activate_flag = 1,name,id", + // placeholder: '请选择视频生成模型', + // }, + // }, + { + label: '视频尺寸', + field: 'size', + component: 'Select', + defaultValue: '1920x1080', + componentProps: { + options: [ + { label: '1280x720 (720P)', value: '1280x720' }, + { label: '720x1280', value: '720x1280' }, + { label: '1024x1024', value: '1024x1024' }, + { label: '1920x1080 (1080P)', value: '1920x1080' }, + { label: '1080x1920', value: '1080x1920' }, + { label: '2048x1080 (2K)', value: '2048x1080' }, + { label: '3840x2160 (4K)', value: '3840x2160' }, + ], + placeholder: '请选择视频尺寸', + }, + }, + { + label: '视频帧率', + field: 'fps', + component: 'Select', + defaultValue: 30, + componentProps: { + options: [ + { label: '30 FPS', value: 30 }, + { label: '60 FPS', value: 60 }, + ], + placeholder: '请选择视频帧率', + }, + }, + { + label: '视频时长', + field: 'duration', + component: 'Select', + defaultValue: 5, + componentProps: { + options: [ + { label: '5秒', value: 5 }, + { label: '10秒', value: 10 }, + ], + placeholder: '请选择视频时长', + }, + }, + { + label: '是否ai合成音效', + field: 'izAiAudio', + component: 'Select', + defaultValue: 0, + componentProps: { + options: [ + { label: '否', value: 0 }, + { label: '是', value: 1 }, + ], + }, + } +]; diff --git a/src/views/super/airag/aivideo/AiVideo.less b/src/views/super/airag/aivideo/AiVideo.less new file mode 100644 index 0000000..003b59d --- /dev/null +++ b/src/views/super/airag/aivideo/AiVideo.less @@ -0,0 +1,375 @@ +// AI Video 页面样式 +.ai-video-page { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + padding: 16px; + background-color: #f0f2f5; + display: flex; + flex-direction: column; + box-sizing: border-box; + overflow: hidden; + + .page-header { + margin-bottom: 16px; + background: #fff; + padding: 16px 24px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: space-between; + + .title { + font-size: 20px; + font-weight: 600; + color: #1f2329; + } + + .subtitle { + color: #8f959e; + font-size: 14px; + } + } + + .content-wrapper { + flex: 1; + display: flex; + gap: 16px; + overflow: hidden; + } + + .control-panel { + width: 380px; + min-width: 320px; + background: #fff; + border-radius: 8px; + display: flex; + flex-direction: column; + padding: 20px; + overflow: hidden; + + .form-container { + flex: 1; + overflow-y: auto; + padding-right: 4px; + + &::-webkit-scrollbar { + width: 4px; + } + &::-webkit-scrollbar-track { + background: transparent; + } + &::-webkit-scrollbar-thumb { + background: #ccc; + border-radius: 2px; + } + + .form-item-group { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 16px; + + .form-label { + font-size: 14px; + font-weight: 500; + color: #1f2329; + } + + :deep(.ant-input-textarea) { + font-size: 13px; + } + } + + .preset-group { + margin-bottom: 2px; + .preset-label { + font-size: 13px; + color: #8f959e; + margin-bottom: 8px; + display: block; + } + + .preset-items { + display: flex; + flex-wrap: wrap; + gap: 8px; + } + + .preset-item { + padding: 6px 14px; + cursor: pointer; + border-radius: 6px; + border: 1px solid #1890ff; + background: #fff; + color: #1890ff; + text-align: center; + font-size: 12px; + font-weight: 500; + transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); + user-select: none; + + &:hover { + background: #1890ff; + color: #fff; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3); + } + + &:active { + transform: translateY(0); + } + } + } + } + + .action-btn-group { + margin-top: 16px; + padding-top: 16px; + border-top: 1px solid #f0f0f0; + } + } + + .panel-title { + font-size: 16px; + font-weight: 600; + color: #1f2329; + margin-bottom: 16px; + padding-left: 8px; + border-left: 4px solid #1890ff; + line-height: 1; + } + + .preview-panel { + flex: 1; + background: #fff; + border-radius: 8px; + display: flex; + flex-direction: column; + padding: 20px; + overflow: hidden; + + .preview-content { + flex: 1; + background: #f7f8fc; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + position: relative; + padding: 24px; + } + + .video-info-section { + padding: 12px 0; + border-top: 1px solid #f0f0f0; + margin-top: 12px; + + .current-video-info { + display: flex; + align-items: flex-start; + gap: 8px; + + .info-label { + font-size: 13px; + color: #8f959e; + flex-shrink: 0; + font-weight: 500; + } + + .info-text { + font-size: 13px; + color: #1f2329; + flex: 1; + word-break: break-word; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + line-height: 1.4; + } + } + } + } + + .history-panel { + width: 400px; + min-width: 280px; + background: #fff; + border-radius: 8px; + display: flex; + flex-direction: column; + padding: 20px; + overflow: hidden; + + .history-list-wrapper { + flex: 1; + overflow-y: auto; + display: flex; + flex-direction: column; + + &::-webkit-scrollbar { + width: 4px; + } + &::-webkit-scrollbar-track { + background: transparent; + } + &::-webkit-scrollbar-thumb { + background: #ccc; + border-radius: 2px; + } + } + + .empty-history { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #b2b8c6; + + p { + margin: 0; + } + } + + .history-list { + display: flex; + flex-direction: column; + gap: 8px; + + .history-item { + padding: 12px; + border: 1px solid #f0f0f0; + border-radius: 6px; + background: #fafbfc; + transition: all 0.3s; + + &:hover { + background: #f5f9ff; + border-color: #d9e8f7; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + } + + .item-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 8px; + gap: 8px; + + .item-title { + flex: 1; + font-size: 12px; + color: #1f2329; + font-weight: 500; + word-break: break-word; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + line-height: 1.4; + } + + .item-time { + font-size: 10px; + color: #b2b8c6; + white-space: nowrap; + flex-shrink: 0; + } + } + + .item-actions { + display: flex; + gap: 4px; + justify-content: flex-end; + + :deep(.ant-btn) { + padding: 0 4px; + height: auto; + min-width: auto; + font-size: 12px; + + &.ant-btn-text { + color: #1890ff; + + &:hover { + color: #40a9ff; + } + } + + &.ant-btn-dangerous.ant-btn-text { + color: #ff4d4f; + + &:hover { + color: #ff7875; + } + } + } + } + } + } + } + + .empty-state { + text-align: center; + color: #8f959e; + + p { + margin-top: 12px; + margin-bottom: 0; + } + + .tip { + font-size: 12px; + color: #b2b8c6; + } + } + + .loading-state { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + + .loading-text { + text-align: center; + color: #595959; + + p { + margin: 4px 0; + } + + .elapsed-time { + font-size: 20px; + font-weight: 600; + color: #1890ff; + } + + .status-text { + font-size: 13px; + color: #8c8c8c; + } + } + } + + .video-player-wrapper { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + + .video-control { + max-width: 100%; + max-height: 100%; + width: auto; + height: auto; + border-radius: 4px; + } + } +} diff --git a/src/views/super/airag/aivideo/AiVideo.vue b/src/views/super/airag/aivideo/AiVideo.vue new file mode 100644 index 0000000..cc9c436 --- /dev/null +++ b/src/views/super/airag/aivideo/AiVideo.vue @@ -0,0 +1,415 @@ + + + + + diff --git a/src/views/super/airag/aivideo2/AiVideo.api.ts b/src/views/super/airag/aivideo2/AiVideo.api.ts new file mode 100644 index 0000000..2f6fb37 --- /dev/null +++ b/src/views/super/airag/aivideo2/AiVideo.api.ts @@ -0,0 +1,22 @@ +import { defHttp } from '@/utils/http/axios'; + +enum Api { + submit = '/airag/video/submit', + query = '/airag/video/query', + prompts = '/airag/video/prompts', +} + +/** + * 提交视频生成任务 + */ +export const submitVideoTask = (params: { prompt: string; category?: string }) => defHttp.post({ url: Api.submit, params }); + +/** + * 查询视频生成任务状态 + */ +export const queryVideoTask = (taskId: string) => defHttp.get({ url: `${Api.query}/${taskId}` }); + +/** + * 获取预设提示词 + */ +export const getPresetPrompts = () => defHttp.get({ url: Api.prompts }); diff --git a/src/views/super/airag/aivideo2/AiVideo.data.ts b/src/views/super/airag/aivideo2/AiVideo.data.ts new file mode 100644 index 0000000..d8c891f --- /dev/null +++ b/src/views/super/airag/aivideo2/AiVideo.data.ts @@ -0,0 +1,50 @@ +import type { FormSchema } from '@/components/Form'; + +/** + * 视频生成表单Schema + */ +export const videoFormSchema: FormSchema[] = [ + { + field: 'prompt', + label: '视频描述', + component: 'InputTextArea', + required: true, + componentProps: { + placeholder: '请描述你想生成的视频内容,例如:一只金毛犬在沙滩上奔跑,海浪拍打岸边,阳光明媚', + rows: 5, + maxlength: 500, + showCount: true, + }, + }, +]; + +/** + * 场景分类 + */ +export const categoryList = ['通用演示', '产品营销', '教育培训', '创意设计']; + +/** + * 备用预设提示词(当后端API不可用时使用) + */ +export const fallbackPrompts: Record = { + 通用演示: [ + '一只金毛犬在金色的沙滩上奔跑,海浪轻轻拍打着岸边,阳光明媚,慢动作镜头', + '航拍壮丽的山脉全景,云雾缭绕在山峰之间,镜头缓缓推进', + '樱花树下,花瓣随风飘落,一条小溪静静流淌,春日午后的宁静氛围', + ], + 产品营销: [ + '一杯咖啡被缓缓倒入透明玻璃杯中,咖啡与牛奶融合形成美丽的纹理,微距特写', + '一款高端智能手表在旋转展示台上缓缓旋转,灯光打在表面上反射出金属光泽,黑色背景', + '一双运动鞋踩入水洼溅起水花,慢动作特写,动感活力的画面', + ], + 教育培训: [ + '地球从太空视角缓缓旋转,可以看到大气层和云层的细节,星空背景', + '一本书的书页被风吹动快速翻动,文字和插图若隐若现,知识流动的意象', + '显微镜下的细胞分裂过程,色彩鲜明的科学可视化风格', + ], + 创意设计: [ + '一座未来主义的城市在日落时分,霓虹灯光倒映在雨水的路面上,赛博朋克风格', + '水墨在水中缓缓扩散,形成抽象的山水画意境,中国风艺术效果', + '星空下的极光在天空中舞动,色彩绚烂,延时摄影效果', + ], +}; diff --git a/src/views/super/airag/aivideo2/AiVideo.vue b/src/views/super/airag/aivideo2/AiVideo.vue new file mode 100644 index 0000000..ae6b323 --- /dev/null +++ b/src/views/super/airag/aivideo2/AiVideo.vue @@ -0,0 +1,418 @@ + + + + + diff --git a/src/views/super/airag/aivoice/AiVoice.api.ts b/src/views/super/airag/aivoice/AiVoice.api.ts new file mode 100644 index 0000000..a468b4a --- /dev/null +++ b/src/views/super/airag/aivoice/AiVoice.api.ts @@ -0,0 +1,34 @@ +import { defHttp } from '@/utils/http/axios'; + +enum Api { + generate = '/airag/voice/generate', + generateAsync = '/airag/voice/generateAsync', + queryTask = '/airag/voice/queryTask', + listByUser = '/airag/voice/listByUser', + deleteVoiceRecord = '/airag/voice/deleteVoiceRecord', +} + +/** + * 提交语音生成任务(同步,保留兼容) + */ +export const submitVoiceTask = (params) => defHttp.post({ url: Api.generate, params }, { isTransformResponse: false }); + +/** + * 异步提交语音生成任务,立即返回 taskId + */ +export const generateVoiceAsync = (params) => defHttp.post({ url: Api.generateAsync, params }, { isTransformResponse: false }); + +/** + * 查询异步语音任务结果 + */ +export const queryVoiceTask = (taskId: string) => defHttp.get({ url: `${Api.queryTask}/${taskId}` }, { isTransformResponse: false }); + +/** + * 根据用户id查询语音列表 + */ +export const getVoiceListByUser = (params: { userId: string }) => defHttp.get({ url: Api.listByUser, params },{ isTransformResponse: false }); + +/** + * 删除语音记录 + */ +export const deleteVoiceRecord = (params) => defHttp.delete({ url: Api.deleteVoiceRecord, params }, { isTransformResponse: false, joinParamsToUrl: true }); \ No newline at end of file diff --git a/src/views/super/airag/aivoice/AiVoice.data.ts b/src/views/super/airag/aivoice/AiVoice.data.ts new file mode 100644 index 0000000..3484584 --- /dev/null +++ b/src/views/super/airag/aivoice/AiVoice.data.ts @@ -0,0 +1,117 @@ +import { FormSchema } from '@/components/Form'; + +// 左侧语音控制表单 +export const voiceFormSchemas: FormSchema[] = [ + /* { + label: '模型', + field: 'model', + component: 'JDictSelectTag', + required: true, + defaultValue: 'voice-generation-1', + componentProps: { + placeholder: '请选择语音模型', + dictCode: "airag_model where model_type = 'VOICE' and activate_flag = 1,name,id", + }, + },*/ + { + label: '倍速', + field: 'speed', + component: 'Slider', + defaultValue: 1, + colProps: { + span: 24, + }, + componentProps: { + min: 0.25, + max: 4, + step: 0.1, + marks: { + 0.5: '0.5x', + 1: '1x', + 1.5: '1.5x', + 2: '2x', + 3: '3x', + 4: '4x', + }, + tooltip: { + formatter: (value: number) => `${value.toFixed(1)}x`, + }, + }, + }, + { + label: '音量增益(dB)', + field: 'volume', + component: 'Slider', + defaultValue: 0, + colProps: { + span: 24, + }, + componentProps: { + min: -10, + max: 10, + step: 1, + marks: { + '-10': '-10', + 0: '0', + 10: '10', + }, + }, + }, + { + label: '声色', + field: 'voice', + component: 'Select', + required: true, + defaultValue: 'tongtong', + componentProps: { + options: [ + { label: '彤彤', value: 'tongtong' }, + { label: '锤锤', value: 'chuichui' }, + { label: '小陈', value: 'xiaochen' }, + { label: 'Jam', value: 'jam' }, + { label: 'Kazi', value: 'kazi' }, + { label: 'Douji', value: 'douji' }, + { label: 'Luodo', value: 'luodo' }, + ], + placeholder: '请选择声色', + }, + }, + { + label: '文案', + field: 'text', + component: 'InputTextArea', + required: true, + colProps: { + span: 24, + }, + componentProps: { + rows: 6, + placeholder: '请输入要合成的文案内容', + maxlength: 500, + showCount: true, + }, + }, +]; + +/** + * 历史记录表格列配置 + */ +export const historyColumns = [ + { + title: '文案', + dataIndex: 'text', + width: 100, + ellipsis: true, + }, + { + title: '创建时间', + dataIndex: 'createTime', + width: 100, + }, + { + title: '操作', + dataIndex: 'action', + width: 100, + fixed: 'right', + }, +]; diff --git a/src/views/super/airag/aivoice/AiVoice.less b/src/views/super/airag/aivoice/AiVoice.less new file mode 100644 index 0000000..7bdc7d2 --- /dev/null +++ b/src/views/super/airag/aivoice/AiVoice.less @@ -0,0 +1,382 @@ +// AI Voice 样式文件 +.ai-voice-page { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + padding: 16px; + background-color: #f0f2f5; + display: flex; + flex-direction: column; + box-sizing: border-box; + overflow: hidden; + + .page-header { + margin-bottom: 16px; + background: #fff; + padding: 16px 24px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: space-between; + + .title { + font-size: 20px; + font-weight: 600; + color: #1f2329; + } + + .subtitle { + color: #8f959e; + font-size: 14px; + } + } + + .content-wrapper { + flex: 1; + display: flex; + gap: 16px; + overflow: hidden; + } + + .control-panel { + width: 300px; + min-width: 260px; + background: #fff; + border-radius: 8px; + display: flex; + flex-direction: column; + padding: 20px; + overflow: hidden; + + .form-container { + flex: 1; + overflow-y: auto; + padding-right: 4px; + + &::-webkit-scrollbar { + width: 4px; + } + &::-webkit-scrollbar-track { + background: transparent; + } + &::-webkit-scrollbar-thumb { + background: #ccc; + border-radius: 2px; + } + } + } + + // 中间区域:试听 + 文案 + 常用场景 + 生成按钮 + .middle-wrapper { + flex: 1; + display: flex; + flex-direction: column; + gap: 16px; + overflow: hidden; + + .preview-panel { + flex: 0 0 auto; + height: 300px; + background: #fff; + border-radius: 8px; + display: flex; + flex-direction: column; + padding: 20px; + overflow: hidden; + + .panel-title { + font-size: 16px; + font-weight: 600; + color: #1f2329; + margin-bottom: 16px; + padding-left: 8px; + border-left: 4px solid #1890ff; + line-height: 1; + } + + .preview-content { + flex: 1; + background: #f7f8fc; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + position: relative; + padding: 24px; + } + + .audio-info-section { + padding: 12px 0; + border-top: 1px solid #f0f0f0; + margin-top: 12px; + + .current-audio-info { + display: flex; + align-items: flex-start; + gap: 8px; + + .info-label { + font-size: 13px; + color: #8f959e; + flex-shrink: 0; + font-weight: 500; + } + + .info-text { + font-size: 13px; + color: #1f2329; + flex: 1; + word-break: break-word; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + line-height: 1.4; + } + } + } + } + + .input-section { + flex: 1; + background: #fff; + border-radius: 8px; + padding: 20px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 16px; + + &::-webkit-scrollbar { + width: 4px; + } + &::-webkit-scrollbar-track { + background: transparent; + } + &::-webkit-scrollbar-thumb { + background: #ccc; + border-radius: 2px; + } + + .form-item-group { + display: flex; + flex-direction: column; + gap: 8px; + + .form-label { + font-size: 14px; + font-weight: 500; + color: #1f2329; + } + + :deep(.ant-input-textarea) { + font-size: 13px; + } + } + + .preset-group { + .preset-label { + font-size: 13px; + color: #8f959e; + margin-bottom: 8px; + display: block; + } + + .preset-items { + display: flex; + flex-wrap: wrap; + gap: 8px; + } + + .preset-item { + flex-shrink: 0; + padding: 6px 14px; + cursor: pointer; + border-radius: 6px; + border: 1px solid #1890ff; + background: #fff; + color: #1890ff; + white-space: nowrap; + text-align: center; + font-size: 12px; + font-weight: 500; + transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); + user-select: none; + + &:hover { + background: #1890ff; + color: #fff; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3); + } + + &:active { + transform: translateY(0); + } + } + } + + .action-btn-group { + margin-top: auto; + padding-top: 12px; + border-top: 1px solid #f0f0f0; + } + } + } + + .history-panel { + width: 400px; + min-width: 280px; + background: #fff; + border-radius: 8px; + display: flex; + flex-direction: column; + padding: 20px; + overflow: hidden; + + .history-list-wrapper { + flex: 1; + overflow-y: auto; + display: flex; + flex-direction: column; + + &::-webkit-scrollbar { + width: 4px; + } + &::-webkit-scrollbar-track { + background: transparent; + } + &::-webkit-scrollbar-thumb { + background: #ccc; + border-radius: 2px; + } + } + + .empty-history { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #b2b8c6; + + p { + margin: 0; + } + } + + .history-list { + display: flex; + flex-direction: column; + gap: 8px; + + .history-item { + padding: 12px; + border: 1px solid #f0f0f0; + border-radius: 6px; + background: #fafbfc; + transition: all 0.3s; + + &:hover { + background: #f5f9ff; + border-color: #d9e8f7; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + } + + .item-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 8px; + gap: 8px; + + .item-title { + flex: 1; + font-size: 12px; + color: #1f2329; + font-weight: 500; + word-break: break-word; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + line-height: 1.4; + } + + .item-time { + font-size: 10px; + color: #b2b8c6; + white-space: nowrap; + flex-shrink: 0; + } + } + + .item-actions { + display: flex; + gap: 4px; + justify-content: flex-end; + + :deep(.ant-btn) { + padding: 0 4px; + height: auto; + min-width: auto; + font-size: 12px; + + &.ant-btn-text { + color: #1890ff; + + &:hover { + color: #40a9ff; + } + } + + &.ant-btn-dangerous.ant-btn-text { + color: #ff4d4f; + + &:hover { + color: #ff7875; + } + } + } + } + } + } + } + + .panel-title { + font-size: 16px; + font-weight: 600; + color: #1f2329; + margin-bottom: 20px; + padding-left: 8px; + border-left: 4px solid #1890ff; + line-height: 1; + } + + .empty-state { + text-align: center; + color: #8f959e; + + p { + margin-top: 12px; + margin-bottom: 0; + } + + .tip { + font-size: 12px; + color: #b2b8c6; + } + } + + .audio-player-wrapper { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } +} diff --git a/src/views/super/airag/aivoice/AiVoice.vue b/src/views/super/airag/aivoice/AiVoice.vue new file mode 100644 index 0000000..db0158c --- /dev/null +++ b/src/views/super/airag/aivoice/AiVoice.vue @@ -0,0 +1,332 @@ + + + + + diff --git a/src/views/super/airag/aiwriter/AiWriter.vue b/src/views/super/airag/aiwriter/AiWriter.vue new file mode 100644 index 0000000..e15f014 --- /dev/null +++ b/src/views/super/airag/aiwriter/AiWriter.vue @@ -0,0 +1,596 @@ + + + + + diff --git a/src/views/super/airag/aiwriter/AiWriterLeft.vue b/src/views/super/airag/aiwriter/AiWriterLeft.vue new file mode 100644 index 0000000..1d1f63e --- /dev/null +++ b/src/views/super/airag/aiwriter/AiWriterLeft.vue @@ -0,0 +1,357 @@ + + + + + diff --git a/src/views/super/airag/ocr/AiOcr.api.ts b/src/views/super/airag/ocr/AiOcr.api.ts new file mode 100644 index 0000000..4f4d2ec --- /dev/null +++ b/src/views/super/airag/ocr/AiOcr.api.ts @@ -0,0 +1,46 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +export enum Api { + list = '/airag/ocr/list', + add = '/airag/ocr/add', + edit = '/airag/ocr/edit', + deleteById = '/airag/ocr/deleteById', + flowRun = '/airag/flow/run', +} + +/** + * 查询ocr列表 + * + * @param params + */ +export const list = (params) => { + return defHttp.get({ url: Api.list, params }); +}; + +/** + * 添加Orc + * @param params + * @param handleSuccess + */ +export const addOcr = (params) => { + return defHttp.post({ url: Api.add, params }); +}; + +/** + * 编辑Orc + * @param params + * @param handleSuccess + */ +export const editOcr = (params) => { + return defHttp.put({ url: Api.edit, params }); +}; + +/** + * 根据id删除 Orc + * @param params + * @param handleSuccess + */ +export const deleteOcrById = (params) => { + return defHttp.delete({ url: Api.deleteById, params }); +}; diff --git a/src/views/super/airag/ocr/AiOcr.data.ts b/src/views/super/airag/ocr/AiOcr.data.ts new file mode 100644 index 0000000..ffa5642 --- /dev/null +++ b/src/views/super/airag/ocr/AiOcr.data.ts @@ -0,0 +1,85 @@ +import { BasicColumn, FormSchema } from '@/components/Table'; + +//ocr表格 +export const columns: BasicColumn[] = [ + { + title: '编号', + dataIndex: 'id', + ifShow: false, + }, + { + title: '标题', + dataIndex: 'title', + ellipsis: true, + width: 300, + }, + { + title: '提示词', + dataIndex: 'prompt', + ellipsis: true, + width: 300, + }, +]; + +//ocr表单 +export const schemas: FormSchema[] = [ + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '标题', + field: 'title', + component: 'Input', + required: true, + }, + { + label: '提示词', + field: 'prompt', + component: 'InputTextArea', + componentProps: { + row: 4, + autosize: { minRows: 4, maxRows: 6 }, + }, + required: true, + }, +]; + +//ocr解析表单 +export const analysisSchemas: FormSchema[] = [ + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '图片', + field: 'url', + component: 'JImageUpload', + required: true, + }, + { + label: '提示词', + field: 'prompt', + component: 'InputTextArea', + componentProps: { + row: 2, + autosize: { minRows: 2, maxRows: 2 }, + placeholder:"请填写提示词,如提取图片中的文字" + } + }, + { + label: '解析结果', + field: 'analysisResult', + component: 'InputTextArea', + componentProps: { + row: 10, + autosize: { minRows: 10, maxRows: 10 }, + readonly: true, + placeholder:"解析结果将在这里显示" + }, + }, +]; diff --git a/src/views/super/airag/ocr/AiOcrList.vue b/src/views/super/airag/ocr/AiOcrList.vue new file mode 100644 index 0000000..9657442 --- /dev/null +++ b/src/views/super/airag/ocr/AiOcrList.vue @@ -0,0 +1,112 @@ + + + diff --git a/src/views/super/airag/ocr/components/AiOcrAnalysisModal.vue b/src/views/super/airag/ocr/components/AiOcrAnalysisModal.vue new file mode 100644 index 0000000..5a92f57 --- /dev/null +++ b/src/views/super/airag/ocr/components/AiOcrAnalysisModal.vue @@ -0,0 +1,151 @@ + + + + diff --git a/src/views/super/airag/ocr/components/AiOcrModal.vue b/src/views/super/airag/ocr/components/AiOcrModal.vue new file mode 100644 index 0000000..cbb70da --- /dev/null +++ b/src/views/super/airag/ocr/components/AiOcrModal.vue @@ -0,0 +1,62 @@ + + + + diff --git a/src/views/super/airag/wordtpl/EoaWordTemplate.api.ts b/src/views/super/airag/wordtpl/EoaWordTemplate.api.ts new file mode 100644 index 0000000..7e8c660 --- /dev/null +++ b/src/views/super/airag/wordtpl/EoaWordTemplate.api.ts @@ -0,0 +1,127 @@ +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { downloadFile } from '/@/api/common/api'; +import { message } from 'ant-design-vue'; + +const { createConfirm } = useMessage(); + +enum Api { + list = '/airag/word/list', + save = '/airag/word/add', + edit = '/airag/word/edit', + deleteOne = '/airag/word/delete', + deleteBatch = '/airag/word/deleteBatch', + downloadTpl = '/airag/word/download', + parseFile = '/airag/word/parse/file', + generateWord = '/airag/word/generate/word', + generateResume = '/airag/flow/run', +} +/** + * 列表接口 + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); + +/** + * 删除单个 + */ +export const deleteOne = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 批量删除 + * @param params + */ +export const batchDelete = (params, handleSuccess) => { + createConfirm({ + iconType: 'warning', + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; +/** + * 保存或者更新 + * @param params + */ +export const saveOrUpdate = (params, isUpdate) => { + const url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url: url, params }); +}; + +/** + * 下载模版 + * @param params + */ +export const downloadTpl = (params) => { + return downloadFile(Api.downloadTpl, params.name + '.docx', { id: params.id }); +}; + +/** + * 生成word + * @param params + */ +export const generateWord = (fileName, params) => { + return defHttp + .post( + { + url: Api.generateWord, + params: params, + responseType: 'blob', + }, + { isTransformResponse: false } + ) + .then((data) => { + if (!data || data.size === 0) { + message.warning('文件下载失败'); + return; + } + if (typeof window.navigator.msSaveBlob !== 'undefined') { + window.navigator.msSaveBlob(new Blob([data]), fileName); + } else { + const url = window.URL.createObjectURL(new Blob([data])); + const link = document.createElement('a'); + link.style.display = 'none'; + link.href = url; + link.setAttribute('download', fileName); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); //下载完成移除元素 + window.URL.revokeObjectURL(url); //释放掉blob对象 + } + }); +}; + +/** + * 生成简历 + * @param params + */ +export const generateResume = (params, handleSuccess) => { + return defHttp + .post( + { + url: Api.generateResume, + params: params, + timeout: 120000, + timeoutErrorMessage: '同步数据库超时,已自动刷新', + }, + { isTransformResponse: false } + ) + .then((value) => { + handleSuccess(value); + }); +}; + +/** + * 解析word文档地址 + * @param params + */ +export const parseFileUrl = Api.parseFile; diff --git a/src/views/super/airag/wordtpl/EoaWordTemplate.data.ts b/src/views/super/airag/wordtpl/EoaWordTemplate.data.ts new file mode 100644 index 0000000..f9df251 --- /dev/null +++ b/src/views/super/airag/wordtpl/EoaWordTemplate.data.ts @@ -0,0 +1,176 @@ +import { BasicColumn } from '/@/components/Table'; +import { FormSchema } from '/@/components/Table'; +//列表数据 +export const columns: BasicColumn[] = [ + { + title: '模版名称', + align: 'center', + dataIndex: 'name', + }, + { + title: '模版编码', + align: 'center', + dataIndex: 'code', + }, + // { + // title: '页眉', + // align: 'center', + // dataIndex: 'header', + // ifShow: false, + // }, + // { + // title: '页脚', + // align: 'center', + // dataIndex: 'footer', + // ifShow: false, + // }, + // { + // title: '主体内容', + // align: 'center', + // dataIndex: 'main', + // ifShow: false, + // }, + // { + // title: '页边距', + // align: 'center', + // dataIndex: 'margins', + // ifShow: false, + // }, + // { + // title: '宽度', + // align: 'center', + // dataIndex: 'width', + // ifShow: false, + // }, + // { + // title: '高度', + // align: 'center', + // dataIndex: 'height', + // ifShow: false, + // }, + // { + // title: '纸张方向', + // align: 'center', + // dataIndex: 'paperDirection', + // ifShow: false, + // }, + // { + // title: '水印', + // align: 'center', + // dataIndex: 'watermark', + // ifShow: false, + // }, +]; +//查询数据 +export const searchFormSchema: FormSchema[] = [ + { + label: '模版名称', + field: 'name', + component: 'Input', + }, + { + label: '模版编码', + field: 'code', + component: 'Input', + }, +]; +//表单数据 +export const formSchema: FormSchema[] = [ + { + label: '模版名称', + field: 'name', + component: 'Input', + dynamicRules: ({ model, schema }) => { + return [{ required: true, message: '请输入模版名称!' }]; + }, + }, + { + label: '模版编码', + field: 'code', + component: 'Input', + dynamicRules: ({ model, schema }) => { + return [ + { required: true, message: '请输入模版编码!' }, + { pattern: /^[A-Za-z_]+$/, message: '只能输入英文和下划线!' }, + ]; + }, + }, + { + label: '页眉', + field: 'header', + component: 'Input', + show: false, + }, + { + label: '页脚', + field: 'footer', + component: 'Input', + show: false, + }, + { + label: '主体内容', + field: 'main', + component: 'Input', + show: false, + }, + { + label: '页边距', + field: 'margins', + component: 'Input', + show: false, + }, + { + label: '宽度', + field: 'width', + component: 'InputNumber', + show: false, + }, + { + label: '高度', + field: 'height', + component: 'InputNumber', + show: false, + }, + { + label: '纸张方向', + field: 'paperDirection', + component: 'Input', + show: false, + }, + { + label: '水印', + field: 'watermark', + component: 'Input', + show: false, + }, + // TODO 主键隐藏字段,目前写死为ID + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, +]; + +// 高级查询数据 +export const superQuerySchema = { + name: { title: '模版名称', order: 0, view: 'text', type: 'string' }, + code: { title: '模版编码', order: 1, view: 'text', type: 'string' }, + header: { title: '页眉', order: 2, view: 'text', type: 'string' }, + footer: { title: '页脚', order: 3, view: 'text', type: 'string' }, + main: { title: '主体内容', order: 4, view: 'text', type: 'string' }, + margins: { title: '页边距', order: 5, view: 'text', type: 'string' }, + width: { title: '宽度', order: 6, view: 'number', type: 'number' }, + height: { title: '高度', order: 7, view: 'number', type: 'number' }, + paperDirection: { title: '纸张方向', order: 8, view: 'text', type: 'string' }, + watermark: { title: '水印', order: 9, view: 'text', type: 'string' }, +}; + +/** + * 流程表单调用这个方法获取formSchema + * @param param + */ +export function getBpmFormSchema(_formData): FormSchema[] { + // 默认和原始表单保持一致 如果流程中配置了权限数据,这里需要单独处理formSchema + return formSchema; +} diff --git a/src/views/super/airag/wordtpl/EoaWordTemplateList.vue b/src/views/super/airag/wordtpl/EoaWordTemplateList.vue new file mode 100644 index 0000000..14a966f --- /dev/null +++ b/src/views/super/airag/wordtpl/EoaWordTemplateList.vue @@ -0,0 +1,408 @@ + + + + + + diff --git a/src/views/super/airag/wordtpl/components/EoaWordTemplateForm.vue b/src/views/super/airag/wordtpl/components/EoaWordTemplateForm.vue new file mode 100644 index 0000000..4bc43f7 --- /dev/null +++ b/src/views/super/airag/wordtpl/components/EoaWordTemplateForm.vue @@ -0,0 +1,70 @@ + + + diff --git a/src/views/super/airag/wordtpl/components/EoaWordTemplateModal.vue b/src/views/super/airag/wordtpl/components/EoaWordTemplateModal.vue new file mode 100644 index 0000000..5fa196e --- /dev/null +++ b/src/views/super/airag/wordtpl/components/EoaWordTemplateModal.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/src/views/super/online/cgform/CgformCopyList.vue b/src/views/super/online/cgform/CgformCopyList.vue new file mode 100644 index 0000000..bab1feb --- /dev/null +++ b/src/views/super/online/cgform/CgformCopyList.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/comp/JOnlineSearchSelect.vue b/src/views/super/online/cgform/auto/comp/JOnlineSearchSelect.vue new file mode 100644 index 0000000..73b8471 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/JOnlineSearchSelect.vue @@ -0,0 +1,168 @@ + + + + + + diff --git a/src/views/super/online/cgform/auto/comp/OnlineForm.vue b/src/views/super/online/cgform/auto/comp/OnlineForm.vue new file mode 100644 index 0000000..541aa59 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/OnlineForm.vue @@ -0,0 +1,1806 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/comp/OnlineFormDetail.vue b/src/views/super/online/cgform/auto/comp/OnlineFormDetail.vue new file mode 100644 index 0000000..ca6e15c --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/OnlineFormDetail.vue @@ -0,0 +1,353 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/comp/OnlinePopForm.vue b/src/views/super/online/cgform/auto/comp/OnlinePopForm.vue new file mode 100644 index 0000000..147a9e9 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/OnlinePopForm.vue @@ -0,0 +1,911 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/comp/OnlinePopListModal.vue b/src/views/super/online/cgform/auto/comp/OnlinePopListModal.vue new file mode 100644 index 0000000..bcab85c --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/OnlinePopListModal.vue @@ -0,0 +1,342 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/comp/OnlinePopModal.vue b/src/views/super/online/cgform/auto/comp/OnlinePopModal.vue new file mode 100644 index 0000000..4f22246 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/OnlinePopModal.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/comp/OnlineQueryForm.vue b/src/views/super/online/cgform/auto/comp/OnlineQueryForm.vue new file mode 100644 index 0000000..005ff21 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/OnlineQueryForm.vue @@ -0,0 +1,863 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/comp/OnlineSearchFormItem.vue b/src/views/super/online/cgform/auto/comp/OnlineSearchFormItem.vue new file mode 100644 index 0000000..d3ad612 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/OnlineSearchFormItem.vue @@ -0,0 +1,415 @@ + + + + + + diff --git a/src/views/super/online/cgform/auto/comp/OnlineSelectCascade.vue b/src/views/super/online/cgform/auto/comp/OnlineSelectCascade.vue new file mode 100644 index 0000000..2ade939 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/OnlineSelectCascade.vue @@ -0,0 +1,234 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/comp/OnlineSubForm.vue b/src/views/super/online/cgform/auto/comp/OnlineSubForm.vue new file mode 100644 index 0000000..c4bf0d2 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/OnlineSubForm.vue @@ -0,0 +1,301 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/comp/OnlineSubFormDetail.vue b/src/views/super/online/cgform/auto/comp/OnlineSubFormDetail.vue new file mode 100644 index 0000000..6dad39d --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/OnlineSubFormDetail.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/comp/factory/FormSchemaFactory.ts b/src/views/super/online/cgform/auto/comp/factory/FormSchemaFactory.ts new file mode 100644 index 0000000..c8613b8 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/FormSchemaFactory.ts @@ -0,0 +1,177 @@ +import InputWidget from './impl/InputWidget'; +import { FormSchema } from '/@/components/Form'; +import DateWidget from './impl/DateWidget'; +import SelectWidget from './impl/SelectWidget'; +import PasswordWidget from './impl/PasswordWidget'; +import FileWidget from './impl/FileWidget'; +import ImageWidget from './impl/ImageWidget'; +import TextAreaWidget from './impl/TextAreaWidget'; +import SelectMultiWidget from './impl/SelectMultiWidget'; +import SelectSearchWidget from './impl/SelectSearchWidget'; +import PopupWidget from './impl/PopupWidget'; +// update-begin--author:liaozhiyang---date:20240130---for:【QQYUN-7961】popupDict字典 +import PopupDictWidget from './impl/PopupDictWidget'; +// update-end--author:liaozhiyang---date:20240130---for:【QQYUN-7961】popupDict字典 +import TreeCategoryWidget from './impl/TreeCategoryWidget'; +import SelectDepartWidget from './impl/SelectDepartWidget'; +import SelectUserWidget from './impl/SelectUserWidget'; +import EditorWidget from './impl/EditorWidget'; +import MarkdownWidget from './impl/MarkdownWidget'; +import PcaWidget from './impl/PcaWidget'; +import AreaLinkage from './impl/AreaLinkage'; +import TreeSelectWidget from './impl/TreeSelectWidget'; +import RadioWidget from './impl/RadioWidget'; +import CheckboxWidget from './impl/CheckboxWidget'; +import SwitchWidget from './impl/SwitchWidget'; +import TimeWidget from './impl/TimeWidget'; +import LinkDownWidget from './impl/LinkDownWidget'; +import SlotWidget from './impl/SlotWidget'; +import NumberWidget from './impl/NumberWidget'; +import LinkTableWidget from './impl/LinkTableWidget' +import LinkTableFieldWidget from './impl/LinkTableFieldWidget' +import LinkTableForQueryWidget from './impl/LinkTableForQueryWidget' +import CascaderPcaForQueryWidget from './impl/CascaderPcaForQueryWidget' +import SelectUser2Widget from './impl/SelectUser2Widget' +import RangeWidget from "./impl/RangeWidget"; + +export default class FormSchemaFactory { + static createFormSchema(key, data, queryItem) { + let view = data.view; + switch (view) { + case 'password': + //2.密码输入框 + return new PasswordWidget(key, data); + case 'list': + //3.下拉框 + return new SelectWidget(key, data); + case 'radio': + // 4. 单选 + return new RadioWidget(key, data); + case 'checkbox': + // 5.多选 + return new CheckboxWidget(key, data); + case 'date': + case 'datetime': + // 6.日期 + // 7.日期时间 + return new DateWidget(key, data, queryItem); + case 'time': + // 8 时间 + return new TimeWidget(key, data); + case 'file': + // 9.文件 + return new FileWidget(key, data); + case 'image': + // 10.图片 + return new ImageWidget(key, data); + case 'textarea': + // 11.多行文本 + return new TextAreaWidget(key, data); + case 'list_multi': + // 12.下拉多选框 + return new SelectMultiWidget(key, data); + case 'sel_search': + // 13.下拉搜索框 + return new SelectSearchWidget(key, data); + case 'popup': + // 14. popup + return new PopupWidget(key, data); + case 'cat_tree': + // 15.分类字典树 + return new TreeCategoryWidget(key, data); + case 'sel_depart': + // 16.部门选择 + return new SelectDepartWidget(key, data); + case 'sel_user': + // 17.用户选择 + return new SelectUserWidget(key, data); + case 'umeditor': + // 18.富文本 + return new EditorWidget(key, data); + case 'markdown': + // 19.MarkDown + return new MarkdownWidget(key, data); + case 'pca': + // 20.省市区 + // update-begin--author:liaozhiyang---date:20240607---for:【TV360X-501】省市区换新组件 + // return new PcaWidget(key, data); + return new AreaLinkage(key, data); + // update-end--author:liaozhiyang---date:20240607---for:【TV360X-501】省市区换新组件 + case 'link_down': + // 21.联动组件 + return new LinkDownWidget(key, data); + case 'sel_tree': + // 22.自定义树控件 + return new TreeSelectWidget(key, data); + case 'switch': + // 23.开关组件 + return new SwitchWidget(key, data); + case 'link_table': + // 24.关联记录 + return new LinkTableWidget(key, data); + case 'link_table_field': + // 25.他表字段 + return new LinkTableFieldWidget(key, data); + // update-begin--author:liaozhiyang---date:20240130---for:【QQYUN-7961】popupDict字典 + case 'popup_dict': + // 14. popup字典 + return new PopupDictWidget(key, data); + // update-end--author:liaozhiyang---date:20240130---for:【QQYUN-7961】popupDict字典 + case 'slot': + // slot + return new SlotWidget(key, data); + case 'LinkTableForQuery': + return new LinkTableForQueryWidget(key, data); + case 'CascaderPcaForQuery': + return new CascaderPcaForQueryWidget(key, data, queryItem); + case 'select_user2': + return new SelectUser2Widget(key, data); + case 'rangeDate': + case 'rangeTime': + case 'rangeNumber': + return new RangeWidget(key, data); + case 'hidden': + // 隐藏的控件 如分类树的文本 + return new InputWidget(key, data).isHidden(); + default: + if (data.type == 'number') { + return new NumberWidget(key, data); + } else { + //1.普通输入框 + return new InputWidget(key, data); + } + } + } + + static createSlotFormSchema(key, data) { + let slotFs = new SlotWidget(key, data); + let view = data.view; + if ('date' == view) { + slotFs.groupDate(); + } else if ('datetime' == view) { + slotFs.groupDatetime(); + } else if ('time' == view) { + // update-begin--author:liaozhiyang---date:20240517---for:【QQYUN-9348】增加online查询区域时间范围查询功能 + slotFs.groupTime(); + // update-end--author:liaozhiyang---date:20240517---for:【QQYUN-9348】增加online查询区域时间范围查询功能 + } else { + let type = data.type; + if (type == 'number' || type == 'integer') { + slotFs.groupNumber(); + } + } + return slotFs; + } + + /** + * 表单ID 默认是隐藏的 + */ + static createIdField(): FormSchema { + return { + label: '', + field: 'id', + component: 'Input', + show: false, + }; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/IFormSchema.ts b/src/views/super/online/cgform/auto/comp/factory/IFormSchema.ts new file mode 100644 index 0000000..44bdb54 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/IFormSchema.ts @@ -0,0 +1,469 @@ +import {computed, watch} from 'vue' +import { FormSchema, Rule } from '/@/components/Form'; +import { FieldExtends, POP_CONTAINER } from '../../../types/onlineRender'; +import { LABELLENGTH } from '../../../util/constant'; +import {replaceUserInfoByExpression} from "@/utils/common/compUtils"; +/** + * 1.部门选择/用户选择 无:单选配置 + * 控件类 + */ +export default abstract class IFormSchema { + _data; + field: string; + label: string; + labelLength: number; + formRef: any; + hidden: boolean; + order: number; + required: boolean; + onlyValidator: any; + hasChange: boolean; + pre: string; + setFieldsValue: any; + schemaProp: any; + searchForm: boolean; + disabled: boolean; + popContainer: string; + inPopover: boolean; + + constructor(key, data) { + // 考虑不需要存data + this._data = data; + this.field = key; + this.label = data.title; + this.hidden = false; + this.order = data.order || 999; + this.required = false; + this.onlyValidator = ''; + this.setFieldsValue = ''; + this.hasChange = true; + if (key.indexOf('@') > 0) { + this.pre = key.substring(0, key.indexOf('@') + 1); + } else { + this.pre = ''; + } + this.schemaProp = {}; + this.searchForm = false; + this.disabled = false; + this.popContainer = ''; + this.handleWidgetAttr(data); + this.inPopover = false; + this.labelLength = LABELLENGTH; + this.initLabelLength(); + } + + /** + * 获取最终的表单配置项,外面获取调用此方法 + */ + getFormItemSchema(): FormSchema { + let schema = this.getItem(); + this.addDefaultChangeEvent(schema); + return schema; + } + + /** + * 获取表单配置,子类重写此方法 + */ + getItem(): FormSchema { + let fs: FormSchema = { + field: this.field, + label: this.label, + labelLength: this.labelLength, + component: 'Input', + itemProps:{ + labelCol:{ + class: 'online-form-label' + } + } + }; + let rules = this.getRule(); + if (rules.length > 0 && this.onlyValidator) { + fs['rules'] = rules; + } + if (this.hidden === true) { + fs['show'] = false; + } + return fs; + } + + /** + * 设置表单ref + * popup、分类树需要关联设置其他表单值的时候用到 + * @param ref + */ + setFormRef(ref) { + this.formRef = ref; + } + + /** + * 设置表单元素隐藏 + */ + isHidden() { + this.hidden = true; + return this; + } + + /** + * 设置是否必填项 + * @param array + */ + isRequired(array) { + // 子表必填 TODO + if (array && array.length > 0) { + if (array.indexOf(this.field) >= 0) { + this.required = true; + } + } + return this; + } + + /** + * 初始化 label长度 + */ + initLabelLength(){ + let obj = this.getExtendData() + if(obj && obj.labelLength){ + this.labelLength = obj.labelLength; + } + } + + /** + * 获取扩展参数 + */ + getExtendData() { + let extend: FieldExtends = {}; + let { fieldExtendJson } = this._data; + if (fieldExtendJson) { + if (typeof fieldExtendJson == 'string') { + try { + let json = JSON.parse(fieldExtendJson); + extend = { ...json }; + } catch (e) { + console.error(e); + } + } + } + return extend; + } + + /*** + * 获取和此字段相关的其他字段 需要设置其为隐藏 + */ + getRelatedHideFields(): string[] { + return []; + } + + /** + * placeholder + */ + getPlaceholder(view) { + let text = '请输入'; + // update-begin--author:liaozhiyang---date:20240521---for:【TV360X-218】针对组件分别提示对应的校验语 + if ( + [ + 'list', + 'radio', + 'checkbox', + 'date', + 'datetime', + 'time', + 'list_multi', + 'sel_search', + 'popup', + 'cat_tree', + 'sel_depart', + 'sel_user', + 'pca', + 'link_down', + 'sel_tree', + 'switch', + 'link_table', + 'link_table_field', + 'popup_dict', + 'LinkTableForQuery', + 'CascaderPcaForQuery', + 'select_user2', + 'rangeDate', + 'rangeTime', + 'rangeNumber', + ].includes(view) + ) { + text = '请选择'; + } else if (['file', 'image'].includes(view)) { + text = '请上传'; + } + // update-end--author:liaozhiyang---date:20240521---for:【TV360X-218】针对组件分别提示对应的校验语 + return text + this.label; + } + + /** + * 唯一校验 + */ + setOnlyValidateFun(validateFun) { + if (validateFun) { + this.onlyValidator = async (rule, value) => { + let error = await validateFun(rule, value); + if (!error) { + return Promise.resolve(); + } else { + return Promise.reject(error); + } + }; + } + } + + /** + * 获取校验规则 + */ + getRule(): any[] { + let rules: Rule[] = []; + const { view, errorInfo, pattern, type, fieldExtendJson } = this._data; + if (this.required === true) { + let msg = this.getPlaceholder(view); + // update-begin--author:liaozhiyang---date:20240520---for:【TV360X-80】扩展参数配置中的校验提示不生效 + if (fieldExtendJson) { + const json = JSON.parse(fieldExtendJson); + if (json.validateError) { + msg = json.validateError; + } + } + // update-end--author:liaozhiyang---date:20240520---for:【TV360X-80】扩展参数配置中的校验提示不生效 + if (errorInfo) { + msg = errorInfo; + } + if (view == 'sel_depart' || view == 'sel_user') { + //如果是部门和用户组件 使用 required:true + this.schemaProp['required'] = true; + // update-begin--author:liaozhiyang---date:20240429---for:【QQYUN-9109】online使用部门和用户组件必填时label前面没有必填的*号 + rules.push({ required: true, message: msg }); + // update-end--author:liaozhiyang---date:20240429---for:【QQYUN-9109】online使用部门和用户组件必填时label前面没有必填的*号 + } else { + rules.push({ required: true, message: msg }); + } + } + if ('sel_user' == view) { + if (pattern === 'only' && this.onlyValidator) { + rules.push({ validator: this.onlyValidator }); + } + } + if ('list' === view || 'radio' === view || 'markdown' === view || 'pca' === view || view.indexOf('sel') >= 0 || 'time' === view) { + return rules; + } + if (view.indexOf('upload') >= 0 || view.indexOf('file') >= 0 || view.indexOf('image') >= 0) { + return rules; + } + if (pattern) { + if (pattern === 'only') { + if (this.onlyValidator) { + rules.push({ validator: this.onlyValidator }); + } + } else if (pattern === 'z') { + if (type == 'number' || type == 'integer') { + // this.onlyInteger=true TODO + } else { + rules.push({ pattern: /^-?\d+$/, message: '请输入整数' }); + } + } else { + let msg = errorInfo || '正则校验失败'; + let reg + try { + reg = new RegExp(pattern); + if (!reg) { + reg = pattern; + } + } catch { + reg = pattern; + } + rules.push({ pattern: reg, message: msg }); + } + } + return rules; + } + + /** + * 添加默认的change事件 + * @param schema + */ + addDefaultChangeEvent(schema) { + if (this.hasChange) { + if (!schema.componentProps) { + schema.componentProps = {}; + } + //update-begin-author:taoyan date:2022-5-24 for: VUEN-1095 只读未控制住 + if (this.disabled == true) { + schema.componentProps.disabled = true; + } + //update-end-author:taoyan date:2022-5-24 for: VUEN-1095 只读未控制住 + if (!schema.componentProps.hasOwnProperty('onChange')) { + schema.componentProps['onChange'] = (value, formData) => { + if (value instanceof Event) { + // 输入框 value是event对象 + value = (value.target as any).value; + } + // 部门组件抛出事件的value是数组 + if (value instanceof Array) { + value = value.join(','); + } + // VUEN-1467【vue3 工作流】流程处理 一对多表单 子表tab切换后,关闭不了 导致整个浏览器无法操作 多操作几次,不一定每次必现--- + if(!this.formRef || !this.formRef.value || !this.formRef.value.$formValueChange){ + console.log('当前表单无法触发change事件,field:'+this.field) + }else{ + this.formRef.value.$formValueChange(this.field, value, formData) + } + }; + // update-begin--author:liaozhiyang---date:20251011---for:【issues/8791】js增强popup弹框的onlChange()没生效 + if (schema.component === 'JPopup') { + schema.componentProps['onPopUpChange'] = schema.componentProps['onChange'] + } + // update-end--author:liaozhiyang---date:20251011---for:【issues/8791】js增强popup弹框的onlChange()没生效 + } + } + // 顺带处理其他的 schemaProp + Object.keys(this.schemaProp).map((k) => { + schema[k] = this.schemaProp[k]; + }); + } + + noChange() { + this.hasChange = false; + } + + updateField(field) { + this.field = field; + } + + /** + * 高级查询 没有表单ref对象 手动设置setFieldValue方法用于 popup设置表单值 + */ + setFunctionForFieldValue(func) { + if (func) { + this.setFieldsValue = func; + } + } + + asSearchForm() { + this.searchForm = true; + } + + /**获取modal作为类下拉组件pop的父容器*/ + getModalAsContainer() { + let ele = this.getPopContainer(); + // update-begin--author:liaozhiyang---date:20231205---for:【QQYUN-7150】online缓存路由打开多页导致下拉类型的组件打不开 + if (ele != 'body') { + const elems = document.querySelectorAll(ele); + if (elems && elems.length > 1) { + const data: HTMLElement[] = []; + elems.forEach((item: HTMLElement) => { + if (!(item.offsetWidth == 0 && item.offsetHeight == 0)) { + data.push(item); + } + }); + if (data.length === 1) { + return data[0]; + } + } + } + // update-end--author:liaozhiyang---date:20231205---for:【QQYUN-7150】online缓存路由打开多页导致下拉类型的组件打不开 + return document.querySelector(ele); + } + + /**区分modal表单和查询表单*/ + getPopContainer() { + if (this.searchForm === true) { + return 'body'; + } else if(this.inPopover === true){ + return `.${this.popContainer}`; + }else if(this.popContainer){ + return `.${this.popContainer} .ant-modal-content` + }else { + return POP_CONTAINER; + } + } + + handleWidgetAttr(data) { + if (data.ui) { + if (data.ui.widgetattrs) { + if (data.ui.widgetattrs.disabled == true) { + this.disabled = true; + } + } + } + } + + /** + * 设置 popContainer + */ + setCustomPopContainer(modalClass){ + this.popContainer = modalClass; + } + + //update-begin-author:taoyan date:2022-8-5 for: 他表字段/关联记录用 + // 获取他表字段的 配置信息 + getLinkFieldInfo():any{ + return ''; + } + + // 1.将他表字段的配置信息设置到关联记录字段上 + setOtherInfo(_arg){ + } + //update-end-author:taoyan date:2022-8-5 for: 他表字段/关联记录用 + + // 表单设计器高级查询用 + isInPopover(){ + this.inPopover = true; + } + + handleDictTableParams() { + if (!this.formRef.value) { + return + } + const dictTable = this._data.dictTable as string + if (!dictTable) { + return + } + const matches = dictTable.match(/\${([^}]+)}/g) + if (!matches || matches.length == 0) { + return + } + // 去除 ${} + const keys = matches.map((item: string) => item.replace('${', '').replace('}', '')) + const values = computed(() => { + const formModel = this.formRef.value.formModel + return keys.map((key) => formModel[key]).join(''); + }) + let timer: ReturnType | null = null; + watch(values, () => { + if (timer) { + clearTimeout(timer) + } + timer = setTimeout(() => { + const formModel = this.formRef.value.formModel + // 替换动态参数,如果有 ${xxx} 则替换为实际值 + let tempDictTable = dictTable.replace(/\${([^}]+)}/g, (_$0, $1) => { + if (formModel[$1] == null) { + return '' + } + return formModel[$1] + }); + this.updateDictTable(tempDictTable) + }, 150) + }, {immediate: true}) + } + + updateDictTable(_dictTable: string) { + console.log('请在子类实现 updateDictTable 方法') + } + + /** + * 获取表字典的编码,可替换系统变量 + * @param dictTable + * @param dictText + * @param dictCode + */ + genDictTableCode(dictTable: string, dictText: string, dictCode: string) { + // 替换系统变量 + dictTable = replaceUserInfoByExpression(dictTable) + return encodeURI(`${dictTable},${dictText},${dictCode}`); + } + +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/AreaLinkage.ts b/src/views/super/online/cgform/auto/comp/factory/impl/AreaLinkage.ts new file mode 100644 index 0000000..9aa09d5 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/AreaLinkage.ts @@ -0,0 +1,26 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * 省市区 + */ +export default class PcaWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + // update-begin--author:liaozhiyang---date:20260204---for:【QQYUN-14694】online支持配置独立的省、市、县 + const extendData: any = this.getExtendData(); + const componentProps: any = {} + if (extendData.displayLevel) { + componentProps.displayLevel = extendData.displayLevel; + componentProps.saveCode = extendData.displayLevel === 'all' ? 'region' : componentProps.displayLevel; + } + // update-end--author:liaozhiyang---date:20260204---for:【QQYUN-14694】online支持配置独立的省、市、县 + return Object.assign({}, item, { + component: 'JAreaLinkage', + componentProps: { + saveCode: 'region', + ...componentProps, + }, + }); + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/CascaderPcaForQueryWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/CascaderPcaForQueryWidget.ts new file mode 100644 index 0000000..1249e57 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/CascaderPcaForQueryWidget.ts @@ -0,0 +1,38 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 表单设计器-省市区查询 + */ +export default class CascaderPcaForQueryWidget extends IFormSchema { + + schema: Recordable; + // 省市县联动级别 + areaLevel: number; + // 是否允许更改级别 + allowChangeLevel: boolean; + + constructor(key: string, data: Recordable, queryItem: Recordable) { + super(key, data); + this.schema = data + this.areaLevel = data['areaLevel'] ?? 3; + // 只有等于和不等于才能更改级别 + this.allowChangeLevel = ['eq', 'ne'].includes(queryItem?.rule) + } + + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'CascaderPcaInFilter', + componentProps:{ + areaLevel: this.areaLevel, + allowChangeLevel: this.allowChangeLevel, + placeholder: '请选择…', + style: { + width: '100%', + } + } + }); + } + +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/CheckboxWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/CheckboxWidget.ts new file mode 100644 index 0000000..15bd047 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/CheckboxWidget.ts @@ -0,0 +1,60 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * checkbox + */ +export default class CheckboxWidget extends IFormSchema { + /*title-value*/ + options: any[]; + constructor(key, data) { + super(key, data); + this.options = this.getOptions(data['enum']); + } + + setFormRef(ref) { + super.setFormRef(ref); + this.handleDictTableParams(); + } + + updateDictTable(dictTable: string) { + this.formRef.value.updateSchema(({ + field: this.field, + componentProps: { + options:[], + dictCode: this.genDictTableCode(dictTable, this._data.dictText, this._data.dictCode), + } + })) + } + + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'JCheckbox', + componentProps: { + options: this.options, + triggerChange: true, + // update-begin--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + useDicColor: true, + // update-end--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + }, + }); + } + + getOptions(array) { + if (!array || array.length == 0) { + return []; + } + let arr: any[] = []; + for (let item of array) { + arr.push({ + value: item.value, + label: item.title, + // update-begin--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + color: item.color, + // update-end--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + }); + } + return arr; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/DateWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/DateWidget.ts new file mode 100644 index 0000000..dbd0c5b --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/DateWidget.ts @@ -0,0 +1,59 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +enum DateFormat { + datetime = 'YYYY-MM-DD HH:mm:ss', + date = 'YYYY-MM-DD', +} + +/** + * 日期、时间 + */ +export default class DateWidget extends IFormSchema { + format: string; + showTime: boolean; + picker: string | undefined; + + allowSelectRange: boolean; + + constructor(key, data, queryItem) { + super(key, data); + this.format = DateFormat[data.view]; + this.showTime = data.view == 'date' ? false : true; + // update-begin--author:liaozhiyang---date:20240430---for:【issues/6094】online 日期(年月日)控件增加年、年月,年周,年季度等格式 + let fieldExtendJson = data.fieldExtendJson; + if (data.view == 'date' && fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + if (fieldExtendJson.picker && fieldExtendJson.picker != 'default') { + this.picker = fieldExtendJson.picker; + } else { + this.picker = undefined; + } + } + // update-end--author:liaozhiyang---date:20240430---for:【issues/6094】online 日期(年月日)控件增加年、年月,年周,年季度等格式 + // 只有等于和不等于才能选择预设范围(今天、昨天、本周等) + this.allowSelectRange = ['eq', 'ne'].includes(queryItem?.rule) + } + + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'DatePickerInFilter', + componentProps: { + placeholder: `请选择${this.label}`, + showTime: this.showTime, + valueFormat: this.format, + allowSelectRange: this.allowSelectRange, + // update-begin--author:liaozhiyang---date:20240430---for:【issues/6094】online 日期(年月日)控件增加年、年月,年周,年季度等格式 + picker: this.picker, + // update-end--author:liaozhiyang---date:20240430---for:【issues/6094】online 日期(年月日)控件增加年、年月,年周,年季度等格式 + style: { + width: '100%', + }, + getPopupContainer: (_node) => { + return this.getModalAsContainer(); + }, + }, + }); + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/EditorWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/EditorWidget.ts new file mode 100644 index 0000000..a1b634f --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/EditorWidget.ts @@ -0,0 +1,25 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * 富文本 + */ +export default class EditorWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'JEditor', + componentProps: { + //update-begin-author:taoyan date:2022-6-1 for: VUEN-1159 第一次加载时,点击第一个输入框,光标会跑到富文本输入框 + options: { + auto_focus: false, + }, + //update-end-author:taoyan date:2022-6-1 for: VUEN-1159 第一次加载时,点击第一个输入框,光标会跑到富文本输入框 + // fileMax:1, + // showImageUpload:false, + // width:"966px", + // height:"200px" + }, + }); + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/FileWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/FileWidget.ts new file mode 100644 index 0000000..e0914b1 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/FileWidget.ts @@ -0,0 +1,26 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 文件 + */ +export default class FileWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JUpload', + componentProps, + }); + } + + getComponentProps() { + let json = this.getExtendData(); + if (json && json.uploadnum) { + return { + maxCount: Number(json.uploadnum), + }; + } + return {}; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/ImageWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/ImageWidget.ts new file mode 100644 index 0000000..d045f06 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/ImageWidget.ts @@ -0,0 +1,28 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; +import { UploadTypeEnum } from '/@/components/Form/src/jeecg/components/JUpload'; + +/** + * 图片 + */ +export default class ImageWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JUpload', + componentProps, + }); + } + + getComponentProps() { + let props = { + fileType: UploadTypeEnum.image, + }; + let json = this.getExtendData(); + if (json && json.uploadnum) { + props['maxCount'] = Number(json.uploadnum); + } + return props; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/InputWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/InputWidget.ts new file mode 100644 index 0000000..211a203 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/InputWidget.ts @@ -0,0 +1,15 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 输入框 + */ +export default class InputWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + if (this.hidden === true) { + item['show'] = false; + } + return item; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/LinkDownWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/LinkDownWidget.ts new file mode 100644 index 0000000..3c0f102 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/LinkDownWidget.ts @@ -0,0 +1,108 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 下拉联动- 原理是: + * 使用JDictSelectTag组件(2022-03-09测试可行版 后续如有改动请注意) + * 监听表单的change事件,清空下级表单值,并改变props + * 问题在于1.没有code的时候 不需要设置选项 + * 优势在于: 可以不考虑组件位置(但是需要改后台接口) + */ +export default class LinkDownWidget extends IFormSchema { + /*title-value*/ + options: any[]; + next: string; + type: string; + table: string; + txt: string; + store: string; + pidField: string; + idField: string; + origin: boolean; + condition: string; + + constructor(key, data) { + super(key, data); + const { dictTable, dictText, dictCode, pidField, idField, origin, condition } = data; + this.table = dictTable; + this.txt = dictText; + this.store = dictCode; + this.idField = idField; + this.pidField = pidField; + this.origin = origin; + this.condition = condition; + // 都是空数组 + this.options = []; + this.next = data.next || ''; + this.type = data.type; + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'OnlineSelectCascade', + componentProps, + }); + } + + getComponentProps() { + let baseProp = { + table: this.table, + txt: this.txt, + store: this.store, + pidField: this.pidField, + idField: this.idField, + origin: this.origin, + pidValue: '-1', + style: { + width: '100%', + }, + onChange: (value) => { + console.log('级联组件-onChange', value); + this.valueChange(value); + }, + onNext: (pidValue) => { + console.log('级联组件-onNext', pidValue); + this.nextOptionsChange(pidValue); + }, + }; + if (this._data.origin === true) { + baseProp['condition'] = this.condition; + } + return baseProp; + } + + async nextOptionsChange(pidValue) { + if (!this.formRef) { + console.error('表单引用找不到'); + return; + } + if (!this.next) { + return; + } + let ref = this.formRef.value; + await ref.updateSchema({ + field: this.next, + componentProps: { + pidValue, + }, + }); + } + + async valueChange(value) { + if (!this.formRef) { + console.error('表单引用找不到'); + return; + } + // update-begin--author:liaozhiyang---date:20240717---for:【TV360X-1856】联动组件最后一个js增强onchang方法不生效 + let ref = this.formRef.value; + // 触发form层级的change事件 + ref.$formValueChange(this.field, value); + if (this.next) { + // 重置value + await ref.setFieldsValue({ [this.next]: '' }); + } + // update-end--author:liaozhiyang---date:20240717---for:【TV360X-1856】联动组件最后一个js增强onchang方法不生效 + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableFieldWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableFieldWidget.ts new file mode 100644 index 0000000..dfbb44d --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableFieldWidget.ts @@ -0,0 +1,42 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 他表字段 + */ +export default class LinkTableFieldWidget extends IFormSchema { + + dictTable: string; + dictText: string; + + constructor(key, data) { + super(key, data); + this.dictTable = data['dictTable']; + this.dictText = data['dictText']; + } + + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + componentProps: { + readOnly: true, + allowClear: false, + disabled: true, + style:{ + background: 'none', + color:'rgba(0, 0, 0, 0.85)', + border:'none' + } + } + }); + return item; + } + + /** + * 获取他表字段的关联信息 + */ + getLinkFieldInfo(){ + let arr = [this.dictTable, `${this.field},${this.dictText}`]; + return arr; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableForQueryWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableForQueryWidget.ts new file mode 100644 index 0000000..5248fea --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableForQueryWidget.ts @@ -0,0 +1,35 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 表单设计器-关联记录查询 使用下拉搜索 + */ +export default class LinkTableForQueryWidget extends IFormSchema { + + code: string; + titleField: string; + multi: boolean; + + constructor(key, data) { + super(key, data); + this.code = data['code']; + this.titleField = data['titleField']; + this.multi = data['multi']||false; + } + + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'LinkTableForQuery', + componentProps:{ + code: this.code, + multi: this.multi, + field: this.titleField, + style: { + width: '100%', + } + } + }); + } + +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableWidget.ts new file mode 100644 index 0000000..a772967 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/LinkTableWidget.ts @@ -0,0 +1,72 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 关联记录 + */ +export default class LinkTableWidget extends IFormSchema { + dictTable: string; + dictText: string; + dictCode: string; + view: string; + componentString: string; + linkFields: Array; + + constructor(key, data) { + super(key, data); + this.dictTable = data.dictTable; + this.dictText = data.dictText; + this.dictCode = data.dictCode; + this.view = data.view; + this.componentString = '' + this.linkFields = [] + } + + getItem(): FormSchema { + let item = super.getItem(); + const componentProps = this.getComponentProps() + return Object.assign({}, item, { + component: this.componentString, + componentProps: componentProps + }); + } + + getComponentProps() { + let props = { + textField: this.dictText, + tableName: this.dictTable, + valueField: this.dictCode, + }; + let extend = this.getExtendData(); + // 是否多选 + if (extend.multiSelect) { + props['multi'] = true; + }else{ + props['multi'] = false; + } + //封面图 + if (extend.imageField) { + props['imageField'] = extend.imageField; + }else{ + props['imageField'] = '' + } + //显示类型 + if (extend.showType=='select') { + this.componentString = 'LinkTableSelect' + let popContainer = this.getPopContainer(); + props['popContainer'] = popContainer + }else{ + this.componentString = 'LinkTableCard' + } + if(this.linkFields.length>0){ + props['linkFields'] = this.linkFields; + } + return props; + } + + // 他表字段用于翻译 + setOtherInfo(arr){ + // ["表单字段,表字典字段","表单字段,表字典字段"] + this.linkFields = arr; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/MarkdownWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/MarkdownWidget.ts new file mode 100644 index 0000000..47ae5dd --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/MarkdownWidget.ts @@ -0,0 +1,17 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * markdown + */ +export default class MarkdownWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'JMarkdownEditor', + componentProps: { + // height: 300, + }, + }); + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/NumberWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/NumberWidget.ts new file mode 100644 index 0000000..a572327 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/NumberWidget.ts @@ -0,0 +1,49 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 输入框-数字 + */ +export default class NumberWidget extends IFormSchema { + dbPointLength: number; + + constructor(key, data) { + super(key, data); + this.dbPointLength = data.dbPointLength; + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + const safeIntRule = { + validator: (_rule, value) => { + if (value !== null && value !== undefined && value !== '') { + if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) { + return Promise.reject(`数值超出安全范围(${Number.MIN_SAFE_INTEGER}~${Number.MAX_SAFE_INTEGER}),精度将丢失,请重新输入`); + } + } + return Promise.resolve(); + }, + }; + const existingRules = item.rules || []; + return Object.assign({}, item, { + component: 'InputNumber', + componentProps, + // update-begin--author:liaozhiyang---date:20260413---for:【QQYUN-9790】online中数字类型超出js语言数值范围加提示 + rules: [...existingRules, safeIntRule], + // update-end--author:liaozhiyang---date:20260413---for:【QQYUN-9790】online中数字类型超出js语言数值范围加提示 + }); + } + + getComponentProps() { + const props = { + style: { + width: '100%', + }, + }; + if (this.dbPointLength >= 0) { + props['precision'] = this.dbPointLength; + } + return props; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/PasswordWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/PasswordWidget.ts new file mode 100644 index 0000000..a3e4a5a --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/PasswordWidget.ts @@ -0,0 +1,14 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 输入框- 密码 + */ +export default class PasswordWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'InputPassword', + }); + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/PcaWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/PcaWidget.ts new file mode 100644 index 0000000..403292f --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/PcaWidget.ts @@ -0,0 +1,14 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * 省市区 + */ +export default class PcaWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'JAreaSelect', + }); + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/PopupDictWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/PopupDictWidget.ts new file mode 100644 index 0000000..2161fc2 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/PopupDictWidget.ts @@ -0,0 +1,43 @@ +import {unref} from 'vue' +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * popupDict + */ +export default class PopupDictWidget extends IFormSchema { + dictCode: string; + multi: boolean; + constructor(key, data) { + super(key, data); + this.dictCode = `${data['code']},${data['destFields']},${data['orgFields']}`; + this.multi = data['popupMulti']; + } + + getItem(): FormSchema { + const item = super.getItem(); + const componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JPopupDict', + componentProps, + }); + } + + getComponentProps() { + const props = { + dictCode: this.dictCode, + multi: this.multi, + }; + // 解决表单设计器高级查询 popup组件弹窗导致高级查询pop关闭 + if (this.inPopover) { + props['getContainer'] = () => { + return this.getModalAsContainer(); + }; + } + + // 获取表单数据 + props['getFormValues'] = () => unref(this.formRef).getFieldsValue(); + + return props; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/PopupWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/PopupWidget.ts new file mode 100644 index 0000000..9224897 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/PopupWidget.ts @@ -0,0 +1,72 @@ +import {unref} from 'vue' +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * popup + */ +export default class PopupWidget extends IFormSchema { + code: string; + multi: boolean; + fieldConfig: any[]; + + constructor(key, data) { + super(key, data); + this.code = data['code']; + this.multi = data['popupMulti']; + this.fieldConfig = this.getFieldConfig(data); + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JPopup', + componentProps, + }); + } + + getComponentProps() { + let props = { + code: this.code, + multi: this.multi, + fieldConfig: this.fieldConfig, + }; + if (this.formRef) { + props['formElRef'] = this.formRef; + } else { + props['setFieldsValue'] = this.setFieldsValue; + } + // 解决表单设计器高级查询 popup组件弹窗导致高级查询pop关闭 + if(this.inPopover === true){ + props['getContainer'] = ()=>{ + return this.getModalAsContainer(); + } + } + + // 获取表单数据 + props['getFormValues'] = () => unref(this.formRef).getFieldsValue(); + + return props; + } + + getFieldConfig(data) { + let { destFields, orgFields, dictText } = data; + if (!destFields || destFields.length == 0) { + return []; + } + let arr1 = destFields.split(','); + let arr2 = orgFields.split(','); + let arr3 = dictText ? dictText.split(',') : null; + let config: any[] = []; + const pre = this.pre; + for (let i = 0; i < arr1.length; i++) { + config.push({ + target: pre + arr1[i], + source: arr2[i], + label: arr3 ? arr3[i] : void 0, + }); + } + return config; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/RadioWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/RadioWidget.ts new file mode 100644 index 0000000..7b5d7a0 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/RadioWidget.ts @@ -0,0 +1,66 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * radio + * 没有现成的 只能借用JDictSelectTag + */ +export default class RadioWidget extends IFormSchema { + dictTable: string; + dictText: string; + dictCode: string; + + constructor(key, data) { + super(key, data); + // 可以从这个里面取 但是换成临时加载的 + //this.options = this.getOptions(data['enum']) + this.dictTable = data['dictTable']; + this.dictText = data['dictText']; + this.dictCode = data['dictCode']; + } + + setFormRef(ref) { + super.setFormRef(ref); + this.handleDictTableParams(); + } + + updateDictTable(dictTable: string) { + this.formRef.value.updateSchema(({ + field: this.field, + componentProps: { + dictCode: this.genDictTableCode(dictTable, this.dictText, this.dictCode), + } + })) + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JDictSelectTag', + componentProps, + }); + } + + getComponentProps() { + if (!this.dictTable && !this.dictCode) { + // 字典表 和 字典 都没填数据 + return {}; + } else { + if (!this.dictTable) { + return { + // update-begin--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + useDicColor: true, + // update-end--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + dictCode: this.dictCode, + type: 'radio', + }; + } else { + return { + dictCode: this.genDictTableCode(this.dictTable, this.dictText, this.dictCode), + type: 'radio', + }; + } + } + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/RangeWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/RangeWidget.ts new file mode 100644 index 0000000..ebc3179 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/RangeWidget.ts @@ -0,0 +1,43 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 日期、时间、数值-范围 + */ +export default class RangeWidget extends IFormSchema { + + componentType: string; + datetime: boolean; + format: string; + + constructor(key, data) { + super(key, data); + let view = data.view; + this.format = data.format; + this.datetime = false; + if('rangeNumber'===view){ + this.componentType = 'JRangeNumber' + }else if('rangeTime'===view){ + this.componentType = 'RangeTime' + }else{ + this.componentType = 'RangeDate' + if(data.datetime===true){ + this.datetime = true; + } + } + } + + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: this.componentType, + componentProps: { + datetime: this.datetime, + format: this.format, + getPopupContainer: (_node) => { + return this.getModalAsContainer(); + }, + }, + }); + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/SelectDepartWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/SelectDepartWidget.ts new file mode 100644 index 0000000..faedd55 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/SelectDepartWidget.ts @@ -0,0 +1,49 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * 部门选择 + */ +export default class SelectDepartWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JSelectDept', + componentProps, + }); + } + + getComponentProps() { + let extend = this.getExtendData(); + let props = { + // update-begin--author:liaozhiyang---date:20260414---for:【QQYUN-9801】修复online点击展开全部,树节点没全部展开 + sync: false, + // update-end--author:liaozhiyang---date:20260414---for:【QQYUN-9801】修复online点击展开全部,树节点没全部展开 + checkStrictly: true, + showButton: false, + }; + if (extend.text) { + props['labelKey'] = extend.text; + } + if (extend.store) { + props['rowKey'] = extend.store; + } + if (extend.multiSelect === false) { + props['multiple'] = false; + } + + if (extend.multiSelect === true) { + props['multiple'] = true; + } + props['maxTagCount'] = 3; + + // 解决表单设计器高级查询 popup组件弹窗导致高级查询pop关闭 + if(this.inPopover === true){ + props['getContainer'] = ()=>{ + return this.getModalAsContainer(); + } + } + return props; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/SelectMultiWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/SelectMultiWidget.ts new file mode 100644 index 0000000..ab39279 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/SelectMultiWidget.ts @@ -0,0 +1,67 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 下拉多选框 + */ +export default class SelectMultiWidget extends IFormSchema { + dictTable: string; + dictText: string; + dictCode: string; + + constructor(key, data) { + super(key, data); + // 可以从这个里面取 但是换成临时加载的 + //this.options = this.getOptions(data['enum']) + this.dictTable = data['dictTable']; + this.dictText = data['dictText']; + this.dictCode = data['dictCode']; + } + + setFormRef(ref) { + super.setFormRef(ref); + this.handleDictTableParams(); + } + + updateDictTable(dictTable: string) { + this.formRef.value.updateSchema(({ + field: this.field, + componentProps: { + dictCode: this.genDictTableCode(dictTable, this.dictText, this.dictCode) + } + })) + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JSelectMultiple', + componentProps: componentProps, + }); + } + + getComponentProps() { + if (!this.dictTable && !this.dictCode) { + // 字典表 和 字典 都没填数据 + return {}; + } else { + let props = {}; + if (!this.dictTable) { + props['dictCode'] = this.dictCode; + // update-begin--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + props['useDicColor'] = true; + // update-end--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + } else { + props['dictCode'] = this.genDictTableCode(this.dictTable, this.dictText, this.dictCode); + // update-begin--author:liaozhiyang---date:20260204---for:【issues/9307】online下拉加载表字典需滚动加载 + // 默认滚动加载字典表数据 + props['scrollLoad'] = true; + // update-end--author:liaozhiyang---date:20260204---for:【issues/9307】online下拉加载表字典需滚动加载 + } + props['triggerChange'] = true; + props['popContainer'] = this.getPopContainer(); + return props; + } + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/SelectSearchWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/SelectSearchWidget.ts new file mode 100644 index 0000000..78691bc --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/SelectSearchWidget.ts @@ -0,0 +1,53 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 下拉搜索 + */ +export default class SelectSearchWidget extends IFormSchema { + dict: string; + type: number; + constructor(key, data) { + super(key, data); + if (data.dictTable && data.dictText && data.dictCode) { + // 字典表 + this.dict = this.genDictTableCode(data.dictTable, data.dictText, data.dictCode); + this.type = 1; + } else { + // 数据字典 + this.dict = encodeURI(`${data.dictCode}`); + this.type = 0; + } + } + + setFormRef(ref) { + super.setFormRef(ref); + this.handleDictTableParams(); + } + + updateDictTable(dictTable: string) { + this.formRef.value.updateSchema(({ + field: this.field, + componentProps: { + dict: this.genDictTableCode(dictTable, this._data.dictText, this._data.dictCode), + } + })) + } + + getItem(): FormSchema { + let item = super.getItem(); + let popContainer = this.getPopContainer(); + return Object.assign({}, item, { + component: 'JSearchSelect', + componentProps: { + dict: this.dict, + pageSize: 10, + // update-begin--author:liaozhiyang---date:20240628---for:【issues/6336】online下拉搜索框设置数据字典编辑弹窗报错 + async: this.type ? true : false, + // update-end--author:liaozhiyang---date:20240628---for:【issues/6336】online下拉搜索框设置数据字典编辑弹窗报错 + useDicColor: true, + popContainer: popContainer, + }, + }); + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/SelectUser2Widget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/SelectUser2Widget.ts new file mode 100644 index 0000000..66a96b6 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/SelectUser2Widget.ts @@ -0,0 +1,44 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * 用户选择 + */ +export default class SelectUser2Widget extends IFormSchema { + + multi: boolean; + store: string; + query: boolean; + + constructor(key, data) { + super(key, data); + this.multi = data.multi === true ? true : false; + this.store = data.store||''; + // 是否是查询条件,查询条件显示为输入框样式 + this.query = data.query||false; + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'UserSelect', + componentProps + }); + } + + getComponentProps() { + let props = { + multi: this.multi, + store: this.store, + query: this.query, + } + // 解决表单设计器高级查询 popup组件弹窗导致高级查询pop关闭 + if(this.inPopover === true){ + props['getContainer'] = ()=>{ + return this.getModalAsContainer(); + } + } + return props; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/SelectUserWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/SelectUserWidget.ts new file mode 100644 index 0000000..0d02b7d --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/SelectUserWidget.ts @@ -0,0 +1,54 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * 用户选择 + */ +export default class SelectUserWidget extends IFormSchema { + + showButton: boolean; + + constructor(key, data) { + super(key, data); + this.showButton = data.showButton === false ? false : true; + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JSelectUser', + componentProps, + }); + } + + getComponentProps() { + let extend = this.getExtendData(); + let props = { + showSelected: false, + allowClear: true, + isRadioSelection: false, + showButton: this.showButton + }; + if (extend.text) { + props['labelKey'] = extend.text; + } + if (extend.store) { + props['rowKey'] = extend.store; + } + if (extend.multiSelect === false) { + //props['multiple'] = false + props['isRadioSelection'] = true; + } + props['maxTagCount'] = 3; + + // 解决表单设计器高级查询 popup组件弹窗导致高级查询pop关闭 + if(this.inPopover === true){ + props['getContainer'] = ()=>{ + return this.getModalAsContainer(); + } + } + + return props; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/SelectWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/SelectWidget.ts new file mode 100644 index 0000000..f9cbe14 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/SelectWidget.ts @@ -0,0 +1,150 @@ +import { FormSchema } from '/@/components/Form'; +import { h } from 'vue'; +import IFormSchema from '../IFormSchema'; + +/** + * 下拉框 + * //待处理: 表字典取数据可以考虑传参前端再请求 + */ +export default class SelectWidget extends IFormSchema { + schema: Recordable; + /*title-value*/ + options: any[]; + dictTable: string; + dictText: string; + dictCode: string; + multi: boolean; + + constructor(key, data) { + super(key, data); + this.schema = data; + // 静态数据选项(enum)转换为 JSelectSingle 所需格式 + this.options = data['enum'] ? this.getOptions(data['enum'], '') : []; + this.dictTable = data['dictTable']; + this.dictText = data['dictText']; + this.dictCode = data['dictCode']; + this.multi = data['multi'] || false; + } + + getItem(): FormSchema { + let item = super.getItem(); + let component = this.getFormComponent() + let componentProps = this.getComponentProps() + return Object.assign({}, item, { + component, + componentProps, + renderComponentContent: this.getSlots(componentProps), + }); + } + + getFormComponent(){ + // update-begin--author:liaozhiyang---date:20260204---for:【issues/9307】online下拉加载表字典需滚动加载 + // if(this.options.length>0){ + // return 'Select' + // }else{ + // return 'JDictSelectTag' + // } + return 'JSelectSingle' + // update-end--author:liaozhiyang---date:20260204---for:【issues/9307】online下拉加载表字典需滚动加载 + } + + setFormRef(ref) { + super.setFormRef(ref); + this.handleDictTableParams(); + } + + updateDictTable(dictTable: string) { + this.formRef.value.updateSchema(({ + field: this.field, + componentProps: { + dictCode: this.genDictTableCode(dictTable, this.dictText, this.dictCode), + } + })) + } + + getComponentProps() { + let mode = this.multi===true?'multiple':'combobox' + let props: any = { + allowClear: true, + mode, + style: { + width: '100%', + }, + getPopupContainer: (_node) => { + return this.getModalAsContainer(); + }, + // update-begin--author:liaozhiyang---date:20260203---for:【issues/9307】online下拉加载表字典需滚动加载 + // 下拉框展开/关闭的回调 + // onDropdownVisibleChange: (visible: boolean)=> { + // if (visible && typeof this.schema.updateOptions === 'function') { + // this.schema.updateOptions() + // } + // }, + // update-end--author:liaozhiyang---date:20260203---for:【issues/9307】online下拉加载表字典需滚动加载 + } + // update-begin--author:liaozhiyang---date:20260203---for:【issues/9307】online下拉加载表字典需滚动加载 + if (!this.dictTable) { + props['dictCode'] = this.dictCode; + // update-begin--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + props['useDicColor'] = true; + // update-end--author:liaozhiyang---date:20230110---for:【QQYUN-7799】字典组件(原生组件除外)加上颜色配置 + // 静态数据(无 dictCode)时,将 enum 选项直接传给组件 + if (!this.dictCode && this.options.length > 0) { + props['options'] = this.options; + } + } else { + props['dictCode'] = this.genDictTableCode(this.dictTable, this.dictText, this.dictCode); + props['scrollLoad'] = true; + delete props.onDropdownVisibleChange; + } + // update-end--author:liaozhiyang---date:20260203---for:【issues/9307】online下拉加载表字典需滚动加载 + return props + } + + getSlots(componentProps: Recordable) { + const {useDicColor} = componentProps; + return function () { + return { + option(option: Recordable) { + const style: Recordable = {}; + if (useDicColor && option.color) { + style.color = '#fff'; + style.height = '20px'; + style.lineHeight = '20px'; + style.padding = '0 6px'; + style.fontSize = '12px'; + style.borderRadius = '8px'; + style.backgroundColor = option.color; + style.display = 'inline-block'; + } + return h('span', { + style, + }, option.text || option.label); + }, + }; + } + } + + getOptions(array, type) { + if (!array || array.length == 0) { + return []; + } + let isNum = 'number' == type; + let arr: any[] = []; + for (let item of array) { + // update-begin--author:liaozhiyang---date:20240517---for:【QQYUN-9359】加强判断,防止数据有null报错 + if (item == null) break; + // update-end--author:liaozhiyang---date:20240517---for:【QQYUN-9359】加强判断,防止数据有null报错 + let value = item.value; + if(isNum){ + value = parseInt(value) + } + arr.push({ + ...item, + value, + label: item.title, + }); + } + return arr; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/SlotWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/SlotWidget.ts new file mode 100644 index 0000000..0863604 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/SlotWidget.ts @@ -0,0 +1,68 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * slot + */ +export default class SlotWidget extends IFormSchema { + slot: string; + picker: string | undefined; + precision: number | undefined; + + constructor(key, data) { + super(key, data); + this.slot = ''; + // update-begin--author:liaozhiyang---date:20240520---for:【TV360X-180】范围查询年,年月,周,季度 + let fieldExtendJson = data.fieldExtendJson; + if (data.view == 'date' && fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + if (fieldExtendJson.picker && fieldExtendJson.picker != 'default') { + this.picker = fieldExtendJson.picker; + } else { + this.picker = undefined; + } + } + // update-end--author:liaozhiyang---date:20240520---for:【TV360X-180】范围查询年,年月,周,季度 + // update-begin--author:liaozhiyang---date:20240606---for:【TV360X-214】范围查询控件没有根据配置格式化 + this.precision = data.dbPointLength; + // update-end--author:liaozhiyang---date:20240606---for:【TV360X-214】范围查询控件没有根据配置格式化 + } + + getItem(): FormSchema { + let item = super.getItem(); + let slot = this.slot; + const componentProps: any = {}; + this.picker && (componentProps.picker = this.picker); + // update-begin--author:liaozhiyang---date:20240606---for:【TV360X-214】范围查询控件没有根据配置格式化 + this.precision && (componentProps.precision = this.precision); + // update-end--author:liaozhiyang---date:20240606---for:【TV360X-214】范围查询控件没有根据配置格式化 + // update-begin--author:liaozhiyang---date:20240520---for:【TV360X-180】范围查询年,年月,周,季度 + return Object.assign({}, item, { + slot, + componentProps, + }); + // update-end--author:liaozhiyang---date:20240520---for:【TV360X-180】范围查询年,年月,周,季度 + } + + groupDate() { + this.slot = 'groupDate'; + return this; + } + + groupDatetime() { + this.slot = 'groupDatetime'; + return this; + } + + groupTime() { + // update-begin--author:liaozhiyang---date:20240517---for:【QQYUN-9348】增加online查询区域时间范围查询功能 + this.slot = 'groupTime'; + return this; + // update-end--author:liaozhiyang---date:20240517---for:【QQYUN-9348】增加online查询区域时间范围查询功能 + } + + groupNumber() { + this.slot = 'groupNumber'; + return this; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/SwitchWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/SwitchWidget.ts new file mode 100644 index 0000000..92204b7 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/SwitchWidget.ts @@ -0,0 +1,43 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; +import { isArray, isObject } from '/@/utils/is'; + +/** + * 开关 + */ +export default class SwitchWidget extends IFormSchema { + constructor(key, data) { + super(key, data); + // update-begin--author:liaozhiyang---date:20240517---for:【TV360X-54】开关只读未生效 + // this.hasChange = false; + // update-end--author:liaozhiyang---date:20240517---for:【TV360X-54】开关只读未生效 + } + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + component: 'JSwitch', + componentProps, + }); + } + + getComponentProps() { + let { fieldExtendJson } = this._data; + let options = ['Y', 'N']; + if (fieldExtendJson) { + if (typeof fieldExtendJson == 'string') { + // update-begin--author:liaozhiyang---date:20240522---for:【TV360X-25】扩展参数配置中增加开关是否选项配置 + const json = JSON.parse(fieldExtendJson); + if (isArray(json) && json.length == 2) { + options = json; + } else if (isObject(json) && isArray(json.switchOptions)) { + options = json.switchOptions; + } + // update-end--author:liaozhiyang---date:20240522---for:【TV360X-25】扩展参数配置中增加开关是否选项配置 + } + } + return { + options, + }; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/TextAreaWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/TextAreaWidget.ts new file mode 100644 index 0000000..a280565 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/TextAreaWidget.ts @@ -0,0 +1,19 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 输入框-textarea + */ +export default class TextAreaWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'InputTextArea', + componentProps:{ + autoSize : { + minRows: 4, maxRows: 10 + } + } + }); + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/TimeWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/TimeWidget.ts new file mode 100644 index 0000000..66a5ad7 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/TimeWidget.ts @@ -0,0 +1,24 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 日期、时间 + */ +export default class TimeWidget extends IFormSchema { + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'TimePicker', + componentProps: { + placeholder: `请选择${this.label}`, + valueFormat: 'HH:mm:ss', + getPopupContainer: (_node) => { + return this.getModalAsContainer(); + }, + style: { + width: '100%', + }, + }, + }); + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/TreeCategoryWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/TreeCategoryWidget.ts new file mode 100644 index 0000000..e159ecd --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/TreeCategoryWidget.ts @@ -0,0 +1,73 @@ +import { FormSchema } from '/@/components/Form'; +import IFormSchema from '../IFormSchema'; + +/** + * 分类字典 + */ +export default class TreeCategoryWidget extends IFormSchema { + pid: string; + multi: boolean; + textField: string; + pcode: string; + + constructor(key, data) { + super(key, data); + this.multi = false; + this.pid = data['pidValue']; + this.pcode = data['pcode']; + this.textField = data['textField']; + } + + getItem(): FormSchema { + let item = super.getItem(); + let componentProps = this.getComponentProps(); + return Object.assign({}, item, { + componentProps, + component: 'JCategorySelect', + }); + } + + /** + * 1. 不带返回值的 + * 2. 带文本返回的 + */ + getComponentProps() { + // VUEN-1049 分类字典保存后,列表不展示 单表 树表 --> 配错编码后,表单界面还显示分类字典选项,可直接不显示字典选项 + let param = { + placeholder: '请选择' + this.label + } + if(this.pcode){ + param['pcode'] = this.pcode; + }else{ + let pidValue = this.pid || 'EMPTY_PID'; + param['pid'] = pidValue; + } + if (!this.textField) { + return { + multiple: this.multi, + ...param + }; + } else { + return { + loadTriggleChange: true, + multiple: this.multi, + ...param, + back: this.textField, + onChange: (val, backVal) => { + if (this.formRef) { + this.formRef.value.setFieldsValue(backVal); + this.formRef.value.$formValueChange(this.field, val) + } + }, + }; + } + } + + getRelatedHideFields(): string[] { + let arr: string[] = []; + if (this.textField) { + arr.push(this.textField); + } + return arr; + } +} diff --git a/src/views/super/online/cgform/auto/comp/factory/impl/TreeSelectWidget.ts b/src/views/super/online/cgform/auto/comp/factory/impl/TreeSelectWidget.ts new file mode 100644 index 0000000..2a97a0d --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/factory/impl/TreeSelectWidget.ts @@ -0,0 +1,41 @@ +import IFormSchema from '../IFormSchema'; +import { FormSchema } from '/@/components/Form'; + +/** + * 自定义树 + */ +export default class TreeSelectWidget extends IFormSchema { + /*表名、显示字段、存储字段*/ + dict: string; + /*父级ID的字段名*/ + pidField: string; + /*父级ID的字段值*/ + pidValue: string; + /*是否有子节点*/ + hasChildField: string; + + constructor(key, data) { + super(key, data); + this.dict = data['dict']; + this.pidField = data['pidField']; + this.pidValue = data['pidValue']; + // update-begin--author:liaozhiyang---date:20240509---for:【issues/6197】解决自定义树组件是否含有子节点功能不生效 + this.hasChildField = data['hasChildField']; + // update-end--author:liaozhiyang---date:20240509---for:【issues/6197】解决自定义树组件是否含有子节点功能不生效 + } + + getItem(): FormSchema { + let item = super.getItem(); + return Object.assign({}, item, { + component: 'JTreeSelect', + componentProps: { + dict: this.dict, + pidField: this.pidField, + pidValue: this.pidValue, + // update-begin--author:liaozhiyang---date:20240509---for:【issues/6197】解决自定义树组件是否含有子节点功能不生效 + hasChildField: this.hasChildField, + // update-end--author:liaozhiyang---date:20240509---for:【issues/6197】解决自定义树组件是否含有子节点功能不生效 + }, + }); + } +} diff --git a/src/views/super/online/cgform/auto/comp/index.ts b/src/views/super/online/cgform/auto/comp/index.ts new file mode 100644 index 0000000..f0a93d5 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/index.ts @@ -0,0 +1,13 @@ +import type { App } from 'vue'; +import {defineAsyncComponent} from 'vue' +const SuperQuery = defineAsyncComponent(() => import('./superquery/SuperQuery.vue')) +const JOnlineSearchSelect = defineAsyncComponent(() => import('./JOnlineSearchSelect.vue')) + +export const registerOnlineComp = { + install(app: App) { + app.component('JOnlineSearchSelect', JOnlineSearchSelect); + app.component('SuperQuery', SuperQuery); + + console.log("---初始化---, 全局注册Online部分组件--------------") + }, +}; diff --git a/src/views/super/online/cgform/auto/comp/superquery/SuperQuery.vue b/src/views/super/online/cgform/auto/comp/superquery/SuperQuery.vue new file mode 100644 index 0000000..14fdea2 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/superquery/SuperQuery.vue @@ -0,0 +1,716 @@ + + + + + + + diff --git a/src/views/super/online/cgform/auto/comp/superquery/SuperQueryValComponent.vue b/src/views/super/online/cgform/auto/comp/superquery/SuperQueryValComponent.vue new file mode 100644 index 0000000..59073a2 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/superquery/SuperQueryValComponent.vue @@ -0,0 +1,108 @@ + \ No newline at end of file diff --git a/src/views/super/online/cgform/auto/comp/superquery/useSuperQuery.ts b/src/views/super/online/cgform/auto/comp/superquery/useSuperQuery.ts new file mode 100644 index 0000000..4b71020 --- /dev/null +++ b/src/views/super/online/cgform/auto/comp/superquery/useSuperQuery.ts @@ -0,0 +1,703 @@ +import { useModalInner } from '/@/components/Modal'; +import { randomString } from '/@/utils/common/compUtils'; +import { reactive, ref, toRaw, watch } from 'vue'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { Modal } from 'ant-design-vue'; +import { createLocalStorage } from '/@/utils/cache'; +import { useRoute } from 'vue-router'; +import FormSchemaFactory from '../factory/FormSchemaFactory'; +import {useExtendComponent} from '../../../hooks/auto/useExtendComponent' +import { cloneDeep } from 'lodash-es'; +/** + * 表单类型转换成查询类型 + * 普通查询和高级查询组件区别 :高级查询不支持联动组件 + */ +const FORM_VIEW_TO_QUERY_VIEW = { + "password": "text", + "file": "text", + "image": "text", + "textarea": "text", + "umeditor": "text", + "markdown": "text", + "checkbox": "list_multi", + "radio": "list", +} + +// 查询条件存储编码前缀 +const SAVE_CODE_PRE = 'JSuperQuerySaved_'; + +/** + * 查询项 + * */ +interface SuperQueryItem { + field: string|undefined; + rule: string|undefined; + val: string|number; + key: string; + // 解决inputNumber组件对不齐样式问题 + curLineAlign: string | undefined; + fileType: string; + // update-begin--author:liaozhiyang---date:20240611---for:【TV360X-461】字段类型是string,控件是text,则默认模糊查询 + view: string; + // 最先原始的组件类型;view字段可能会被改变 + originView?: string; + // update-end--author:liaozhiyang---date:20240611---for:【TV360X-461】字段类型是string,控件是text,则默认模糊查询 +} +/** + * 查询项-第一个控件树model + * */ +interface TreeModel { + title: string, + value: string, + isLeaf?: boolean, + disabled?: boolean, + children?: TreeModel[], + order?: number, + fieldType?: string; + // update-begin--author:liaozhiyang---date:20240611---for:【TV360X-461】字段类型是string,控件是text,则默认模糊查询 + view: string; + originView?: string; + // update-end--author:liaozhiyang---date:20240611---for:【TV360X-461】字段类型是string,控件是text,则默认模糊查询 +} + +/** + * 查询信息保存结构 + * */ +interface SaveModel{ + title: string, + content: string, + type: string, +} + +export function useSuperQuery(props){ + // 添加表单组件 + const {linkTableCard2Select} = useExtendComponent(); + + const { createMessage: $message } = useMessage(); + /** 表单ref*/ + const formRef = ref(); + + /** 数据*/ + const dynamicRowValues = reactive<{ values: SuperQueryItem[] }>({ + values: [], + }); + /** and/or */ + const matchType = ref('and'); + + // 保存查询弹窗确定按钮loading状态 + const saveModalLoading = ref(false); + // 弹框显示 + const [registerModal, {setModalProps}] = useModalInner(() => { + setModalProps({confirmLoading: false}); + }) + + // 高级查询类型不支持联动组件,需要额外设置联动组件的view为text + const view2QueryViewMap = Object.assign({}, {"link_down":"text"}, FORM_VIEW_TO_QUERY_VIEW) + + /** + * 确认按钮事件 + */ + function handleSubmit() { + console.log('handleSubmit', dynamicRowValues.values) + } + + /** + * 关闭按钮事件 + */ + function handleCancel() { + //closeModal(); + } + + /** + * val组件赋值 + */ + function setFormModel(key: string, value: any, item: any) { + console.log('setFormModel', key, value) + // formModel[key] = value; + item['val'] = value; + } + + // 字段-Properties + const fieldProperties = ref({}) + // 字段-左侧查询项-树控件数据 + const fieldTreeData = ref([]) + // update-begin--author:liaozhiyang---date:20240607---for:【TV360X-503】过滤图片,文件、密码组件 + const filterComponent = (data) => { + const { properties = {} } = data; + Object.entries(properties).forEach(([field, value]) => { + if (value.view === 'table') { + filterComponent(value); + } + if (['link_down'].includes(value.originView || value.view)) { + delete properties[field]; + } + }); + }; + // update-end--author:liaozhiyang---date:20240607---for:【TV360X-503】过滤图片,文件、密码组件 + /** + * 初始化数据-最开始的方法 + * 1.获取 表名@字段名-->配置 这样的一个map + * 2.获取树形结构的数据 显示:文本; 存储:表名@字段名 + * 当树改变时,及时获取配置更新表单 + * @param json + */ + function init(json) { + console.log('=============') + console.log('=============', json) + console.log('=============') + // update-begin--author:liaozhiyang---date:20240607---for:【TV360X-503】过滤图片,文件、密码组件 + filterComponent(json); + // update-end--author:liaozhiyang---date:20240607---for:【TV360X-503】过滤图片,文件、密码组件 + let { allFields, treeData } = getAllFields(json); + fieldProperties.value = allFields; + // update-end--author:liaozhiyang---date:20240612---for:【TV360X-1005】有子表时结构化主表且超长省略 + const properties = json.properties ?? {}; + const subTable: string[] = []; + const tableName = json.table; + Object.entries(properties).forEach(([key, value]: [string, any]) => { + if (value.view === 'table') { + subTable.push(key); + } + }); + if (subTable.length) { + let arr: TreeModel[] = []; + arr = treeData.filter((item) => !subTable.includes(item.value)); + for (let i = 0, len = treeData.length; i < len; i++) { + const item = treeData[i]; + if (!subTable.includes(item.value)) { + treeData.splice(i, 1); + i--; + len--; + } + } + treeData.unshift({ title: '主表', value: tableName, disabled: true, order: 200, children: arr, view: 'table' }); + } + // update-end--author:liaozhiyang---date:20240612---for:【TV360X-1005】有子表时结构化主表且超长省略 + fieldTreeData.value = treeData; + } + + /** + * 左侧查询项 添加一行 + * @param index + */ + function addOne(index) { + let item = { + field: undefined, + rule: 'eq', + val:'', + key: randomString(16) + } + if(index===false){ + // 重置后需要调用 + dynamicRowValues.values = [] + dynamicRowValues.values.push(item) + }else if(index===true){ + // 打开弹框是需要调用 + if(dynamicRowValues.values.length==0){ + dynamicRowValues.values.push(item) + } + }else{ + // 其余就是 正常的点击加号增加行 + dynamicRowValues.values.splice(++index, 0, item) + } + } + + /** + * 左侧查询项 删除一行 + */ + function removeOne(item: SuperQueryItem) { + let arr = toRaw(dynamicRowValues.values); + let index = -1; + for(let i=0;i{ + item['val'] = values[item.field] + } + temp.setFunctionForFieldValue(setFieldValue) + let schema = temp.getFormItemSchema() + //schema['valueField'] = 'val' + // 特殊规则,需要禁用组件 + // 为空、不为空 + if (['empty', 'not_empty'].includes(item.rule)) { + schema.componentProps = { ...schema.componentProps, disabled: true }; + } + linkTableCard2Select(schema); + // update-begin--author:liaozhiyang---date:20240607---for:【TV360X-389】普通查询关联记录去掉编辑按钮 + if (schema.component === 'LinkTableSelect') { + let componentProps = schema.componentProps ?? {}; + schema.componentProps = { ...componentProps, editBtnShow: false }; + } + // update-end--author:liaozhiyang---date:20240607---for:【TV360X-389】普通查询关联记录去掉编辑按钮 + // update-begin--author:liaozhiyang---date:20231219---for:【QQYUN-7640】高级查询数字组件会偏移 + if (schema && schema.component === 'InputNumber') { + item.curLineAlign = 'start'; + } + // update-end--author:liaozhiyang---date:20231219---for:【QQYUN-7640】高级查询数字组件会偏移 + // update-begin--author:liaozhiyang---date:20240223---for:【QQYUN-8229】高级选择自定义树下拉显示不全 + if (schema?.component === 'JTreeSelect') { + let componentProps: any = schema.componentProps; + if (componentProps) { + componentProps.getPopupContainer = () => document.body + } else { + schema.componentProps = { getPopupContainer: () => document.body } + }; + } + // update-end--author:liaozhiyang---date:20240223---for:【QQYUN-8229】高级选择自定义树下拉显示不全 + // update-begin--author:liaozhiyang---date:20240529---for:【TV360X-499】高级查询开关组件换成下拉,用户组件不显示按钮 + if (schema?.component === 'JSwitch') { + const componentProps = schema.componentProps ?? {}; + schema.componentProps = { ...componentProps, query: true }; + } + if (schema?.component === 'JSelectUser') { + const componentProps = schema.componentProps ?? {}; + schema.componentProps = { ...componentProps, showButton: false }; + } + // update-end--author:liaozhiyang---date:20240529---for:【TV360X-499】高级查询开关换成下拉,用户组件不显示按钮 + return schema + } + + /*-----------------------右侧保存信息相关-begin---------------------------*/ + + /** + * 右侧树 的 数据 + */ + const saveTreeData = ref('') + // 本地缓存 + const $ls = createLocalStorage(); + //需要保存的信息(一条) + const saveInfo = reactive({ + visible: false, + title: '', + content: '', + saveCode: '' + }); + //按钮loading + const loading = ref(false) + + // 当前页面路由 + const route = useRoute(); + // update-begin--author:liaozhiyang---date:20240514---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + if (props.isCustomSave) { + watch(props.saveSearchData, () => { + currentPageSavedArray.value = props.saveSearchData; + }); + } else { + // 监听路由信息,路由发生改变,则重新获取保存的查询信息-->currentPageSavedArray + watch(()=>route.fullPath, (val)=>{ + console.log('fullpath', val); + initSaveQueryInfoCode(); + }); + } + // update-end--author:liaozhiyang---date:20240514---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + + // 当前页面存储的 查询信息 + const currentPageSavedArray = ref([]); + // 监听当前页面是否有新的数据保存了,然后更新右侧数据->saveTreeData + watch(()=>currentPageSavedArray.value, (val)=>{ + let temp:any[] = [] + if(val && val.length>0){ + val.map(item=>{ + let key = randomString(16) + temp.push({ + title: item.title, + slots: { icon: 'custom' }, + value: key + }) + }) + } + saveTreeData.value = temp + }, {immediate:true, deep: true}) + + + // 重新获取保存的查询信息 + function initSaveQueryInfoCode(){ + // update-begin--author:liaozhiyang---date:20240514---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + if (props.isCustomSave) { + currentPageSavedArray.value = cloneDeep(props.saveSearchData); + } else { + let code = SAVE_CODE_PRE + route.fullPath; + saveInfo.saveCode = code; + let list = $ls.get(code); + if(list && list instanceof Array){ + currentPageSavedArray.value = list + } + } + // update-end--author:liaozhiyang---date:20240514---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + } + + // 执行一次 获取保存的查询信息 + initSaveQueryInfoCode(); + + /** + * 保存按钮事件 + */ + function handleSave(){ + // 获取实际数据转成字符串 + let fieldArray = getQueryInfo(); + if(!fieldArray){ + $message.warning('空条件不能保存') + return; + } + let content = JSON.stringify(fieldArray) + openSaveInfoModal(content) + } + + // 输入保存标题 弹框显示 + function openSaveInfoModal(content){ + saveInfo.visible = true; + saveInfo.title = ''; + saveInfo.content = content + } + + /** + * 确认保存查询信息 + */ + function doSaveQueryInfo(){ + let { title, content, saveCode } = saveInfo; + let index = getTitleIndex(title); + const saveSearchCondition = (type) => { + // update-begin--author:liaozhiyang---date:20240514---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + const curPageSave: any = cloneDeep(currentPageSavedArray.value); + saveModalLoading.value = true; + if (type) { + // 覆盖已有 + curPageSave.splice(index, 1, { + content, + title, + type: matchType.value, + }); + } else { + curPageSave.push({ + content, + title, + type: matchType.value, + }); + } + const run = () => { + saveInfo.visible = false; + $message.success('保存成功'); + currentPageSavedArray.value = curPageSave; + saveModalLoading.value = false; + }; + if (props.isCustomSave) { + props + .save(curPageSave, 0) + .then(() => { + run(); + }) + .catch((err) => { + saveModalLoading.value = false; + }); + } else { + // update-begin--author:liaozhiyang---date:20240306---for:【QQYUN-8357】高级查询保存的查询条件缓存改成一个月 + const expire = 60 * 60 * 24 * 30; + // update-end--author:liaozhiyang---date:20240306---for:【QQYUN-8357】高级查询保存的查询条件缓存改成一个月 + $ls.set(saveCode, curPageSave, expire); + run(); + } + // update-end--author:liaozhiyang---date:20240514---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + }; + if (index >= 0) { + // 已存在是否覆盖 + Modal.confirm({ + title: '提示', + content: `${title} 已存在,是否覆盖?`, + okText: '确认', + cancelText: '取消', + onOk: () => { + saveSearchCondition(1); + }, + }); + } else { + saveSearchCondition(0); + } + } + + // 根据填入的 title找本地存储的信息,如果有需要询问是否覆盖 + function getTitleIndex(title){ + let savedArray = currentPageSavedArray.value + let index = -1; + for(let i=0;i v) + let tempVal:any = toRaw(item.val) + if(tempVal instanceof Array){ + tempVal = tempVal.map(v => formatValue(v)).join(',') + } else { + tempVal = formatValue(tempVal) + } + let fieldName = getRealFieldName(item) + let obj = { + field: fieldName, + rule: item.rule, + val: tempVal, + fileType: item.fileType , + }; + if(isEmit===true){ + //如果当前数据用于emit事件,需要设置dbtype和type + let prop = fieldProps[item.field] + if(prop){ + obj['type'] = prop.view + obj['dbType'] = prop.type + } + } + fieldArray.push(obj) + } + } + if(fieldArray.length==0){ + return false; + } + return fieldArray + } + + //update-begin-author:taoyan date:2022-5-31 for: VUEN-1148 主子联动下,高级查询查子表数据,无效 + /** + * 高级查询参数 字段名 + * 获取后台需要的 字段名格式:表名,字段名 + * @param item + */ + function getRealFieldName(item){ + let fieldName = item.field + if(fieldName.indexOf('@')>0){ + fieldName = fieldName.replace('@', ',') + } + return fieldName; + } + //update-end-author:taoyan date:2022-5-31 for: VUEN-1148 主子联动下,高级查询查子表数据,无效 + + /** + * 右侧数据 点击事件,重新将数据显示到左侧 + * @param key + * @param node + */ + function handleTreeSelect(key, {node}){ + console.log(key, node) + let title = node.dataRef.title + let arr = currentPageSavedArray.value.filter(item=>item.title==title) + if(arr && arr.length>0){ + // 拿到数据渲染 + let { content, type } = arr[0] + let data = JSON.parse(content) + let rowsValues: SuperQueryItem[] = [] + for(let item of data){ + // update-begin--author:liaozhiyang---date:20240108---for:【issues/962】高级查询保存的查询是子表,下次查询不出结果 + item.field = item.field.replace(',','@'); + // update-end--author:liaozhiyang---date:20240108---for:【issues/962】高级查询保存的查询是子表,下次查询不出结果 + rowsValues.push(Object.assign({}, {key: randomString(16)}, item)) + } + dynamicRowValues.values = rowsValues + matchType.value = type + } + } + + /** + * 右侧数据 删除事件 + */ + function handleRemoveSaveInfo(title){ + console.log(title) + let index = getTitleIndex(title) + if(index>=0){ + // update-begin--author:liaozhiyang---date:20240513---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + if (props.isCustomSave) { + const curPageSave = cloneDeep(currentPageSavedArray.value); + curPageSave.splice(index, 1); + props + .save(curPageSave, 1) + .then(() => { + currentPageSavedArray.value = curPageSave; + }) + .catch((err) => { + console.log(`删除是吧~,${err}`); + }); + } else { + currentPageSavedArray.value.splice(index, 1); + $ls.set(saveInfo.saveCode, currentPageSavedArray.value); + } + // update-end--author:liaozhiyang---date:20240513---for:【issues/6205】高级查询组件增加保存条件自定义存储方式 + } + } + + /*-----------------------右侧保存信息相关-end---------------------------*/ + + // 获取所有字段配置信息 + function getAllFields(properties){ + // 获取所有配置 查询字段 是否联合查询 + // const {properties, table, title } = json; + let allFields = {} + let order = 1; + let treeData:TreeModel[] = [] + /* let mainNode:TreeModel = { + title, + value: table, + disabled: true, + children: [] + };*/ + //treeData.push(mainNode) + if(properties.properties){ + properties = properties.properties + } + Object.keys(properties).map(field=>{ + let item = properties[field] + if(item.view == 'table'){ + // 子表字段 + // 联合查询开启才需要子表字段作为查询条件 + let subProps = item['properties'] || item['fields'] + let subTableOrder = order * 100; + let subNode:TreeModel = { + title: item.title, + value: field, + disabled: true, + children: [], + order: subTableOrder, + // update-begin--author:liaozhiyang---date:20240306---for:【TV360X-461】字段类型是string,则默认模糊查询 + fieldType: item.type, + // update-end--author:liaozhiyang---date:20240306---for:【TV360X-461】字段类型是string,则默认模糊查询 + } + Object.keys(subProps).map(subField=>{ + let subItem = subProps[subField]; + // 保证排序统一 + subItem['order'] = subTableOrder + subItem['order'] + let subFieldKey = field+'@'+subField + allFields[subFieldKey] = subItem + subNode.children!.push({ + title: subItem.title, + value: subFieldKey, + isLeaf: true, + order: subItem['order'], + // update-begin--author:liaozhiyang---date:20240306---for:【TV360X-461】字段类型是string,则默认模糊查询 + fieldType: subItem.type, + view: subItem.view, + originView: subItem.view, + // update-end--author:liaozhiyang---date:20240306---for:【TV360X-461】字段类型是string,则默认模糊查询 + }) + }); + orderField(subNode); + treeData.push(subNode); + order++; + }else{ + // 主表字段 + //let fieldKey = table+'@'+field + let fieldKey = field + allFields[fieldKey] = item + treeData.push({ + title: item.title, + value: fieldKey, + isLeaf: true, + order: item.order, + // update-begin--author:liaozhiyang---date:20240306---for:【TV360X-461】字段类型是string,则默认模糊查询 + fieldType: item.type, + view: item.view, + originView: item.view, + // update-end--author:liaozhiyang---date:20240306---for:【TV360X-461】字段类型是string,则默认模糊查询 + }); + } + }); + orderField(treeData); + return {allFields, treeData} + } + + //根据字段的order重新排序 + function orderField(data){ + let arr = data.children || data; + arr.sort(function (a, b) { + return a.order - b.order + }); + } + + function initDefaultValues(values) { + const { params, matchType } = values + if(params){ + let rowsValues: SuperQueryItem[] = [] + for(let item of params){ + rowsValues.push(Object.assign({}, {key: randomString(16)}, item)) + } + dynamicRowValues.values = rowsValues + matchType.value = matchType + } + } + + return { + formRef, + init, + dynamicRowValues, + matchType, + registerModal, + handleSubmit, + handleCancel, + handleSave, + doSaveQueryInfo, + saveInfo, + saveTreeData, + handleRemoveSaveInfo, + handleTreeSelect, + fieldTreeData, + addOne, + removeOne, + setFormModel, + getSchema, + loading, + getQueryInfo, + initDefaultValues, + saveModalLoading, + fieldProperties, + } +} diff --git a/src/views/super/online/cgform/auto/default/OnlineAutoList.vue b/src/views/super/online/cgform/auto/default/OnlineAutoList.vue new file mode 100644 index 0000000..1dec481 --- /dev/null +++ b/src/views/super/online/cgform/auto/default/OnlineAutoList.vue @@ -0,0 +1,532 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/default/OnlineAutoModal.vue b/src/views/super/online/cgform/auto/default/OnlineAutoModal.vue new file mode 100644 index 0000000..6fb4b50 --- /dev/null +++ b/src/views/super/online/cgform/auto/default/OnlineAutoModal.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/default/OnlineCustomModal.vue b/src/views/super/online/cgform/auto/default/OnlineCustomModal.vue new file mode 100644 index 0000000..6ef4341 --- /dev/null +++ b/src/views/super/online/cgform/auto/default/OnlineCustomModal.vue @@ -0,0 +1,286 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/default/OnlineDetailModal.vue b/src/views/super/online/cgform/auto/default/OnlineDetailModal.vue new file mode 100644 index 0000000..382f25f --- /dev/null +++ b/src/views/super/online/cgform/auto/default/OnlineDetailModal.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/default/OnlineFormUrlAdd.vue b/src/views/super/online/cgform/auto/default/OnlineFormUrlAdd.vue new file mode 100644 index 0000000..82107c7 --- /dev/null +++ b/src/views/super/online/cgform/auto/default/OnlineFormUrlAdd.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/default/OnlineFormUrlDetail.vue b/src/views/super/online/cgform/auto/default/OnlineFormUrlDetail.vue new file mode 100644 index 0000000..d3f37da --- /dev/null +++ b/src/views/super/online/cgform/auto/default/OnlineFormUrlDetail.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/default/OnlineFormUrlEdit.vue b/src/views/super/online/cgform/auto/default/OnlineFormUrlEdit.vue new file mode 100644 index 0000000..4f72444 --- /dev/null +++ b/src/views/super/online/cgform/auto/default/OnlineFormUrlEdit.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/default/OnlineFormUrlSuccess.vue b/src/views/super/online/cgform/auto/default/OnlineFormUrlSuccess.vue new file mode 100644 index 0000000..05765ab --- /dev/null +++ b/src/views/super/online/cgform/auto/default/OnlineFormUrlSuccess.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/erp/OnlCgformErpList.vue b/src/views/super/online/cgform/auto/erp/OnlCgformErpList.vue new file mode 100644 index 0000000..1333133 --- /dev/null +++ b/src/views/super/online/cgform/auto/erp/OnlCgformErpList.vue @@ -0,0 +1,537 @@ + + + + + + diff --git a/src/views/super/online/cgform/auto/erp/OnlCgformErpSubTable.vue b/src/views/super/online/cgform/auto/erp/OnlCgformErpSubTable.vue new file mode 100644 index 0000000..fdfb5ff --- /dev/null +++ b/src/views/super/online/cgform/auto/erp/OnlCgformErpSubTable.vue @@ -0,0 +1,403 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/innerTable/OnlCgformInnerSubTable.vue b/src/views/super/online/cgform/auto/innerTable/OnlCgformInnerSubTable.vue new file mode 100644 index 0000000..18934cc --- /dev/null +++ b/src/views/super/online/cgform/auto/innerTable/OnlCgformInnerSubTable.vue @@ -0,0 +1,246 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/innerTable/OnlCgformInnerTableList.vue b/src/views/super/online/cgform/auto/innerTable/OnlCgformInnerTableList.vue new file mode 100644 index 0000000..b41dfa7 --- /dev/null +++ b/src/views/super/online/cgform/auto/innerTable/OnlCgformInnerTableList.vue @@ -0,0 +1,458 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/tab/OnlCgformTabList.vue b/src/views/super/online/cgform/auto/tab/OnlCgformTabList.vue new file mode 100644 index 0000000..f445863 --- /dev/null +++ b/src/views/super/online/cgform/auto/tab/OnlCgformTabList.vue @@ -0,0 +1,395 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/tab/modal/OnlineTabAutoModal.vue b/src/views/super/online/cgform/auto/tab/modal/OnlineTabAutoModal.vue new file mode 100644 index 0000000..e9bd2ca --- /dev/null +++ b/src/views/super/online/cgform/auto/tab/modal/OnlineTabAutoModal.vue @@ -0,0 +1,303 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/tab/modal/OnlineTabDetailModal.vue b/src/views/super/online/cgform/auto/tab/modal/OnlineTabDetailModal.vue new file mode 100644 index 0000000..28b4a76 --- /dev/null +++ b/src/views/super/online/cgform/auto/tab/modal/OnlineTabDetailModal.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/tab/modal/OnlineTabForm.vue b/src/views/super/online/cgform/auto/tab/modal/OnlineTabForm.vue new file mode 100644 index 0000000..dd32af5 --- /dev/null +++ b/src/views/super/online/cgform/auto/tab/modal/OnlineTabForm.vue @@ -0,0 +1,1284 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/tab/modal/OnlineTabFormDetail.vue b/src/views/super/online/cgform/auto/tab/modal/OnlineTabFormDetail.vue new file mode 100644 index 0000000..6f11fb8 --- /dev/null +++ b/src/views/super/online/cgform/auto/tab/modal/OnlineTabFormDetail.vue @@ -0,0 +1,343 @@ + + + + + diff --git a/src/views/super/online/cgform/auto/tree/OnlineAutoTreeList.vue b/src/views/super/online/cgform/auto/tree/OnlineAutoTreeList.vue new file mode 100644 index 0000000..e54f940 --- /dev/null +++ b/src/views/super/online/cgform/auto/tree/OnlineAutoTreeList.vue @@ -0,0 +1,588 @@ + + + + + diff --git a/src/views/super/online/cgform/cgform.api.ts b/src/views/super/online/cgform/cgform.api.ts new file mode 100644 index 0000000..cdabae9 --- /dev/null +++ b/src/views/super/online/cgform/cgform.api.ts @@ -0,0 +1,83 @@ +import { defHttp } from '/@/utils/http/axios'; + +export enum Api { + list = '/online/cgform/head/list', + delete = '/online/cgform/head/delete', + deleteBatch = '/online/cgform/head/deleteBatch', + databaseSync = '/online/cgform/api/doDbSynch', + removeRecord = '/online/cgform/head/removeRecord', + copyOnline = '/online/cgform/head/copyOnline', + copyTable = '/online/cgform/head/copyOnlineTable', + + // CgformModal页面API + addAll = '/online/cgform/api/addAll', + editAll = '/online/cgform/api/editAll', + queryField = '/online/cgform/field/listByHeadId', + queryIndex = '/online/cgform/index/listByHeadId', + checkOnlyTable = '/online/cgform/api/checkOnlyTable', + // 只修改表配置,不改字段 + editHead = '/online/cgform/head/edit' +} + +/** + * 列表接口 + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); + +// 批量移除(移除只会删除表单配置) +export const doBatchRemove = (idList: string[]) => doRemove(idList, 0); +export const doSingleRemove = (pid) => defHttp.delete({ url: Api.removeRecord, params: { id: pid } }, + { joinParamsToUrl: true }); +// 批量删除(删除会删除对应的数据库表以及子表) +export const doBatchDelete = (idList: string[]) => doRemove(idList, 1); +export const doSingleDelete = (pid) => defHttp.delete({ url: Api.delete, params: { id: pid } }, + { joinParamsToUrl: true }); + +// 执行删除操作 +function doRemove(idList: string[], flag: number) { + return defHttp.delete( + { + url: Api.deleteBatch, + params: { + ids: idList.join(','), + flag: flag, + }, + }, + { joinParamsToUrl: true } + ); +} + +// 同步数据库 +export const doDatabaseSync = (id, method) => + defHttp.post({ url: `${Api.databaseSync}/${id}/${method}`, timeout: 12000, timeoutErrorMessage: '同步数据库超时,已自动刷新' }); + +export const doCopyOnlineView = (id) => defHttp.post({ url: `${Api.copyOnline}?code=${id}` }); + +/** + * 复制表 + * @param id 要复制的表的id + * @param tableName 新的表名 + * @param params 其他参数 + */ +export const doCopyTable = (id, tableName, params?) => defHttp.get({ url: `${Api.copyTable}/${id}`, params: { tableName, ...params } }); + +// 弹窗formApi +export const formApi = { + // 查询表字段 e3e3NcxzbUiGa53YYVXxWc8ADo5ISgQGx/gaZwERF91oAryDlivjqBv3wqRArgChupi+Y/Gg/swwGEyL0PuVFg== + doQueryField: (headId: string, params?) => defHttp.get({ url: Api.queryField, params: { headId, ...params } }), + // 查询表index配置 + doQueryIndexes: (headId: string, params?) => defHttp.get({ url: Api.queryIndex, params: { headId, ...params } }), + // 新增或修改 + doSaveOrUpdate: (params, isUpdate) => { + if (isUpdate) { + return defHttp.put({ url: Api.editAll, params }); + } else { + return defHttp.post({ url: Api.addAll, params }); + } + }, + //只是修改表配置不改字段 + editHead: (params)=>{ + return defHttp.put({ url: Api.editHead, params }); + } +}; diff --git a/src/views/super/online/cgform/cgform.data.ts b/src/views/super/online/cgform/cgform.data.ts new file mode 100644 index 0000000..4e67255 --- /dev/null +++ b/src/views/super/online/cgform/cgform.data.ts @@ -0,0 +1,308 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { getDictItemsByCode } from '/@/utils/dict'; +import { filterDictText } from '/@/utils/dict/JDictSelectUtil'; +import { buildUUID } from '/@/utils/uuid'; + +// 校验失败 flag +export const VALIDATE_FAILED = 'validate-failed'; + +export const columns: BasicColumn[] = [ + { + title: '表类型', + align: 'center', + sorter: true, + dataIndex: 'tableType', + width: 140, + customRender({ text, record }) { + let tableTypeDictOptions = getDictItemsByCode('cgform_table_type'); + let tbTypeText = filterDictText(tableTypeDictOptions, text); + if (record.isTree === 'Y') { + tbTypeText += '(树)'; + } + if (record.themeTemplate === 'innerTable') { + tbTypeText += '(内嵌)'; + } else if (record.themeTemplate === 'erp') { + tbTypeText += '(ERP)'; + } else if (record.themeTemplate === 'tab') { + tbTypeText += '(TAB)'; + } + return tbTypeText; + }, + }, + { + title: '表名', + sorter: true, + align: 'center', + dataIndex: 'tableName', + width: 240, + }, + { + title: '表描述', + align: 'center', + dataIndex: 'tableTxt', + width: 220, + }, + { + title: '版本', + align: 'center', + dataIndex: 'tableVersion', + width: 120, + }, + { + title: '同步状态', + align: 'center', + sorter: true, + dataIndex: 'isDbSynch', + slots: { customRender: 'dbSync' }, + width: 120, + }, + { + title: '创建时间', + align: 'center', + sorter: true, + dataIndex: 'createTime', + width: 240, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + label: '表名', + field: 'tableName', + component: 'JInput', + }, + { + label: '表类型', + field: 'tableType_MultiString', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'cgform_table_type', + mode: 'multiple', + }, + }, + { + label: '表描述', + field: 'tableTxt', + component: 'JInput', + }, +]; + +/** 扩展JSON默认值 */ +export const ExtConfigDefaultJson = { + // 对接报表打印 + reportPrintShow: 0, + reportPrintUrl: '', + joinQuery: 0, + modelFullscreen: 0, + modalMinWidth: '', + commentStatus: 0, + tableFixedAction: 1, + tableFixedActionType: 'right', + // update-begin--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + formLabelLengthShow: 0, + formLabelLength: null, + // update-begin--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + // 是否启用外部链接 + enableExternalLink: 0, + externalLinkActions: 'add,edit,detail', +}; + +/** 获取主表的初始化数据 */ +export function useInitialData() { + let initialData = [ + { + dbFieldName: 'id', + dbFieldTxt: '主键', + dbLength: 36, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'string', + dbIsKey: '1', + dbIsNull: '0', + // table2 + isShowForm: '0', + isShowList: '0', + isReadOnly: '1', + fieldShowType: 'text', + fieldLength: '200', + queryMode: 'single', + dbIsSync: '1' + }, + { + dbFieldName: 'create_by', + dbFieldTxt: '创建人', + dbLength: 50, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'string', + dbIsKey: '0', + dbIsNull: '1', + // table2 + isShowForm: '0', + isShowList: '0', + fieldShowType: 'text', + fieldLength: '200', + queryMode: 'single', + dbIsSync: '1' + }, + { + dbFieldName: 'create_time', + dbFieldTxt: '创建日期', + dbLength: 0, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'Datetime', + dbIsKey: '0', + dbIsNull: '1', + // table2 + isShowForm: '0', + isShowList: '0', + fieldShowType: 'datetime', + fieldLength: '200', + queryMode: 'single', + dbIsSync: '1' + }, + { + dbFieldName: 'update_by', + dbFieldTxt: '更新人', + dbLength: 50, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'string', + dbIsKey: '0', + dbIsNull: '1', + // table2 + isShowForm: '0', + isShowList: '0', + fieldShowType: 'text', + fieldLength: '200', + queryMode: 'single', + dbIsSync: '1' + }, + { + dbFieldName: 'update_time', + dbFieldTxt: '更新日期', + dbLength: 0, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'Datetime', + dbIsKey: '0', + dbIsNull: '1', + // table2 + isShowForm: '0', + isShowList: '0', + fieldShowType: 'datetime', + fieldLength: '200', + queryMode: 'single', + dbIsSync: '1' + }, + { + dbFieldName: 'sys_org_code', + dbFieldTxt: '所属部门', + dbLength: 64, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'string', + dbIsKey: '0', + dbIsNull: '1', + // table2 + isShowForm: '0', + isShowList: '0', + fieldShowType: 'text', + fieldLength: '200', + queryMode: 'single', + dbIsSync: '1' + }, + // { + // dbFieldName: 'sys_org_code', + // dbFieldTxt: '所属部门', + // dbLength: 50, + // dbPointLength: 0, + // dbDefaultVal: '', + // dbType: 'string', + // dbIsKey: false, + // dbIsNull: true + // }, + // { + // dbFieldName: 'sys_company_code', + // dbFieldTxt: '所属公司', + // dbLength: 50, + // dbPointLength: 0, + // dbDefaultVal: '', + // dbType: 'string', + // dbIsKey: false, + // dbIsNull: true + // }, + // { + // dbFieldName: 'bpm_status', + // dbFieldTxt: '流程状态', + // dbLength: 32, + // dbPointLength: 0, + // dbDefaultVal: '', + // dbType: 'string', + // dbIsKey: false, + // dbIsNull: true + // } + ]; + // 临时 id,不保存到数据库 + let tempIds: string[] = []; + initialData.forEach((record) => { + record['id'] = buildUUID(); + tempIds.push(record['id']); + }); + return { initialData, tempIds }; +} + +/** 获取树的初始化数据 */ +export function useTreeNeedFields() { + return [ + { + dbFieldName: 'pid', + dbFieldTxt: '父级节点', + dbLength: 32, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'string', + dbIsKey: '0', + dbIsNull: '1', + // table2 + isShowForm: '1', + isShowList: '0', + fieldShowType: 'text', + fieldLength: '200', + queryMode: 'single', + dbIsSync: '1' + }, + { + dbFieldName: 'has_child', + dbFieldTxt: '是否有子节点', + dbLength: 3, + dbPointLength: 0, + dbDefaultVal: '', + dbType: 'string', + dbIsKey: '0', + dbIsNull: '1', + // table2 + isShowForm: '0', + isShowList: '0', + fieldShowType: 'list', + fieldLength: '200', + queryMode: 'single', + // table3 + dictField: 'yn', + dbIsSync: '1' + }, + ]; +} + +/** + * online 默认按钮 + */ +export const onlineDefaultButton = [ + { code: 'add', title: '新增', status: 0 }, + { code: 'edit', title: '编辑', status: 0 }, + { code: 'delete', title: '删除', status: 0 }, + { code: 'export', title: '导出', status: 0 }, + { code: 'import', title: '导入', status: 0 }, + { code: 'query', title: '查询', status: 0 }, +]; diff --git a/src/views/super/online/cgform/components/AiModal.vue b/src/views/super/online/cgform/components/AiModal.vue new file mode 100644 index 0000000..014d130 --- /dev/null +++ b/src/views/super/online/cgform/components/AiModal.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/src/views/super/online/cgform/components/CgformAddressModal.vue b/src/views/super/online/cgform/components/CgformAddressModal.vue new file mode 100644 index 0000000..32f19a6 --- /dev/null +++ b/src/views/super/online/cgform/components/CgformAddressModal.vue @@ -0,0 +1,232 @@ + + + + + + diff --git a/src/views/super/online/cgform/components/CgformFieldItem.vue b/src/views/super/online/cgform/components/CgformFieldItem.vue new file mode 100644 index 0000000..3c06369 --- /dev/null +++ b/src/views/super/online/cgform/components/CgformFieldItem.vue @@ -0,0 +1,62 @@ + + + diff --git a/src/views/super/online/cgform/components/CgformHeadForm.vue b/src/views/super/online/cgform/components/CgformHeadForm.vue new file mode 100644 index 0000000..94eb8e9 --- /dev/null +++ b/src/views/super/online/cgform/components/CgformHeadForm.vue @@ -0,0 +1,533 @@ + + + + + diff --git a/src/views/super/online/cgform/components/CgformModal.vue b/src/views/super/online/cgform/components/CgformModal.vue new file mode 100644 index 0000000..47af990 --- /dev/null +++ b/src/views/super/online/cgform/components/CgformModal.vue @@ -0,0 +1,894 @@ + + + + + + diff --git a/src/views/super/online/cgform/components/CodeFileListModal.vue b/src/views/super/online/cgform/components/CodeFileListModal.vue new file mode 100644 index 0000000..049f03e --- /dev/null +++ b/src/views/super/online/cgform/components/CodeFileListModal.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/src/views/super/online/cgform/components/CodeFileViewModal.vue b/src/views/super/online/cgform/components/CodeFileViewModal.vue new file mode 100644 index 0000000..a431e02 --- /dev/null +++ b/src/views/super/online/cgform/components/CodeFileViewModal.vue @@ -0,0 +1,378 @@ + + + + + diff --git a/src/views/super/online/cgform/components/CodeGeneratorModal.vue b/src/views/super/online/cgform/components/CodeGeneratorModal.vue new file mode 100644 index 0000000..13fb649 --- /dev/null +++ b/src/views/super/online/cgform/components/CodeGeneratorModal.vue @@ -0,0 +1,345 @@ + + + + + diff --git a/src/views/super/online/cgform/components/DbToOnlineModal.vue b/src/views/super/online/cgform/components/DbToOnlineModal.vue new file mode 100644 index 0000000..b6e5758 --- /dev/null +++ b/src/views/super/online/cgform/components/DbToOnlineModal.vue @@ -0,0 +1,205 @@ + + + + + diff --git a/src/views/super/online/cgform/components/ExtendConfigModal.vue b/src/views/super/online/cgform/components/ExtendConfigModal.vue new file mode 100644 index 0000000..2ed195f --- /dev/null +++ b/src/views/super/online/cgform/components/ExtendConfigModal.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/src/views/super/online/cgform/components/FileSelectModal.vue b/src/views/super/online/cgform/components/FileSelectModal.vue new file mode 100644 index 0000000..c1f96c8 --- /dev/null +++ b/src/views/super/online/cgform/components/FileSelectModal.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/views/super/online/cgform/components/auth/AuthManagerDrawer.vue b/src/views/super/online/cgform/components/auth/AuthManagerDrawer.vue new file mode 100644 index 0000000..28cf4b1 --- /dev/null +++ b/src/views/super/online/cgform/components/auth/AuthManagerDrawer.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/views/super/online/cgform/components/auth/AuthSetterModal.vue b/src/views/super/online/cgform/components/auth/AuthSetterModal.vue new file mode 100644 index 0000000..c224431 --- /dev/null +++ b/src/views/super/online/cgform/components/auth/AuthSetterModal.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/src/views/super/online/cgform/components/auth/auth.api.ts b/src/views/super/online/cgform/components/auth/auth.api.ts new file mode 100644 index 0000000..810e056 --- /dev/null +++ b/src/views/super/online/cgform/components/auth/auth.api.ts @@ -0,0 +1,81 @@ +import { defHttp } from '/@/utils/http/axios'; + +export enum Api { + authField = '/online/cgform/api/authColumn', + authButton = '/online/cgform/api/authButton', + authData = '/online/cgform/api/authData', + authPage = '/online/cgform/api/authPage', + roleAuth = '/online/cgform/api/roleAuth', + saveButton = '/online/cgform/api/roleButtonAuth', + saveData = '/online/cgform/api/roleDataAuth', + validData = '/online/cgform/api/validAuthData', + saveField = '/online/cgform/api/roleColumnAuth', + batchAuthField = '/online/cgform/api/authColumn/batch', +} + +// 字段权限,查询数据 +export const authFieldLoadData = (cgformId, params?) => defHttp.get({ url: `${Api.authField}/${cgformId}`, params }); +// 字段权限,更新启用状态 +export const authFieldUpdateStatus = (params) => defHttp.put({ url: Api.authField, params }); +// 字段权限,更新权限状态 +export const authFieldUpdateCheckbox = (params) => defHttp.post({ url: Api.authField, params }); + +// 字段权限,批量更新启用状态 +export const batchAuthFieldUpdateStatus = (params) => defHttp.put({ url: Api.batchAuthField, params }); +// 字段权限,批量更新权限状态 +export const batchAuthFieldUpdateCheckbox = (params) => defHttp.post({ url: Api.batchAuthField, params }); + + +// 按钮权限,查询数据 +export const authButtonLoadData = (cgformId, params?) => defHttp.get({ url: `${Api.authButton}/${cgformId}`, params }); +// 按钮权限,启用 +export const authButtonEnable = (params) => defHttp.post({ url: Api.authButton, params }); +// 按钮权限,禁用 +export const authButtonDisable = (id: string, params?) => defHttp.put({ url: `${Api.authButton}/${id}`, params }); + +// 数据权限,查询数据 +export const authDataLoadData = (cgformId, params?) => defHttp.get({ url: `${Api.authData}/${cgformId}`, params }); +// 数据权限,更新启用状态 +export const authDataUpdateStatus = (params) => defHttp.put({ url: Api.authData, params }); +// 数据权限,保存或修改 +export const authDataSaveOrUpdate = (params, isUpdate: boolean) => { + if (isUpdate) { + return defHttp.put({ url: Api.authData, params }); + } else { + return defHttp.post({ url: Api.authData, params }); + } +}; +// 数据权限,删除 +export const authDataDelete = (id: string, params?) => defHttp.delete({ url: `${Api.authData}/${id}`, params }); + +export const authFieldLoadTree = (cgformId: string, authType: number, params?) => { + let url = `${Api.authPage}/${cgformId}/${authType}`; + return defHttp.get({ url, params }); +}; + +export const authDataLoadTree = (cgformId: string, params?) => { + let url = `${Api.validData}/${cgformId}`; + return defHttp.get({ url, params }); +}; + +export const authButtonLoadTree = (cgformId: string, authType: number, params?) => { + let url = `${Api.authPage}/${cgformId}/${authType}`; + return defHttp.get({ url, params }); +}; + +export const loadRoleAuthChecked = (params) => defHttp.get({ url: Api.roleAuth, params }); + +export const saveAuthField = (roleId: string, cgformId: string, params?) => { + let url = `${Api.saveField}/${roleId}/${cgformId}`; + return defHttp.post({ url, params }); +}; + +export const saveAuthData = (roleId: string, cgformId: string, params?) => { + let url = `${Api.saveData}/${roleId}/${cgformId}`; + return defHttp.post({ url, params }); +}; + +export const saveAuthButton = (roleId: string, cgformId: string, params?) => { + let url = `${Api.saveButton}/${roleId}/${cgformId}`; + return defHttp.post({url, params}, {successMessageMode: 'none', isTransformResponse: false}); +}; diff --git a/src/views/super/online/cgform/components/auth/auth.data.ts b/src/views/super/online/cgform/components/auth/auth.data.ts new file mode 100644 index 0000000..63f3f6b --- /dev/null +++ b/src/views/super/online/cgform/components/auth/auth.data.ts @@ -0,0 +1,235 @@ +import { computed } from 'vue'; +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { useConditionFilter } from '/@/utils/index'; + +// 字段权限列配置 +export const authFieldColumns: BasicColumn[] = [ + { + title: '启用', + dataIndex: 'switch', + width: 100, + align: 'center', + slots: { customRender: 'switch' }, + }, + { + title: '字段名称', + width: 200, + dataIndex: 'code', + }, + { + title: '字段描述', + // width: 200, + dataIndex: 'title', + }, + { + title: '列表控制', + dataIndex: 'list', + width: 120, + slots: { customRender: 'list' }, + }, + { + title: '表单控制', + dataIndex: 'form', + width: 180, + slots: { customRender: 'form' }, + }, +]; + +// 按钮权限列配置 +export const authButtonColumns: BasicColumn[] = [ + { + title: '启用', + dataIndex: 'switch', + width: 80, + slots: { customRender: 'switch' }, + }, + { + title: '名称', + dataIndex: 'title', + }, + { + title: '编码', + dataIndex: 'code', + }, + { + title: '权限控制', + dataIndex: 'control', + width: 180, + slots: { customRender: 'control' }, + }, +]; + +export const authButtonFixedList = [ + { code: 'add', title: '新增', status: 0 }, + { code: 'edit', title: '编辑', status: 0 }, + { code: 'detail', title: '详情', status: 0 }, + { code: 'delete', title: '删除', status: 0 }, + { code: 'batch_delete', title: '批量删除', status: 0 }, + { code: 'export', title: '导出', status: 0 }, + { code: 'import', title: '导入', status: 0 }, + { code: 'query', title: '查询', status: 0 }, + { code: 'reset', title: '重置', status: 0 }, + { code: 'aigc_mock_data', title: '生成测试数据', status: 0 }, + { code: 'bpm', title: '提交流程', status: 0 }, + { code: 'super_query', title: '高级查询', status: 0 }, + { code: 'form_confirm', title: '确定', status: 0 }, +]; + +export const USE_SQL_RULES = 'USE_SQL_RULES'; +// 数据权限列配置 +export const authDataColumns: BasicColumn[] = [ + { + title: '启用', + dataIndex: 'switch', + width: 80, + slots: { customRender: 'switch' }, + }, + { + title: '规则名称', + dataIndex: 'ruleName', + width: 130, + }, + { + title: '规则描述', + dataIndex: 'description', + customRender({ record: { ruleOperator, ruleValue, ruleColumn } }) { + if (ruleOperator == USE_SQL_RULES) { + return `自定义SQL: ${ruleValue}`; + } else { + return `${ruleColumn} ${ruleOperator} ${ruleValue}`; + } + }, + }, +]; + +export function useAuthDataFormSchemas(props, methods) { + const formSchemas = computed(() => [ + { + label: '规则名称', + field: 'ruleName', + required: true, + component: 'Input', + componentProps: { + onChange: methods.onRuleNameChange, + }, + }, + { + label: '规则字段', + field: 'ruleColumn', + component: 'JSearchSelect', + componentProps: { + dictOptions: props.authFields, + getPopupContainer: () => document.body, + onChange: methods.onRuleColumnChange, + }, + dynamicRules({ model }) { + return [{ required: model.ruleOperator != USE_SQL_RULES, message: '请输入规则字段' }]; + }, + show: ({ model }) => model.ruleOperator != USE_SQL_RULES, + }, + // -update-begin--author:liaozhiyang---date:20240617---for:【TV360X-201】权限管理条件根据控件过滤 + { + label: '条件规则', + field: 'ruleOperator', + required: true, + component: 'JDictSelectTag', + componentProps: { + options: [], + onChange: methods.onRuleOperatorChange, + getPopupContainer: () => document.body, + }, + dynamicPropskey: 'options', + dynamicPropsVal: ({ model, field }) => { + const getFieldType = (type) => { + if (['BigDecimal', 'double', 'int'].includes(type)) { + return 'number'; + } else { + return; + } + }; + const { filterCondition } = useConditionFilter(); + if (model.ruleColumn) { + const findItem = props.authFields.find((item) => item.value === model.ruleColumn) ?? {}; + const result = filterCondition({ view: findItem.view, fieldType: getFieldType(findItem.dbType) }).map((item) => ({ + label: item.title ?? item.label, + value: item.val ?? item.value, + })); + result.push({ value: 'USE_SQL_RULES', label: '自定义SQL' }); + return result; + } else { + return [{ value: 'USE_SQL_RULES', label: '自定义SQL' }]; + } + }, + }, + // { + // label: '条件规则', + // field: 'ruleOperator', + // required: true, + // component: 'JDictSelectTag', + // componentProps: { + // dictCode: 'rule_conditions', + // onChange: methods.onRuleOperatorChange, + // getPopupContainer: () => document.body, + // }, + // }, + // -update-end--author:liaozhiyang---date:20240617---for:【TV360X-201】权限管理条件根据控件过滤 + { + label: '规则值', + field: 'ruleValue', + required: true, + // -update-begin--author:liaozhiyang---date:20240607---for:【TV360X-536】数据权限配置配置优化及新增JInputSelect组件 + component: 'JInputSelect', + componentProps: { + selectPlaceholder: '可选择系统变量', + inputPlaceholder: '请输入', + getPopupContainer: () => document.body, + selectWidth: '200px', + options: [ + { + label: '登录用户账号', + value: '#{sys_user_code}', + }, + { + label: '登录用户名称', + value: '#{sys_user_name}', + }, + { + label: '当前日期', + value: '#{sys_date}', + }, + { + label: '当前时间', + value: '#{sys_time}', + }, + { + label: '登录用户部门', + value: '#{sys_org_code}', + }, + { + label: '用户拥有的部门', + value: '#{sys_multi_org_code}', + }, + { + label: '登录用户租户', + value: '#{tenant_id}', + }, + ], + }, + // -update-end--author:liaozhiyang---date:20240607---for:【TV360X-536】数据权限配置配置优化及新增JInputSelect组件 + }, + { + label: '状态', + field: 'status', + required: true, + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '有效', value: 1 }, + { label: '无效', value: 0 }, + ], + }, + defaultValue: 1, + }, + ]); + return { formSchemas }; +} diff --git a/src/views/super/online/cgform/components/auth/manager/AuthButtonConfig.vue b/src/views/super/online/cgform/components/auth/manager/AuthButtonConfig.vue new file mode 100644 index 0000000..32aa925 --- /dev/null +++ b/src/views/super/online/cgform/components/auth/manager/AuthButtonConfig.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/src/views/super/online/cgform/components/auth/manager/AuthDataConfig.vue b/src/views/super/online/cgform/components/auth/manager/AuthDataConfig.vue new file mode 100644 index 0000000..3652864 --- /dev/null +++ b/src/views/super/online/cgform/components/auth/manager/AuthDataConfig.vue @@ -0,0 +1,229 @@ + + + diff --git a/src/views/super/online/cgform/components/auth/manager/AuthFieldConfig.vue b/src/views/super/online/cgform/components/auth/manager/AuthFieldConfig.vue new file mode 100644 index 0000000..3af8ed2 --- /dev/null +++ b/src/views/super/online/cgform/components/auth/manager/AuthFieldConfig.vue @@ -0,0 +1,364 @@ + + + + + diff --git a/src/views/super/online/cgform/components/auth/setter/AuthButtonTree.vue b/src/views/super/online/cgform/components/auth/setter/AuthButtonTree.vue new file mode 100644 index 0000000..fed7e33 --- /dev/null +++ b/src/views/super/online/cgform/components/auth/setter/AuthButtonTree.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/src/views/super/online/cgform/components/auth/setter/AuthDataTree.vue b/src/views/super/online/cgform/components/auth/setter/AuthDataTree.vue new file mode 100644 index 0000000..967ef9b --- /dev/null +++ b/src/views/super/online/cgform/components/auth/setter/AuthDataTree.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/src/views/super/online/cgform/components/auth/setter/AuthFieldTree.vue b/src/views/super/online/cgform/components/auth/setter/AuthFieldTree.vue new file mode 100644 index 0000000..45abe2a --- /dev/null +++ b/src/views/super/online/cgform/components/auth/setter/AuthFieldTree.vue @@ -0,0 +1,205 @@ + + + + + diff --git a/src/views/super/online/cgform/components/auth/setter/LeftDepart.vue b/src/views/super/online/cgform/components/auth/setter/LeftDepart.vue new file mode 100644 index 0000000..ad929b0 --- /dev/null +++ b/src/views/super/online/cgform/components/auth/setter/LeftDepart.vue @@ -0,0 +1,70 @@ + + + diff --git a/src/views/super/online/cgform/components/auth/setter/LeftRole.vue b/src/views/super/online/cgform/components/auth/setter/LeftRole.vue new file mode 100644 index 0000000..146f448 --- /dev/null +++ b/src/views/super/online/cgform/components/auth/setter/LeftRole.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/views/super/online/cgform/components/auth/setter/LeftUser.vue b/src/views/super/online/cgform/components/auth/setter/LeftUser.vue new file mode 100644 index 0000000..686b277 --- /dev/null +++ b/src/views/super/online/cgform/components/auth/setter/LeftUser.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/src/views/super/online/cgform/components/button/BuiltInButtonList.vue b/src/views/super/online/cgform/components/button/BuiltInButtonList.vue new file mode 100644 index 0000000..03798f3 --- /dev/null +++ b/src/views/super/online/cgform/components/button/BuiltInButtonList.vue @@ -0,0 +1,164 @@ + + + + diff --git a/src/views/super/online/cgform/components/button/CustomButtonList.vue b/src/views/super/online/cgform/components/button/CustomButtonList.vue new file mode 100644 index 0000000..ca4eb19 --- /dev/null +++ b/src/views/super/online/cgform/components/button/CustomButtonList.vue @@ -0,0 +1,228 @@ + + + + diff --git a/src/views/super/online/cgform/components/button/button.api.ts b/src/views/super/online/cgform/components/button/button.api.ts new file mode 100644 index 0000000..79eac35 --- /dev/null +++ b/src/views/super/online/cgform/components/button/button.api.ts @@ -0,0 +1,42 @@ +import { defHttp } from '/@/utils/http/axios'; + +export enum Api { + list = '/online/cgform/button/list/', + delete = '/online/cgform/button/delete', + deleteBatch = '/online/cgform/button/deleteBatch', + save = '/online/cgform/button/add', + edit = '/online/cgform/button/edit', + + builtInList = '/online/cgform/button/builtInList/', +} + +export const list = (code: string, params) => defHttp.get({ url: Api.list + code, params }); + +// 执行删除操作 +export function doBatchDelete(idList: string[]) { + return defHttp.delete( + { + url: Api.deleteBatch, + params: { + ids: idList.join(','), + }, + }, + { joinParamsToUrl: true } + ); +} + +/** + * 保存或者更新 + */ +export const saveOrUpdate = (params, isUpdate: boolean) => { + if (isUpdate) { + return defHttp.put({ url: Api.edit, params }); + } else { + return defHttp.post({ url: Api.save, params }); + } +}; + +/** + * 加载内置按钮列表 + */ +export const builtInList = (code: string, params) => defHttp.get({url: Api.builtInList + code, params}); diff --git a/src/views/super/online/cgform/components/button/button.data.ts b/src/views/super/online/cgform/components/button/button.data.ts new file mode 100644 index 0000000..696eb46 --- /dev/null +++ b/src/views/super/online/cgform/components/button/button.data.ts @@ -0,0 +1,194 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +// @ts-ignore +import {getButtonIconRender} from "./button.data.tsx"; + +export const columns: BasicColumn[] = [ + { title: '按钮编码', align: 'center', dataIndex: 'buttonCode' }, + { title: '按钮名称', align: 'center', dataIndex: 'buttonName' }, + { + title: '按钮样式', + align: 'center', + dataIndex: 'buttonStyle', + customRender({ text, record }) { + if (text === 'form') { + let p = record.optPosition; + return text + '(' + (p == '2' ? '底部' : '侧面') + ')'; + } else { + return text; + } + }, + }, + { title: '按钮类型', align: 'center', dataIndex: 'optType' }, + { title: '排序', align: 'center', dataIndex: 'orderNum' }, + { + title: '按钮图标', + align: 'center', + dataIndex: 'buttonIcon', + customRender: ({text}) => { + return getButtonIconRender({text}); + }, + }, + { title: '表达式', align: 'center', dataIndex: 'exp' }, + { + title: '按钮状态', + align: 'center', + dataIndex: 'buttonStatus', + customRender({ text }) { + if (text == 1) { + return '激活'; + } else { + return '未激活'; + } + }, + }, +]; + +export const formSchemas = ({ redoModalHeight }): FormSchema[] => { + return [ + { + label: '按钮编码', + field: 'buttonCode', + component: 'Input', + required: true, + // update-begin--author:liaozhiyang---date:20240521---for:【TV360X-139】按钮编码加上正则校验 + dynamicRules: () => { + return [ + { + validator: (_, value) => { + //需要return 一个Promise对象 + return new Promise((resolve, reject) => { + const reg = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/; + if (reg.test(value)) { + resolve(); + } else { + reject('编码只能包含字母、数字、下划线 (_) 和美元符号 ($)且不能以数字开头'); + } + }); + }, + }, + // update-begin--author:liaozhiyang---date:20240701---for:【TV360X-1693】自定义按钮编码排除sql和java系统内置编码 + { + validator: (_, value) => { + //需要return 一个Promise对象 + return new Promise((resolve, reject) => { + const exclude = ['add', 'edit', 'detail', 'delete', 'batch_delete', 'import', 'export', 'query', 'reset', 'bpm', 'super_query', 'form_confirm']; + if (exclude.includes(value)) { + reject('不可使用内置按钮编码,请在“管理内置按钮”中修改内置按钮'); + } else { + resolve(); + } + }); + }, + }, + // update-end--author:liaozhiyang---date:20240701---for:【TV360X-1693】自定义按钮编码排除sql和java系统内置编码 + ]; + }, + // update-end--author:liaozhiyang---date:20240521---for:【TV360X-139】按钮编码加上正则校验 + }, + { + label: '按钮名称', + field: 'buttonName', + component: 'Input', + required: true, + }, + { + label: '按钮样式', + field: 'buttonStyle', + component: 'Select', + componentProps: { + options: [ + { label: 'Link', value: 'link' }, + { label: 'Button', value: 'button' }, + { label: 'Form', value: 'form' }, + ], + // update-begin--author:liaozhiyang---date:20240618---for:【TV360X-1306】自定义按钮弹窗按钮样式切换是重置弹窗高度 + onChange: () => { + redoModalHeight(); + }, + // update-end--author:liaozhiyang---date:20240618---for:【TV360X-1306】自定义按钮弹窗按钮样式切换是重置弹窗高度 + }, + defaultValue: 'link', + }, + { + label: '按钮位置', + field: 'optPosition', + component: 'Select', + componentProps: { + allowClear: false, + options: [ + // { label: '侧面', value: '1' }, + { label: '底部', value: '2' }, + ], + }, + defaultValue: '2', + show: ({ model }) => model.buttonStyle === 'form', + }, + { + label: '按钮类型', + field: 'optType', + component: 'Select', + componentProps: { + allowClear: false, + options: [ + { label: 'Js', value: 'js' }, + { label: 'Action', value: 'action' }, + ], + }, + defaultValue: 'js', + }, + { + label: '排序', + field: 'orderNum', + component: 'InputNumber', + componentProps: { + style: 'width: 100%', + }, + }, + { + label: '按钮图标', + field: 'buttonIcon', + // update-begin--author:liaozhiyang---date:20240528---for:【TV360X-136】按钮图标改成图标组件选择 + component: 'IconPicker', + componentProps: { + clearSelect: true, + iconPrefixSave: false, + }, + // update-end--author:liaozhiyang---date:20240528---for:【TV360X-136】按钮图标改成图标组件选择 + ifShow: ({ values, model }) => { + if (values.buttonStyle == 'button' || values.buttonStyle == 'form') { + return true; + } else { + // model.buttonIcon = null; + return false; + } + }, + }, + { + label: '表达式', + field: 'exp', + component: 'Input', + // update-begin--author:liaozhiyang---date:20240603---for:【TV360X-89】自定义按钮样式是link时,展示表达式配置 + ifShow: ({ values, model }) => { + if (values.buttonStyle == 'link') { + return true; + } else { + model.exp = ''; + return false; + } + }, + // update-end--author:liaozhiyang---date:20240603---for:【TV360X-89】自定义按钮样式是link时,展示表达式配置 + }, + { + label: '按钮状态', + field: 'buttonStatus', + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '激活', value: '1' }, + { label: '未激活', value: '0' }, + ], + }, + defaultValue: '1', + }, + ] +}; diff --git a/src/views/super/online/cgform/components/button/button.data.tsx b/src/views/super/online/cgform/components/button/button.data.tsx new file mode 100644 index 0000000..4594d3f --- /dev/null +++ b/src/views/super/online/cgform/components/button/button.data.tsx @@ -0,0 +1,9 @@ +import {Icon} from "@/components/Icon"; + +export function getButtonIconRender({text}) { + if (!text) { + return '' + } + // @ts-ignore + return ; +} diff --git a/src/views/super/online/cgform/components/enhance/EnhanceJavaModal.vue b/src/views/super/online/cgform/components/enhance/EnhanceJavaModal.vue new file mode 100644 index 0000000..73af6db --- /dev/null +++ b/src/views/super/online/cgform/components/enhance/EnhanceJavaModal.vue @@ -0,0 +1,209 @@ + + + + diff --git a/src/views/super/online/cgform/components/enhance/EnhanceJsHistory.vue b/src/views/super/online/cgform/components/enhance/EnhanceJsHistory.vue new file mode 100644 index 0000000..39c5ddd --- /dev/null +++ b/src/views/super/online/cgform/components/enhance/EnhanceJsHistory.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/src/views/super/online/cgform/components/enhance/EnhanceJsModal.vue b/src/views/super/online/cgform/components/enhance/EnhanceJsModal.vue new file mode 100644 index 0000000..d4e9550 --- /dev/null +++ b/src/views/super/online/cgform/components/enhance/EnhanceJsModal.vue @@ -0,0 +1,283 @@ + + + + + diff --git a/src/views/super/online/cgform/components/enhance/EnhanceSqlModal.vue b/src/views/super/online/cgform/components/enhance/EnhanceSqlModal.vue new file mode 100644 index 0000000..aefb907 --- /dev/null +++ b/src/views/super/online/cgform/components/enhance/EnhanceSqlModal.vue @@ -0,0 +1,213 @@ + + + + diff --git a/src/views/super/online/cgform/components/enhance/codeHinting.ts b/src/views/super/online/cgform/components/enhance/codeHinting.ts new file mode 100644 index 0000000..20ad74e --- /dev/null +++ b/src/views/super/online/cgform/components/enhance/codeHinting.ts @@ -0,0 +1,268 @@ +export const keywords: any = { + list: [ + //------ 列表api ------- + // 属性 + { text: '.acceptHrefParams', displayText: 'acceptHrefParams', superiors: 'this', desc: '获取地址栏上的条件' }, + { text: '.currentPage', displayText: 'currentPage', superiors: 'this', desc: '获取当前页数,默认1' }, + { text: '.currentTableName', displayText: 'currentTableName', desc: '获取当前表名' }, + { text: '.description', displayText: 'description', superiors: 'this', desc: '获取当前表描述' }, + { text: '.hasChildrenField', displayText: 'hasChildrenField', superiors: 'this', desc: '如果是树形列表,获取是否有子节点字段名' }, + { text: '.ID', displayText: 'ID', superiors: 'this', desc: '获取当前表的配置ID' }, + { text: '.pageSize', displayText: 'pageSize', superiors: 'this', desc: '获取当前每页条数,默认10' }, + { text: '.queryParam', displayText: 'queryParam', superiors: 'this', desc: '获取查询表单的查询条件' }, + { text: '.selectedRowKeys', displayText: 'selectedRowKeys', superiors: 'this', desc: '获取选中行的id的数组' }, + { text: '.selectedRows', displayText: 'selectedRows', superiors: 'this', desc: '获取选中行的数据数组' }, + { text: '.sortField', displayText: 'sortField', superiors: 'this', desc: '获取排序字段,默认‘id’' }, + { text: '.sortType', displayText: 'sortType', superiors: 'this', desc: '获取排序类型,默认升序‘asc’' }, + { text: '.total', displayText: 'total', superiors: 'this', desc: '获取总条数' }, + { text: '.loading', displayText: 'loading', superiors: 'this', desc: '设置/获取loading' }, + // 方法 + { text: '.loadData()', displayText: 'loadData()', superiors: 'this', desc: '加载数据' }, + { text: '.clearSelectedRow()', displayText: 'clearSelectedRow()', superiors: 'this', desc: '清除选中的行' }, + { + text: '.getLoadDataParams()', + displayText: 'getLoadDataParams()', + superiors: 'this', + desc: '获取所有的查询条件,返回一个对象,包括:查询表单,高级查询,地址栏参数,分页信息,排序信息等', + }, + { text: '.isTree()', displayText: 'isTree()', superiors: 'this', desc: '判断当前表是不是树,返回布尔值' }, + // 事件(前置) + { + text: `beforeEdit(row){ + return new Promise((resolve, reject) => { + if(row.字段名 == '字段值'){ + reject('测试~'); + }else{ + resolve(); + } + }) +}`, + displayText: 'beforeEdit(row){}', + desc: '点击操作列下的编辑按钮触发,返回promise对象', + }, + { + text: `beforeDelete(row){ + return new Promise((resolve, reject) => { + if(row.字段名 == '字段值'){ + reject('测试~'); + }else{ + resolve(); + } + }) +}`, + displayText: 'beforeDelete(row){}', + desc: '点击操作列下的删除按钮触发,返回promise对象', + }, + { text: 'console.log()', displayText: 'console.log()', desc: '打印日志' }, + ], + form: [ + //------ 表单api ------- + // 属性 + { text: '.loading', displayText: 'loading', superiors: 'this', desc: '是否加载中,返回的是一个ref对象' }, + { text: '.isUpdate', displayText: 'isUpdate', superiors: 'this', desc: '是否是编辑页面,返回的是一个ref对象' }, + { text: '.onlineFormRef', displayText: 'onlineFormRef', superiors: 'this', desc: '主表/单表表单的ref对象' }, + { text: '.refMap', displayText: 'refMap', superiors: 'this', desc: '子表表单/子表table的ref对象map,key为子表表名' }, + { text: '.subActiveKey', displayText: 'subActiveKey', superiors: 'this', desc: '子表的激活的tab索引值对应的字符串,从‘0’开始,返回的是一个ref对象' }, + { text: '.sh', displayText: 'sh', superiors: 'this', desc: '单表/主表字段的显示隐藏状态' }, + { text: '.submitFlowFlag', displayText: 'submitFlowFlag', superiors: 'this', desc: '是否提交表单后自动提交流程,返回一个ref对象' }, + { text: '.subFormHeight', displayText: 'subFormHeight', superiors: 'this', desc: '一对一子表表单的高度,不需要设置,返回一个ref对象' }, + { text: '.subTableHeight', displayText: 'subTableHeight', superiors: 'this', desc: '一对多子表table的高度,不需要设置,返回一个ref对象' }, + { text: '.tableName', displayText: 'tableName', superiors: 'this', desc: '当前表名,返回的是一个ref对象' }, + { text: '.$nextTick', displayText: '$nextTick', superiors: 'this', desc: '调用的是vue3的nextTick' }, + { text: '.字段名_load', displayText: '字段名_load', superiors: 'this', desc: '控制字段的加载与否,设置为false表示当前字段不加载' }, + { text: '.字段名_disabled', displayText: '字段名_disabled', superiors: 'this', desc: '控制字段的禁用与否,设置为true表示当前字段禁用' }, + // 方法 + { text: '.addSubRows(tableName, rows)', displayText: 'addSubRows(tableName, rows)', superiors: 'this', desc: '往一对多子表table里添加数据' }, + { + text: '.changeOptions(field, options)', + texdisplayTextt: 'changeOptions(field, options)', + superiors: 'this', + desc: '改变单表/主笔 下拉控件的下拉选项', + }, + { text: '.clearSubRows(tableName)', displayText: 'clearSubRows(tableName)', superiors: 'this', desc: '清空一对多子表table的数据' }, + { + text: '.clearThenAddRows(tableName, rows)', + displayText: 'clearThenAddRows(tableName, rows)', + superiors: 'this', + desc: '先清空一对多子表table的数据,再往里添加数据', + }, + { text: '.getFieldsValue()', displayText: 'getFieldsValue()', superiors: 'this', desc: '获取主表/单表 所有字段的值' }, + { + text: '.getSubTableInstance(tableName)', + displayText: 'getSubTableInstance(tableName)', + superiors: 'this', + desc: '获取子表的实例对象,这个对象可以调用子表table的方法', + }, + { text: '.setFieldsValue(row)', displayText: 'setFieldsValue(row)', superiors: 'this', desc: '设置主表/单表 字段的值' }, + { + text: '.triggleChangeValues(values,id,target)', + displayText: 'triggleChangeValues(values,id,target)', + superiors: 'this', + desc: '改变单表/主表/子表 字段的值,一般用于change事件,其中id,target需要通过change事件的内置参数获取,如果不传id,target的值,则改变的是主表的字段', + }, + { text: '.triggleChangeValue(field, value)', displayText: 'triggleChangeValue(field, value)', superiors: 'this', desc: '设置单表/主表 字段的值' }, + { + text: '.onlineFormValueChange(field, value, otherValus)', + displayText: 'onlineFormValueChange(field, value, otherValus)', + superiors: 'this', + desc: '定义后,当表单值改变的时候会触发该方法(因js增强hook方式不支持原来的onlChange,所以定义此方法)', + }, + { + text: '.changeSubTableOptions(tableName,field,options)', + displayText: 'changeSubTableOptions(tableName,field,options)', + superiors: 'this', + desc: '改变一对一子表下拉框options', + }, + { + text: '.changeSubFormbleOptions(tableName,field,options)', + displayText: 'changeSubFormbleOptions(tableName,field,options)', + superiors: 'this', + desc: '改变一对多子表下拉框options', + }, + { + text: '.changeRemoteOptions({ field, dict, label, type?, subTableName? })', + displayText: 'changeRemoteOptions({ field, dict, label, type?, subTableName? })', + superiors: 'this', + desc: '改变动态下拉框options', + }, + { + text: '.submitFormAndFlow()', + displayText: 'submitFormAndFlow()', + superiors: 'this', + desc: '表单提交且发起流程', + }, + // 提交前置事件 + { + text: `beforeSubmit(row){ + return new Promise((resolve, reject)=>{ + //此处模拟等待时间,可能需要发起请求 + setTimeout(()=>{ + if(row.字段名 == '字段值'){ + // 当某个字段不满足要求的时候可以reject + reject('测试~'); + }else{ + resolve(); + } + },3000) + }) +}`, + displayText: 'beforeSubmit(row){}', + desc: '提交前置事件', + }, + // 表单加载事件 + { + text: `loaded(){ + this.$nextTick(()=>{ + // let text = '测试js增强设置默认值'; + // if(this.isUpdate.value === true){ + // text = '测试js增强修改表单值'; + // } + this.setFieldsValue({ + 字段名: 修改的值 + }) + }) +}`, + displayText: 'loaded(){}', + desc: '表单加载事件', + }, + // 单表#表单值改变事件 + { + text: `onlChange(){ + return { + 字段名(){ + let value = event.value + console.log(value) + this.triggleChangeValues({'字段名': '修改后的值'}) + } + } + }`, + displayText: 'onlChange(){}', + desc: '单表#表单值改变事件', + }, + // 子表#表单值改变事件 + { + text: `子表名_onlChange(){ + return { + 字段名(){ + let value = event.value; + console.log(value); + let row = {'字段名': '测试一对多值改变:'+value}; + this.triggleChangeValues(row, event.row.id, event.target) + } + } +}`, + displayText: '子表名_onlChange(){}', + desc: '子表#表单值改变事件', + }, + // 子改主#表单值改变事件 + { + text: `子表名_onlChange(){ + return { + 子表字段01(){ + this.getSubTableInstance('子表名').getValues((err,values)=>{ + this.triggleChangeValues({'主表字段名': '修改后的值'}) + }) + }, + } +} +`, + displayText: '子表名_onlChange(){}', + desc: '子改主#表单值改变事件', + }, + // js增强实现下拉联动 + { + text: `onlChange(){ + return { + 字段名01(){ + let value = event.value + this.changeOptions('字段名02', '修改后的值'); + } + 字段名02(){ + let value = event.value + this.changeOptions('字段名03', '修改后的值'); + } + } +}`, + displayText: 'changeOptions()', + desc: 'js增强实现下拉联动', + }, + { text: 'console.log()', displayText: 'console.log()', desc: '打印日志' }, + ], + common: [ + // JS增强 http请求 + { + text: `getAction('请求url', { 'key': 'value'}).then(res => { + console.log(res) +})`, + displayText: 'getAction(url, param)', + desc: 'get请求', + }, + { + text: `postAction('请求url', { 'key': 'value'}).then(res => { + console.log(res) +})`, + displayText: 'postAction(url, param)', + desc: 'post请求', + }, + { + text: `putAction('请求url', { 'key': 'value'}).then(res => { + console.log(res) +})`, + displayText: 'putAction(url, param)', + desc: 'put请求', + }, + { + text: `deleteAction('请求url', { 'key': 'value'}).then(res => { + console.log(res) +})`, + displayText: 'deleteAction(url, param)', + desc: 'delete请求', + }, + { text: 'this', displayText: 'this', desc: '上下文' }, + { + text: '.openCustomModal({title,width,row,formComponent,requestUrl,hide,show})', + displayText: 'openCustomModal({title,width,row,formComponent,requestUrl,hide,show})', + desc: '打开一个弹窗-参考 Js增强打开自定义弹窗', + }, + ], +}; diff --git a/src/views/super/online/cgform/components/enhance/enhance.api.ts b/src/views/super/online/cgform/components/enhance/enhance.api.ts new file mode 100644 index 0000000..d4e38f7 --- /dev/null +++ b/src/views/super/online/cgform/components/enhance/enhance.api.ts @@ -0,0 +1,141 @@ +import { defHttp } from '/@/utils/http/axios'; +import { isArray } from '/@/utils/is'; + +export enum Api { + enhanceJs = '/online/cgform/head/enhanceJs/', + enhanceButton = '/online/cgform/head/enhanceButton/', +} + +export enum EnhanceJavaApi { + enhanceJava = '/online/cgform/head/enhanceJava', + deleteBatch = '/online/cgform/head/deleteBatchEnhanceJava', +} + +export enum EnhanceSqlApi { + enhanceSql = '/online/cgform/head/enhanceSql', + deleteBatch = '/online/cgform/head/deletebatchEnhanceSql', +} + +/** + * 获取JS增强 + * @param code + * @param type + * @param params + */ +export async function getEnhanceJsByCode(code: string, type: string, params?) { + let { success, result } = await defHttp.get( + { + url: Api.enhanceJs + code, + params: { + ...params, + type, + }, + }, + { isTransformResponse: false } + ); + if (!success) { + result = { cgJs: '' }; + } + return result; +} + +/** + * 保存 JS 增强 + * @param code + * @param params + * @param isUpdate 是否更新 + */ +export const saveEnhanceJs = (code: string, params, isUpdate: boolean) => { + let url = `${Api.enhanceJs}${code}`; + if (isUpdate) { + return defHttp.put({ url, params }, { successMessageMode: 'none' }); + } else { + return defHttp.post({ url, params }, { successMessageMode: 'none' }); + } +}; + +// 加载Java增强数据 +export async function getEnhanceJavaByCode(code: string, params) { + // 先加载按钮 + let btnRes = await defHttp.get({ url: Api.enhanceButton + code }, { isTransformResponse: false }); + let btnList = []; + if (btnRes.success && isArray(btnRes.result)) { + // 按钮过滤 java增强只看action按钮 + btnList = btnRes.result.filter((item) => item.optType == 'action'); + } + // 再加载数据 + let path = `${EnhanceJavaApi.enhanceJava}/${code}`; + let dataSource = await defHttp.get({ url: path, params }); + return { btnList, dataSource }; +} + +// 执行删除操作 +export function doEnhanceJavaBatchDelete(idList: string[]) { + return defHttp.delete( + { + url: EnhanceJavaApi.deleteBatch, + params: { + ids: idList.join(','), + }, + }, + { joinParamsToUrl: true } + ); +} + +/** + * 保存 Java 增强 + * @param code + * @param params + * @param isUpdate 是否更新 + */ +export const saveEnhanceJava = (code: string, params, isUpdate: boolean) => { + let url = `${EnhanceJavaApi.enhanceJava}/${code}`; + if (isUpdate) { + return defHttp.put({ url, params }); + } else { + return defHttp.post({ url, params }); + } +}; + +// 加载Sql增强数据 +export async function getEnhanceSqlByCode(code: string, params) { + // 先加载按钮 + let btnRes = await defHttp.get({ url: Api.enhanceButton + code }, { isTransformResponse: false }); + let btnList = []; + if (btnRes.success && isArray(btnRes.result)) { + // 按钮过滤 java增强只看action按钮 + btnList = btnRes.result.filter((item) => item.optType == 'action'); + } + // 再加载数据 + let path = `${EnhanceSqlApi.enhanceSql}/${code}`; + let dataSource = await defHttp.get({ url: path, params }); + return { btnList, dataSource }; +} + +// 执行删除操作 +export function doEnhanceSqlBatchDelete(idList: string[]) { + return defHttp.delete( + { + url: EnhanceSqlApi.deleteBatch, + params: { + ids: idList.join(','), + }, + }, + { joinParamsToUrl: true } + ); +} + +/** + * 保存 Java 增强 + * @param code + * @param params + * @param isUpdate 是否更新 + */ +export const saveEnhanceSql = (code: string, params, isUpdate: boolean) => { + let url = `${EnhanceSqlApi.enhanceSql}/${code}`; + if (isUpdate) { + return defHttp.put({ url, params }); + } else { + return defHttp.post({ url, params }); + } +}; diff --git a/src/views/super/online/cgform/components/enhance/enhance.data.ts b/src/views/super/online/cgform/components/enhance/enhance.data.ts new file mode 100644 index 0000000..a959635 --- /dev/null +++ b/src/views/super/online/cgform/components/enhance/enhance.data.ts @@ -0,0 +1,204 @@ +import { computed, Ref } from 'vue'; +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { onlineDefaultButton } from '../../cgform.data'; + +export function useJavaColumns(btnList: Ref) { + let columns: BasicColumn[] = [ + { + title: '页面按钮', + align: 'center', + dataIndex: 'buttonCode', + customRender: ({ text }) => renderButtonText(text, btnList.value), + }, + { + title: '事件状态', + align: 'center', + dataIndex: 'event', + customRender: ({ text }) => (text == 'start' ? '开始' : '结束'), + }, + { + title: '类型', + align: 'center', + dataIndex: 'cgJavaType', + customRender: ({ text }) => { + if (text == 'spring') { + return 'spring-key'; + } else if (text === 'class') { + return 'java-class'; + } else if (text === 'http') { + return 'http-api'; + } else { + return text; + } + }, + }, + { + title: '内容', + align: 'center', + dataIndex: 'cgJavaValue', + }, + { + title: '是否生效', + align: 'center', + dataIndex: 'activeStatus', + customRender: ({ text }) => { + if (text == '1') { + return '有效'; + } else { + return '无效'; + } + }, + }, + ]; + return { columns }; +} + +export function useJavaFormSchemas(btnList: Ref) { + const formSchemas = computed(() => { + return [ + { + label: '页面按钮', + field: 'buttonCode', + component: 'Select', + componentProps: { + options: [ + { label: '新增', value: 'add' }, + { label: '编辑', value: 'edit' }, + { label: '删除', value: 'delete' }, + { label: '导入', value: 'import' }, + { label: '导出', value: 'export' }, + { label: '查询', value: 'query' }, + ...btnList.value.map((item) => ({ label: item.buttonName, value: item.buttonCode })), + ], + }, + defaultValue: 'add', + }, + { + label: '事件状态', + field: 'event', + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '开始', value: 'start' }, + { label: '结束', value: 'end' }, + ], + }, + defaultValue: 'end', + }, + { + label: '类型', + field: 'cgJavaType', + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: 'spring-key', value: 'spring' }, + { label: 'java-class', value: 'class' }, + { label: 'http-api', value: 'http' }, + ], + }, + defaultValue: 'spring', + }, + { + label: '内容', + field: 'cgJavaValue', + component: 'Input', + required: true, + }, + { + label: '是否生效', + field: 'activeStatus', + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '有效', value: '1' }, + { label: '无效', value: '0' }, + ], + }, + defaultValue: '1', + }, + ]; + }); + + return { formSchemas }; +} + +export function useSqlColumns(btnList: Ref) { + let columns: BasicColumn[] = [ + { + title: '页面按钮', + align: 'center', + dataIndex: 'buttonCode', + customRender: ({ text }) => renderButtonText(text, btnList.value), + }, + { + title: '增强SQL', + align: 'center', + dataIndex: 'cgbSql', + ellipsis: true, + }, + ]; + return { columns }; +} + +export function useSqlFormSchemas(btnList: Ref) { + const formSchemas = computed(() => { + return [ + { + label: '页面按钮', + field: 'buttonCode', + component: 'Select', + componentProps: { + allowClear: false, + options: [ + { label: '新增', value: 'add' }, + { label: '编辑', value: 'edit' }, + { label: '删除', value: 'delete' }, + ...btnList.value.map((item) => ({ label: item.buttonName, value: item.buttonCode })), + ], + }, + defaultValue: 'add', + }, + { + label: '增强SQL', + field: 'cgbSql', + component: 'JCodeEditor', + componentProps: { + language: 'sql', + placeholder: '请输入SQL语句', + languageChange: false, + lineNumbers: false, + fullScreen: true, + height: '320px', + }, + defaultValue: '', + }, + { + label: '描述', + field: 'content', + component: 'InputTextArea', + defaultValue: '', + }, + ]; + }); + + return { formSchemas }; +} + +function renderButtonText(text: string, btnList: any[]) { + let str = ''; + for (let item of onlineDefaultButton) { + if (item.code === text) { + str = item.title; + break; + } + } + if (!str) { + for (let item of btnList) { + if (item.buttonCode === text) { + str = item.buttonName; + break; + } + } + } + return str || text; +} diff --git a/src/views/super/online/cgform/components/tables/CheckDictTable.vue b/src/views/super/online/cgform/components/tables/CheckDictTable.vue new file mode 100644 index 0000000..015695c --- /dev/null +++ b/src/views/super/online/cgform/components/tables/CheckDictTable.vue @@ -0,0 +1,266 @@ + + + + diff --git a/src/views/super/online/cgform/components/tables/DBAttributeTable.vue b/src/views/super/online/cgform/components/tables/DBAttributeTable.vue new file mode 100644 index 0000000..6b25ff4 --- /dev/null +++ b/src/views/super/online/cgform/components/tables/DBAttributeTable.vue @@ -0,0 +1,641 @@ + + + + diff --git a/src/views/super/online/cgform/components/tables/ForeignKeyTable.vue b/src/views/super/online/cgform/components/tables/ForeignKeyTable.vue new file mode 100644 index 0000000..ac34c93 --- /dev/null +++ b/src/views/super/online/cgform/components/tables/ForeignKeyTable.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/src/views/super/online/cgform/components/tables/IndexTable.vue b/src/views/super/online/cgform/components/tables/IndexTable.vue new file mode 100644 index 0000000..d2d2e49 --- /dev/null +++ b/src/views/super/online/cgform/components/tables/IndexTable.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/src/views/super/online/cgform/components/tables/PageAttributeTable.vue b/src/views/super/online/cgform/components/tables/PageAttributeTable.vue new file mode 100644 index 0000000..fd70c05 --- /dev/null +++ b/src/views/super/online/cgform/components/tables/PageAttributeTable.vue @@ -0,0 +1,642 @@ + + + + + diff --git a/src/views/super/online/cgform/components/tables/QueryTable.vue b/src/views/super/online/cgform/components/tables/QueryTable.vue new file mode 100644 index 0000000..a1f34af --- /dev/null +++ b/src/views/super/online/cgform/components/tables/QueryTable.vue @@ -0,0 +1,158 @@ + + + diff --git a/src/views/super/online/cgform/components/tables/components/aiModal.vue b/src/views/super/online/cgform/components/tables/components/aiModal.vue new file mode 100644 index 0000000..2f67cc0 --- /dev/null +++ b/src/views/super/online/cgform/components/tables/components/aiModal.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/src/views/super/online/cgform/extend/FieldExtendJsonModal.vue b/src/views/super/online/cgform/extend/FieldExtendJsonModal.vue new file mode 100644 index 0000000..5e70ed6 --- /dev/null +++ b/src/views/super/online/cgform/extend/FieldExtendJsonModal.vue @@ -0,0 +1,380 @@ + + + + + diff --git a/src/views/super/online/cgform/extend/SetSwitchOptions.vue b/src/views/super/online/cgform/extend/SetSwitchOptions.vue new file mode 100644 index 0000000..818ef5d --- /dev/null +++ b/src/views/super/online/cgform/extend/SetSwitchOptions.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/src/views/super/online/cgform/extend/form/DetailForm.vue b/src/views/super/online/cgform/extend/form/DetailForm.vue new file mode 100644 index 0000000..6186cf3 --- /dev/null +++ b/src/views/super/online/cgform/extend/form/DetailForm.vue @@ -0,0 +1,332 @@ + + + + + diff --git a/src/views/super/online/cgform/extend/form/useDetailForm.ts b/src/views/super/online/cgform/extend/form/useDetailForm.ts new file mode 100644 index 0000000..7d91b73 --- /dev/null +++ b/src/views/super/online/cgform/extend/form/useDetailForm.ts @@ -0,0 +1,520 @@ +import { FormSchema, RenderCallbackParams } from '/@/components/Form'; +import { computed, ref, watch } from 'vue'; +import { getDictItemsByCode } from '/@/utils/dict'; +import { filterMultiDictText, filterDictText } from '/@/utils/dict/JDictSelectUtil'; +import { initDictOptions } from '/@/utils/dict/index'; +import { loadDictItem, queryDepartTreeSync, getUserList } from '/@/api/common/api'; +import { defHttp } from '/@/utils/http/axios'; +import { getAreaTextByCodeAnyLevel } from '/@/components/Form/src/utils/Area'; +import { getFileAccessHttpUrl } from '/@/utils/common/compUtils'; +import { createImgPreview } from '/@/components/Preview/index'; +import { useMessage } from '/@/hooks/web/useMessage'; + +/*** + * 表单字段的扩展配置解析结果 + */ +interface FieldExtends { + //上传数量 + uploadnum?: number | string; + + //限制大文本在列表页面的展示长度 + showLength?: number | string; + + //popup是否支持多选 + popupMulti?: boolean; + + //部门、用户组件 用于存储的字段名 + store?: string; + + //部门、用户组件 用于展示的字段名 + text?: string; + + //部门、用户组件 是否多选 + multiSelect?: boolean; + + //查询排序规则 + orderRule?: 'asc' | 'desc'; + //关联记录展示类型 + showType?:string; +} + +export interface DetailFormSchema { + field: string; + label: string; + span?: number; + view?: string; + isHtml?: boolean; + isImage?: boolean; + isFile?: boolean; + isCard?: boolean; + multi?:boolean; + order?: any; + dictTable?: string; + dictText?: string; + dictCode?: string; + dict?: string; + fieldExtendJson?: string; + ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean); + // update-begin--author:liaozhiyang---date:20240425---for:【issues/6139】online详情支持js增强loaded事件及设置值、获取值、隐藏功能 + // js增强隐藏 + hidden?: boolean; + // update-end--author:liaozhiyang---date:20240425---for:【issues/6139】online详情支持js增强loaded事件及设置值、获取值、隐藏功能 +} + +/*interface DetailFormProps { + span?: number; + schemas?: DetailFormSchema[]; + data?: any; + containerClass?: string; +}*/ + +export function useDetailForm(props: any) { + console.log(props); + const dictOptionsMap = {}; + const currentLinkFields: string[] = []; + const detailFormData = ref({}); + const { createMessage } = useMessage(); + + const formContainerClass = computed(() => { + if (props.containerClass) { + return `jeecg-detail-form ${props.containerClass}`; + } else { + return 'jeecg-detail-form'; + } + }); + + watch( + () => props.data, + async (formData) => { + if (formData) { + let arr = props.schemas; + let temp = {}; + if (arr && arr.length > 0) { + for (let item of arr) { + let field = item.field; + try { + temp[field] = await getItemContent(item); + } catch (e) { + console.error('字段【' + field + '】文本获取失败', e); + } + } + } + detailFormData.value = temp; + } + }, + { deep: true, immediate: true } + ); + + async function getItemContent(item) { + let formData = props.data; + if (formData) { + let value = formData[item.field]; + if (!value && value !== '0' && value !== 0) { + return ''; + } + let str = value; + let view = item.view; + if (view == 'list' || view == 'radio' || view == 'checkbox' || view == 'list_multi') { + str = await getSelectText(item, formData); + } else if (view == 'sel_search') { + str = await getTableDataText(item, formData); + } else if (view == 'cat_tree') { + //分类字典树 + str = await getCategoryDataText(item, formData); + } else if (view == 'link_table') { + str = await getLinkTableData(item, formData); + } else if (view == 'sel_depart') { + //部门选择 + str = await getDepartDataText(item, formData); + } else if (view == 'sel_user') { + // 用户选择 + str = await getUserDataText(item, formData); + } else if (view == 'pca') { + //省市区 + // update-begin--author:liaozhiyang---date:20260227---for:【QQYUN-14788】online详情单独的省市没显示 + let includeParent = true; + let fieldExtendJson = item?.fieldExtendJson; + let level = 3; + if (fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + if (['province', 'city', 'region'].includes(fieldExtendJson.displayLevel)) { + if (fieldExtendJson.displayLevel === 'province') { + level = 1; + } else if (fieldExtendJson.displayLevel === 'city') { + level = 2; + } else if (fieldExtendJson.displayLevel === 'region') { + level = 3; + } + includeParent = false; + } + } + str = getAreaTextByCodeAnyLevel(value, includeParent, level as 1 | 2 | 3); + // update-end--author:liaozhiyang---date:20260227---for:【QQYUN-14788】online详情单独的省市没显示 + } else if (view == 'link_down') { + //联动组件 + str = await getLinkDownDataText(item, formData); + } else if (view == 'sel_tree') { + //自定义树控件 + str = await getTreeDataText(item, formData); + } else if (view == 'switch') { + //开关组件 + str = await getSwitchDataText(item, formData); + } else if (view == 'image' || view == 'file') { + str = getFileList(item, formData); + } else if (view == 'popup_dict') { + // update-begin--author:liaozhiyang---date:20240402---for:【QQYUN-8833】JPopupDict的列表翻译 + const ditc = formData[`${item.field}_dictText`]; + if (ditc !== undefined) { + str = ditc; + } + // update-end--author:liaozhiyang---date:20240402---for:【QQYUN-8833】JPopupDict的列表翻译 + } else { + if (currentLinkFields.indexOf(item.field) >= 0) { + let arr = dictOptionsMap[item.field]; + if (arr && arr.length > 0) { + str = filterMultiDictText(arr, value); + } + } + } + return str; + } + return ''; + } + + // 数据字典/表字典 + async function getSelectText(item, formData) { + // 先从缓存取 + let dictCode = getRequestDictCode(item); + let value = formData[item.field]; + if (!dictCode) { + return value; + } + let options = getDictItemsByCode(dictCode); + if (options && options.length > 0) { + return filterMultiDictText(options, value); + } else { + let dictRes = []; + if (dictOptionsMap[dictCode]) { + dictRes = dictOptionsMap[dictCode]; + } else { + //取不到再请求 + dictRes = (await initDictOptions(dictCode)) || []; + } + if (dictRes && dictRes.length > 0) { + dictOptionsMap[dictCode] = dictRes; + return filterMultiDictText(dictRes, value); + } + } + return ''; + } + + function getRequestDictCode(item) { + let temp = ''; + let { dictCode, dictTable, dictText } = item; + if (!dictTable) { + temp = dictCode; + } else { + temp = encodeURI(`${dictTable},${dictText},${dictCode}`); + } + return temp; + } + + // 表字典-下拉搜索 + async function getTableDataText(item, formData) { + let dictCode = getRequestDictCode(item); + let value = formData[item.field]; + if (!value) { + return ''; + } + + let arr: any[] = []; + // update-begin--author:liaozhiyang---date:20250813---for:【issues/8689】online下拉搜索框详情时无法读取数据字典 + // 系统字典 + if (dictCode.indexOf(',') === -1) { + const options = await initDictOptions(dictCode); + if (options && options.length > 0) { + options.forEach((item: any) => { + if (item.value === value) { + arr.push(item.text || item.label); + } + }); + } + } else { + // 表字典 + if (dictOptionsMap[dictCode+value]) { + arr = dictOptionsMap[dictCode+value]; + } else { + //取不到再请求 + arr = (await defHttp.get({ url: `/sys/dict/loadDictItem/${dictCode}`, params: { key: value } })) || []; + } + } + // update-end--author:liaozhiyang---date:20250813---for:【issues/8689】online下拉搜索框详情时无法读取数据字典 + if (arr && arr.length > 0) { + dictOptionsMap[dictCode+value] = arr; + return arr.join(',') + //return filterMultiDictText(arr, value); + } + return ''; + } + + // 分类字典 + async function getCategoryDataText(item, formData) { + let value = formData[item.field]; + if (!value) { + return ''; + } + let arr = (await loadDictItem({ ids: value })) || []; + if (arr && arr.length > 0) { + return arr.join(','); + } + return ''; + } + + // 部门数据 + async function getDepartDataText(item, formData) { + let value = formData[item.field]; + if (!value) { + return ''; + } + let extend = getExtendConfig(item); + let storeField = extend.store || 'id'; + let labelKey = extend.text || 'departName'; + let arr = (await queryDepartTreeSync({ ids: value, primaryKey: storeField })) || []; + if (arr && arr.length > 0) { + let temp: string[] = []; + for (let item of arr) { + if (item[labelKey]) { + temp.push(item[labelKey]); + } else { + temp.push(item.title); + } + } + return temp.join(','); + } + return ''; + } + + //用户数据 + async function getUserDataText(item, formData) { + let value = formData[item.field]; + if (!value) { + return ''; + } + let extend = getExtendConfig(item); + let storeField = extend.store || 'username'; + let params = { + [storeField]: value, + }; + let res = (await getUserList(params)) || {}; + let arr = res.records || []; + if (arr && arr.length > 0) { + let temp: string[] = []; + console.log('getUserDataText', arr); + let textField = extend.text || 'realname'; + for (let item of arr) { + temp.push(item[textField]); + } + return temp.join(','); + } + return ''; + } + + function getExtendConfig(item) { + let extend: FieldExtends = {}; + let { fieldExtendJson } = item; + if (fieldExtendJson) { + if (typeof fieldExtendJson == 'string') { + try { + let json = JSON.parse(fieldExtendJson); + extend = { ...json }; + } catch (e) { + console.error(e); + } + } + } + return extend; + } + + // 联动组件 + async function getLinkDownDataText(item, formData) { + let { dictTable, field } = item; + let arr: any[] = []; + if (dictOptionsMap[field]) { + arr = dictOptionsMap[field]; + } else { + if (dictTable) { + let json = JSON.parse(dictTable); + if (json) { + let { table, txt, key, linkField } = json; + let dictCode = `${table},${txt},${key}`; + let temp: any[] = (await initDictOptions(dictCode)) || []; + arr = [...temp]; + if (arr && arr.length > 0) { + dictOptionsMap[field] = arr; + if (linkField) { + let fieldArray = linkField.split(','); + for (let item of fieldArray) { + dictOptionsMap[item] = arr; + currentLinkFields.push(item); + } + } + } + } + } + } + if (arr && arr.length > 0) { + let value = formData[field]; + return filterMultiDictText(arr, value); + } + return ''; + } + + //自定义树 + async function getTreeDataText(item, formData) { + let { dict, field } = item; + let arr = []; + if (dictOptionsMap[field]) { + arr = dictOptionsMap[field]; + } else { + if (dict) { + arr = await initDictOptions(dict); + } + } + if (arr && arr.length > 0) { + let value = formData[field]; + return filterMultiDictText(arr, value); + } + return ''; + } + + //开关 + async function getSwitchDataText(item, formData) { + let { fieldExtendJson, field } = item; + let options = ['Y', 'N']; + if (fieldExtendJson) { + //update-begin---author:wangshuai---date:2025-11-03---for:【issues/9036】online 表单开发, 设置字段 控件类型为开关时,查看详情页时 开关字段显示原始值--- + options = JSON.parse(fieldExtendJson)?.switchOptions; + //update-end---author:wangshuai---date:2025-11-03---for:【issues/9036】online 表单开发, 设置字段 控件类型为开关时,查看详情页时 开关字段显示原始值--- + } + let arr: any[] = [ + { value: options[0], text: '是' }, + { value: options[1], text: '否' }, + { value: options[0]+'', text: '是' }, + { value: options[1]+'', text: '否' }, + ]; + let value = formData[field]; + return filterDictText(arr, value); + } + + function getItemSpan(item) { + if (item.span) { + return item.span; + } + return props.span; + } + + function getFileList(item, formData) { + let str = formData[item.field]; + if (!str) { + return []; + } + let arr = str.split(','); + let result: string[] = []; + for (let item of arr) { + let src = getFileAccessHttpUrl(item) || ''; + if (src) { + result.push(src); + } + } + return result; + } + + function handleDownloadFile(url) { + if (url) { + window.open(url); + } + } + + function handleViewImage(field) { + let values = detailFormData.value[field]; + if (!values || values.length == 0) { + createMessage.warning('无图片!'); + return; + } + createImgPreview({ imageList: values }); + } + + function getFilename(url) { + if (!url) { + return ''; + } + return url.substring(url.lastIndexOf('/') + 1); + } + + /** + * VUEN-1772【vue3 online 详情】ai—ai_control_single 开关组件未翻译、字段为一行时,未居左对齐 + */ + const span24ViewArray = ['file', 'image', 'markdown', 'umeditor']; + function getLabelWidthClass(item) { + if(span24ViewArray.indexOf(item.view)>=0){ + if(props.span==12){ + return 'span12'; + }else if(props.span==8){ + return 'span8'; + }else if(props.span==6){ + return 'span6'; + }else{ + return 'span24'; + } + } + return '' + } + + // 关联记录 + async function getLinkTableData(item, formData) { + let value = formData[item.field]; + let extend = getExtendConfig(item); + if(extend.showType=='select'){ + if (!value) { + return ''; + } + return formData[item.field+'_dictText']; + }else{ + if (!value) { + return ''; + } + return formData[item.field]; + } + + let storeField = extend.store || 'id'; + let arr = (await queryDepartTreeSync({ ids: value, primaryKey: storeField })) || []; + if (arr && arr.length > 0) { + let temp: string[] = []; + for (let item of arr) { + temp.push(item.title); + } + return temp.join(','); + } + return ''; + } + + return { + formContainerClass, + detailFormData, + getItemSpan, + handleDownloadFile, + handleViewImage, + getFilename, + getLabelWidthClass + }; +} + +/** + * TODO 尚未实现 + * 获取 DetailFormSchema[自定义开发用] + */ +export function transDetailFormSchemas(formSchemas: FormSchema[]) { + const detailFormSchemas = ref([]); + console.log(formSchemas); + return detailFormSchemas; +} diff --git a/src/views/super/online/cgform/extend/linkTable/JModalTip.vue b/src/views/super/online/cgform/extend/linkTable/JModalTip.vue new file mode 100644 index 0000000..f6c328c --- /dev/null +++ b/src/views/super/online/cgform/extend/linkTable/JModalTip.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/src/views/super/online/cgform/extend/linkTable/LinkTableCard.vue b/src/views/super/online/cgform/extend/linkTable/LinkTableCard.vue new file mode 100644 index 0000000..e20fd8b --- /dev/null +++ b/src/views/super/online/cgform/extend/linkTable/LinkTableCard.vue @@ -0,0 +1,387 @@ + + + + + diff --git a/src/views/super/online/cgform/extend/linkTable/LinkTableConfigModal.vue b/src/views/super/online/cgform/extend/linkTable/LinkTableConfigModal.vue new file mode 100644 index 0000000..2647f82 --- /dev/null +++ b/src/views/super/online/cgform/extend/linkTable/LinkTableConfigModal.vue @@ -0,0 +1,289 @@ + + + + + diff --git a/src/views/super/online/cgform/extend/linkTable/LinkTableFieldConfigModal.vue b/src/views/super/online/cgform/extend/linkTable/LinkTableFieldConfigModal.vue new file mode 100644 index 0000000..220c681 --- /dev/null +++ b/src/views/super/online/cgform/extend/linkTable/LinkTableFieldConfigModal.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/src/views/super/online/cgform/extend/linkTable/LinkTableInput.vue b/src/views/super/online/cgform/extend/linkTable/LinkTableInput.vue new file mode 100644 index 0000000..32fa1d6 --- /dev/null +++ b/src/views/super/online/cgform/extend/linkTable/LinkTableInput.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/src/views/super/online/cgform/extend/linkTable/LinkTableListPiece.vue b/src/views/super/online/cgform/extend/linkTable/LinkTableListPiece.vue new file mode 100644 index 0000000..9caa01f --- /dev/null +++ b/src/views/super/online/cgform/extend/linkTable/LinkTableListPiece.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/src/views/super/online/cgform/extend/linkTable/LinkTableSelect.vue b/src/views/super/online/cgform/extend/linkTable/LinkTableSelect.vue new file mode 100644 index 0000000..c1cc4f3 --- /dev/null +++ b/src/views/super/online/cgform/extend/linkTable/LinkTableSelect.vue @@ -0,0 +1,412 @@ + + + + + diff --git a/src/views/super/online/cgform/extend/linkTable/useLinkTable.ts b/src/views/super/online/cgform/extend/linkTable/useLinkTable.ts new file mode 100644 index 0000000..e28f318 --- /dev/null +++ b/src/views/super/online/cgform/extend/linkTable/useLinkTable.ts @@ -0,0 +1,335 @@ +import { defHttp } from '/@/utils/http/axios'; +import { ref, watchEffect, computed, reactive } from 'vue' +import { pick } from 'lodash-es'; +import { filterMultiDictText } from '/@/utils/dict/JDictSelectUtil'; +import { getFileAccessHttpUrl } from '/@/utils/common/compUtils'; + +function queryTableData(tableName, params){ + const url = '/online/cgform/api/getData/'+tableName; + return defHttp.get({ url, params }); +} + +function queryTableColumns(tableName, params){ + const url = '/online/cgform/api/getColumns/'+tableName; + return defHttp.get({ url, params }); +} + +export function useLinkTable(props) { + + //TODO 目前只支持查询第一页的数据,可以输入关键字搜索 + const pageNo = ref('1'); + // 查询列 + const baseParam = ref({}); + // 搜素条件 + const searchParam = ref({}); + // 第一个文本列 + const mainContentField = ref(''); + //权限数据 + const auths = reactive({ + add: true, + update: true + }); + + //显示列 + const textFieldArray = computed(()=>{ + if(props.textField){ + return props.textField.split(',') + } + return [] + }); + const otherColumns = ref([]); + // 展示的列 配置的很多列,但是只展示三行 + const realShowColumns = computed(()=>{ + let columns = otherColumns.value; + if(props.multi == true){ + return columns.slice(0, 3) + }else{ + return columns.slice(0, 6) + } + }); + + watchEffect(async ()=>{ + let table = props.tableName; + if(table){ + let valueField = props.valueField || ''; + let textField = props.textField || ''; + let arr:any[] = []; + if(valueField){ + arr.push(valueField) + } + if(textField){ + let temp = textField.split(',') + mainContentField.value = temp[0] + for(let field of temp){ + arr.push(field) + } + } + let imageField = props.imageField || ''; + if(imageField){ + arr.push(imageField) + } + baseParam.value = { + linkTableSelectFields: arr.join(',') + }; + await resetTableColumns() + await reloadTableLinkOptions() + } + }); + + const otherFields = computed(()=>{ + let textField = props.textField || ''; + let others:any[] = []; + let labelField = '' + if(textField){ + let temp = textField.split(','); + labelField = temp[0]; + for(let i=0;i0){ + others.push(temp[i]) + } + } + } + return { + others, + labelField + }; + }); + + // 选项 + const selectOptions = ref([]); + const tableColumns = ref([]); + const dictOptions = ref({}); + //const tableTitle = ref('') + + async function resetTableColumns(){ + let params = baseParam.value; + const data = await queryTableColumns(props.tableName, params); + tableColumns.value = data.columns; + if(data.columns){ + let imageField = props.imageField; + let arr = data.columns.filter(c=>c.dataIndex!=mainContentField.value && c.dataIndex!=imageField) + otherColumns.value = arr; + } + dictOptions.value = data.dictOptions; + // 权限数据 + console.log('隐藏的按钮', data.hideColumns); + if(data.hideColumns){ + let hideCols = data.hideColumns; + if(hideCols.indexOf('add')>=0){ + auths.add = false + }else{ + auths.add = true + } + if(hideCols.indexOf('update')>=0){ + auths.update = false + }else{ + auths.update = true + } + } + } + + async function reloadTableLinkOptions(){ + let params = getLoadDataParams(); + const data = await queryTableData(props.tableName, params); + let records = data.records; + //tableTitle.value = data.head.tableTxt; + let dataList:any[] = []; + let { others, labelField } = otherFields.value; + let imageField = props.imageField; + if(records && records.length>0){ + for(let rd of records){ + let temp = {...rd}; + transData(temp); + let result = Object.assign({}, pick(temp, others), {id:temp.id, label: temp[labelField], value: temp[props.valueField]}); + if(imageField){ + result[imageField] = temp[imageField] + } + dataList.push(result); + } + } + //添加一个空对象 为add操作占位 + // update-begin--author:liaozhiyang---date:20240607---for:【TV360X-1095】高级查询关联记录去掉编辑按钮及去掉记录按钮 + props.editBtnShow && dataList.push({}); + // update-end--author:liaozhiyang---date:20240607---for:【TV360X-1095】高级查询关联记录去掉编辑按钮及去掉记录按钮 + selectOptions.value = dataList; + } + + /** + * 数据简单翻译-字典 + * @param data + */ + function transData(data) { + let columns = tableColumns.value; + let dictInfo = dictOptions.value; + for (let c of columns) { + const { dataIndex, customRender } = c; + if (data[dataIndex] || data[dataIndex] === 0) { + if (customRender && customRender == dataIndex) { + //这样的就是 字典数据了 可以直接翻译 + if (dictInfo[customRender]) { + data[dataIndex] = filterMultiDictText(dictInfo[customRender], data[dataIndex]); + continue; + } + } + } + // 兼容后台翻译字段 + let dictText = data[dataIndex + '_dictText']; + if (dictText) { + data[dataIndex] = dictText + } + } + } + + + //获取加载数据的查询条件 + function getLoadDataParams(){ + let params = Object.assign({pageSize: 100, pageNo: pageNo.value}, baseParam.value, searchParam.value); + return params; + } + + //设置查询条件 + function addQueryParams(text){ + if(!text){ + searchParam.value = {} + }else{ + let arr = textFieldArray.value; + let params:any[] = [] + let fields:any[] = [] + for(let i=0;i0){ + for(let item of records){ + let temp = {...item} + transData(temp); + dataList.push(temp); + } + } + return dataList; + } + + + /** + * true:数据一致;false:数据不一致 + * @param arr + * @param value + */ + function compareData(arr, value){ + if(!arr || arr.length==0){ + return false + } + let valueArray = value.split(','); + if(valueArray.length!=arr.length){ + return false; + } + let flag = true; + for(let item of arr){ + let temp = item[props.valueField]; + if(valueArray.indexOf(temp)<0){ + flag = false; + } + } + return flag; + } + + function formatData(formData){ + Object.keys(formData).map(k=>{ + if(formData[k] instanceof Array){ + formData[k] = formData[k].join(',') + } + }) + } + + function initFormData(formData, linkFieldArray, record){ + if(!record){ + record = {} + } + if(linkFieldArray && linkFieldArray.length>0){ + for(let str of linkFieldArray){ + let arr = str.split(',') + //["表单字段,表字典字段"] + let field = arr[0]; + let dictField = arr[1]; + if(!formData[field]){ + let value = record[dictField] || ''; + formData[field] = [value] + }else{ + formData[field].push(record[dictField]) + } + } + } + } + + + // 获取图片地址 + function getImageSrc(item){ + if(props.imageField){ + let url = item[props.imageField]; + // update-begin--author:liaozhiyang---date:20250517---for:【TV360X-38】关联记录空间,被关联数据优多个图片时,封面图片不展示 + if(typeof url === 'string') { + // 有多张图时默认取第一张 + url = url.split(',')[0] + } + // update-end--author:liaozhiyang---date:20250517---for:【TV360X-38】关联记录空间,被关联数据优多个图片时,封面图片不展示 + return getFileAccessHttpUrl(url); + } + return '' + } + const showImage = computed(()=>{ + if(props.imageField){ + return true; + }else{ + return false; + } + }); + + + return { + pageNo, + otherColumns, + realShowColumns, + selectOptions, + reloadTableLinkOptions, + textFieldArray, + addQueryParams, + tableColumns, + transData, + mainContentField, + loadOne, + compareData, + formatData, + initFormData, + getImageSrc, + showImage, + auths + } +} diff --git a/src/views/super/online/cgform/hooks/aitest/useOnlineTest.ts b/src/views/super/online/cgform/hooks/aitest/useOnlineTest.ts new file mode 100644 index 0000000..231a852 --- /dev/null +++ b/src/views/super/online/cgform/hooks/aitest/useOnlineTest.ts @@ -0,0 +1,69 @@ +import { FormActionType, JCodeEditor } from '/@/components/Form'; +import { ref } from 'vue'; + +export function useOnlineTest(data, methods, form: Nullable) { + // Online单元测试开关 + const aiTestMode = ref(false); + const aiTestTable = ref([]); + const aiTableList = ref([]); + + function initVirtualData() { + } + + // 自定义按钮 + function genButtons(code) { + } + + // 生成java增强 + function genEnhanceJavaData(code) { + } + + // 生成js增强 + function genEnhanceJsData(tableName, type, codeEditor: InstanceType) { + } + + // 自定义sql增强 + function genEnhanceSqlData(code, tableName) { + } + + /** + * 加载配置信息 + */ + function setTaleConfig() { + } + + function tableJsonGetHelper(pickAfter) { + console.log('表的配置信息', JSON.stringify(pickAfter)); + console.log('---------------------------------------'); + } + + /** + * json 获取小助手 + * @param fields + */ + function fieldsJsonGetHelper(fields) { + } + + function refreshCacheTableName(oldValue, newValue) { + } + + function getCacheTableName(name) { + } + + // noinspection JSUnusedGlobalSymbols + return { + aiTestMode, + aiTestTable, + aiTableList, + initVirtualData, + genButtons, + genEnhanceJavaData, + genEnhanceJsData, + genEnhanceSqlData, + setTaleConfig, + tableJsonGetHelper, + fieldsJsonGetHelper, + refreshCacheTableName, + getCacheTableName, + }; +} \ No newline at end of file diff --git a/src/views/super/online/cgform/hooks/auto/useAutoForm.ts b/src/views/super/online/cgform/hooks/auto/useAutoForm.ts new file mode 100644 index 0000000..af58fea --- /dev/null +++ b/src/views/super/online/cgform/hooks/auto/useAutoForm.ts @@ -0,0 +1,1128 @@ +import { ref, watch, reactive, toRaw, nextTick, computed } from 'vue'; +import { FormSchema } from '/@/components/Form'; +import FormSchemaFactory from '../../auto/comp/factory/FormSchemaFactory'; +import IFormSchema from '../../auto/comp/factory/IFormSchema'; +import { JVxeTableInstance } from '/@/components/jeecg/JVxeTable/types'; +import { duplicateCheck } from '/@/views/system/user/user.api'; +import { initDefValueConfig, initSubTableDefValueConfig } from '../../util/FieldDefVal'; +import { usePermissionStore } from '/@/store/modules/permission'; +import { ONL_AUTH_PRE } from '../../types/onlineRender'; +import { pick } from 'lodash-es'; +import { DetailFormSchema } from '../../extend/form/useDetailForm'; +import {useExtendComponent} from './useExtendComponent' +import componentSetting from '/@/settings/componentSetting'; +import { LABELLENGTH } from '../../util/constant'; +import { useAppInject } from '/@/hooks/web/useAppInject'; + +export const LINK_DOWN = 'link_down'; +export const LINK_TABLE_FIELD = 'link_table_field'; +export const LINK_TABLE = 'link_table'; + +export interface OnlSubTab { + key: string; + properties?: any[]; + columns?: any[]; + foreignKey: string; + describe: string; + relationType: number; + requiredFields?: string[]; + order: number; + id?:string; +} +/** + * 获取实际表单的配置信息 + */ +export function useFormItems(props, onlineFormRef) { + // 添加表单组件-专门给 online 表单用 + useExtendComponent(); + + // 下拉框等组件需要通过此class作为父级container + const modalClass = props.modalClass; + // 表单渲染用到的配置 + const formSchemas = ref([]); + // 表名 + const tableName = ref(''); + // 编辑页面 数据库原数据 + const dbData = ref({}); + // 字段展示状态 + const fieldDisplayStatus = reactive({}); + const hasSubTable = ref(false); + + const subTabInfo = ref([]); + const subDataSource = ref({}); + const refMap = {}; + // 联动组件列表 + const linkDownList = ref([]); + /** + * 有表单默认值的字段 + * 表名: [{field, value, type}] + */ + const defaultValueFields = reactive({}); + // 表单栅格 + const baseColProps = ref(''); + baseColProps.value = { sm: 24, xs: 24, md: 12, lg: 12, xl: 12, xxl: 12 }; + // update-begin--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + const labelCol = ref({ xs: { span: 24 }, sm: { span: 4 }, md: { span: 4 }, lg: { span: 4 }, xl: { span: 4 }, xxl: { span: 4 } }); + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + const wrapperCol = ref(null); + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + // update-end--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + // update-begin--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + const labelWidth = ref(6 * 14 + 10); + // update-end--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + + function createFormSchemas(properties: any[], required, checkOnlyFieldValue, onlineExtConfigJson = {}) { + //let properties:any[] = result.schema.properties + clearObj(defaultValueFields); + defaultValueFields[tableName.value] = []; + let subInfo: OnlSubTab[] = []; + let arr: IFormSchema[] = []; + let hideFields: string[] = []; + let dataSourceObj = {}; + let tableLinkInfo:any = {} + Object.keys(properties).map((key) => { + const item = properties[key]; + // uiSchema 无用 + //const uiItem = this.uiSchema[key];// method、formTemplate、url + if (item.view == 'tab') { + hasSubTable.value = true; + defaultValueFields[key] = []; + let temp: OnlSubTab = { + key, + // 这个foreignKey是主表的字段 + foreignKey: item['foreignKey'], + describe: item.describe, + relationType: item.relationType, + requiredFields: item.required || [], + order: item.order, + id: item.id + }; + if (item.relationType == 1) { + refMap[key] = ref(null); + temp['properties'] = item.properties; + } else { + dealSubProerties(item); + refMap[key] = ref(); + temp['columns'] = item.columns; + dataSourceObj[key] = []; + // TODO 处理子表的新增删除按钮权限 + //this.handleSubTableButtonAuth(item) + } + subInfo.push(temp); + // 记录子表按钮权限 + handleSubTableButtonAuth(key, item); + } else { + initDefValueConfig(key, item, defaultValueFields[tableName.value]); + if (item.view === LINK_DOWN) { + let array = handleLinkDown(item, key); + for (let linkDownItem of array) { + // update-begin--author:liaozhiyang---date:20240522---for:【TV360X-314】联动组件添加组件默认值 + const fItem = linkDownItem.key == key ? item : item.others?.find((item) => item.field === linkDownItem.key); + fItem && initDefValueConfig(linkDownItem.key, fItem, defaultValueFields[tableName.value]); + // update-end--author:liaozhiyang---date:20240522---for:【TV360X-314】联动组件添加组件默认值 + fieldDisplayStatus[linkDownItem.key] = true; + fieldDisplayStatus[linkDownItem.key + '_load'] = true; + // update-begin--author:liaozhiyang---date:20240521---for:【TV360X-75】表单label长度设置,联动组件没生效 + setFieldExtend(onlineExtConfigJson, linkDownItem); + // update-end--author:liaozhiyang---date:20240521---for:【TV360X-75】表单label长度设置,联动组件没生效 + let temp = FormSchemaFactory.createFormSchema(linkDownItem.key, linkDownItem); + // update-begin--author:liaozhiyang---date:20251230---for:【issues/9223】js增强,用loaded方法里加入某个字段隐藏,导致打开窗口时其他已经设置只读字段,恢复成可写 + fieldDisplayStatus[linkDownItem.key + '_disabled'] = temp.disabled ?? false; + // update-end--author:liaozhiyang---date:20251230---for:【issues/9223】js增强,用loaded方法里加入某个字段隐藏,导致打开窗口时其他已经设置只读字段,恢复成可写 + if (checkOnlyFieldValue) { + temp.setOnlyValidateFun(checkOnlyFieldValue); + } + temp.isRequired(required); + temp.setFormRef(onlineFormRef); + // 联动控件 只读由第一个控件的只读状态决定 + temp.handleWidgetAttr(item); + let tempIndex = getFieldIndex(arr, linkDownItem.key); + if (tempIndex == -1) { + arr.push(temp); + } else { + arr[tempIndex] = temp; + } + } + } else { + // update-begin--author:liaozhiyang---date:20240522---for:【TV360X-314】联动组件添加组件默认值 + initDefValueConfig(key, item, defaultValueFields[tableName.value]); + // update-end--author:liaozhiyang---date:20240522---for:【TV360X-314】联动组件添加组件默认值 + fieldDisplayStatus[key] = true; + fieldDisplayStatus[key + '_load'] = true; + let tempIndex = getFieldIndex(arr, key); + if (tempIndex == -1) { + // update-begin--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + setFieldExtend(onlineExtConfigJson, item); + // update-end--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + let temp = FormSchemaFactory.createFormSchema(key, item); + // update-begin--author:liaozhiyang---date:20251230---for:【issues/9223】js增强,用loaded方法里加入某个字段隐藏,导致打开窗口时其他已经设置只读字段,恢复成可写 + fieldDisplayStatus[key + '_disabled'] = temp.disabled ?? false; + // update-end--author:liaozhiyang---date:20251230---for:【issues/9223】js增强,用loaded方法里加入某个字段隐藏,导致打开窗口时其他已经设置只读字段,恢复成可写 + if (checkOnlyFieldValue) { + temp.setOnlyValidateFun(checkOnlyFieldValue); + } + temp.isRequired(required); + temp.setFormRef(onlineFormRef); + arr.push(temp); + hideFields.push(...temp.getRelatedHideFields()); + + //update-begin-author:taoyan date:2022-8-5 for: 获取他表字段信息 + //如果是他表字段获取关联信息 + if(item.view === LINK_TABLE_FIELD){ + let tempInfo = temp.getLinkFieldInfo(); + if(tempInfo){ + if(tableLinkInfo[tempInfo[0]]){ + let tableLinkInfoEle:string[] = tableLinkInfo[tempInfo[0]]; + tableLinkInfoEle.push(tempInfo[1]); + }else{ + tableLinkInfo[tempInfo[0]] = [tempInfo[1]] + } + } + } + //update-end-author:taoyan date:2022-8-5 for: 获取他表字段信息 + + } + } + //fp.checkOnlyMethod = this.$Jdebounce(this.checkOnlyFieldValue, 1000); + } + }); + // 1.对arr排序 + arr.sort(function (a, b) { + return a.order - b.order; + }); + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7499】多列风格富文本、markdown增加独占一行功能 + const oneRowData: any = []; + (() => { + for (let i = 0, len = arr.length; i < len; i++) { + const item = arr[i]; + if (getFieldExtend(item?._data, 'isOneRow')) { + oneRowData.push(arr.splice(i, 1)[0]); + i--; + len--; + } + } + })(); + arr = [...arr,...oneRowData]; + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7499】多列风格富文本、markdown增加独占一行功能 + // 2.获取真实表单配置 + let formSchemaArray: FormSchema[] = []; + formSchemaArray.push(FormSchemaFactory.createIdField()); + let longestLabelComponet: any = null; + let isComponetRequired = false; + for (let a of arr) { + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + const curLabelLen = a.label.length; + if (longestLabelComponet) { + if (longestLabelComponet.label.length < curLabelLen) { + longestLabelComponet = a; + } else if(longestLabelComponet.label.length === curLabelLen) { + // 文字labael相同,则判断是否必填。(必填*占13像素) + if(!longestLabelComponet.required && a.required) { + longestLabelComponet = a; + } + } + } else { + longestLabelComponet = a; + } + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + // update-begin--author:liaozhiyang---date:20230105---for:【TV360X-209】表单label长度设置了且字段有必填宽度计算不正确 + if (a.required) { + isComponetRequired = true; + } + // update-end--author:liaozhiyang---date:20230105---for:【TV360X-209】表单label长度设置了且字段有必填宽度计算不正确 + //update-begin-author:taoyan date:2022-8-5 for: 将他表字段的配置信息 添加至 关联字段上 + //关联记录字段设置新的配置 + if(a['view'] && a['view']==LINK_TABLE){ + if(tableLinkInfo[a.field]){ + a.setOtherInfo(tableLinkInfo[a.field]) + } + } + //update-end-author:taoyan date:2022-8-5 for: 将他表字段的配置信息 添加至 关联字段上 + //设置hidden的字段 + if (hideFields.indexOf(a.field) >= 0) { + a.isHidden(); + } + // popModal-下拉框的父级容器需要自定义,否则会被遮挡 + if(modalClass){ + a.setCustomPopContainer(modalClass) + } + // update-begin--author:liaozhiyang---date:20231222---for:【QQYUN-7515】online 下拉字典、单选组件options跟随数据库类型 + const result = a.getFormItemSchema(); + if (result.component === 'JDictSelectTag' && a?._data?.type === 'number') { + result.componentProps.stringToNumber = true; + } + // update-end-author:liaozhiyang---date:20231222---for:【QQYUN-7515】online 下拉字典、单选组件options跟随数据库类型 + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7499】多列风格富文本、markdown增加独占一行功能 + if (props.formTemplate > 1 && getFieldExtend(a?._data, 'isOneRow')) { + result.colProps = { span: 24 }; + const colGrid = getFormItemColProps(); + const { labelCol = {} } = colGrid; + const itemLabelCol = {}; + const itemWrapperCol = {}; + Object.keys(labelCol).forEach((key) => { + if (['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].includes(key)) { + const span = labelCol[key].span; + const value = Math.round(span / props.formTemplate); + itemLabelCol[key] = { span: value }; + itemWrapperCol[key] = { span: 24 - value - 1 }; + } + }); + result.itemProps = { labelCol: itemLabelCol, wrapperCol: itemWrapperCol }; + } + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7499】多列风格富文本、markdown增加独占一行功能 + // update-begin--author:liaozhiyang---date:20251011---for:【issues/8791】js增强popup弹框的onlChange()没生效 + if (result.component === 'JPopup') { + result.changeEvent = 'popUpChange'; + } + // update-end--author:liaozhiyang---date:20251011---for:【issues/8791】js增强popup弹框的onlChange()没生效 + formSchemaArray.push(result); + } + formSchemas.value = formSchemaArray; + //return formSchemaArray; + //update-begin-author:taoyan date:2022-5-31 for: VUEN-1147 主子表 子表顺序并不是按照设置顺序排列 + subInfo.sort(function (a, b) { + return a.order - b.order; + }); + //update-end-author:taoyan date:2022-5-31 for: VUEN-1147 主子表 子表顺序并不是按照设置顺序排列 + // update-begin--author:liaozhiyang---date:20231009---for:【issues/5371】一对多子表popup增加多选 + subInfo.forEach((sItem) => { + const columns: any = sItem.columns; + if(sItem.columns){ + columns.forEach((cItem) => { + // update-begin--author:liaozhiyang---date:20240529---for:【TV360X-452】一对多子表popup默认多选没生效 + if (sItem.relationType == 0) { + if (['popup', 'popup_dict'].includes(cItem.type)) { + // 只有1对多才需要处理,1对1或者单表直接组件中处理了。 + let popupMulti = true; + if (cItem.fieldExtendJson) { + const fieldExtendJson = JSON.parse(cItem.fieldExtendJson); + popupMulti = fieldExtendJson.popupMulti; + } + const props = cItem.props ?? {}; + cItem.props = { ...props, multi: popupMulti }; + } + } + // update-end--author:liaozhiyang---date:20240529---for:【TV360X-452】一对多子表popup默认多选没生效 + // update-begin--author:liaozhiyang---date:20240509---for:【QQYUN-9205】一对多(jVxetable组件date)支持年,年月,年度度,年周 + if (cItem.type === 'date' && cItem.fieldExtendJson) { + const fieldExtendJson = JSON.parse(cItem.fieldExtendJson); + if (fieldExtendJson.picker && fieldExtendJson.picker != 'default') { + Object.assign(cItem, { picker: fieldExtendJson.picker }); + } + } + // update-end--author:liaozhiyang---date:20240509---for:【QQYUN-9205】一对多(jVxetable组件date)支持年,年月,年度度,年周 + }); + } + }); + // update-end--author:liaozhiyang---date:20231009---for:【issues/5371】一对多子表popup增加多选 + subTabInfo.value = subInfo; + subDataSource.value = dataSourceObj; + // update-begin--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + if (onlineExtConfigJson.formLabelLengthShow && onlineExtConfigJson.formLabelLength) { + // 14是文字size,24是间隙 + labelWidth.value = onlineExtConfigJson.formLabelLength * 14 + 10 + (+`${isComponetRequired ? 13 : 0}`); + wrapperCol.value = null; + } else { + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + // num这个值是label没截取之前真实长度,如果大于初始值则还是得使用初始值 + if (longestLabelComponet) { + let realLabelLen = longestLabelComponet.label.length; + realLabelLen = realLabelLen > LABELLENGTH ? LABELLENGTH : realLabelLen; + const required = longestLabelComponet.required; + const num = realLabelLen * 14 + 10 + (+`${required ? 13 : 0}`); + labelWidth.value = num; + } + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + } + // update-end--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + } + + watch( + fieldDisplayStatus, + (val) => { + let ref = onlineFormRef.value; + let arr: any[] = []; + let map = toRaw(val); + Object.keys(map).map((k) => { + if (k.endsWith('_load')) { + } else { + let item = { + field: k, + show: map[k], + }; + let loadKey = k + '_load'; + if (map.hasOwnProperty(loadKey)) { + item['ifShow'] = map[loadKey]; + } + // update-begin--author:liaozhiyang---date:20240321---for:【QQYUN-8537】js增强,控制表单字段的禁用 + let disabledKey = k + '_disabled'; + if (map.hasOwnProperty(disabledKey)) { + item['dynamicDisabled'] = () => { + return map[disabledKey]; + }; + } + // update-end--author:liaozhiyang---date:20240321---for:【QQYUN-8537】js增强,控制表单字段的禁用 + arr.push(item); + } + }); + if (ref) { + ref.updateSchema(arr); + } + }, + { immediate: false } + ); + + function dealSubProerties(subInfo) { + useOnlineVxeTableColumns(subInfo, (column)=>{ + initSubTableDefValueConfig(column, defaultValueFields[subInfo.key]); + }) + } + /* + 2024-03-06 + liaozhiyang + 表单中的扩展参数的labelLength设置为onlineExtConfigJson.formLabelLength + */ + function setFieldExtend(onlineExtConfigJson, data, key = 'labelLength') { + const { formLabelLengthShow, formLabelLength } = onlineExtConfigJson; + if (formLabelLengthShow && formLabelLength) { + let fieldExtendJson = data?.fieldExtendJson; + if (fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + fieldExtendJson[key] = formLabelLength; + } else { + fieldExtendJson = { [key]: formLabelLength }; + } + data.fieldExtendJson = JSON.stringify(fieldExtendJson); + } + } + + /* + 2024-01-05 + liaozhiyang + 获取扩展参数 + */ + function getFieldExtend(data: any = {}, key: string) { + let fieldExtendJson = data?.fieldExtendJson; + if (fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + return fieldExtendJson[key]; + } + } + + //监听主表表单的formTemplate + watch( + () => props.formTemplate, + () => { + //重新渲染表单 + const result = getFormItemColProps() + baseColProps.value = result.baseColProps; + labelCol.value = result.labelCol; + wrapperCol.value = result.wrapperCol; + }, + { immediate: true } + ); + + function getFormItemColProps() { + let temp = props.formTemplate; + // update-begin--author:liaozhiyang---date:20240105---for:【QQYUN-7499】多列风格富文本、markdown增加独占一行功能 + // update-begin--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + // const form: any = componentSetting.form || {}; + // const { labelCol = {} } = form; + // const { wrapperCol = {} } = form; + // update-end--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + if (temp == 2) { + // update-begin--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + return { + baseColProps: { sm: 24, xs: 24, md: 12, lg: 12, xl: 12, xxl: 12 }, + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + // labelCol: { xs: { span: 24 }, sm: { span: 4 }, md: { span: 4 }, lg: { span: 4 }, xl: { span: 4 }, xxl: { span: 4 } }, + // wrapperCol: { xs: { span: 24 }, sm: { span: 19 }, md: { span: 19 }, lg: { span: 19 }, xl: { span: 19 }, xxl: { span: 19 } }, + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + }; + // update-end--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + } else if (temp == 3) { + return { + baseColProps: { sm: 24, xs: 24, md: 8, lg: 8, xl: 8, xxl: 8 }, + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + // labelCol: { xs: { span: 24 }, sm: { span: 6 }, md: { span: 6 }, lg: { span: 6 }, xl: { span: 6 }, xxl: { span: 6 } }, + // wrapperCol: { xs: { span: 24 }, sm: { span: 17 }, md: { span: 17 }, lg: { span: 17 }, xxl: { span: 17 } }, + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + }; + } else if (temp == 4) { + return { + baseColProps: { sm: 24, xs: 24, md: 6, lg: 6, xl: 6, xxl: 6 }, + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + // labelCol: { xs: { span: 24 }, sm: { span: 4 }, md: { span: 4 }, lg: { span: 4 }, xl: { span: 4 }, xxl: { span: 4 } }, + // wrapperCol: { xs: { span: 24 }, sm: { span: 18 }, md: { span: 18 }, lg: { span: 18 }, xl: { span: 18 }, xxl: { span: 18 } }, + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + }; + } else { + // update-begin--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + return { + baseColProps: { sm: 24, xs: 24, md: 24, lg: 24, xl: 24, xxl: 24 }, + // update-begin--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + // labelCol: { xs: { span: 24 }, sm: { span: 4 }, md: { span: 4 }, lg: { span: 4 }, xl: { span: 4 }, xxl: { span: 4 } }, + // wrapperCol:{ xs: { span: 24 }, sm: { span: 18 }, md: { span: 18 }, lg: { span: 18 }, xl: { span: 18 }, xxl: { span: 18 } }, + // update-end--author:liaozhiyang---date:20230105---for:【QQYUN-7632】 label栅格改成labelwidth固宽 + }; + // update-end--author:liaozhiyang---date:20240311---for:【QQYUN-8440】小屏幕居中(跟vue2栅格同步) + } + // update-end--author:liaozhiyang---date:20240105---for:【QQYUN-7499】多列风格富文本、markdown增加独占一行功能 + } + // 唯一校验 + function checkOnlyFieldValue(rule, value) { + return new Promise((resolve) => { + if (!value) { + resolve(''); + } + //对于视图 需要将表名后的$+数字 取掉 + let realTableName = tableName.value.replace(/\$\d+/, ''); + let param = { + tableName: realTableName, + fieldName: rule.field, + fieldVal: value, + }; + let formData: any = dbData.value; + if (formData.id) { + param['dataId'] = formData.id; + } + //console.log("唯一校验---》",param) + duplicateCheck(param) + .then((res) => { + if (res.success) { + resolve(''); + } else { + resolve(res.message); + } + }) + .catch((msg) => { + resolve(msg); + }); + }); + } + + /** + * 有些数据是数组格式的 强转成字符串 + */ + function changeDataIfArray2String(data) { + Object.keys(data).map((k) => { + if (data[k]) { + if (data[k] instanceof Array) { + data[k] = data[k].join(','); + } + } + }); + return data; + } + + return { + formSchemas, + defaultValueFields, + tableName, + dbData, + checkOnlyFieldValue, + createFormSchemas, + fieldDisplayStatus, + subTabInfo, + hasSubTable, + subDataSource, + baseColProps, + changeDataIfArray2String, + linkDownList, + refMap: refMap, + labelCol, + wrapperCol, + labelWidth, + }; +} + +/** + * 处理online子表 jvxeTable的列配置信息 + */ +export function useOnlineVxeTableColumns(subInfo, callback?){ + // 新旧jvxetable 列的类型不一致 + const vxeTypeMap = { + inputNumber: 'input-number', + sel_depart: 'depart-select', + sel_user: 'user-select', + list_multi: 'select-multiple', + input_pop: 'textarea', + sel_search: 'select-search', + 'select-dict-search': 'selectDictSearch', + }; + + //一对多子表如果为单选按钮改为下拉框 + subInfo.columns.forEach((column) => { + if (column.type === 'radio') { + column.type = 'select'; + } else if (vxeTypeMap[column.type]) { + column.type = vxeTypeMap[column.type]; + } else if (column.type === 'popup') { + handleSubPopup(column); + } else if (column.type === 'link_table') { + handleSubLinkTable(column, subInfo.columns); + } else if (column.type === 'link_table_field') { + // update-begin--author:liaozhiyang---date:20260317---for:【QQYUN-9441】online一对多加上关联记录和他表字段 + column.type = 'input'; + column.flag = 'link-table-field'; + column.props = { + ...(column.props ?? {}), + disabled: true, + }; + // update-end--author:liaozhiyang---date:20260317---for:【QQYUN-9441】online一对多加上关联记录和他表字段 + } + // 部门树选择组件需要设置 父子节点不关联 + if (column.type === 'depart-select') { + column['checkStrictly'] = true; + } + // 子表用户选择 控制是否多选 + if (column.type === 'user-select') { + handleSubUserSelect(column); + } + if (column.type === 'pca') { + column.width = '230px'; + } + // update-begin--author:liaozhiyang---date:20260413---for:【issues/7633】online子表支持分类字典树,自定义树 + // 自定义树 + if (column.type === 'sel_tree') { + const { dictTable, dictCode, dictText } = column; + const [id, pid, name, child] = dictText.split(','); + column.type = 'sel-tree'; + column.dict = `${dictTable},${name},${id}`; + column.pidField = pid; + column.pidValue = dictCode ?? '0'; + column.hasChildField = child; + delete column.dictText; + delete column.dictCode; + delete column.dictTable; + } + // 分类字典书 + if (column.type === 'cat_tree') { + const { dictCode } = column; + column.type = 'cat-tree'; + column.pcode = dictCode ?? '0'; + delete column.dictCode; + } + // update-end--author:liaozhiyang---date:20260413---for:【issues/7633】online子表支持分类字典树,自定义树 + //update-begin-author:taoyan date:2022-4-24 for: VUEN-855 对多子表 文件、图片右侧少个边框 + if ((column.width == 120 || column.width == '120px') && (column.type == 'image' || column.type == 'file')) { + column.width = '130px'; + } + //如果没有宽度 默认设置一个宽度 + if (!column.width) { + column.width = '200px'; + } + if(callback){ + callback(column) + } + //update-end-author:taoyan date:2022-4-24 for: VUEN-855 对多子表 文件、图片右侧少个边框 + }); + + // 子表popup特殊处理 + function handleSubPopup(column) { + let { destFields, orgFields } = column; + let fieldConfig: any[] = []; + if (!destFields || destFields.length == 0) { + } else { + let arr1 = destFields.split(','); + let arr2 = orgFields.split(','); + for (let i = 0; i < arr1.length; i++) { + fieldConfig.push({ + target: arr1[i], + source: arr2[i], + }); + } + } + column.fieldConfig = fieldConfig; + } + + // 子表 用户选择特殊处理 + function handleSubUserSelect(column) { + let str = column.fieldExtendJson; + let isRadioSelection = false; + if (str) { + try { + let json = JSON.parse(str); + if (json.multiSelect === false) { + isRadioSelection = true; + } + } catch (e) { + console.log('子表获取用户组件的扩展配置出现错误', e); + } + } + column.isRadioSelection = isRadioSelection; + } + /** + * 20260317 + * 【QQYUN-9441】online一对多加上关联记录和他表字段 + * liaozhiang + * */ + function handleSubLinkTable(column, columns) { + column.type = 'link-table'; + column.tableName = column.dictTable || ''; + column.valueField = column.dictCode || 'id'; + column.textField = column.dictText || ''; + column.multi = false; + column.linkFields = []; + columns.forEach(item => { + if (item.type === 'link_table_field' && item.dictTable === column.key) { + column.linkFields.push(`${item.key},${item.dictText}`); + } + }); + delete column.dictTable; + delete column.dictCode; + delete column.dictText; + let str = column.fieldExtendJson; + if (str) { + try { + let json = JSON.parse(str); + if (json.multiSelect === true) { + column.multi = true; + } + } catch (e) { + console.log('子表获取关联记录组件的扩展配置出现错误', e); + } + } + if (!column.width || column.width === '200px') { + column.width = '240px'; + } + } + +} + +/*** + * 表单上下文 + */ +export function useOnlineFormContext(props) { + let that = {}; + const CONTEXT_DESCRIPTION = { + addSubRows: ' 一对多子表,新增自定义行', + changeOptions: ' 改变下拉框选项', + clearSubRows: ' 清空一对多子表行', + clearThenAddRows: ' 清空一对多子表行,然后新增自定义行', + executeMainFillRule: ' 刷新主表的增值规制值', + executeSubFillRule: ' 刷新子表的增值规制值', + getFieldsValue: ' 获取表单控件的值', + getSubTableInstance: ' 获取子表实例', + isUpdate: '

判断是否为编辑模式', + loading: '

页面加载状态', + onlineFormRef: '

当前表单ref对象', + refMap: '

子表ref对象map', + setFieldsValue: ' 设置表单控件的值', + sh: '

表单控件的显示隐藏状态', + subActiveKey: '

子表激活tab,对应子表表名', + subFormHeight: '

一对一子表表单高度', + submitFlowFlag: '

是否提交流程状态', + subTableHeight: '

一对多子表表格高度', + tableName: '

当前表名', + triggleChangeValues: ' 修改多个表单值', + triggleChangeValue: ' 修改表单值', + updateSchema: ' 修改表单控件配置', + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8350】js增强根据主表限制子表options + changeSubTableOptions: ' 改变一对多子表下拉框选项', + changeSubFormbleOptions: ' 改变一对一子表下拉框选项', + // update-end--author:liaozhiyang---date:20240313---for:【QQYUN-8350】js增强根据主表限制子表options + // update-begin--author:liaozhiyang---date:20240321---for:【QQYUN-5806】js增强改变下拉搜索options + changeRemoteOptions:' 改变远程下拉框选项', + // update-end--author:liaozhiyang---date:20240321---for:【QQYUN-5806】js增强改变下拉搜索options + // update-begin--author:liaozhiyang---date:20240705---for:【TV360X-1754】js增强-提交表单并且发起流程 + submitFormAndFlow: ' 提交表单且发起流程', + // update-end--author:liaozhiyang---date:20240705---for:【TV360X-1754】js增强-提交表单并且发起流程 + }; + const onlineFormContext = new Proxy(CONTEXT_DESCRIPTION, { + get(_target: any, prop: string): any { + return Reflect.get(that, prop); + }, + }); + + function addObject2Context(prop, object) { + that[prop] = object; + } + + function resetContext(context) { + Object.keys(context).map((k) => { + that[k] = context[k]; + }); + } + addObject2Context('$nextTick', nextTick); + addObject2Context('addObject2Context', addObject2Context); + + // 自定义按钮 + const createBIButtonCfg = (btnKey, defBtn) => computed(() => { + const {buttonSwitch} = props + const cfg = { + enabled: true, + buttonIcon: defBtn[0], + buttonName: defBtn[1], + } + if (buttonSwitch?.[btnKey] === false) { + cfg.enabled = false + return cfg + } + const {cgBIBtnMap} = props + return cgBIBtnMap?.[btnKey] ? cgBIBtnMap[btnKey] : cfg + }) + + const getSubAddBtnCfg = createBIButtonCfg('form_sub_add', ['ant-design:plus-outlined', '新增']) + const getSubRemoveBtnCfg = createBIButtonCfg('form_sub_batch_delete', ['ant-design:minus-outlined', '删除']) + const getSubOpenAddBtnCfg = createBIButtonCfg('form_sub_open_add', ['ant-design:expand-alt-outlined', '新增']) + const getSubOpenEditBtnCfg = createBIButtonCfg('form_sub_open_edit', ['ant-design:form-outlined', '']) + + return { + onlineFormContext, + addObject2Context, + resetContext, + + getSubAddBtnCfg, + getSubRemoveBtnCfg, + getSubOpenAddBtnCfg, + getSubOpenEditBtnCfg, + }; +} + +/** + * 找联动组件 + * @param properties + */ +export function handleLinkDown(item, field) { + const { + config: { table, key, txt, linkField, idField, pidField, condition }, + others, + order, + title, + } = item; + let commonProp = { + dictTable: table, + dictText: txt, + dictCode: key, + pidField: pidField, + idField: idField, + view: LINK_DOWN, + type: item.type, + }; + let array: any = []; + let main = { + key: field, + title, + order, + condition, + origin: true, + ...commonProp, + }; + + if (linkField && linkField.length > 0) { + let fields = linkField.split(','); + main['next'] = fields[0]; + for (let i = 0; i < fields.length; i++) { + for (let o of others) { + if (o.field == fields[i]) { + let temp = { + key: o.field, + title: o.title, + order: o.order, + origin: false, + ...commonProp, + }; + if (i + 1 < fields.length) { + temp['next'] = fields[i + 1]; + } + array.push(temp); + } + } + } + } + array.push(main); + //let ls = linkDownList.value + // ls.push(...array) + // linkDownList.value = ls; + return array; +} + +/** + * 获取 字段的索引 + * @param arr + * @param key + */ +export function getFieldIndex(arr: IFormSchema[], key: string) { + let index = -1; + for (let i = 0; i < arr.length; i++) { + let item = arr[i]; + if (item.field === key) { + index = i; + break; + } + } + return index; +} + +/** + * 轮询获取 ref 对象的值,获取为true或是真实存在 就执行下一步逻辑,可用于判断状态或组件的加载是否完成 + * @param componentRef + */ +export function getRefPromise(componentRef) { + return new Promise((resolve) => { + (function next() { + let ref = componentRef.value; + if (ref) { + resolve(ref); + } else { + setTimeout(() => { + next(); + }, 100); + } + })(); + }); +} + +function clearObj(obj) { + Object.keys(obj).map((k) => { + delete obj[k]; + }); +} + +//update-begin-author:taoyan date:2022-6-1 for: VUEN-1162 子表按钮没控制 +/** + * 记录子表按钮权限-隐藏的按钮编码 + */ +const permissionStore = usePermissionStore(); +function handleSubTableButtonAuth(tableName, item) { + let arr = item.hideButtons; + let code = ONL_AUTH_PRE + tableName + ':'; + if (!arr) { + arr = []; + } + permissionStore.setOnlineSubTableAuth(code, arr); +} +//update-end-author:taoyan date:2022-6-1 for: VUEN-1162 子表按钮没控制 + + + +/** + * 获取 DetailFormSchema[online用] + */ +export function getDetailFormSchemas(props) { + const detailFormSchemas = ref([]); + const refMap = {}; + const showStatus = reactive({ + + }); + const hasSubTable = ref(false); + const subTabInfo = ref([]); + const subDataSource = ref({}); + const { getIsMobile } = useAppInject(); + const formSpan = computed(() => { + let temp = props.formTemplate; + // update-begin--author:liaozhiyang---date:20240522---for:【TV360X-82】详情页移动端只显示一列 + if (getIsMobile.value) { + return 24; + } + // update-end--author:liaozhiyang---date:20240522---for:【TV360X-82】详情页移动端只显示一列 + if (temp == '2') { + return 12; + } else if (temp == '3') { + return 8; + } else if (temp == '4') { + return 6; + } else { + return 24; + } + }); + + function createFormSchemas(properties: any[]) { + //let properties:any[] = result.schema.properties + let subInfo: OnlSubTab[] = []; + console.log('111', properties); + let arr: DetailFormSchema[] = []; + let dataSourceObj = {}; + // update-begin--author:liaozhiyang---date:20260413---for:【QQYUN-14951】一对一他表字段详情没值 + const tableLinkInfo: any = {}; + // update-end--author:liaozhiyang---date:20260413---for:【QQYUN-14951】一对一他表字段详情没值 + Object.keys(properties).map((key) => { + const item = properties[key]; + // uiSchema 无用 + //const uiItem = this.uiSchema[key];// method、formTemplate、url + if (item.view == 'tab') { + hasSubTable.value = true; + let temp: OnlSubTab = { + key, + // 这个foreignKey是主表的字段 + foreignKey: item['foreignKey'], + describe: item.describe, + relationType: item.relationType, + requiredFields: item.required || [], + order: item.order, + }; + if (item.relationType == 1) { + refMap[key] = ref(null); + temp['properties'] = item.properties; + } else { + dealSubProerties(item); + refMap[key] = ref(); + temp['columns'] = item.columns; + dataSourceObj[key] = []; + showStatus[key] = false; + } + subInfo.push(temp); + } else { + if (item.view === LINK_DOWN) { + let array = handleLinkDown(item, key); + for (let linkDownItem of array) { + let tempIndex = getFieldIndex(arr, linkDownItem.key); + let temp = { + field: linkDownItem.key, + label: linkDownItem.title, + view: linkDownItem.view, + order: linkDownItem.order, + dictTable: linkDownItem.dictTable, + linkField: linkDownItem.linkField||'', + }; + if (tempIndex == -1) { + arr.push(temp); + } else { + arr[tempIndex] = temp; + } + } + } else if (item.view == 'hidden') { + //隐藏的不处理 + } else { + let tempIndex = getFieldIndex(arr, key); + if (tempIndex == -1) { + let temp = Object.assign( + { + field: key, + label: item.title, + }, + pick(item, ['view', 'order', 'fieldExtendJson', 'dictTable', 'dictText', 'dictCode', 'dict']) + ); + if (item.view == 'file') { + temp['span'] = 24; + temp['isFile'] = true; + } + if (item.view == 'image') { + temp['span'] = 24; + temp['isImage'] = true; + } + if (item.view == 'link_table') { + // 判断是不是卡片 + if(item.fieldExtendJson){ + try{ + let json = JSON.parse(item.fieldExtendJson); + if(json.showType!='select'){ + // temp['span'] = 24; + temp['isCard'] = true; + } + if(json.multiSelect==true){ + temp['multi'] = true; + } + }catch (e) { + console.error('解析json字符串出错', item.fieldExtendJson) + } + } + } + if (item.view == 'umeditor' || item.view == 'markdown') { + temp['isHtml'] = true; + temp['span'] = 24; + } + arr.push(temp); + // update-begin--author:liaozhiyang---date:20260413---for:【QQYUN-14951】一对一他表字段详情没值 + if (item.view === 'link_table_field') { + if (!tableLinkInfo[item.dictTable]) { + tableLinkInfo[item.dictTable] = []; + } + tableLinkInfo[item.dictTable].push(`${key},${item.dictText}`); + } + // update-end--author:liaozhiyang---date:20260413---for:【QQYUN-14951】一对一他表字段详情没值 + } + } + } + }); + // 1.对arr排序 + arr.sort(function (a, b) { + return a.order - b.order; + }); + // update-begin--author:liaozhiyang---date:20260413---for:【QQYUN-14951】一对一他表字段详情没值 + arr.forEach((item) => { + if (item.view === 'link_table' && tableLinkInfo[item.field]) { + item['linkFields'] = tableLinkInfo[item.field]; + } + }); + // update-end--author:liaozhiyang---date:20260413---for:【QQYUN-14951】一对一他表字段详情没值 + // 2.对子表排序 + subInfo.sort(function (a, b) { + return a.order - b.order; + }); + subTabInfo.value = subInfo; + for (let i = 0; i < arr.length; i++) { + let temp = arr[i]; + if (temp.isFile === true || temp.isImage === true || temp.isHtml === true) { + if (i > 0) { + let last = arr[i - 1]; + let span = last.span || formSpan.value; + last.span = span; + } + } + } + detailFormSchemas.value = arr; + subDataSource.value = dataSourceObj; + console.log('adadad', arr); + } + + function dealSubProerties(subInfo) { + useOnlineVxeTableColumns(subInfo); + } + + function getFieldIndex(arr: DetailFormSchema[], key: string) { + let index = -1; + for (let i = 0; i < arr.length; i++) { + let item = arr[i]; + if (item.field === key) { + index = i; + break; + } + } + return index; + } + + function handleLinkDown(item, field){ + let all:any[] = [] + const { + config: { table, key, txt, linkField }, + order, + title, + others, + } = item; + let obj = { + table, key, txt + } + let temp = { + view: 'link_down', + order, + title, + dictTable: JSON.stringify(obj) + }; + all.push(Object.assign({}, {linkField, key: field}, temp)); + if(linkField){ + let arr = linkField.split(','); + for(let a of arr){ + let title = '' + for(let o of others){ + if(o.field==a){ + title = o.title + } + } + all.push(Object.assign({}, {key: a}, temp, {title})); + } + } + return all; + } + return { + detailFormSchemas, + hasSubTable, + subTabInfo, + refMap, + showStatus, + createFormSchemas, + formSpan, + subDataSource, + }; +} diff --git a/src/views/super/online/cgform/hooks/auto/useAutoFormDetail.ts b/src/views/super/online/cgform/hooks/auto/useAutoFormDetail.ts new file mode 100644 index 0000000..5194a0c --- /dev/null +++ b/src/views/super/online/cgform/hooks/auto/useAutoFormDetail.ts @@ -0,0 +1,34 @@ + +import { nextTick } from 'vue'; + +/*** + * 表单上下文 + */ +export function useOnlineFormDetailContext() { + const that = {}; + const CONTEXT_DESCRIPTION = { + setFieldsValue: ' 设置表单控件的值', + getFieldsValue: ' 获取表单控件的值', + sh: '

表单控件的显示隐藏状态', + isUpdate: '

判断是否为编辑模式', + isDetail: '

判断是否为详情模式', + }; + const onlineFormDetailContext = new Proxy(CONTEXT_DESCRIPTION, { + get(_target: any, prop: string): any { + return Reflect.get(that, prop); + }, + }); + + function addObject2Context(prop, object) { + that[prop] = object; + } + + function resetContext(context) { + Object.keys(context).map((k) => { + that[k] = context[k]; + }); + } + addObject2Context('$nextTick', nextTick); + addObject2Context('addObject2Context', addObject2Context); + return { onlineFormDetailContext, addObject2Context, resetContext }; +} diff --git a/src/views/super/online/cgform/hooks/auto/useAutoModal.ts b/src/views/super/online/cgform/hooks/auto/useAutoModal.ts new file mode 100644 index 0000000..242b6f9 --- /dev/null +++ b/src/views/super/online/cgform/hooks/auto/useAutoModal.ts @@ -0,0 +1,377 @@ +import { useModalInner } from '/@/components/Modal'; +import { computed, nextTick, reactive, ref, unref } from 'vue'; +import { getRefPromise } from '../../hooks/auto/useAutoForm'; +import { defHttp } from '/@/utils/http/axios'; +import {ONL_FORM_TABLE_NAME} from '../../types/onlineRender' +import { useAppInject } from '/@/hooks/web/useAppInject'; +/** + * 创建online表单弹窗用 + */ +export function useAutoModal(isBpm?: boolean, { emit } = {} as any, callback?:any) { + const onlineFormCompRef = ref(null); + // 是否隐藏确认按钮 + const disableSubmit = ref(false); + //表单风格 1列 2列 3列 决定了弹框的宽度 + const formTemplate = ref(1); + // 自定义按钮 + const cgButtonList = ref([]); + // js增强 + //const enhanceJsObject = ref('') + // 表单是否渲染完成 + const formRendered = ref(false); + // 表单弹框最小宽度 取决于扩展配置 + const modalMinWidth = ref(0); + // 判断是否是树的表单- 【VUEN-1056 15、严重——online树表单,添加的时候,父亲节点是空的】 + const isTreeForm = ref(false); + // 如果是树,父id字段对应的字段名-【VUEN-1056 15、严重——online树表单,添加的时候,父亲节点是空的】 + const pidFieldName = ref(''); + // VUEN-1105 表单 确定按钮可多次点击,保存多条数据 + const submitLoading = ref(false); + // 是否是修改页面 + const isUpdate = ref(false); + // 是否是单表 + const single = ref(true); + // extConfigJson + const extConfigJson = reactive({}); + // 显示子表- 详情页面modal 也会调用此hook,给详情页面用 + const showSub = ref(true); + const customTitle = ref('') + // 表单保存完后是否关闭modal + const successThenClose = ref(true); + // 提示是否保存 + const topTipVisible = ref(false) + // 弹窗高度控制 + const { popModalFixedWidth, resetBodyStyle, popBodyStyle } = useFixedHeightModal(); + // 没有编辑权限: 默认false + const FORM_DISABLE_UPDATE = ref(false); + // 主题模板类型 + const themeTemplate = ref(''); + + const { getIsMobile } = useAppInject(); + const modalObject = { + handleOpenModal: (_data) => {}, + }; + + //评论区域参数 + const tableId = ref('') + const tableName = ref('') + const formDataId = ref('') + const enableComment = ref(false); + let onlineExtConfig = {} + + //弹框标题 + const title = computed(() => { + let temp = customTitle.value; + if(temp){ + return temp; + } + if (unref(disableSubmit) === true) { + return '详情'; + } + if (unref(isUpdate) === true) { + return '编辑'; + } + return '新增'; + }); + + // 弹框显示 触发onlineFormCompRef---show + const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { + customTitle.value = ''; + topTipVisible.value = false; + if (isBpm === true) { + await modalObject.handleOpenModal(data); + } else { + await handleOpenOnlineModal(data); + } + // 调整modal宽高 + resetBodyStyle(); + if(callback){ + callback(); + } + }); + + /** + * 用于控制关联记录的字段 在列表上的弹窗权限,若无编辑权限,只能打开详情页面 + */ + const loadItemSuccess = ref(false); + async function getFormStatus(){ + await getRefPromise(loadItemSuccess); + return FORM_DISABLE_UPDATE.value; + } + + + async function handleOpenOnlineModal(data) { + setModalProps({ confirmLoading: false }); + isUpdate.value = data.isUpdate; + disableSubmit.value = data.disableSubmit || false; + // href跳转的不需要查看子表 + if(data?.hideSub===true){ + showSub.value = false; + } + // 设置标题,支持从传入参数读取 + if(data?.title){ + customTitle.value = data.title; + } + if(data?.record){ + formDataId.value = data.record.id; + }else{ + formDataId.value = '' + } + await nextTick(async () => { + await getRefPromise(formRendered); + + //必须等formRendered之后才能调用 + handleCommentConfig(); + await onlineFormCompRef.value.show(data?.isUpdate, data?.record, data?.param); + }); + } + + // 渲染完成改变状态 + function renderSuccess(extConfig) { + formRendered.value = true; + modalMinWidth.value = extConfig.modalMinWidth; + if (extConfig.modelFullscreen == 1) { + //如果全屏 + setModalProps({ defaultFullscreen: true }); + } else { + setModalProps({ defaultFullscreen: false }); + } + onlineExtConfig = extConfig + // update-begin--author:liaozhiyang---date:20230327---for:【QQYUN-8644】移动端效果关闭聊天窗口(详情页) + if (getIsMobile.value) { + onlineExtConfig['commentStatus'] = 0; + } + // update-end--author:liaozhiyang---date:20230327---for:【QQYUN-8644】移动端效果关闭聊天窗口(详情页) + } + + // 评论区域相关配置 + function handleCommentConfig(){ + let dataIdValue = formDataId.value; + //如果有评论配置 且 编辑/详情页面 才需要开启评论 + if(onlineExtConfig['commentStatus'] == 1 && dataIdValue){ + enableComment.value = true; + setModalProps({ defaultFullscreen: true }); + }else{ + enableComment.value = false; + } + } + + const singleWidth = 800; + const one2ManyWidth = 1100; + const modalWidth = computed(() => { + // 不同的列数展示不同的宽度 + let diff = 200 * (formTemplate.value - 1); + // 基值加上阈值 + let width = (!unref(single) ? one2ManyWidth : singleWidth) + diff; + // 文本太长时,会遮挡页面【issues/I44F0R】 + width = calcModalMixWidth(width); + let minWidth = modalMinWidth.value; + console.log({ minWidth }); + //判断计算出来的宽度 是不是比扩展配置中的宽度参数小 如果是取扩展配置的参数 + if (minWidth && width < minWidth) { + width = minWidth; + } + console.log({ width }); + return width; + }); + + /** 计算弹窗最小宽度 */ + function calcModalMixWidth(width) { + let minWidth = extConfigJson.modalMinWidth; + if (minWidth != null && minWidth !== '') { + try { + minWidth = Number.parseInt(minWidth); + if (width < minWidth) { + return minWidth; + } + } catch { + console.warn('error modalMinWidth value: ', minWidth); + } + } + return width; + } + + // 自定义按钮 增强触发事件 + function handleCgButtonClick(optType, buttonCode) { + onlineFormCompRef.value.handleCgButtonClick(optType, buttonCode); + } + + function handleSubmit() { + //update-begin-author:taoyan date:2022-5-25 for: VUEN-1105 表单 确定按钮可多次点击,保存多条数据 + submitLoading.value = true; + setTimeout(() => { + submitLoading.value = false; + }, 1500); + //update-end-author:taoyan date:2022-5-25 for: VUEN-1105 表单 确定按钮可多次点击,保存多条数据 + onlineFormCompRef.value.handleSubmit(); + } + + function handleCancel() { + closeModal(); + } + + function loadFormItems(id, params={}) { + let url = `/online/cgform/api/getFormItem/${id}`; + return new Promise((resolve, reject) => { + defHttp + .get({ url, params }, { isTransformResponse: false }) + .then((res) => { + console.log('表单结果》》modal:', res); + if (res.success) { + resolve(res.result); + } else { + reject(res.message); + } + }) + .catch(() => { + reject(); + }); + }); + } + + async function handleFormConfig(id, params, callBack?, taskId?, currentTableName?) { + // -update-begin--author:liaozhiyang---date:20240613---for:【TV360X-1000】流程一对多走流程的接口 + let result: any = null; + if (taskId && currentTableName) { + const url = `/online/cgform/api/getFormItemBytbname/${currentTableName}`; + const params = { taskId }; + result = await defHttp.get({ url, params }); + } else { + result = await loadFormItems(id, params); + } + // -update-end--author:liaozhiyang---date:20240613---for:【TV360X-1000】流程一对多走流程的接口 + // modal页面只处理按钮、JS增强、弹框宽度 + let dataFormTemplate = result.head.formTemplate; + formTemplate.value = dataFormTemplate ? Number(dataFormTemplate) : 1; + cgButtonList.value = result.cgButtonList; + isTreeForm.value = result.head.isTree === 'Y'; + pidFieldName.value = result.head.treeParentIdField || ''; + tableId.value = result.head.id; + tableName.value = result.head.tableName; + themeTemplate.value = result.head.themeTemplate; + //enhanceJsObject.value = initCgEnhanceJs(result.enhanceJs) + if(result['form_disable_update']===true){ + FORM_DISABLE_UPDATE.value = true + }else{ + FORM_DISABLE_UPDATE.value = false; + } + loadItemSuccess.value = true; + emit && emit('formConfig', result); + // -update-begin--author:liaozhiyang---date:20230823---for:【QQYUN-6305】tab主题一对多-- + callBack && callBack(result); + // -update-end--author:liaozhiyang---date:20230823---for:【QQYUN-6305】tab主题一对多-- + await nextTick(async () => { + let myForm = (await getRefPromise(onlineFormCompRef)) as any; + await myForm.createRootProperties(result); + }); + } + + /** + * 表单保存完后的事件 + */ + function handleSuccess(formData) { + // 将表名设置到数据中 + formData[ONL_FORM_TABLE_NAME] = tableName.value; + emit('success', formData); + if(successThenClose.value == true){ + closeModal(); + }else{ + // 不关闭弹窗 提示成功 + } + //恢复默认值 + topTipVisible.value = false; + successThenClose.value = true; + } + + /** + * modal关闭事件 + */ + function onCloseEvent(){ + if(onlineFormCompRef.value){ + onlineFormCompRef.value.onCloseModal(); + } + // update-begin--author:liaozhiyang---date:20240618---for:【TV360X-1305】打开评论编辑弹窗会全屏,关闭弹窗时把全屏去掉,否者会影响新增弹窗 + if (isUpdate.value) { + const extConfig: any = onlineExtConfig ?? {}; + if (extConfig.commentStatus == 1) { + setModalProps({ defaultFullscreen: false }); + } + } + // update-end--author:liaozhiyang---date:20240618---for:【TV360X-1305】打开评论编辑弹窗会全屏,关闭弹窗时把全屏去掉,否者会影响新增弹窗 + } + + return { + // modal + title, + modalWidth, + registerModal, + closeModal, + modalObject, + onCloseEvent, + + // 自定义按钮 + cgButtonList, + handleCgButtonClick, + + // 提交/关闭按钮 + disableSubmit, + handleSubmit, + submitLoading, + handleCancel, + successThenClose, + handleSuccess, + topTipVisible, + + //表单 + handleFormConfig, + onlineFormCompRef, + formTemplate, + isTreeForm, + pidFieldName, + renderSuccess, + formRendered, + isUpdate, + showSub, + themeTemplate, + + // 评论区域参数 + tableId, + tableName, + formDataId, + enableComment, + popBodyStyle, + popModalFixedWidth, + getFormStatus + }; +} + + +/** + * 使用固定高度的modal + */ +export function useFixedHeightModal() { + const minWidth = 800; + const popModalFixedWidth = ref(800); + let tempWidth = window.innerWidth - 300; + if(tempWidth < minWidth){ + tempWidth = minWidth; + } + popModalFixedWidth.value = tempWidth; + + // 弹窗高度控制 + const popBodyStyle = ref({}); + function resetBodyStyle(){ + let height = window.innerHeight - 210; + popBodyStyle.value = { + height: height+'px', + overflowY: 'auto' + } + } + + return { + popModalFixedWidth, + popBodyStyle, + resetBodyStyle + } +} + diff --git a/src/views/super/online/cgform/hooks/auto/useCustomHook.ts b/src/views/super/online/cgform/hooks/auto/useCustomHook.ts new file mode 100644 index 0000000..ccda281 --- /dev/null +++ b/src/views/super/online/cgform/hooks/auto/useCustomHook.ts @@ -0,0 +1,110 @@ +import * as vue from 'vue'; +import * as UTIL_CACHE from '/@/utils/cache'; +import * as UTIL_AXIOS from '/@/utils/http/axios'; +import * as HOOK_MESSAGE from '/@/hooks/web/useMessage'; +import { randomString } from '/@/utils/common/compUtils'; +import * as HOOK_USERINFO from '/@/store/modules/user'; +import * as UTIL_AUTH from "/@/utils/auth"; + +// 在这里定义JS增强里可以使用的内容 +const $exports = { + vue, + '@': { + hooks: { + // 调用示例:@/hooks/useMessage + useMessage: HOOK_MESSAGE, + useUserStore: HOOK_USERINFO + }, + utils: { + // 调用示例:@/utils/axios + axios: UTIL_AXIOS, + cache: UTIL_CACHE, + auth: UTIL_AUTH, + }, + }, +}; + +/** + * 用于处理js增强中自定义的hook代码 + * 增强定义方法:useCustomHook(),建议不要使用双引号 + * 其他导出对象 + * @param otherExports + */ +export function useCustomHook(otherExports?: Recordable, context?: any) { + const assignExports = Object.assign({}, $exports, otherExports); + + /** + * 自定义 import 方法 + * @param path 引用路径 + */ + function doImport(path: string) { + if (path != null && path != '') { + let paths = path.toString().split('/'); + let result = assignExports[paths[0]]; + for (let i = 1; i < paths.length; i++) { + result = result[paths[i]]; + } + return result; + } + return null; + } + + function doExport() {} + + /** + * 执行JS增强代码 + * @param code 要执行的代码 + */ + function executeJsEnhanced(code: string, row?) { + // 为了避免方法名冲突,所以使用随机方法名 + let randomKey = randomString(6); + // let importKey = '__import_' + randomKey + //let importKey = 'customImport' + let exportKey = '__export_' + randomKey; + // 替换 import 关键字 + //code = replaceImportKey(code, importKey) + + //update-begin-author:taoyan date:2023-5-15 for: issues/516 自定义按钮_hook后的参数row未定义问题(参见#410) #516 + if(row){ + const executeCode = `return function (row, customImport, ${exportKey}) {"use strict"; ${code}}`; + console.group('executeJsEnhanced'); + console.log(executeCode); + console.groupEnd(); + const fun = new Function(executeCode)(); + fun.call(context, row, doImport, doExport); + }else{ + const executeCode = `return function (customImport, ${exportKey}) {"use strict"; ${code}}`; + console.group('executeJsEnhanced'); + console.log(executeCode); + console.groupEnd(); + const fun = new Function(executeCode)(); + fun.call(context, doImport, doExport); + } + //update-end-author:taoyan date:2023-5-15 for: issues/516 自定义按钮_hook后的参数row未定义问题(参见#410) #516 + + } + + /** + * 替换 import 关键字 + * @param code + * @param fnKey import 方法的key + */ + /* function replaceImportKey(code: string, fnKey: string) { + let lines = code.split('\n') + for (let i = 0; i < lines.length; i++) { + let line = lines[i].trim() + if (line.startsWith('import ')) { + let regexp = /import (.*) from (.*)/g + lines[i] = line.replace(regexp, `const $1 = ${fnKey}($2)`) + } + } + return lines.join('\n') + }*/ + + return { + executeJsEnhanced, + }; +} + +/**获取函数体的内容作为字符串*/ +export const GET_FUN_BODY_REG = /(?:\/\*[\s\S]*?\*\/|\/\/.*?\r?\n|[^{])+\{([\s\S]*)\}$/; diff --git a/src/views/super/online/cgform/hooks/auto/useEnhance.ts b/src/views/super/online/cgform/hooks/auto/useEnhance.ts new file mode 100644 index 0000000..e1e28b0 --- /dev/null +++ b/src/views/super/online/cgform/hooks/auto/useEnhance.ts @@ -0,0 +1,142 @@ +/** + * js增强 + */ +import { reactive } from 'vue'; +import { defHttp } from '/@/utils/http/axios'; +import { _eval } from '/@/utils'; +import { useMessage } from '/@/hooks/web/useMessage'; + +export function useEnhance(onlineTableContext, isList = true) { + let EnhanceJS = reactive({}); + + const getAction = (url, params) => { + return defHttp.get({ url: url, params }, { isTransformResponse: false }); + }; + + const postAction = (url, params) => { + return defHttp.post({ url: url, params }, { isTransformResponse: false }); + }; + + const putAction = (url, params) => { + return defHttp.put({ url: url, params }, { isTransformResponse: false }); + }; + + const deleteAction = (url, params) => { + return defHttp.delete({ url: url, params }, { isTransformResponse: false }); + }; + + if (isList === true) { + onlineTableContext['_getAction'] = getAction; + onlineTableContext['_postAction'] = postAction; + onlineTableContext['_putAction'] = putAction; + onlineTableContext['_deleteAction'] = deleteAction; + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8342】js增强提供useMessage方法 + onlineTableContext['_useMessage'] = useMessage; + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8342】js增强提供useMessage方法 + } else { + onlineTableContext.addObject2Context('_getAction', getAction); + onlineTableContext.addObject2Context('_postAction', postAction); + onlineTableContext.addObject2Context('_putAction', putAction); + onlineTableContext.addObject2Context('_deleteAction', deleteAction); + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8342】js增强提供useMessage方法 + onlineTableContext.addObject2Context('_useMessage', useMessage); + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8342】js增强提供useMessage方法 + } + + /** + * 初始化 + * @param str (res.result.enhanceJs) + */ + function initCgEnhanceJs(str: string) { + //console.log("--onlineList-js增强"+isList,str) + if (str) { + // update-begin--author:liaozhiyang---date:20240517---for:【TV360X-338】js增强代码报错不能影响页面渲染 + let Obj: any; + let result; + try { + // update-begin--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + Obj = _eval(str); + // update-end--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + result = new Obj(getAction, postAction, deleteAction); + //return new Function(str)(getAction,postAction,deleteAction); + } catch (error) { + result = {}; + const { createMessage } = useMessage(); + createMessage.warning(`js增强代码有语法错误,请检查代码~ ${error}`); + } + return result; + // update-end--author:liaozhiyang---date:20240517---for:【TV360X-338】js增强代码报错不能影响页面渲染 + } else { + return {}; + } + } + + /** + * 【】 + * 触发js增强方法 + * @param that + * @param formData + */ + function triggerJsFun(that, buttonCode) { + if (EnhanceJS && EnhanceJS[buttonCode]) { + EnhanceJS[buttonCode](that); + } + } + + /** + * 【表单】 + * 处理js增强 自定义 提交前事件 + * @param that + * @param formData + */ + function customBeforeSubmit(that, formData) { + if (EnhanceJS && EnhanceJS['beforeSubmit']) { + return EnhanceJS['beforeSubmit'](that, formData); + } else { + return Promise.resolve(); + } + } + + /** + * 删除前业务处理 + * @param that + * @param record + */ + function beforeDelete(that, record) { + if (EnhanceJS && EnhanceJS['beforeDelete']) { + return EnhanceJS['beforeDelete'](that, record); + } else { + return Promise.resolve(); + } + } + + if (isList === true) { + if (onlineTableContext) { + onlineTableContext['beforeDelete'] = (record) => { + const onlEnhanceJS = onlineTableContext['EnhanceJS']; + if (onlEnhanceJS && onlEnhanceJS['beforeDelete']) { + return onlEnhanceJS['beforeDelete'](onlineTableContext, record); + } else { + return Promise.resolve(); + } + }; + + onlineTableContext['beforeEdit'] = (record) => { + const onlEnhanceJS = onlineTableContext['EnhanceJS']; + if (onlEnhanceJS && onlEnhanceJS['beforeEdit']) { + return onlEnhanceJS['beforeEdit'](onlineTableContext, record); + } else { + return Promise.resolve(); + } + }; + } + } + + return { + EnhanceJS, + initCgEnhanceJs, + customBeforeSubmit, + beforeDelete, + triggerJsFun, + }; +} diff --git a/src/views/super/online/cgform/hooks/auto/useExtendComponent.ts b/src/views/super/online/cgform/hooks/auto/useExtendComponent.ts new file mode 100644 index 0000000..fe78b57 --- /dev/null +++ b/src/views/super/online/cgform/hooks/auto/useExtendComponent.ts @@ -0,0 +1,41 @@ +import {add} from "/@/components/Form/src/componentMap"; +import LinkTableSelect from '../../extend/linkTable/LinkTableSelect.vue'; +import LinkTableCard from '../../extend/linkTable/LinkTableCard.vue'; +import OnlineSelectCascade from '../../auto/comp/OnlineSelectCascade.vue'; + +const componentKeyMap = {}; + +/** + * 用于往form中添加组件 + */ +export function useExtendComponent() { + + addComponent('OnlineSelectCascade', OnlineSelectCascade); + addComponent('LinkTableSelect', LinkTableSelect); + addComponent('LinkTableCard', LinkTableCard); + + /** + * 避免重复添加 + */ + function addComponent(key, comp) { + if(!componentKeyMap[key]){ + add(key, comp) + componentKeyMap[key] = 1; + } + } + + /** + * 关联记录的查询控件不宜用卡片模式 统一使用下拉模式 + */ + function linkTableCard2Select(schema) { + if("LinkTableCard"==schema.component){ + schema.component = 'LinkTableSelect'; + schema.componentProps.popContainer = 'body'; + } + } + + return { + addComponent, + linkTableCard2Select + } +} \ No newline at end of file diff --git a/src/views/super/online/cgform/hooks/auto/useFormUrl.ts b/src/views/super/online/cgform/hooks/auto/useFormUrl.ts new file mode 100644 index 0000000..04a671c --- /dev/null +++ b/src/views/super/online/cgform/hooks/auto/useFormUrl.ts @@ -0,0 +1,42 @@ +import { ref, unref } from 'vue'; +import { useRoute } from 'vue-router'; +import { useUserStore } from '/@/store/modules/user'; +import { message } from 'ant-design-vue'; + +export function useFormUrl() { + const route = useRoute(); + const token = ref(route.query.token); + const createToken = ref(''); + const userStore = useUserStore(); + if (unref(token)) { + // 校验token是否合法 + } else { + if (userStore.getToken) { + // 登录了系统弄,有了token + token.value = userStore.getToken; + } else { + // 既没登录,url也没token + getToken(); + } + } + // 没有token,通过接口去获取token,再缓存到本地并设置到url上重新渲染 + function getToken() { + const hide = message.loading('获取token中...', 0); + setTimeout(() => { + createToken.value = + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MTAwMTc2NzUsInVzZXJuYW1lIjoiYWRtaW4ifQ.0q5ywkLF144SaqumsSgEXO5ERtqaYp8jqouIUxxhELc'; + + setToken(); + setUrlToken(); + hide(); + }, 3e3); + } + function setToken() { + userStore.setToken(createToken.value); + // userStore.setTenant(1); + } + function setUrlToken() { + window.location.replace(`${route.fullPath}?token=${createToken.value}`); + } + return { token }; +} diff --git a/src/views/super/online/cgform/hooks/auto/useListButton.ts b/src/views/super/online/cgform/hooks/auto/useListButton.ts new file mode 100644 index 0000000..f301cb9 --- /dev/null +++ b/src/views/super/online/cgform/hooks/auto/useListButton.ts @@ -0,0 +1,717 @@ +import type { Ref } from 'vue'; +import type { ExtConfigType } from '../../types'; +import { computed, reactive, toRaw, ref } from 'vue'; +import { CgFormButton } from '../../types/onlineRender'; +import { pick } from 'lodash-es'; +import { useModal } from '/@/components/Modal'; +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { Modal } from 'ant-design-vue'; +import { filterObj } from '/@/utils/common/compUtils'; +import { useMethods } from '/@/hooks/system/useMethods'; +import { getToken } from '/@/utils/auth'; +import { goJmReportViewPage } from '/@/utils' +import { Tree } from '../../util/constant'; + +/**工作流编码前缀*/ +const FLOW_CODE_PRE = 'onl_'; +/** + * 负责列表页面按钮及其事件--在initAutoList之后执行,必须先获取按钮信息 + * - 这个是所有风格都通用的且必须要有的 + */ +export function useListButton(onlineTableContext, extConfigJson: Ref, extraParameter = {}) { + const buttonStatus = { + add: true, + addSub: true, + // edit = 编辑按钮的code + edit: true, + // update = 编辑按钮的老code + update: true, + delete: true, + batch_delete: true, + import: true, + export: true, + detail: true, + query: true, + reset: true, + super_query: true, + bpm: true, + form_confirm: true, + // 子表新增 + form_sub_add: true, + // 子表删除 + form_sub_batch_delete: true, + // 子表新增 + form_sub_open_add: true, + // 子表编辑 + form_sub_open_edit: true, + // 生成测试数据 + aigc_mock_data: true, + }; + + // 弹框事件 + const [registerModal, { openModal }] = useModal(); + const [registerImportModal, { openModal: openImportModal }] = useModal(); + const [registerDetailModal, { openModal: openDetailModal }] = useModal(); + const [registerBpmModal, { openModal: openBpmModal }] = useModal(); + const { createMessage: $message } = useMessage(); + + // 按钮相关 + const buttonSwitch = reactive(buttonStatus); + const cgLinkButtonList = reactive([]); + const cgTopButtonList = reactive([]); + + interface CgBIBtnType extends CgFormButton { + enabled: boolean, + } + + // Online表单内置按钮列表 + const cgBIBtnMap = reactive>({}); + + // 创建内置按钮配置(用于控制按钮权限) + const createBIButtonCfg = (btnKey: string) => computed(() => buttonSwitch[btnKey] === true ? cgBIBtnMap[btnKey] : {enabled: false}) + // 查询按钮配置 + const getQueryButtonCfg = createBIButtonCfg('query') + // 重置按钮配置 + const getResetButtonCfg = createBIButtonCfg('reset') + // 表单弹窗的确定按钮配置 + const getFormConfirmButtonCfg = createBIButtonCfg('form_confirm') + + const testDataLoading = ref(false); + const testDataBtnShow = ref(true); + setTimeout(() => { + testDataBtnShow.value = false; + }, 4e3); + /** + * 根据配置获取 button和link + */ + function initButtonList(btnList) { + cgLinkButtonList.length = 0; + cgTopButtonList.length = 0; + if (btnList && btnList.length > 0) { + for (let i = 0; i < btnList.length; i++) { + let temp = pick(btnList[i], 'buttonCode', 'buttonName', 'buttonStyle', 'optType', 'exp', 'buttonIcon', 'buttonStatus', 'enabled'); + if (temp.buttonStyle == 'button') { + cgTopButtonList.push(temp); + } else if (temp.buttonStyle == 'link') { + cgLinkButtonList.push(temp); + } else if (temp.buttonStyle == 'built-in') { + if (temp.buttonIcon) { + temp.buttonIcon = 'ant-design:' + temp.buttonIcon; + } + temp.enabled = temp.buttonStatus === '1' + cgBIBtnMap[temp.buttonCode] = temp; + } + } + } + } + + /** + * 根据配置设置按钮的 显示/隐藏 状态 + */ + function initButtonSwitch(hideColumns) { + Object.keys(buttonSwitch).forEach((key) => { + buttonSwitch[key] = true; + }); + if (hideColumns && hideColumns.length > 0) { + Object.keys(buttonSwitch).forEach((key) => { + if (hideColumns.indexOf(key) >= 0) { + buttonSwitch[key] = false; + } + }); + } + } + + // 增加事件 + function handleAdd(param) { + let data = { isUpdate: false }; + if (param) { + data['param'] = param; + } + openModal(true, data); + } + + // 修改事件 + function handleEdit(record) { + onlineTableContext + .beforeEdit(record) + .then(() => { + openModal(true, { + isUpdate: true, + record, + }); + }) + .catch((msg) => { + $message.warning(msg); + }); + } + + /** + * + * [更多]下拉项中的 [删除] + */ + const getDeleteButton = (record) => { + return { + label: cgBIBtnMap['delete'].buttonName, + ifShow: () => cgBIBtnMap['delete'].enabled, + popConfirm: { + title: '是否删除?', + confirm: handleDeleteOne.bind(null, record), + }, + }; + }; + + // 删除事件 + function handleDeleteOne(record) { + onlineTableContext + .beforeDelete(record) + .then(() => { + handleDelete(record.id, false); + }) + .catch((msg) => { + $message.warning(msg); + }); + } + + /** + * 操作列定义 + * @param record + */ + function getActions(record) { + //update-begin-author:taoyan date:2022-10-17 for: VUEN-2351【vue3 online表单】online表单 发起流程后,仍然可以编辑数据 + let bpmStatusValue = getBpmStatusValue(record); + // 允许编辑的情况--> bpm有值且值为1,bpm没有值, + //update-begin-author:taoyan date:2023-2-6 for: QQYUN-4135【online】审批完成的流程和取回作废的流程,可以编辑 + let canEdit = (bpmStatusValue && (bpmStatusValue=='1' || bpmStatusValue=='3' || bpmStatusValue=='4')) || !bpmStatusValue; + //update-end-author:taoyan date:2023-2-6 for: QQYUN-4135【online】审批完成的流程和取回作废的流程,可以编辑 + if ((toRaw(buttonSwitch.edit) === true && toRaw(buttonSwitch.update) === true) && canEdit) { + //update-end-author:taoyan date:2022-10-17 for: VUEN-2351【vue3 online表单】online表单 发起流程后,仍然可以编辑数据 + return [ + { + label: cgBIBtnMap['edit'].buttonName, + ifShow: () => cgBIBtnMap['edit'].enabled, + onClick: (e) => { + // update-begin--author:liaozhiyang---date:20231128---for:【QQYUN-7260】erp主表编辑时保存子表记录 + // erp主表点击编辑时需要防止当前选中数据反选 + extraParameter['editClickCallback'] && extraParameter['editClickCallback'](record.id, e); + handleEdit(record); + // update-end--author:liaozhiyang---date:20231128---for:【QQYUN-7260】erp主表编辑时保存子表记录 + }, + }, + ]; + } + return []; + } + + /** + * 操作列[提交流程] + */ + function getSubmitFlowButton(record) { + return { + label: cgBIBtnMap['bpm'].buttonName, + ifShow: () => cgBIBtnMap['bpm'].enabled, + popConfirm: { + title: '确认提交流程吗?', + confirm: handleSubmitFlow.bind(null, record), + }, + }; + } + + /** + * 操作列[审批进度] + * @param record + */ + function getViewBpmGraphicButton(record){ + return { + label: '审批进度', + onClick: handleViewGraphic.bind(null, record), + }; + } + + /** + * 查看流程图 + * @param record + */ + function handleViewGraphic(record){ + const { currentTableName } = onlineTableContext; + + //判断 currentTableName如果是视图,截掉后缀 + let currentTableNameVariable = currentTableName; + if (currentTableName.includes('$')) { + currentTableNameVariable = currentTableName.split('$')[0]; + } + + let flowCode = FLOW_CODE_PRE + currentTableNameVariable; + let dataId = record.id; + openBpmModal(true, { + flowCode, + dataId + }) + } + + /** + * 操作列[更多下拉项] + */ + function getDropDownActions(record, params = {} ) { + let arr: any = []; + if (toRaw(buttonSwitch.detail) === true) { + arr.push({ + label: cgBIBtnMap['detail'].buttonName, + ifShow: () => cgBIBtnMap['detail'].enabled, + onClick: handleDetail.bind(null, record), + }); + } + // update-begin--author:liaozhiyang---date:20240724---for:【TV360X-1101】树表放开提交流程按钮且加上审批详情弹窗 + if (onlineTableContext['hasBpmStatus'] === true && toRaw(buttonSwitch.bpm) === true) { + // 提交流程按钮显示条件: 有bpm_status字段,并且有操作bpm按钮的权限 + let bpmStatusValue = getBpmStatusValue(record); + if (!bpmStatusValue || bpmStatusValue == '1') { + //并且 bpm_status的值为1 或者为空 + arr.push(getSubmitFlowButton(record)); + }else{ + arr.push(getViewBpmGraphicButton(record)) + } + } + // update-end--author:liaozhiyang---date:20240724---for:【TV360X-1101】树表放开提交流程按钮且加上审批详情弹窗 + // 对接积木报表打印 + if (extConfigJson.value) { + let { reportPrintShow, reportPrintUrl } = extConfigJson.value; + if (reportPrintShow && reportPrintUrl) { + arr.push({ + label: '打印', + onClick() { + //跳转至积木报表页面 + let url = reportPrintUrl; + let id = record.id; + let token = getToken(); + goJmReportViewPage(url, id, token); + }, + }); + } + } + //update-begin-author:taoyan date:2022-10-17 for: VUEN-2351【vue3 online表单】online表单 发起流程后,仍然可以编辑数据 + // 允许删除的情况--> bpm有值且值为1,bpm没有值 + let bpmStatusValue = getBpmStatusValue(record); + let canDelete = (bpmStatusValue && bpmStatusValue=='1') || !bpmStatusValue; + if (toRaw(buttonSwitch.delete) === true && canDelete) { + //update-end-author:taoyan date:2022-10-17 for: VUEN-2351【vue3 online表单】online表单 发起流程后,仍然可以编辑数据 + arr.push(getDeleteButton(record)); + } + let buttonList = cgLinkButtonList; + if (buttonList && buttonList.length > 0) { + for (let item of buttonList) { + if (showLinkButtonOfExpression(item.exp || '', record) === true) { + arr.push({ + label: item.buttonName, + onClick: cgButtonLinkHandler.bind(null, record, item.buttonCode, item.optType), + }); + } + } + } + return arr; + } + + /** + * 获取bpm_status的值 大小写都获取一遍 + * @param record + */ + function getBpmStatusValue(record) { + const key = 'bpm_status'; + let value = record[key]; + if (!value) { + value = record[key.toUpperCase()]; + } + return value; + } + + /** + * 查看详情 + * @param record + */ + function handleDetail(record) { + openDetailModal(true, { + isUpdate: true, + disableSubmit: true, + record, + }); + } + + /** + * 提交流程请求 + * @param record + */ + function startProcess(record) { + const { + currentTableName, + onlineUrl: { startProcess }, + } = onlineTableContext; + + //判断 currentTableName如果是视图,截掉后缀 + let currentTableNameVariable = currentTableName; + if (currentTableName.includes('$')) { + currentTableNameVariable = currentTableName.split('$')[0]; + } + + let postConfig = { + url: startProcess, + params: { + flowCode: FLOW_CODE_PRE + currentTableNameVariable, + id: record.id, + // TODO 流程表单没有 + formUrl: 'modules/bpm/task/form/OnlineFormDetail', + formUrlMobile: 'check/onlineForm/detail', + }, + }; + let postOption = { isTransformResponse: false }; + return new Promise((resolve, reject) => { + defHttp.post(postConfig, postOption).then((res) => { + if (res.success) { + resolve(res); + $message.success(res.message); + } else { + reject(); + $message.warning(res.message); + } + }); + }); + } + + /** + * 提交流程按钮触发事件 + * @param record + */ + async function handleSubmitFlow(record) { + await startProcess(record); + onlineTableContext.loadData(); + } + + //删除请求 + function handleDelete(dataId: String, isBatch = true) { + console.log('删除数据id值', dataId); + let url = `${onlineTableContext.onlineUrl.optPre}${onlineTableContext.ID}/${dataId}`; + // update-begin--author:liaozhiyang---date:20240428---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权删除和导出从表数据 + if (onlineTableContext['isErpSubTable'] === true) { + url = `${url}?tabletype=3`; + } + // update-end--author:liaozhiyang---date:20240428---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权删除和导出从表数据 + return new Promise((resolve, reject) => { + defHttp + .delete( + { + url, + }, + { isTransformResponse: false } + ) + .then((res) => { + if (res.success) { + $message.success(res.message); + // update-begin--author:liaozhiyang---date:20240528---for:【TV360X-206】列表删除最后一页数据,页面跳到前一页数据为空 + onlineTableContext.loadData({ delNum: dataId.split(',').length }); + // update-end--author:liaozhiyang---date:20240528---for:【TV360X-206】列表删除最后一页数据,页面跳到前一页数据为空 + // update-begin--author:liaozhiyang---date:20231128---for:【QQYUN-7260】erp主表编辑时保存子表记录 + // erp主表记录选中时被删除需要清空选中的key + if (!isBatch) { + extraParameter['singleDelCallback'] && extraParameter['singleDelCallback'](dataId); + } + // update-end--author:liaozhiyang---date:20231128---for:【QQYUN-7260】erp主表编辑时保存子表记录 + resolve(true); + } else { + $message.warning(res.message); + reject(); + } + }); + }); + } + + // 批量删除事件 + function handleBatchDelete() { + let arr = onlineTableContext['selectedRowKeys']; + if (arr.length <= 0) { + $message.warning('请选择一条记录!'); + return false; + } else { + let idSet: any = []; + arr.forEach(function (val) { + let temp = val; + //树形列表 key后面会带有_loadChild + if (temp && temp.endsWith('_loadChild')) { + temp = temp.replace('_loadChild', ''); + } + // 去重 + if (idSet.indexOf(temp) < 0) { + idSet.push(temp); + } + }); + let ids = idSet.join(','); + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: async () => { + await handleDelete(ids); + onlineTableContext.clearSelectedRow(); + }, + }); + } + } + /* + * liaozhiyang + * 20250403 + * 【QQYUN-11801】生成测试数据 + * */ + const handleAddTestData = (currentTableName, reload) => { + testDataLoading.value = true; + defHttp + .post({ url: `/online/cgform/api/aigc/mock/data/${currentTableName}`, timeout: 120000 }, { isTransformResponse: false }) + .then((res) => { + if (res.code == 200) { + $message.success('生成测试数据成功~'); + reload(); + } else { + $message.warn(res.message); + } + testDataLoading.value = false; + }) + .catch((err) => { + testDataLoading.value = false; + console.log(err); + }); + } + + /** + * 自定义按钮触发事件-link按钮 + * @param record + * @param buttonCode + * @param optType js/bus + */ + function cgButtonLinkHandler(record, buttonCode, optType) { + if (optType == 'js') { + onlineTableContext['execButtonEnhance'](buttonCode, record); + } else if (optType == 'action') { + let params = { + formId: onlineTableContext['ID'], + buttonCode: buttonCode, + dataId: record.id, + }; + //console.log("自定义按钮link请求后台参数:",params) + let url = `${onlineTableContext.onlineUrl.buttonAction}`; + defHttp + .post( + { + url, + params, + }, + { isTransformResponse: false } + ) + .then((res) => { + if (res.success) { + onlineTableContext.loadData(); + $message.success('处理完成!'); + } else { + $message.warning(res.message); + } + }); + } + } + + /** + * 列表上方按钮 -js事件 + * @param buttonCode + */ + function cgButtonJsHandler(buttonCode) { + // 待测 + onlineTableContext['execButtonEnhance'](buttonCode); + } + + /** + * 列表上方按钮 -action事件 + * @param buttonCode + */ + function cgButtonActionHandler(buttonCode) { + let arr = onlineTableContext['selectedRowKeys']; + if (!arr || arr.length == 0) { + $message.warning('请先选中一条记录'); + return false; + } + let dataId = arr.join(','); + let params = { + formId: onlineTableContext['ID'], + buttonCode: buttonCode, + dataId: dataId, + }; + //console.log("自定义按钮请求后台参数:",params) + let url = `${onlineTableContext.onlineUrl.buttonAction}`; + defHttp + .post( + { + url, + params, + }, + { isTransformResponse: false } + ) + .then((res) => { + if (res.success) { + onlineTableContext.loadData(); + onlineTableContext.clearSelectedRow(); + $message.success('处理完成!'); + } else { + $message.warning(res.message); + } + }); + } + + // 导入事件 + function onImportExcel() { + // update-begin--author:liaozhiyang---date:20240429---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权导入从表数据 + if (onlineTableContext['foreignKeyField'] && onlineTableContext['foreignKeyValue']) { + openImportModal(true, { + [onlineTableContext['foreignKeyField']]: onlineTableContext['foreignKeyValue'], + }); + } else { + openImportModal(true); + } + // update-end--author:liaozhiyang---date:20240429---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权导入从表数据 + } + + // 导入地址 + const importUrl = () => { + // update-begin--author:liaozhiyang---date:20240428---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权删除和导出从表数据 + let url = `${onlineTableContext.onlineUrl.importXls}${onlineTableContext.ID}`; + if (onlineTableContext['isErpSubTable'] === true) { + url = `${url}?tabletype=3`; + } + return url; + // update-end--author:liaozhiyang---date:20240428---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权删除和导出从表数据 + }; + + // 导出事件 + const { handleExportXlsx } = useMethods(); + function onExportExcel() { + let params = onlineTableContext.getLoadDataParams(); + let selections = onlineTableContext['selectedRowKeys']; + if (selections && selections.length > 0) { + params['selections'] = selections.join(','); + } + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + let tabletype = {}; + if (onlineTableContext['isErpSubTable'] === true) { + // update-begin--author:liaozhiyang---date:20240428---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权删除和导出从表数据 + tabletype = { tabletype: 3 }; + // update-end--author:liaozhiyang---date:20240428---for:【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权删除和导出从表数据 + if (onlineTableContext['foreignKeyField'] && onlineTableContext['foreignKeyValue']) { + params[onlineTableContext['foreignKeyField']] = onlineTableContext['foreignKeyValue']; + } + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + //console.log("导出参数",params) + let paramsStr = JSON.stringify(filterObj(params)); + let url = `${onlineTableContext.onlineUrl.exportXls}${onlineTableContext.ID}`; + const description = onlineTableContext.description; + return handleExportXlsx(description, url, { paramsStr: paramsStr, ...tabletype }); + } + + /** + * liaozhiyang + * 20231008 + * 先把自定义表单是转成布尔值,再利用new Function换算真正的规则 + */ + function multipleLinkButtonOfExpression(expression, row) { + const gather: any = []; + expression.split('||').forEach(oItem => { + const arr: any = []; + oItem + .trim() + .split('&&') + .forEach(nItem => { + arr.push(oneLinkButtonOfExpression(nItem.trim(), row)); + }); + gather.push(arr.join('&&')); + }); + const r = gather.join('||'); + console.log('---多个表达式---', r); + return new Function(`return ${r}`)(); + } + /** + * liaozhiyang + * 20231008 + * 区分有是否有表达式 + */ + function showLinkButtonOfExpression(expression, row) { + if (!expression || expression == '') { + return true; + } + if (expression.indexOf('||') == -1 && expression.indexOf('&&') == -1) { + return oneLinkButtonOfExpression(expression, row); + } else { + return multipleLinkButtonOfExpression(expression, row); + } + } + + /** + * 用于处理 link按钮的表达式 返回布尔值 + * @param expression 表达式 + * @param row 所在行的数据 + */ + function oneLinkButtonOfExpression(expression: string, row: any): boolean { + if (!expression || expression == '') { + return true; + } + // 字段名#条件#值 + let arr = expression.split('#'); + //获取字段值 + let fieldValue = row[arr[0]]; + //获取表达式 + let exp = arr[1].toLowerCase(); + //判断表达式 + if (exp === 'eq') { + return fieldValue == arr[2]; + } else if (exp === 'ne') { + return !(fieldValue == arr[2]); + } else if (exp === 'empty') { + if (arr[2] === 'true') { + return !fieldValue || fieldValue == ''; + } else { + return fieldValue && fieldValue.length > 0; + } + } else if (exp === 'in') { + let arr2 = arr[2].split(','); + return arr2.indexOf(String(fieldValue)) >= 0; + } + return false; + } + + return { + buttonSwitch, + cgLinkButtonList, + cgBIBtnMap, + getQueryButtonCfg, + getResetButtonCfg, + getFormConfirmButtonCfg, + cgTopButtonList, + importUrl, + registerModal, + handleAdd, + handleEdit, + handleBatchDelete, + handleAddTestData, + testDataLoading, + testDataBtnShow, + registerImportModal, + onImportExcel, + onExportExcel, + getDropDownActions, + getActions, + cgButtonJsHandler, + cgButtonActionHandler, + cgButtonLinkHandler, + initButtonList, + initButtonSwitch, + getDeleteButton, + handleSubmitFlow, + getSubmitFlowButton, + registerDetailModal, + registerBpmModal, + openDetailModal + }; +} diff --git a/src/views/super/online/cgform/hooks/auto/useOnlinePopEvent.ts b/src/views/super/online/cgform/hooks/auto/useOnlinePopEvent.ts new file mode 100644 index 0000000..29524e0 --- /dev/null +++ b/src/views/super/online/cgform/hooks/auto/useOnlinePopEvent.ts @@ -0,0 +1,109 @@ +import type { InjectionKey } from 'vue'; +import type { Emitter } from '/@/utils/mitt'; +import { createContext, useContext } from '/@/hooks/core/useContext'; +import mitt from '/@/utils/mitt'; +import {onMounted, onUnmounted} from 'vue' + +export interface OnlineEmitterContextProps { + /*activeName?: 'onlineEvent'*/ + onlineEmitter: Emitter +} +const key: InjectionKey = Symbol(); + +/** + * 事件编码-打开弹窗 + */ +export const EVENT_OPEN_CODE: string = 'openpopmodal'; +/** + * 事件编码-关闭弹窗,获取表单数据 + */ +const EVENT_SUCCESS_CODE: string = 'successpopmodal'; + +/** + * 设置弹框事件-online列表 + * @param callback + */ +export function useOnlineListPopEvent(callback){ + const emitter = mitt(); + function openPopModal(params){ + callback(params); + console.log('事件触发完成,', params) + } + + emitter.on(EVENT_OPEN_CODE, openPopModal) +/* onUnmounted(()=>{ + emitter.off(EVENT_OPEN_CODE, openPopModal) + console.log('事件解绑完成-createOpenPopModalEvent') + }); + onMounted(()=>{ + + console.log('事件绑完成-createOpenPopModalEvent') + });*/ + createOnlineEventContext({ + onlineEmitter: emitter + }); + console.log('事件绑完成,') +} + +/** + * 关闭弹窗,返回表单数据 + * @param params + */ +export function useOnlinePopFormEvent(){ + const { onlineEmitter } = useOnlineEventContext(); + function emitFormData(data){ + onlineEmitter.emit(EVENT_SUCCESS_CODE, data) + } + return { + emitFormData + } +} + + +/** + * 关闭弹窗,返回表单数据 + * @param params + */ +export function useOnlineFormEvent(callback){ + const context = useOnlineEventContext(); + const { onlineEmitter } = context; + function emitData(data){ + callback(data); + console.log('useOnlineFormEvent事件触发完成,', data) + } + onUnmounted(()=>{ + onlineEmitter && onlineEmitter.off(EVENT_SUCCESS_CODE, emitData) + console.log('事件解绑完成-createOpenPopModalEvent') + }); + onMounted(()=>{ + onlineEmitter && onlineEmitter.on(EVENT_SUCCESS_CODE, emitData) + console.log('事件绑完成-createOpenPopModalEvent') + }); + function openPopModal(emitData){ + console.log('openPopModal', emitData) + onlineEmitter && onlineEmitter.emit(EVENT_OPEN_CODE, emitData) + } + return { + openPopModal + } +} + +/** + * 触发弹框事件 + * @param params + */ +/*export function getOnlinePopEvent(){ + const { onlineEmitter } = useOnlineEventContext(); + return { + onlineEmitter, + eventCode: EVENT_OPEN_CODE + }; +}*/ + +function createOnlineEventContext(context: OnlineEmitterContextProps) { + return createContext(context, key, { readonly: false, native: true }); +} + +export function useOnlineEventContext() { + return useContext(key); +} \ No newline at end of file diff --git a/src/views/super/online/cgform/hooks/auto/useOnlineTableContext.ts b/src/views/super/online/cgform/hooks/auto/useOnlineTableContext.ts new file mode 100644 index 0000000..de5c904 --- /dev/null +++ b/src/views/super/online/cgform/hooks/auto/useOnlineTableContext.ts @@ -0,0 +1,903 @@ +import type { ExtConfigType } from '../../types'; +import { Page, SpecialConfig, SETUP, ENHANCEJS } from '../../../cgform/types/onlineRender'; +import { useRoute } from 'vue-router'; +import { router } from '/@/router'; +import { onBeforeUnmount, ref, toRaw, nextTick, provide } from 'vue'; +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { filterObj } from '/@/utils/common/compUtils'; +import { useCustomHook, GET_FUN_BODY_REG } from './useCustomHook'; +import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated'; +import { useModal } from '/@/components/Modal'; +import { ERP } from "../../util/constant"; +import {useMultipleTabStore} from "/@/store/modules/multipleTab"; +import {useCgformStore} from "../../store/cgformState"; +import { getMenus } from '/@/router/menus'; +/** + * context对象属性的描述 + * 控制台写js增强的时候 打印console.log(this)可以获取到该对象说明 + */ +const CONTEXT_PROP_DESCRIPTION = { + acceptHrefParams: '

跳转时获取的参数信息', + currentPage: '

当前页数', + currentTableName: '

当前表名', + description: '

当前表描述', + hasChildrenField: '

是否有子节点的字段名,仅树形表单下有效', + isDesForm: '

xx', + isTree: ' 是否是树形表单 ', + loadData: ' 加载列表数据', + pageSize: '

每一页显示条数', + queryParam: '

查询条件对象,每次点击查询后才会更新此数据', + selectedRowKeys: '

选中的行的id数组', + sortField: '

排序字段', + sortType: '

排序规则', + total: '

总页数', + foreignKeyValue: '

Erp一对多子表外键选中对应主表字段的值', + isErpSubTable: '

是否Erp一对多子表', + foreignKeyField: '

Erp一对多子表外键字段', + themeTemplate: '

主题模板', + isInnerSubTable: '

是否内嵌一对多子表', + innerSubTableId: '

内嵌一对多子表ID', + innerSubTableName: '

内嵌一对多子表名', + mTableSelectedRcordId: '

内嵌主表展开行的id', + innerSubTableFk: '

内嵌子表的外键字段', + loading: '

设置/获取loading', +}; + +/** + * online地址-常量 + */ +const onlineUrl = { + getColumns: '/online/cgform/api/getColumns/', + getQueryInfo: '/online/cgform/api/getQueryInfo/', + getData: '/online/cgform/api/getData/', + getTreeData: '/online/cgform/api/getTreeData/', + optPre: '/online/cgform/api/form/', + buttonAction: '/online/cgform/api/doButton', + exportXls: '/online/cgform/api/exportXlsOld/', + importXls: '/online/cgform/api/importXls/', + startProcess: '/act/process/extActProcess/startMutilProcess', + getErpColumns: '/online/cgform/api/getErpColumns/', + // 内嵌主题一对多子表数据请求接口 + list: '/online/cgform/api/subform/list/', +}; + +// 没一张表配置的初始值 +let config: SpecialConfig = { + sortField: 'id', + sortType: 'asc', + currentPage: 1, + pageSize: 10, + total: 0, + selectedRowKeys: [], + queryParam: {}, + acceptHrefParams: {}, + description: '', + currentTableName: '', + isDesForm: false, + desFormCode: '', + cache: false, + isTree: false, + hasChildrenField: '', +}; + +/** + * 分页配置 + */ +const metaPagination = { + current: 1, + pageSize: 10, + pageSizeOptions: ['10', '20', '30'], + showTotal: (total, range) => { + return range[0] + '-' + range[1] + ' 共' + total + '条'; + }, + showQuickJumper: true, + showSizeChanger: true, + total: 0, +}; + +/** + * 获取online 列表上下文 + * 1.常量,全局使用的- 请求url + * 2.特殊参数-查询条件,排序方式,分页信息,选中行的keys(待测试,是否只和key有关,如果和row有关需去掉此配置) + * 3.loadData方法,这个方法很多地方调用 + * setup最开头执行一次即可 + */ +const { createMessage: $message, createErrorModal } = useMessage(); + +export function useOnlineTableContext(params: any = {}) { + console.log('-------------------------useOnlineTableContext----------------------->'); + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + const tableId = params.code ?? ''; + const ID = ref(tableId); + provide('tableId', ID); + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + const route = useRoute(); + // 列表页面查询表单的ref + const onlineQueryFormOuter = ref(); + // 高级查询按钮 + const superQueryButtonRef = ref(); + // 分页配置 + const pagination = ref(false); + // table 数据 切换路由即发生数据改变 正常情况下会走一遍setup 缓存情况下 走事件onActivated----问题是不用重新请求column吗? 只需要加载数据? 待测试 + const dataSource = ref>([]); + // 表格是否重载 + const tableReloading = ref(true); + // online表单扩展配置 + const onlineExtConfigJson = ref(); + // Online表单全局状态 + const cgformStore = useCgformStore(); + // 多Tab状态 + const tabStore = useMultipleTabStore(); + + const isConfigCurRoute = ref(false); + const pageLoading = ref(false); + + let specialConfigMap: { [key: string | symbol ]: SpecialConfig } = {}; + const methods = { + execButtonEnhance: function (code, record) { + if (onlineTableContext[ENHANCEJS][code]) { + if (SETUP === code) { + executeEnhanceJsHook(code); + } else { + let row = toRaw(record); + return onlineTableContext[ENHANCEJS][code].call(onlineTableContext, onlineTableContext, row); + } + } else if (onlineTableContext[ENHANCEJS][code + '_hook']) { + + //update-begin-author:taoyan date:2023-5-15 for: issues/516 自定义按钮_hook后的参数row未定义问题(参见#410) #516 + if(record){ + let row = toRaw(record); + executeEnhanceJsHook(code + '_hook', row); + }else{ + executeEnhanceJsHook(code + '_hook'); + } + //update-end-author:taoyan date:2023-5-15 for: issues/516 自定义按钮_hook后的参数row未定义问题(参见#410) #516 + + } else { + console.log('增强没找到!', code); + } + }, + /** + * get 是否是树形表单 + * @param status 如果有值 则视为set方法 + */ + isTree: function (status?) { + if (typeof status === 'boolean') { + //传了参数则设置值 + onlineTableContext['isTreeTable'] = status; + return status; + } else { + return onlineTableContext['isTreeTable']; + } + }, + }; + + function executeEnhanceJsHook(code, row?) { + let str = onlineTableContext[ENHANCEJS][code].toLocaleString(); + let arr = str.match(GET_FUN_BODY_REG); + if (arr.length > 1) { + let temp = arr[1]; + executeJsEnhanced(temp, row); + } + } + /** + * 定义数据代理 取值方便 onlineTableContext.queryParam + * 直接读取onlineTableContext 取到的是{} + */ + const onlineTableContext: any = new Proxy(CONTEXT_PROP_DESCRIPTION, { + get(_target: any, prop: string): any { + //console.log('从SpecialConfig中读取属性:'+prop) + if (typeof methods[prop] === 'function') { + return methods[prop]; + } else { + let temp = specialConfigMap[ID.value]; + if (temp == null) { + return temp; + } + return Reflect.get(temp, prop); + } + }, + set(_target: any, prop: string, value: any): boolean { + // console.log('设置SpecialConfig属性:'+ prop, value) + let temp = getCurrentPageSpecialConfigMap(); + if (typeof value === 'function') { + // 如果是函数放到methods中去 + return Reflect.set(methods, prop, value); + } else { + return Reflect.set(temp, prop, value); + } + }, + deleteProperty(_target, key) { + // 在路由切换、关闭页面的时候需要调用一下这个方法清除配置 + if (key === ID.value) { + delete specialConfigMap[key]; + return true; + } else { + return false; + } + }, + }); + + // 新的js增强 + const { executeJsEnhanced } = useCustomHook({}, onlineTableContext); + + /** + * 获取路由地址上的表单ID + */ + function getTableId() { + let idValue = route.params.id as string; + if (!idValue) { + idValue = ''; + } + return idValue; + } + + onMountedOrActivated(({type}) => { + // 缓存路由走Activated,没缓存的走Mounted,均需走一次 + console.log('-------------------onMountedOrActivated-------------------'); + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + //erp一对多子表的id不能从页面路由获取(当tableId存在时,不从页面路由获取) + !tableId && handlePageChange(); + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + + if (type === 'activated') { + // 【QQYUN-7151】表单修改后,已经打开的功能测试页面不会自动刷新 + if (cgformStore.checkIsChanged(ID.value)) { + tabStore.refreshPage(router) + } + } + + if (ID.value) { + cgformStore.removeChangedTable(ID.value); + } + + }); + + // 路由关闭前 清空map里面的配置 + onBeforeUnmount(() => { + console.log('-------------------onBeforeUnmount-------------------'); + delete specialConfigMap[ID.value]; + // 如果缓存了 关闭时会调用 + // 没有缓存 切换路由就会调用--这个没关系 + // 但是测试online无此效果 + }); + + /** + * 获取当前页面配置 + */ + function getCurrentPageSpecialConfigMap() { + let temp = specialConfigMap[ID.value]; + if (!temp) { + let obj = Object.assign({}, config, { onlineUrl }); + temp = JSON.parse(JSON.stringify(obj)); + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + if (params['themeTemplate'] == ERP) { + // update-begin--author:liaozhiyang---date:20240306---for:【QQYUN-8387】Erp风格和其他风格同时在,导致其他风格当前页码不正常 + temp.pageSize = 5; + // update-end--author:liaozhiyang---date:20240306---for:【QQYUN-8387】Erp风格和其他风格同时在,导致其他风格当前页码不正常 + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + // update-begin--author:liaozhiyang---date:20250423---for:【issues/8117】js增强可设置获取loading + // @ts-ignore + temp.loading = pageLoading; + // update-end--author:liaozhiyang---date:20250423---for:【issues/8117】js增强可设置获取loading + specialConfigMap[ID.value] = temp; + } + return temp; + } + + //接受URL参数 + function handleAcceptHrefParams() { + let acceptHrefParams = {}; + let hrefParam = route.query; + if (hrefParam) { + Object.keys(hrefParam).map((key) => { + acceptHrefParams[key] = hrefParam[key]; + }); + // queryParam.value raw对象 + onlineTableContext['acceptHrefParams'] = acceptHrefParams; + } + } + + /** + * 查询table列信息 及其他配置 + */ + function getColumnList(themeTemplate = '') { + let url; + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + if (themeTemplate == ERP) { + // Erp一对多获取主子表columns + url = `${onlineTableContext.onlineUrl.getErpColumns}${ID.value}`; + } else { + url = `${onlineTableContext.onlineUrl.getColumns}${ID.value}`; + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + + //update-begin---author:wangshuai---date:2025-10-21---for:【issues/8933】内嵌子表主题(一对多)列表点+号展开明细提示:无权限访问(操作)--- + if(onlineTableContext['isInnerSubTable'] === true){ + url = url+ '?tabletype=3'; + } + //update-end---author:wangshuai---date:2025-10-21---for:【issues/8933】内嵌子表主题(一对多)列表点+号展开明细提示:无权限访问(操作)--- + return new Promise((resolve, reject) => { + defHttp + .get( + { + url, + }, + { isTransformResponse: false } + ) + .then((res) => { + // console.log(res) + if (res.success) { + resolve(res.result); + } else { + $message.warning(res.message); + reject(); + } + }) + .catch(() => { + reject(); + }); + }); + } + + //查询数据 + /** + * @param delNum number 删除的条数【批量删除,删除】调用会传 + */ + function loadData(options = {}) { + const { delNum } = options; + return new Promise((resolve, reject) => { + // update-begin--author:liaozhiyang---date:20240528---for:【TV360X-206】列表删除最后一页数据,页面跳到前一页数据为空 + if (delNum != null) { + const { total, pageSize, current } = pagination.value; + const lastPage = Math.ceil(total / pageSize); + // 只有当前页是最后一页时删除数据才判断是否要跳到前一页 + if (current === lastPage) { + pagination.value.current = Math.ceil((total - delNum) / pageSize); + } + } + // update-end--author:liaozhiyang---date:20240528---for:【TV360X-206】列表删除最后一页数据,页面跳到前一页数据为空 + let params = getLoadDataParams(); + let url = `${onlineTableContext.onlineUrl.getData}${ID.value}`; + if (onlineTableContext.isTree() === true) { + url = `${onlineTableContext.onlineUrl.getTreeData}${ID.value}`; + } else if (onlineTableContext['isInnerSubTable'] === true) { + // update-begin--author:liaozhiyang---date:20230822---for:【QQYUN-6305】内嵌主题一对多 + url = `${onlineTableContext.onlineUrl.getData}${onlineTableContext['innerSubTableId']}`; + params = {pageSize: -521, } + // update-begin--author:liaozhiyang---date:20240514---for:【QQYUN-9340】内嵌子表数据都查出来了 + if (onlineTableContext['innerSubTableFk'] && onlineTableContext['mTableSelectedRcordId']) { + params[onlineTableContext['innerSubTableFk']] = onlineTableContext['mTableSelectedRcordId']; + } + // update-end--author:liaozhiyang---date:20240514---for:【QQYUN-9340】内嵌子表数据都查出来了 + // update-end--author:liaozhiyang---date:20230822---for:【QQYUN-6305】内嵌主题一对多 + //update-begin---author:wangshuai---date:2025-10-21---for:【issues/8933】内嵌子表主题(一对多)列表点+号展开明细提示:无权限访问(操作)--- + url = url+ '?tabletype=3'; + //update-end---author:wangshuai---date:2025-10-21---for:【issues/8933】内嵌子表主题(一对多)列表点+号展开明细提示:无权限访问(操作)--- + } + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + // erp一对多子表查询需加参数 + if (onlineTableContext['isErpSubTable'] === true) { + // update-begin--author:liaozhiyang---date:20250722---for:【issues/8575】erp默认选中第一个及没选中主表时子表不查询 + if (onlineTableContext['foreignKeyValue'] == undefined) { + return; + } + // update-end--author:liaozhiyang---date:20250722---for:【issues/8575】erp默认选中第一个及没选中主表时子表不查询 + params[onlineTableContext['foreignKeyField']] = onlineTableContext['foreignKeyValue']; + // 【issues/6124】当用户没有【Online表单开发】页面的权限时用户无权查看从表的数据 + params['tabletype'] = 3; + delete params.hasQuery; + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + console.log('------查询参数-----', params); + defHttp + .get({ url, params }, { isTransformResponse: false }) + .then((res) => { + console.log('--onlineList-查询列表数据', res); + if (res.success) { + handleDataResult(res.result); + resolve(true); + } else { + if (res.message === 'NO_DB_SYNC') { + createErrorModal({ + title: '数据库未同步', + content: '请先同步数据库再查看此页面!', + // 点击确定后自动返回上一页 + onOk: () => router.back(), + }); + } else { + $message.warning(res.message); + } + reject(false); + } + }) + .catch(() => { + let error = '请求列表数据异常!'; + $message.warning(error); + reject(false); + }); + }); + } + + /** + * 获取查询条件 + */ + function getLoadDataParams() { + const { sortField, sortType, acceptHrefParams, queryParam } = onlineTableContext; + // 树用到的参数 + const treeParam = { + hasQuery: 'true', + }; + if (onlineTableContext.isTree() === true) { + // update-begin--author:liaozhiyang---date:20231205---for:【issues/888】online树表子节点搜索不生效且有警告 + if (!!queryParam || Object.keys(queryParam).length <= 0) { + treeParam['hasQuery'] = 'false'; + } + // update-end--author:liaozhiyang---date:20231205---for:【issues/888】online树表子节点搜索不生效且有警告 + } + let params = Object.assign({}, treeParam, acceptHrefParams, queryParam, { column: sortField, order: sortType }); + // TODO 范围查询 原固定值需要清楚 待删除 + /*let queryFields = queryFieldArray.value; + for(let item of queryFields){ + if(item.mode!='single'){ + params[item.field] = '' + } + }*/ + if (pagination.value) { + //如果分页 + params.pageNo = pagination.value.current; + params.pageSize = pagination.value.pageSize; + } else { + // 不分页传一个固定值 + params['pageSize'] = -521; + } + + let superQueryData = getSuperQueryData(); + //高级查询 + params.superQueryMatchType = superQueryData.matchType || ''; + params.superQueryParams = superQueryData.params || ''; + return filterObj(params); + } + + // 查询玩数据后 获取页面数据、数据总数 + function handleDataResult(result) { + let total = 0; + if (Number(result.total) > 0) { + if (onlineTableContext.isTree() === true) { + dataSource.value = getTreeDataByResult(result.records); + nextTick(() => { + loadDataByExpandedRows(dataSource.value); + }); + } else { + // update-begin--author:liaozhiyang---date:20250508---for:【issues/8168】id重复排序数据重了 + dataSource.value = []; + nextTick(() => { + dataSource.value = result.records; + }); + // update-end--author:liaozhiyang---date:20250508---for:【issues/8168】id重复排序数据重了 + } + total = Number(result.total); + } else { + dataSource.value = []; + } + if (pagination.value) { + pagination.value = { ...pagination.value, total }; + } + } + + //分页、排序、筛选变化时触发 + function handleChangeInTable($pagination, _filters, sorter) { + if (sorter && sorter.order) { + // 需要排序,先获取排序规则 + onlineTableContext['sortField'] = sorter.field; + onlineTableContext['sortType'] = 'ascend' == sorter.order ? 'asc' : 'desc'; + } else { + // 没有规则 走默认排序 + onlineTableContext['sortField'] = 'id'; + onlineTableContext['sortType'] = 'asc'; + } + if (pagination.value) { + //console.log('$pagination111', $pagination) + pagination.value = $pagination; + } + loadData(); + } + + /** + * 页面id改变后,执行查询loadData之前会执行该方法 + * 根据查询的结果设置当前表信息、设置查询条件、设置高级查询条件、分页信息、排序信息 + * @param result + */ + function handleSpecialConfig(result) { + //1.根据查询的结果设置当前表信息 + onlineTableContext['description'] = result.description; + onlineTableContext['currentTableName'] = result.currentTableName; + onlineTableContext['isDesForm'] = result.isDesForm; + onlineTableContext['desFormCode'] = result.desFormCode; + onlineTableContext['ID'] = ID.value; + //2.设置查询条件 + let { acceptHrefParams, queryParam, superQuery, currentPage, pageSize } = onlineTableContext; + handleAcceptHrefParams(); + if (!queryParam) { + onlineTableContext['queryParam'] = {}; + } else { + // 加强判断,防止没有查询 + if (onlineQueryFormOuter.value) { + onlineQueryFormOuter.value.initDefaultValues(queryParam, acceptHrefParams); + } + } + //3.设置高级查询条件 + if (!superQuery) { + onlineTableContext['superQuery'] = { params: '', matchType: '' }; + } else { + // erp一对多子表没有高级查询按钮 + if (superQueryButtonRef.value) { + superQueryButtonRef.value.initDefaultValues(superQuery); + } + } + //4.分页信息 + if (result.paginationFlag == 'Y') { + // update-begin--author:liaozhiyang---date:20240527---for:【TV360X-332】erp默认每页5条,切换之后每页5条没了 + let pageSizeOptions: any = metaPagination.pageSizeOptions; + if (params['themeTemplate'] == ERP) { + pageSizeOptions = ['5', '10', '30']; + } + // update-end--author:liaozhiyang---date:20240527---for:【TV360X-332】erp默认每页5条,切换之后每页5条没了 + pagination.value = { ...metaPagination, ...{ current: currentPage, pageSize, pageSizeOptions } }; + } else { + pagination.value = false; + } + //5.排序信息 不需要设置 没有显示声明,所以缺点是:界面上看不出来哪一列被排序了 + } + + /** 重载表格,在columns等信息变化时需要调用 */ + async function reloadTable() { + tableReloading.value = true; + await nextTick(); + tableReloading.value = false; + } + + const add2Context = { + loadData, + getLoadDataParams, + reloadTable, + }; + Object.keys(add2Context).map((key) => { + onlineTableContext[key] = add2Context[key]; + }); + + //----------------------------------- 以下为查询相关的-------------------------------- + + // 查询加载状态 + let loading = ref(false); + // 查询首页 + async function reload(parameter:any = {}) { + if (pagination.value) { + // update-begin--author:liaozhiyang---date:20231207---for:【QQYUN-7414】online操作除了查询其他数据刷新都是当前页(包括新增) + pagination.value = { ...pagination.value, current: parameter.mode == 'search' || !pagination.value.current ? 1 : pagination.value.current }; + // update-end--author:liaozhiyang---date:20231207---for:【QQYUN-7414】online操作除了查询其他数据刷新都是当前页(包括新增) + } + // update-begin--author:liaozhiyang---date:20231128---for:【QQYUN-7260】erp主表编辑时保存子表记录 + if (params['themeTemplate'] !== ERP) { + onlineTableContext.clearSelectedRow(); + } + // update-end--author:liaozhiyang---date:20231128---for:【QQYUN-7260】erp主表编辑时保存子表记录 + //loading.value = true + await loadData(); + //loading.value = false + } + + //------------------------树形列表-------------------------- + function getTreeDataByResult(result) { + if (result) { + return result.map((item) => { + //判断是否标记了带有子节点 + let hasChildrenField = onlineTableContext['hasChildrenField']; + if (item[hasChildrenField] == '1') { + let loadChild = { id: item.id + '_loadChild', name: 'loading...', isLoading: true }; + loadChild['jeecg_row_key'] = loadChild.id; + item.children = [loadChild]; + } + return item; + }); + } + } + + const expandedRowKeys = ref([]); + + function handleExpandedRowsChange(expandedRowKeysValue) { + //console.log(a,b) + //console.log('handleExpandedRowsChange', expandedRowKeysValue, toRaw(expandedRowKeys.value)) + expandedRowKeys.value = expandedRowKeysValue; + } + + // 根据已展开的行查询数据(用于保存后刷新时异步加载子级的数据) + function loadDataByExpandedRows(dataList) { + let expandedRowKeysValue = expandedRowKeys.value; + if (expandedRowKeysValue.length > 0) { + const { sortField, sortType, pidField } = onlineTableContext; + let params = Object.assign({}, { column: sortField, order: sortType }); + params['hasQuery'] = 'in'; + //已展开节点批量查询子节点 + let superParams = Object.assign({}); + superParams.rule = 'in'; + superParams.type = 'text'; + superParams.val = expandedRowKeysValue.join(','); + superParams.field = pidField; + superParams = [superParams]; + params['superQueryParams'] = encodeURI(JSON.stringify(superParams)); + params['superQueryMatchType'] = 'and'; + params['batchFlag'] = 'true'; + let url = `${onlineTableContext.onlineUrl.getTreeData}${ID.value}`; + console.log('--onlineList-查询子节点参数', superParams); + defHttp + .get({ url, params }, { isTransformResponse: false }) + .then((res) => { + console.log('--onlineList-查询子节点列表数据', res); + if (res.success && res.result.records && res.result.records.length > 0) { + //已展开的数据批量子节点 + let records = res.result.records; + const listMap = new Map(); + for (let item of records) { + let pid = item[pidField]; + if (expandedRowKeysValue.join(',').includes(pid)) { + let mapList = listMap.get(pid); + if (mapList == null) { + mapList = []; + } + mapList.push(item); + listMap.set(pid, mapList); + } + } + let childrenMap = listMap; + let fn = (list) => { + if (list) { + list.forEach((data) => { + if (expandedRowKeysValue.includes(data.id)) { + data.children = getTreeDataByResult(childrenMap.get(data.id)); + fn(data.children); + } + }); + } + }; + fn(dataList); + } + }) + .catch(() => { + let error = 'loadDataByExpandedRows请求列表数据异常!'; + $message.warning(error); + }); + } else { + return Promise.resolve(); + } + } + + /** + * 获取高级查询条件 + */ + function getSuperQueryData() { + if (!onlineTableContext.superQuery) { + return {}; + } + const { + superQuery: { params, matchType }, + currentTableName, + } = onlineTableContext; + let pre = currentTableName + '@'; + let arr: any[] = []; + if (params.length > 0) { + for (let data of params) { + let item = { ...data }; + let field = item.field; + if (field.startsWith(pre)) { + item.field = field.replace(pre, ''); + } + arr.push(item); + } + } + let str = arr.length > 0 ? JSON.stringify(arr) : ''; + console.log('高级查询条件', arr, matchType); + return { + params: encodeURIComponent(str), + matchType, + }; + } + + /**高级查询状态-是否有查询条件*/ + const superQueryStatus = ref(false); + + /** + * 高级查询对象 + * 1.执行高级查询组件的search事件,需要将值赋值给context + * 2.查询的时候从context中获取参数值 + * 3.id发生改变需要做点什么?nothing,状态值需要改变 + */ + function handleSuperQuery(params, matchType) { + // params一定是个数组,可能size为0 + onlineTableContext['superQuery'] = { + params, + matchType, + }; + // update-begin--author:liaozhiyang---date:20231128---for:【QQYUN-7309】online高级查询第二次按钮没动画 + if (params.length == 0 || params.length == undefined) { + superQueryStatus.value = false; + } else { + superQueryStatus.value = true; + } + // update-end--author:liaozhiyang---date:20231128---for:【QQYUN-7309】online高级查询第二次按钮没动画 + // update-begin--author:liaozhiyang---date:20231207---for:【QQYUN-7414】online操作除了查询其他数据刷新都是当前页(包括新增) + pagination.value.current = 1; + // update-end--author:liaozhiyang---date:20231207---for:【QQYUN-7414】online操作除了查询其他数据刷新都是当前页(包括新增) + loadData(); + } + + /*------------------------自定义弹窗------------------------------*/ + const [registerCustomModal, { openModal: doOpenCustomModal }] = useModal(); + /** + * 自定义按钮 触发弹框 + * @param param + */ + function openCustomModal(param) { + if (!param) { + param = {}; + } + if (!param.row) { + let rows = onlineTableContext['selectedRows']; + if (!rows || rows.length == 0 || rows.length > 1) { + $message.warning('请选择一条数据'); + return; + } + param.row = rows[0]; + } + param['code'] = ID.value; + doOpenCustomModal(true, param); + } + onlineTableContext['openCustomModal'] = openCustomModal; + /*------------------------自定义弹窗------------------------------*/ + + /** + * 页面发生改变的时候触发 + */ + function handlePageChange() { + let idValue = getTableId(); + ID.value = idValue; + } + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + //erp子表的id不能从页面路由获取(当tableId存在时,不从页面路由获取) + if (!tableId && !ID.value) { + handlePageChange() + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + + /** 表单配置查询成功后触发的事件(不用等表单打开才触发) */ + function handleFormConfig(formConfig) { + // 处理扩展参数 + let extConfigJson = formConfig.head.extConfigJson; + if (extConfigJson) { + onlineExtConfigJson.value = JSON.parse(extConfigJson); + } + } + /** + * liaozhiyang + * 20250407 + * 【QQYUN-11801】生成测试数据 + * 判断是配置的菜单还是功能测试打开的 + * */ + async function isConfigUrl() { + const getMatchingisConfigUrl = (menus, path) => { + for (let i = 0, len = menus.length; i < len; i++) { + const item = menus[i]; + if (item.path === path && !item.redirect && !item.paramPath) { + return true; + } else if (item.children?.length) { + const result = getMatchingisConfigUrl(item.children, path); + if (result) { + return result; + } + } + } + return false; + }; + const path = route.path; + const menus = await getMenus(); + const result = getMatchingisConfigUrl(menus, path); + isConfigCurRoute.value = result; + } + isConfigUrl(); + return { + ID, + onlineQueryFormOuter, + superQueryButtonRef, + loading, + reload, + dataSource, + pagination, + tableReloading, + handleSpecialConfig, + onlineTableContext, + handleChangeInTable, + getColumnList, + getTreeDataByResult, + expandedRowKeys, + handleExpandedRowsChange, + onlineExtConfigJson, + handleFormConfig, + superQueryStatus, + handleSuperQuery, + registerCustomModal, + isConfigCurRoute, + pageLoading, + ...add2Context, + }; +} + +/** + * 兼容老版js增强 封装对象--暂不支持 + * + */ +export function useCompatibleOldVersion(context) { + Object.defineProperty(context, 'table', { + get() { + const arr = context['selectedRowKeys']; + const arr2 = context['selectedRows']; + return { + selectedRowKeys: arr, + selectedRows: arr2, + }; + }, + }); +} + +/** + * 链式调用?? + */ +export class AopSetup { + before: Function = () => {}; + after: Function = () => {}; + + constructor(before, after) { + if (typeof before == 'function') { + this.before = before; + } + if (typeof after == 'function') { + this.after = after; + } + } + + addTarget(prop, context) { + let key; + if (typeof prop == 'function') { + key = prop.name; + } else if (typeof prop == 'string') { + key = prop; + } else { + return; + } + if (typeof context == 'object') { + context[key] = this.around(context[key], context); + } + } + addTargets(array, context) { + for (let item of array) { + this.addTarget(item, context); + } + } + + around(targetFunction, context) { + const _that = this; + return async function () { + //console.log('this2', this) + let res1 = await _that.before(arguments); + console.log('before返回值', res1); + if (res1) { + console.log('错误信息', res1); + return; + } + let result = await targetFunction.apply(context, arguments); + await _that.after(arguments); + return result; + }; + } +} diff --git a/src/views/super/online/cgform/hooks/auto/useSuperQuery.ts b/src/views/super/online/cgform/hooks/auto/useSuperQuery.ts new file mode 100644 index 0000000..cc6ed1a --- /dev/null +++ b/src/views/super/online/cgform/hooks/auto/useSuperQuery.ts @@ -0,0 +1,494 @@ +import { useModalInner } from '/@/components/Modal'; +import { randomString } from '/@/utils/common/compUtils'; +import { reactive, ref, toRaw, watch } from 'vue'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { Modal } from 'ant-design-vue'; +import { createLocalStorage } from '/@/utils/cache'; +import { useRoute } from 'vue-router'; +import FormSchemaFactory from '../../auto/comp/factory/FormSchemaFactory'; +import { FORM_VIEW_TO_QUERY_VIEW } from '../../types/onlineRender'; + +// 查询条件存储编码前缀 +const SAVE_CODE_PRE = 'JSuperQuerySaved_'; + +/** + * 查询项 + * */ +interface SuperQueryItem { + field: string | undefined; + rule: string | undefined; + val: string | number; + key: string; +} +/** + * 查询项-第一个控件树model + * */ +interface TreeModel { + title: string; + value: string; + isLeaf?: boolean; + disabled?: boolean; + children?: TreeModel[]; + order?: number; +} + +/** + * 查询信息保存结构 + * */ +interface SaveModel { + title: string; + content: string; + type: string; +} + +export function useSuperQuery() { + const { createMessage: $message } = useMessage(); + /** 表单ref*/ + const formRef = ref(); + + /** 数据*/ + const dynamicRowValues = reactive<{ values: SuperQueryItem[] }>({ + values: [], + }); + /** and/or */ + const matchType = ref('and'); + + // 弹框显示 + const [registerModal, { setModalProps }] = useModalInner(() => { + setModalProps({ confirmLoading: false }); + }); + + // 高级查询类型不支持联动组件,需要额外设置联动组件的view为text + const view2QueryViewMap = Object.assign({}, { link_down: 'text' }, FORM_VIEW_TO_QUERY_VIEW); + + /** + * 确认按钮事件 + */ + function handleSubmit() { + console.log('handleSubmit', dynamicRowValues.values); + } + + /** + * 关闭按钮事件 + */ + function handleCancel() { + //closeModal(); + } + + /** + * val组件赋值 + */ + function setFormModel(key: string, value: any, item: any) { + console.log('setFormModel', key, value); + // formModel[key] = value; + item['val'] = value; + } + + // 字段-Properties + const fieldProperties = ref({}); + // 字段-左侧查询项-树控件数据 + const fieldTreeData = ref([]); + + /** + * 初始化数据-最开始的方法 + * 1.获取 表名@字段名-->配置 这样的一个map + * 2.获取树形结构的数据 显示:文本; 存储:表名@字段名 + * 当树改变时,及时获取配置更新表单 + * @param json + */ + function init(json) { + let { allFields, treeData } = getAllFields(json); + fieldProperties.value = allFields; + fieldTreeData.value = treeData; + } + + /** + * 左侧查询项 添加一行 + * @param index + */ + function addOne(index) { + let item = { + field: undefined, + rule: 'eq', + val: '', + key: randomString(16), + }; + if (index === false) { + // 重置后需要调用 + dynamicRowValues.values = []; + dynamicRowValues.values.push(item); + } else if (index === true) { + // 打开弹框是需要调用 + if (dynamicRowValues.values.length == 0) { + dynamicRowValues.values.push(item); + } + } else { + // 其余就是 正常的点击加号增加行 + dynamicRowValues.values.splice(++index, 0, item); + } + } + + /** + * 左侧查询项 删除一行 + */ + function removeOne(item: SuperQueryItem) { + let arr = toRaw(dynamicRowValues.values); + let index = -1; + for (let i = 0; i < arr.length; i++) { + if (item.key == arr[i].key) { + index = i; + break; + } + } + if (index != -1) { + dynamicRowValues.values.splice(index, 1); + } + } + + // 默认的输入框 + const defaultInput = { + field: 'val', + label: '测试', + component: 'Input', + }; + + /** + * 左侧查询项 val组件 schema获取, 替代左侧字段树的change事件 + * @param item + * @param index + */ + function getSchema(item, index) { + let map = fieldProperties.value; + let prop = map[item.field]; + if (!prop) { + return defaultInput; + } + if (view2QueryViewMap[prop.view]) { + // 如果出现查询条件联动组件出来的场景,请跟踪此处 + prop.view = view2QueryViewMap[prop.view]; + } + let temp = FormSchemaFactory.createFormSchema(item.field, prop); + // temp.setFormRef(formRef) + temp.noChange(); + // 查询条件中的 下拉框popContainer为parentNode + temp.asSearchForm(); + temp.updateField(item.field + index); + const setFieldValue = (values) => { + item['val'] = values[item.field]; + }; + temp.setFunctionForFieldValue(setFieldValue); + let schema = temp.getFormItemSchema(); + //schema['valueField'] = 'val' + return schema; + } + + /*-----------------------右侧保存信息相关-begin---------------------------*/ + + /** + * 右侧树 的 数据 + */ + const saveTreeData = ref(''); + // 本地缓存 + const $ls = createLocalStorage(); + //需要保存的信息(一条) + const saveInfo = reactive({ + visible: false, + title: '', + content: '', + saveCode: '', + }); + //按钮loading + const loading = ref(false); + + // 当前页面路由 + const route = useRoute(); + // 监听路由信息,路由发生改变,则重新获取保存的查询信息-->currentPageSavedArray + watch( + () => route.fullPath, + (val) => { + console.log('fullpath', val); + initSaveQueryInfoCode(); + } + ); + + // 当前页面存储的 查询信息 + const currentPageSavedArray = ref([]); + // 监听当前页面是否有新的数据保存了,然后更新右侧数据->saveTreeData + watch( + () => currentPageSavedArray.value, + (val) => { + let temp: any[] = []; + if (val && val.length > 0) { + val.map((item) => { + let key = randomString(16); + temp.push({ + title: item.title, + slots: { icon: 'custom' }, + value: key, + }); + }); + } + saveTreeData.value = temp; + }, + { immediate: true, deep: true } + ); + + // 重新获取保存的查询信息 + function initSaveQueryInfoCode() { + let code = SAVE_CODE_PRE + route.fullPath; + saveInfo.saveCode = code; + let list = $ls.get(code); + if (list && list instanceof Array) { + currentPageSavedArray.value = list; + } + } + + // 执行一次 获取保存的查询信息 + initSaveQueryInfoCode(); + + /** + * 保存按钮事件 + */ + function handleSave() { + // 获取实际数据转成字符串 + let fieldArray = getQueryInfo(); + if (!fieldArray) { + $message.warning('空条件不能保存'); + return; + } + let content = JSON.stringify(fieldArray); + openSaveInfoModal(content); + } + + // 输入保存标题 弹框显示 + function openSaveInfoModal(content) { + saveInfo.visible = true; + saveInfo.title = ''; + saveInfo.content = content; + } + + /** + * 确认保存查询信息 + */ + function doSaveQueryInfo() { + let { title, content, saveCode } = saveInfo; + let index = getTitleIndex(title); + if (index >= 0) { + // 已存在是否覆盖 + Modal.confirm({ + title: '提示', + content: `${title} 已存在,是否覆盖?`, + okText: '确认', + cancelText: '取消', + onOk: () => { + currentPageSavedArray.value.splice(index, 1, { + content, + title, + type: matchType.value, + }); + $ls.set(saveCode, currentPageSavedArray.value); + saveInfo.visible = false; + }, + }); + } else { + currentPageSavedArray.value.push({ + content, + title, + type: matchType.value, + }); + $ls.set(saveCode, currentPageSavedArray.value); + saveInfo.visible = false; + } + } + + // 根据填入的 title找本地存储的信息,如果有需要询问是否覆盖 + function getTitleIndex(title) { + let savedArray = currentPageSavedArray.value; + let index = -1; + for (let i = 0; i < savedArray.length; i++) { + if (savedArray[i].title == title) { + index = i; + break; + } + } + return index; + } + + /** + * 获取左侧所有查询条件,如果没有/或者条件无效则返回false + */ + function getQueryInfo(isEmit = false) { + let arr = dynamicRowValues.values; + if (!arr || arr.length == 0) { + return false; + } + let fieldArray: any = []; + let fieldProps = fieldProperties.value; + for (let item of arr) { + if (item.field && (item.val || item.val === 0) && item.rule) { + let tempVal: any = toRaw(item.val); + if (tempVal instanceof Array) { + tempVal = tempVal.join(','); + } + let obj = { + field: item.field, + rule: item.rule, + val: tempVal, + }; + if (isEmit === true) { + //如果当前数据用于emit事件,需要设置dbtype和type + let prop = fieldProps[item.field]; + if (prop) { + obj['type'] = prop.view; + obj['dbType'] = prop.type; + } + } + fieldArray.push(obj); + } + } + if (fieldArray.length == 0) { + return false; + } + return fieldArray; + } + + /** + * 右侧数据 点击事件,重新将数据显示到左侧 + * @param key + * @param node + */ + function handleTreeSelect(key, { node }) { + console.log(key, node); + let title = node.dataRef.title; + let arr = currentPageSavedArray.value.filter((item) => item.title == title); + if (arr && arr.length > 0) { + // 拿到数据渲染 + let { content, type } = arr[0]; + let data = JSON.parse(content); + let rowsValues: SuperQueryItem[] = []; + for (let item of data) { + rowsValues.push(Object.assign({}, { key: randomString(16) }, item)); + } + dynamicRowValues.values = rowsValues; + matchType.value = type; + } + } + + /** + * 右侧数据 删除事件 + */ + function handleRemoveSaveInfo(title) { + console.log(title); + let index = getTitleIndex(title); + if (index >= 0) { + currentPageSavedArray.value.splice(index, 1); + $ls.set(saveInfo.saveCode, currentPageSavedArray.value); + } + } + + /*-----------------------右侧保存信息相关-end---------------------------*/ + + // 获取所有字段配置信息 + function getAllFields(json) { + // 获取所有配置 查询字段 是否联合查询 + const { properties, table, title } = json; + let allFields = {}; + let order = 1; + let treeData: TreeModel[] = []; + let mainNode: TreeModel = { + title, + value: table, + disabled: true, + children: [], + }; + treeData.push(mainNode); + Object.keys(properties).map((field) => { + let item = properties[field]; + if (item.view == 'table') { + // 子表字段 + // 联合查询开启才需要子表字段作为查询条件 + let subProps = item['properties']; + let subTableOrder = order * 100; + let subNode: TreeModel = { + title: item.title, + value: field, + disabled: true, + children: [], + }; + Object.keys(subProps).map((subField) => { + let subItem = subProps[subField]; + // 保证排序统一 + subItem['order'] = subTableOrder + subItem['order']; + // 子表的分隔符要改成逗号,后台才能识别 + let subFieldKey = field + ',' + subField; + allFields[subFieldKey] = subItem; + subNode.children!.push({ + title: subItem.title, + value: subFieldKey, + isLeaf: true, + order: subItem['order'], + }); + }); + orderField(subNode); + treeData.push(subNode); + order++; + } else { + // 主表字段 + let fieldKey = table + '@' + field; + allFields[fieldKey] = item; + mainNode.children!.push({ + title: item.title, + value: fieldKey, + isLeaf: true, + order: item.order, + }); + } + }); + orderField(mainNode); + return { allFields, treeData }; + } + + //根据字段的order重新排序 + function orderField(data) { + let arr = data.children; + arr.sort(function (a, b) { + return a.order - b.order; + }); + } + + function initDefaultValues(values) { + const { params, matchType } = values; + if (params) { + let rowsValues: SuperQueryItem[] = []; + for (let item of params) { + rowsValues.push(Object.assign({}, { key: randomString(16) }, item)); + } + dynamicRowValues.values = rowsValues; + matchType.value = matchType; + } + } + + return { + formRef, + init, + dynamicRowValues, + matchType, + registerModal, + handleSubmit, + handleCancel, + handleSave, + doSaveQueryInfo, + saveInfo, + saveTreeData, + handleRemoveSaveInfo, + handleTreeSelect, + fieldTreeData, + addOne, + removeOne, + setFormModel, + getSchema, + loading, + getQueryInfo, + initDefaultValues, + }; +} diff --git a/src/views/super/online/cgform/hooks/auto/useTableColumns.ts b/src/views/super/online/cgform/hooks/auto/useTableColumns.ts new file mode 100644 index 0000000..4390782 --- /dev/null +++ b/src/views/super/online/cgform/hooks/auto/useTableColumns.ts @@ -0,0 +1,648 @@ +import type { Ref } from 'vue'; +import type { ExtConfigType } from '../../types'; +import { HrefSlots, OnlineColumn } from '/@/components/jeecg/OnLine/types/onlineConfig'; +import { filterMultiDictObjs } from '/@/utils/dict/JDictSelectUtil'; +import { computed, defineAsyncComponent, h, reactive, ref, toRaw, unref, watch, markRaw } from 'vue'; +import { useRouter } from 'vue-router'; +import { Tag as ATag } from 'ant-design-vue'; +import { getFileAccessHttpUrl } from '/@/utils/common/compUtils'; +import { getAreaTextByCodeAnyLevel } from '/@/components/Form/src/utils/Area'; +import { createImgPreview } from '@/components/Preview'; +import { importViewsFile, _eval } from '/@/utils'; +import { useModal } from '/@/components/Modal'; +import LinkTableListPiece from '../../extend/linkTable/LinkTableListPiece.vue' +import { getToken } from "/@/utils/auth"; +import { downloadFile } from '/@/api/common/api'; +import { getWeekMonthQuarterYear, split } from '/@/utils'; +import { getItemColor } from "@/utils/dict/DictColors"; + +/** + * 获取实际列表需要的column配置 + * @param onlineTableContext 从数据库中查出来的数据 + * @param extConfigJson 扩展配置JSON + */ +export function useTableColumns(onlineTableContext, extConfigJson: Ref) { + // 获取路由器对象 href跳转用到 + let router = useRouter(); + + // 列信息 + const columns = ref>([]); + /** + * 20260309 + * liaozhiyang + * 【issues/9336】列宽拖动不了 + * */ + function applyResizableColumns(cols: OnlineColumn[]) { + cols.forEach((column) => { + if (!column.width) { + if (column.fieldType === 'date' || column.fieldType === 'Date') { + column.width = 120; + } else if (column.fieldType === 'link_table') { + column.width = 180; + } else { + column.width = 150; + } + } + column.resizable = true; + }); + } + + // 是否有bpm_status + //const hasBpmStatus = ref(false) + // 字典信息 + const dictOptionInfo = ref({}); + //已选择的值 + const selectedKeys = ref([]); + //选择的行记录 + //const selectRows = ref>([]); + // 选择列配置 --computed有问题 + const rowSelection = ref(null); + // 是否有滚动条 + let enableScrollBar = ref(true); + // table属性scroll + let tableScroll = computed(() => { + if (enableScrollBar.value == true) { + return undefined; + } else { + // X轴没有滚动条 + return { x: false }; + } + }); + + //用于 online列表的 某列的点击弹窗事件-弹窗显示其他表单 + const [registerOnlineHrefModal, { openModal: openOnlineHrefModal }] = useModal(); + const hrefMainTableId = ref('') + // 用于 online表单中 弹出别的表单 + const [registerPopModal, { openModal: openPopModal }] = useModal(); + const popTableId = ref('') + + // 对查询列信息的请求结果 处理方法 + function handleColumnResult(result, type = 'checkbox') { + // 字典设置 + dictOptionInfo.value = result.dictOptions; + // rowSelection设置 + if (result.checkboxFlag == 'Y') { + rowSelection.value = { + selectedRowKeys: selectedKeys, + onChange: onSelectChange, + type, + }; + } else { + rowSelection.value = null; + } + // 是否允许滚动条 + enableScrollBar.value = result.scrollFlag == 1; + + let dataColumns = result.columns; + dataColumns.forEach((column) => { + if (extConfigJson?.value?.canResizeColumn === 1) { + // update-begin--author:liaozhiyang---date:20260309---for:【issues/9336】列宽拖动不了 + applyResizableColumns([column]); + // update-end--author:liaozhiyang---date:20260309---for:【issues/9336】列宽拖动不了 + } + + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-4161】列支持固定功能 + if (column.fieldExtendJson) { + const json = JSON.parse(column.fieldExtendJson); + if (!!json.isFixed) { + column.fixed = 'left'; + } + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-4161】列支持固定功能 + // update-begin--author:liaozhiyang---date:20240517---for:【TV360X-129】增加富文本控件配置href跳转 + if (column.hrefSlotName && column.scopedSlots) { + const obj = result.fieldHrefSlots?.find((item) => item.slotName === column.hrefSlotName); + if (obj) { + column.fieldHref = obj; + } + } + // update-end--author:liaozhiyang---date:20240517---for:【TV360X-129】增加富文本控件配置href跳转 + Object.keys(column).map((key) => { + // 删掉空值的字段(不删除 空字符串('') 或 数字 0 ) + if (column[key] == null) { + delete column[key]; + } + }); + }); + + // href 跳转 + let fieldHrefSlots: HrefSlots[] = result.fieldHrefSlots; + const fieldHrefSlotKeysMap = {}; + fieldHrefSlots.forEach((item) => (fieldHrefSlotKeysMap[item.slotName] = item)); + + let tableColumns: OnlineColumn[] = []; + // 处理列中的 href 跳转和 dict 字典,使两者可以兼容存在 + tableColumns = handleColumnHrefAndDict(dataColumns, fieldHrefSlotKeysMap); + // 是否有 bpm_status字段 如果有,列表操作按钮需要增加提交流程按钮 + bpmStatusFilter(tableColumns); + + console.log('-----列表列配置----', tableColumns); + // 如果是树列表 需要设置第一列字段 及 第一列align + if (onlineTableContext.isTree() === true) { + // 找到第一列的配置 + let firstField = result.textField; + let index = -1; + for (let i = 0; i < tableColumns.length; i++) { + if (tableColumns[i].dataIndex == firstField) { + index = i; + break; + } + } + if (index > 0) { + //如果是0或是-1不需要处理 + let deleteColumns = tableColumns.splice(index, 1); + tableColumns.unshift(deleteColumns[0]); + } + //第一列居左 + if (tableColumns.length > 0) { + tableColumns[0].align = 'left'; + } + } + columns.value = tableColumns; + // 列发生了变化,需要重新渲染表格 + onlineTableContext.reloadTable(); + } + + /** + * 表格选择事件 [expose] + * @param selectedRowKeys + * @param selectRow + */ + function onSelectChange(selectedRowKeys, selectedRows) { + selectedKeys.value = selectedRowKeys; + onlineTableContext['selectedRows'] = toRaw(selectedRows); + onlineTableContext['selectedRowKeys'] = toRaw(selectedRowKeys); + } + + /** + * 处理列的href和字典翻译 + */ + function handleColumnHrefAndDict(columns: OnlineColumn[], fieldHrefSlotKeysMap: {}): OnlineColumn[] { + for (let column of columns) { + let { customRender, hrefSlotName, fieldType } = column; + // online 报表中类型配置为日期(yyyy-MM-dd ),但是实际展示为日期时间格式(yyyy-MM-dd HH:mm:ss) issues/3042 + if (fieldType == 'date' || fieldType == 'Date') { + column.customRender = ({ text }) => { + if (!text) { + return ''; + } + if (text.length > 10) { + return text.substring(0, 10); + } + return text; + }; + } else if (fieldType == 'link_table') { + // 关联记录列表展示 + // update-begin--author:liaozhiyang---date:20250318---for:【issues/7930】表格列表中支持关联记录配置是否只读 + const fieldExtendJson = column.fieldExtendJson ?? '{}'; + const json = JSON.parse(fieldExtendJson); + // update-end--author:liaozhiyang---date:20250318---for:【issues/7930】表格列表中支持关联记录配置是否只读 + column.customRender = ({ text, record }) => { + if (!text) { + return ''; + } + if(onlineTableContext.isPopList===true){ + // 如果是弹窗的列表,关联记录的列只支持数据翻译,不需要跳转逻辑 + return record[column.dataIndex+"_dictText"] + }else{ + let tempIdArray = (text+'').split(','); + //update-begin-author:taoyan date:2023-2-15 for: QQYUN-4286【online表单】主子表开启联合查询 功能测试报错打不开 + let tempLabelArray = []; + if(record[column.dataIndex+"_dictText"]){ + tempLabelArray = record[column.dataIndex+"_dictText"].split(','); + } + //update-end-author:taoyan date:2023-2-15 for: QQYUN-4286【online表单】主子表开启联合查询 功能测试报错打不开 + let renderResult:any = [] + for(let i=0;ihandleClickLinkTable(id, hrefSlotName, json.isListReadOnly) + } + ); + renderResult.push(renderObj) + } + if(renderResult.length==0){ + return '' + } + //如果需要显示全,但是会换行:display: flex;width: 100%;flex-wrap: wrap;flex-direction: row; + return h('div',{style:{'overflow':'hidden'}}, renderResult); + } + }; + } else if (fieldType === 'popup_dict') { + // update-begin--author:liaozhiyang---date:20240402---for:【QQYUN-8833】JPopupDict的列表翻译 + column.customRender = ({ text, record }) => { + const dict = record[column.dataIndex + '_dictText']; + if (dict != undefined) { + return record[column.dataIndex + '_dictText']; + } + return text; + }; + // update-end--author:liaozhiyang---date:20240402---for:【QQYUN-8833】JPopupDict的列表翻译 + } else { + if (!hrefSlotName && column.scopedSlots && column.scopedSlots.customRender) { + //【Online报表】字典和href互斥 这里通过fieldHrefSlotKeysMap 先找到是href的列 + if (fieldHrefSlotKeysMap.hasOwnProperty(column.scopedSlots.customRender)) { + hrefSlotName = column.scopedSlots.customRender; + } + } + // 如果 customRender 有值则代表使用了字典 + // 如果 hrefSlotName 有值则代表使用了href跳转 + // 两者可以兼容。兼容的具体思路为:先获取到字典替换的值,再添加href链接跳转 + if (customRender || hrefSlotName) { + let dictCode = customRender as string; + let replaceFlag = '_replace_text_'; + // 自定义渲染函数的列 需要手动配置ellipsis + column.ellipsis = true; + column.customRender = ({ text, record }) => { + let value = text; + const valueSpan: any[] = []; + const getValue = () => valueSpan.length ? valueSpan : value; + // 如果 dictCode 有值,就进行字典转换 + if (dictCode) { + if (dictCode.startsWith(replaceFlag)) { + let textFieldName = dictCode.replace(replaceFlag, ''); + value = record[textFieldName]; + } else { + const dictItems = filterMultiDictObjs(unref(dictOptionInfo)[dictCode], text); + value = dictItems.map((item) => { + if (item.hasColor) { + //获取字体颜色 + const fontColor = getItemColor(item.color); + valueSpan.push(h(ATag, { + color: item.color, + style: { + 'color': fontColor, + 'margin-left': '5px', + }, + }, () => item.text)) + } + return item.text; + }).join(','); + } + } + // 扩展参数设置列的内容长度 + if (column.showLength) { + if (value && value.length > column.showLength) { + value = value.substr(0, column.showLength) + '...'; + } + } + // 如果 hrefSlotName 有值,就生成一个 a 标签,包裹住字典替换后(或原生)的值 + if (hrefSlotName) { + let field = fieldHrefSlotKeysMap[hrefSlotName]; + if (field) { + return h( + 'a', + { + onClick: () => handleClickFieldHref(field, record), + }, + getValue(), + ); + } + } + return h('span', {}, getValue()); + }; + } + + // 老版本叫scopedSlots 新版叫slots + if (column.scopedSlots) { + // slot的列 需要手动配置ellipsis + column.ellipsis = true; + let slots = column.scopedSlots; + column['slots'] = slots; + delete column.scopedSlots; + } + } + } + return columns; + } + + /** + * href 点击事件 + * @param field + * @param record + */ + function handleClickFieldHref(field, record) { + let href = field.href; + let urlPattern = /(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_]*)?/; + let compPattern = /\.vue(\?.*)?$/; + let jsPattern = /{{([^}]+)}}/g; // {{ xxx }} + if (typeof href === 'string') { + if(href.startsWith('ONLINE:')){ + // ONLINE:tableId:fieldName + let arr = href.split(':') + hrefMainTableId.value = arr[1]; + let fieldName = arr[2]; + openOnlineHrefModal(true, { + isUpdate: true, + disableSubmit: true, + hideSub: true, + record:{id: record[fieldName]}, + }) + }else{ + href = href.trim().replace(/\${([^}]+)?}/g, (_s1, s2) => record[s2]); + // 执行 {{...}} JS增强语句 + if (jsPattern.test(href)) { + href = href.replace(jsPattern, function (text, s0) { + try { + // 支持 {{ ACCESS_TOKEN }} 占位符 + if (s0.trim() === 'ACCESS_TOKEN') { + return getToken() + } + + // update-begin--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + return _eval(s0); + // update-end--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + } catch (e) { + console.error(e); + return text; + } + }); + } + if (urlPattern.test(href)) { + window.open(href, '_blank'); + } else if (compPattern.test(href)) { + // 处理弹框 + openHrefCompModal(href); + } else { + router.push(href); + } + } + } + } + + // 样式 + const dialogStyle = { + top: 0, + left: 0, + height: '100%', + margin: 0, + padding: 0, + }; + + // update-begin--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x + // 弹窗属性配置 + const hrefComponent = reactive({ + model: { + title: '', + okText: '关闭', + width: '100%', + open: false, + destroyOnClose: true, + style: dialogStyle, + // dialogStyle: dialogStyle, + bodyStyle: { padding: '8px', height: 'calc(100vh - 108px)', overflow: 'auto', overflowX: 'hidden' }, + // 隐藏掉取消按钮 + cancelButtonProps: { style: { display: 'none' } }, + }, + on: { + ok: () => (hrefComponent.model.open = false), + cancel: () => (hrefComponent.model.open = false), + }, + is: null, + params: {}, + }); + // update-end--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x + + // 超链点击事件--> 打开一个modal窗口 + function openHrefCompModal(href) { + // 解析 href 参数 + let index = href.indexOf('?'); + let path = href; + if (index !== -1) { + path = href.substring(0, index); + let paramString = href.substring(index + 1, href.length); + let paramArray = paramString.split('&'); + let params = {}; + paramArray.forEach((paramObject) => { + let paramItem = paramObject.split('='); + params[paramItem[0]] = paramItem[1]; + }); + hrefComponent.params = params; + } else { + hrefComponent.params = {}; + } + // update-begin--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x + hrefComponent.model.open = true; + // update-end--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x + hrefComponent.model.title = '操作'; + hrefComponent.is = markRaw(defineAsyncComponent(() => importViewsFile(path))); + } + + //如果是树列表 操作列只能右侧固定 + let fixedAction:any = 'right'; + if(onlineTableContext.isTree()){ + fixedAction = 'right' + } + const actionColumn = reactive({ + title: '操作', + dataIndex: 'action', + slots: { customRender: 'action' }, + fixed: fixedAction, + align: 'center', + width: 150, + }); + + // 监听扩展参数的固定列配置,动态改变操作列的固定方式 +watch(() => extConfigJson?.value, () => { + if (extConfigJson?.value?.tableFixedAction === 1) { + actionColumn.fixed = extConfigJson?.value?.tableFixedActionType || 'right'; + if (onlineTableContext.isTree()) { + actionColumn.fixed = 'right'; + } + } + // update-begin--author:liaozhiyang---date:20260309---for:【issues/9336】列宽拖动不了 + if (extConfigJson?.value?.canResizeColumn === 1 && columns.value.length > 0) { + applyResizableColumns(columns.value); + onlineTableContext.reloadTable(); + } + // update-end--author:liaozhiyang---date:20260309---for:【issues/9336】列宽拖动不了 +}); + + // 流程按钮状态 + function bpmStatusFilter(tableColumns: OnlineColumn[]): boolean { + let flag = false; + for (let i = 0; i < tableColumns.length; i++) { + let item = tableColumns[i]; + let fieldName = item.dataIndex; + if (fieldName!.toLowerCase() == 'bpm_status') { + flag = true; + break; + } + } + onlineTableContext['hasBpmStatus'] = flag; + return flag; + } + + /** + * 文件 + * @param text + */ + function downloadRowFile(text, record, column, id) { + if (!text) { + return; + } + // update-begin--author:liaozhiyang---date:20240124---for:【QQYUN-8020】online 表单有多个文件走下载接口 + if (text.indexOf(',') > 0) { + downloadFile(`/online/cgform/field/download/${id}/${record.id}/${column.dataIndex}`, `文件_${record.id}.zip`); + } else { + const url = getFileAccessHttpUrl(text); + window.open(url); + } + // update-end--author:liaozhiyang---date:20240124---for:【QQYUN-8020】online 表单有多个文件走下载接口 + } + + /** + * 图片 + * @param text + */ + function getImgView(text) { + if (text && text.indexOf(',') > 0) { + // update-begin--author:liaozhiyang---date:20250325---for:【issues/7990】图片参数中包含逗号会错误的识别成多张图 + text = split(text)[0]; + // update-end--author:liaozhiyang---date:20250325---for:【issues/7990】图片参数中包含逗号会错误的识别成多张图 + } + return getFileAccessHttpUrl(text); + } + + /** + * 根据编码获取省市区文本 + * @param code + */ + function getPcaText(code, column) { + if (!code) { + return ''; + } + // update-begin--author:liaozhiyang---date:20260204---for:【QQYUN-14694】online支持配置独立的省、市、县 + let includeParent = true; + let fieldExtendJson = column?.fieldExtendJson; + let level = 3; + if (fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + if (['province', 'city', 'region'].includes(fieldExtendJson.displayLevel)) { + if (fieldExtendJson.displayLevel === 'province') { + level = 1; + } else if (fieldExtendJson.displayLevel === 'city') { + level = 2; + } else if (fieldExtendJson.displayLevel === 'region') { + level = 3; + } + includeParent = false; + } + } + return getAreaTextByCodeAnyLevel(code, includeParent, level as 1 | 2 | 3); + // update-end--author:liaozhiyang---date:20260204---for:【QQYUN-14694】online支持配置独立的省、市、县 + } + + /** + * 日期格式化 + * @param text + */ + function getFormatDate(text, column) { + if (!text) { + return ''; + } + let a = text; + if (a.length > 10) { + a = a.substring(0, 10); + } + // update-begin--author:liaozhiyang---date:20240430---for:【issues/6094】online 日期(年月日)控件增加年、年月,年周,年季度等格式 + let fieldExtendJson = column?.fieldExtendJson; + if (fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + if (fieldExtendJson.picker && fieldExtendJson.picker != 'default') { + const result = getWeekMonthQuarterYear(a); + return result[fieldExtendJson.picker]; + } + } + // update-end--author:liaozhiyang---date:20240430---for:【issues/6094】online 日期(年月日)控件增加年、年月,年周,年季度等格式 + return a; + } + + watch(selectedKeys, () => { + onlineTableContext['selectedRowKeys'] = toRaw(selectedKeys.value); + }); + + onlineTableContext['clearSelectedRow'] = () => { + selectedKeys.value = []; + onlineTableContext['selectedRows'] = []; + onlineTableContext['selectedRowKeys'] = []; + }; + + /** + * 预览列表 cell 图片 + * @param text + */ + function viewOnlineCellImage(text) { + if (text) { + let imgList: any = []; + // update-begin--author:liaozhiyang---date:20250325---for:【issues/7990】图片参数中包含逗号会错误的识别成多张图 + const arr = split(text); + // update-end--author:liaozhiyang---date:20250325---for:【issues/7990】图片参数中包含逗号会错误的识别成多张图 + for (let str of arr) { + if (str) { + imgList.push(getFileAccessHttpUrl(str)); + } + } + createImgPreview({ imageList: imgList }); + } + } + + /** + * link table控件在列表上显示 支持点击跳转表单 + * @param id + * @param hrefTableName + */ + const onlinePopModalRef = ref(); + async function handleClickLinkTable(id, hrefTableName, isListReadOnly){ + popTableId.value = hrefTableName; + let formStatus = await onlinePopModalRef.value.getFormStatus(); + // 判断当前表单是否支持编辑,不能编辑跳详情表单 + if(formStatus==true){ + hrefMainTableId.value = hrefTableName; + openOnlineHrefModal(true, { + isUpdate: true, + disableSubmit: true, + hideSub: true, + record:{id: id}, + }) + }else{ + openPopModal(true, { + isUpdate: true, + // update-begin--author:liaozhiyang---date:20250318---for:【issues/7930】表格列表中支持关联记录配置是否只读 + disableSubmit: isListReadOnly ? true : false, + // update-end--author:liaozhiyang---date:20250318---for:【issues/7930】表格列表中支持关联记录配置是否只读 + record: { + id: id + } + }); + } + } + + return { + columns, + actionColumn, + selectedKeys, + rowSelection, + enableScrollBar, + tableScroll, + downloadRowFile, + getImgView, + getPcaText, + getFormatDate, + handleColumnResult, + onSelectChange, + hrefComponent, + viewOnlineCellImage, + hrefMainTableId, + registerOnlineHrefModal, + registerPopModal, + openPopModal, + openOnlineHrefModal, + onlinePopModalRef, + popTableId, + handleClickFieldHref, + }; +} diff --git a/src/views/super/online/cgform/hooks/useCgformList.ts b/src/views/super/online/cgform/hooks/useCgformList.ts new file mode 100644 index 0000000..e9f195c --- /dev/null +++ b/src/views/super/online/cgform/hooks/useCgformList.ts @@ -0,0 +1,503 @@ +import { h, ref, nextTick } from 'vue'; +import { useRouter } from 'vue-router'; +import { Input, Radio, RadioGroup } from 'ant-design-vue'; +import { ActionItem, BasicColumn, FormSchema } from '/@/components/Table'; +import { useModal } from '/@/components/Modal'; +import { useDrawer } from '/@/components/Drawer'; +import { CgformPageType } from '../types'; +import { useListPage } from '/@/hooks/system/useListPage'; +import { + doBatchDelete, + doBatchRemove, + doSingleDelete, + doSingleRemove, + doCopyOnlineView, + doDatabaseSync, + doCopyTable, + list, +} from '../cgform.api'; +// import { useCopyModal } from './useCopyCgformModal'; +import { isArray } from '/@/utils/is'; +import {useCgformStore} from "../store/cgformState"; +import {showListDeleteConfirm} from "./useCgformWidgets.jsx"; + +interface IOptions { + // 页面类型 + pageType: CgformPageType; + designScope: string; + columns: BasicColumn[]; + formSchemas: FormSchema[]; +} + +export function useCgformList(options: IOptions) { + const isNormalPage = options.pageType === CgformPageType.normal; + const router = useRouter(); + const cgformStore = useCgformStore(); + const tableRef = ref(); + // 列表页面公共参数、方法 + const pageContext = useListPage({ + designScope: options.designScope, + tableProps: { + api: list, + columns: options.columns, + formConfig: { + //labelWidth: 200, + schemas: options.formSchemas, + }, + beforeFetch: (params) => { + let copyType = isNormalPage ? 0 : 1; + let physicId = isNormalPage ? undefined : router.currentRoute.value.params.code; + // TODO 等字典组件支持逗号分割后删除改代码【LOWCOD-2371】 + if (isArray(params.tableType_MultiString)) { + params.tableType_MultiString = params.tableType_MultiString.join(','); + } + return Object.assign(params, { copyType, physicId }); + }, + }, + }); + const { tableContext, createMessage: $message, createConfirm: $confirm } = pageContext; + // 注册table数据 + const [, { reload, setLoading }, { selectedRowKeys, selectedRows }] = tableContext; + + // 注册编辑弹窗 e3e3NcxzbUiGa53YYVXxWc8ADo5ISgQGx/gaZwERF91oAryDlivjqBv3wqRArgChupi+Y/Gg/swwGEyL0PuVFg== + const [registerCgformModal, cgformModal] = useModal(); + // 注册从数据库导入表单 + const [registerDbToOnlineModal, dbToOnlineModal] = useModal(); + // 注册ai建表弹窗 + const [registerAiToOnlineModal, aiToOnlineModal] = useModal(); + // 注册代码生成弹窗 + const [registerCodeGeneratorModal, codeGeneratorModal] = useModal(); + // 注册自定义按钮弹窗 + const [registerCustomButtonModal, customButtonModal] = useModal(); + // 注册JS增强弹窗 + const [registerEnhanceJsModal, enhanceJsModal] = useModal(); + // 注册SQL增强弹窗 + const [registerEnhanceSqlModal, enhanceSqlModal] = useModal(); + // 注册Java增强弹窗 + const [registerEnhanceJavaModal, enhanceJavaModal] = useModal(); + // 注册权限管理抽屉 + const [registerAuthManagerDrawer, authManagerDrawer] = useDrawer(); + // 注册角色授权弹窗 + const [registerAuthSetterModal, authSetterModal] = useModal(); + + function onAdd() { + cgformModal.openModal(true, { isUpdate: false }); + } + function onAiCreateTable(){ + aiToOnlineModal.openModal(true); + } + function onCreateAiTable() { + reload(); + } + let thatRecord: Nullable = null + + function onEdit(record) { + thatRecord = record + cgformModal.openModal(true, { isUpdate: true, record }); + } + + function onSuccess() { + if (thatRecord?.id) { + cgformStore.addChangedTable(thatRecord.id) + thatRecord = null + } + reload(); + } + + /** + * 删除事件 + */ + async function handleDelete(id) { + await doSingleDelete(id); + reload(); + } + + /** + * 移除事件 + */ + async function handleRemove(id) { + await doSingleRemove(id); + reload(); + } + + /** + * 删除单条数据 + * @param record + */ + function onDeleteRecord(record: Recordable) { + return showListDeleteConfirm(() => handleDelete(record.id), () => handleRemove(record.id)) + } + + // 批量删除 + function onDeleteBatch() { + let idList = selectedRowKeys.value as string[]; + if (idList.length <= 0) { + $message.warning('请先选择一条记录!'); + return; + } + showListDeleteConfirm( + () => executeDelete(doBatchDelete, idList, true), + () => executeDelete(doBatchRemove, idList, true) + ) + } + + /** + * 执行删除操作 + * @param fn 删除方法 + * @param idList 删除参数 + * @param clearSelected 清空选择 + */ + async function executeDelete(fn: Fn, idList: string[], clearSelected = false) { + try { + setLoading(true); + const res = await fn(idList); + reload(); + if (clearSelected) { + selectedRowKeys.value = []; + } + return res; + } finally { + setLoading(false); + } + return Promise.reject(); + } + + // 显示自定义按钮弹窗 + function onShowCustomButton() { + getSelectedRows(([row]) => customButtonModal.openModal(true, { row })); + } + + // 显示 js 增强弹窗 + function onShowEnhanceJs() { + getSelectedRows(([row]) => enhanceJsModal.openModal(true, { row })); + } + + // 显示 sql 增强弹窗 + function onShowEnhanceSql() { + getSelectedRows(([row]) => enhanceSqlModal.openModal(true, { row })); + } + + // 显示 java 增强弹窗 + function onShowEnhanceJava() { + getSelectedRows(([row]) => enhanceJavaModal.openModal(true, { row })); + } + + // 显示导入数据库表弹窗 + function onImportDbTable() { + dbToOnlineModal.openModal(true, {}); + } + + function getSelectedRows(fn: Fn, min = 1, max = 1) { + if (selectedRows.value.length < min) { + $message.warning(`请先至少选中 ${min} 条记录`); + } else if (selectedRows.value.length > max) { + $message.warning(`最多只能选中 ${min} 条记录`); + } else { + fn(selectedRows.value); + } + } + + // 显示代码生成弹窗 + function onGenerateCode() { + if (selectedRows.value.length === 0) { + $message.warning('请先选中一条记录'); + } else if (selectedRows.value.length > 1) { + $message.warning('代码生成只能选中一条记录'); + } else { + let row = selectedRows.value[0]; + if (!row) { + $message.warning('请选中当前页的数据!'); + } else if (row.isDbSynch != 'Y') { + $message.warning('请先同步数据库!'); + } else if (row.tableType == 3) { + $message.warning('请选中该表对应的主表'); + } else { + codeGeneratorModal.openModal(true, { code: row.id }); + } + } + } + + // 功能测试 + function onGoToTest(record) { + console.log(record); + if (record.isTree == 'Y') { + router.push({ path: '/online/cgformTreeList/' + record.id }); + } else { + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + switch (record.themeTemplate) { + case 'erp': + router.push({ path: '/online/cgformErpList/' + record.id }); + break; + case 'tab': + router.push({ path: '/online/cgformTabList/' + record.id }); + break; + case 'innerTable': + router.push({ path: '/online/cgformInnerTableList/' + record.id }); + break; + default: + router.push({ path: '/online/cgformList/' + record.id }); + break; + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-5803】online一对多Erp风格 + } + } + + // 同步数据库 + function onSyncDatabase(record) { + const syncMethod = ref('normal'); + const disabled = ref(false); + const modalFunc = $confirm({ + iconType: 'info', + title: '同步数据库', + content: () => + h( + 'div', + { + style: 'margin: 20px 0;', + }, + h( + RadioGroup, + { + value: syncMethod.value, + disabled: disabled.value, + 'onUpdate:value': (v) => (syncMethod.value = v), + }, + () => [h(Radio, { value: 'normal' }, () => '普通同步(保留表数据)'), h(Radio, { value: 'force' }, () => '强制同步(删除表,重新生成)')] + ) + ), + maskClosable: true, + okText: '开始同步', + async onOk() { + disabled.value = true; + modalFunc.update({ + maskClosable: false, + keyboard: false, + okText: '同步中…', + okButtonProps: { loading: disabled.value }, + cancelButtonProps: { disabled: disabled.value }, + }); + try { + await doDatabaseSync(record.id, syncMethod.value); + } catch (e: any) { + // update-begin--author:liaozhiyang---date:20240521---for:【TV360X-244】同步数据库没权限时提示两次 + // $message.warn(e.message || e); + // update-end--author:liaozhiyang---date:20240521---for:【TV360X-244】同步数据库没权限时提示两次 + } finally { + await reload(); + // update-begin--author:liaozhiyang---date:20250318---for:【issues/7931】勾选后再同步数据库,再点击代码生成无法生成代码 + nextTick(() => { + if (selectedRows.value.length) { + selectedRows.value.forEach((item) => { + const dataSource = tableRef.value.getDataSource() ?? []; + const findItem = dataSource.find((o) => o['id'] === item['id']); + if (findItem) { + Object.assign(item, findItem); + } + }); + } + }); + // update-end--author:liaozhiyang---date:20250318---for:【issues/7931】勾选后再同步数据库,再点击代码生成无法生成代码 + } + }, + }); + } + + // const { createCopyModal } = useCopyModal(); + const [registerAddressModal, addressModal] = useModal(); + + // 显示online地址弹窗 + function onShowOnlineUrl(record) { + let onlineUrl: string; + if (record.themeTemplate === 'erp') { + onlineUrl = `/online/cgformErpList/${record.id}`; + } else if (record.themeTemplate === 'innerTable') { + onlineUrl = `/online/cgformInnerTableList/${record.id}`; + } else if (record.themeTemplate === 'tab') { + onlineUrl = `/online/cgformTabList/${record.id}`; + } else if (record.isTree == 'Y') { + onlineUrl = `/online/cgformTreeList/${record.id}`; + } else { + onlineUrl = `/online/cgformList/${record.id}`; + } + addressModal.openModal(true, { + title: `菜单链接【${record.tableTxt}】`, + content: onlineUrl, + copyText: onlineUrl, + copyTitle: `${record.tableTxt}`, + record, + }); + } + + /** + * 显示复制表弹窗 + * @param record + */ + function onCopyTable(record) { + const tableName = ref(record.tableName + '_copy'); + $confirm({ + title: '复制表', + content: () => + h( + 'div', + { + style: 'margin: 20px 0;', + }, + [ + '请输入新表名:', + h(Input, { + value: tableName.value, + 'onUpdate:value': (v) => (tableName.value = v), + }), + ] + ), + iconType: 'info', + closable: true, + okText: '复制', + onOk() { + if (!tableName.value) { + $message.warning('请输入新表名'); + } else if (tableName.value === record.tableName) { + $message.warning('新表名和旧表名不能一致'); + } else { + doCopyTable(record.id, tableName.value).then(reload); + } + }, + }); + } + + /** + * 删除视图 + */ + function doDeleteView(record) { + $confirm({ + title: '删除', + content: '确定要删除该视图吗?', + iconType: 'warning', + closable: true, + maskClosable: true, + onOk: () => { + handleRemove(record.id) + } + }) + } + + /** + * 操作栏 + */ + function getTableAction(record) { + return [ + { + label: '编辑', + onClick: () => onEdit(record), + }, + ]; + } + + /** + * 下拉操作栏 + */ + function getDropDownAction(record): ActionItem[] { + return [ + { + label: '同步数据库', + onClick: () => onSyncDatabase(record), + ifShow: () => isNormalPage && record.isDbSynch != 'Y', + }, + { + // TODO 功能测试 + label: '功能测试', + class: ['low-app-hide'], + onClick: () => onGoToTest(record), + ifShow: () => (isNormalPage ? record.isDbSynch == 'Y' && record.tableType !== 3 : true), + }, + { + label: '配置地址', + class: ['low-app-hide'], + onClick: () => onShowOnlineUrl(record), + ifShow: () => (isNormalPage ? record.isDbSynch == 'Y' && record.tableType !== 3 : true), + }, + { + label: '权限控制', + onClick: () => authManagerDrawer.openDrawer(true, { cgformId: record.id, tableType: record.tableType}), + }, + { + label: '角色授权', + onClick: () => authSetterModal.openModal(true, { cgformId: record.id }), + }, + { + label: '视图管理', + class: ['low-app-hide'], + onClick: () => router.push(`/online/copyform/${record.id}`), + ifShow: () => isNormalPage && record.hascopy == 1, + }, + { + label: '生成视图', + class: ['low-app-hide'], + // @ts-ignore + popConfirm: { + title: '确定生成视图吗?', + placement: 'left', + confirm: () => { + setLoading(true); + doCopyOnlineView(record.id) + .then(() => { + $message.success('已成功生成视图'); + }) + .finally(() => { + setLoading(false); + reload(); + }); + }, + }, + ifShow: () => isNormalPage, + }, + { + label: '复制表', + onClick: () => onCopyTable(record), + ifShow: () => isNormalPage, + }, + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8485】online删除提示优化 + { + label: '删除', + onClick: () => onDeleteRecord(record), + ifShow: () => isNormalPage, + }, + // update-end--author:liaozhiyang---date:20240313---for:【QQYUN-8485】online删除提示优化 + { + label: '删除视图', + onClick: () => doDeleteView(record), + ifShow: () => !isNormalPage, + }, + ]; + } + + return { + router, + pageContext, + onAdd, + onAiCreateTable, + onSuccess, + onDeleteBatch, + onImportDbTable, + onGenerateCode, + onShowCustomButton, + onShowEnhanceJs, + onShowEnhanceSql, + onShowEnhanceJava, + onCreateAiTable, + getTableAction, + getDropDownAction, + registerCustomButtonModal, + registerEnhanceJsModal, + registerEnhanceSqlModal, + registerEnhanceJavaModal, + registerAuthManagerDrawer, + registerAuthSetterModal, + registerCgformModal, + registerDbToOnlineModal, + registerCodeGeneratorModal, + registerAiToOnlineModal, + registerAddressModal, + tableRef, + }; +} diff --git a/src/views/super/online/cgform/hooks/useCgformWidgets.jsx b/src/views/super/online/cgform/hooks/useCgformWidgets.jsx new file mode 100644 index 0000000..70bb3ff --- /dev/null +++ b/src/views/super/online/cgform/hooks/useCgformWidgets.jsx @@ -0,0 +1,42 @@ +import {Button, Space} from 'ant-design-vue' +import {useMessage} from "@/hooks/web/useMessage"; + +const {createConfirm} = useMessage() + +/** + * 创建列表删除时的提示弹窗 + * @param deleteFn 删除方法 + * @param removeFn 移出方法 + */ +export function showListDeleteConfirm(deleteFn, removeFn) { + const {destroy} = createConfirm({ + title: '确认删除表单吗?', + content: () => ( +

+
移除:仅删除配置,保留数据库表和数据
+
删除:同时删除数据库表和数据(不可恢复)
+
+ ), + iconType: 'info', + closable: true, + maskClosable: true, + width: 400, + footer: () => ( +
+ + + + + +
+ ), + }); + + function getFn(func) { + return async () => { + await func() + destroy() + } + } + +} diff --git a/src/views/super/online/cgform/hooks/useCopyCgformModal.ts b/src/views/super/online/cgform/hooks/useCopyCgformModal.ts new file mode 100644 index 0000000..b211e8b --- /dev/null +++ b/src/views/super/online/cgform/hooks/useCopyCgformModal.ts @@ -0,0 +1,94 @@ +import { isRef, unref, watch, Ref, ComputedRef } from 'vue'; +import Clipboard from 'clipboard'; +import { ModalOptionsEx, useMessage } from '/@/hooks/web/useMessage'; +import {buildUUID} from "/@/utils/uuid"; + +/** 带复制按钮的弹窗 */ +interface IOptions extends ModalOptionsEx { + // 要复制的文本,可以是一个 ref 对象,动态更新 + copyText: string | Ref | ComputedRef; + copyTitle: string; + componentName: string; +} + +const COPY_CLASS = 'copy-this-text'; +const CLIPBOARD_TEXT = 'data-clipboard-text'; + +export function useCopyModal() { + return { createCopyModal }; +} + +const { createMessage, createConfirm } = useMessage(); + +/** 创建复制弹窗 */ +function createCopyModal(options: Partial) { + const url = unref(options.copyText); + let menuComponentName = options.componentName? unref(options.componentName): null; + const insertMenuSql = `INSERT INTO sys_permission(id, parent_id, name, url, component, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_route, is_leaf, keep_alive, hidden, hide_tab, description, status, del_flag, rule_flag, create_by, create_time, update_by, update_time, internal_or_external) + VALUES ('${buildUUID()}', NULL, '${options.copyTitle}', '${url}', '1', '${menuComponentName}', NULL, 0, NULL, '1', 0.00, 0, NULL, 0, 1, 0, 0, 0, NULL, '1', 0, 0, 'admin', null, NULL, NULL, 0)`; + + + let modal = createConfirm({ + ...options, + iconType: options.iconType ?? 'info', + width: options.width ?? 500, + title: options.title ?? '复制', + closable: true, + maskClosable: options.maskClosable ?? true, + cancelText: '复制SQL', + okText: options.okText ?? '复制URL', + cancelButtonProps: { + class: 'copy-this-sql', + 'data-clipboard-text': insertMenuSql, + } as any, + okButtonProps: { + ...options.okButtonProps, + class: COPY_CLASS, + [CLIPBOARD_TEXT]: url, + } as any, + onOk() { + return new Promise((resolve: any) => { + const clipboard = new Clipboard('.' + COPY_CLASS); + clipboard.on('success', () => { + clipboard.destroy(); + createMessage.success('复制URL成功'); + resolve(); + }); + clipboard.on('error', () => { + createMessage.error('该浏览器不支持自动复制'); + clipboard.destroy(); + resolve(); + }); + }); + }, + onCancel() { + return new Promise((resolve: any) => { + const clipboard = new Clipboard('.copy-this-sql'); + clipboard.on('success', () => { + clipboard.destroy(); + createMessage.success('复制插入菜单SQL成功'); + resolve(); + }); + clipboard.on('error', () => { + createMessage.error('该浏览器不支持自动复制'); + clipboard.destroy(); + resolve(); + }); + }); + }, + }); + + // 动态更新 copyText + if (isRef(options.copyText)) { + watch(options.copyText, (copyText) => { + modal.update({ + okButtonProps: { + ...options.okButtonProps, + class: COPY_CLASS, + [CLIPBOARD_TEXT]: copyText, + } as any, + }); + }); + } + return modal; +} diff --git a/src/views/super/online/cgform/hooks/useGuide.ts b/src/views/super/online/cgform/hooks/useGuide.ts new file mode 100644 index 0000000..e80ff0c --- /dev/null +++ b/src/views/super/online/cgform/hooks/useGuide.ts @@ -0,0 +1,95 @@ +import { onMounted, ref } from 'vue'; +import { useDesign } from '/@/hooks/web/useDesign'; +import intro from 'intro.js'; +import 'intro.js/minified/introjs.min.css'; + +export const useGuide = () => { + const { prefixVar } = useDesign(''); + const aiCreateTable = ref(`${prefixVar}-online-aiCreateTable`); + const newAddBtn = ref(`${prefixVar}-online-newAddBtn`); + const customBtn = ref(`${prefixVar}-online-customBtn`); + const enhanceJsBtn = ref(`${prefixVar}-online-enhanceJsBtn`); + const enhanceSqlBtn = ref(`${prefixVar}-online-enhanceSqlBtn`); + const enhanceJavaBtn = ref(`${prefixVar}-online-enhanceJavaBtn`); + const exportDbBtn = ref(`${prefixVar}-online-exportDbBtn`); + const codeGeneratorBtn = ref(`${prefixVar}-online-codeGenerator`); + const key = `${prefixVar}-online-guide`; + const guide = () => { + let boot = intro(); + boot.setOptions({ + nextLabel: '下一步', + prevLabel: '上一步', + //skipLabel: '跳过', + doneLabel: '完成', + steps: [ + { + title: '第一步', + element: document.querySelector(`.${newAddBtn.value}`)!, + intro: '点击新增按钮,新建一个表。', + }, + { + title: '第二步', + intro: `在列表中找到刚才新建数据,在操作列点击"更多",选择"同步数据库"。`, + }, + { + title: '第三步', + intro: `在列表中找到刚才新建数据,在操作列点击"更多",选择"功能测试"。`, + }, + { + title: 'AI建表', + element: document.querySelector(`.${aiCreateTable.value}`)!, + intro: `输入修饰词即可通过AI创建工作表`, + }, + { + title: '代码生成', + element: document.querySelector(`.${codeGeneratorBtn.value}`)!, + intro: `选中一条记录,通过代码生成可将已配置好的表单,一键生成前后端代码,复杂需求可在此基础上进行二次开发。`, + }, + { + title: '自定义按钮', + element: document.querySelector(`.${customBtn.value}`)!, + intro: `选中一条记录,点击自定义按钮,配置按钮相关信息即可在当前记录的"功能测试"页面新增一个按钮`, + }, + { + title: 'JS强增', + element: document.querySelector(`.${enhanceJsBtn.value}`)!, + intro: `选中一条记录,通过js增强可为"自定义按钮"添加不同操作,可操作列表和表单数据等,也可以添加表单前置事件。`, + }, + { + title: 'SQL增强', + element: document.querySelector(`.${enhanceSqlBtn.value}`)!, + intro: `选中一条记录,通过增强SQL,可以关联修改业务数据。`, + }, + { + title: 'java增强', + element: document.querySelector(`.${enhanceJavaBtn.value}`)!, + intro: `选中一条记录,通过Java增强可在表单的增加、修改、和删除数据时实现额外的功能,类似spring中的AOP切面编程。`, + }, + { + title: '导入数据库表', + element: document.querySelector(`.${exportDbBtn.value}`)!, + intro: `可将已有数据库中的表,直接导入生成表单。`, + }, + ], + }); + boot.start(); + }; + onMounted(() => { + if (!localStorage.getItem(key)) { + setTimeout(() => { + guide(); + localStorage.setItem(key, '1'); + }, 2e3); + } + }); + return { + newAddBtn, + customBtn, + enhanceJsBtn, + enhanceSqlBtn, + exportDbBtn, + enhanceJavaBtn, + codeGeneratorBtn, + aiCreateTable + }; +}; diff --git a/src/views/super/online/cgform/hooks/useSchemas.ts b/src/views/super/online/cgform/hooks/useSchemas.ts new file mode 100644 index 0000000..56d4d43 --- /dev/null +++ b/src/views/super/online/cgform/hooks/useSchemas.ts @@ -0,0 +1,928 @@ +import { computed, h, Ref } from 'vue'; +import { FormSchema, RenderCallbackParams } from '/@/components/Form'; +import { Input, Button } from 'ant-design-vue'; +import { FolderOpenOutlined } from '@ant-design/icons-vue'; +import { bindMapFormSchema } from '/@/utils/common/compUtils'; +import { usePermission } from '/@/hooks/web/usePermission'; +import { rules } from '/@/utils/helper/validator'; + +const { isDisabledAuth } = usePermission(); + +export function useFormSchemas(_props, expandingConfig, handlers) { + type SpanType = 'one' | 'tow' | 'three'; + // 动态布局 + const mapFormSchema = bindMapFormSchema( + { + // 一列 + one: { + colProps: { xs: 24, sm: 24 }, + itemProps: { + labelCol: { xs: 24, sm: 2 }, + wrapperCol: { xs: 24, sm: 22 }, + }, + }, + // 两列 + tow: { + colProps: { xs: 24, sm: 12 }, + itemProps: { + labelCol: { xs: 24, sm: 4 }, + wrapperCol: { xs: 24, sm: 20 }, + }, + }, + // 三列 + three: { + colProps: { xs: 24, sm: 8 }, + itemProps: { + labelCol: { xs: 24, sm: 6 }, + wrapperCol: { xs: 24, sm: 18 }, + }, + }, + }, + 'three' + ); + + // 表单 FormSchemas + const formSchemas: FormSchema[] = [ + { label: '', field: 'id', component: 'Input', show: false }, + { label: '', field: 'tableVersion', component: 'Input', show: false }, + mapFormSchema({ + label: '表名', + field: 'tableName', + component: 'Input', + required: true, + // 如果版本号为1 表示未曾修改 未曾同步 可以修改表名 + dynamicDisabled: ({ model }) => model.tableVersion && model.tableVersion != 1, + dynamicRules: ({ model, schema }) => { + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8492】online表名校验不允许输入中文 + return [ + { + validator: (_, value) => { + return new Promise((resolve, reject) => { + let reg = /[\u4E00-\u9FA5]/g; + if (reg.test(value)) { + reject('不允许输入中文'); + } + resolve(); + }); + }, + }, + // update-begin--author:liaozhiyang---date:20240603---for:【TV360X-631】表名字段名表描述字段备注长度校验 + { + validator: (_, value) => { + return new Promise((resolve, reject) => { + if (value.length > 50) { + reject('表名最长50个字符'); + } + resolve(); + }); + }, + }, + // update-end--author:liaozhiyang---date:20240603---for:【TV360X-631】表名字段名表描述字段备注长度校验 + ...rules.duplicateCheckRule('onl_cgform_head', 'table_name', model, schema, true), + ]; + // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8492】online表名校验不允许输入中文 + }, + }), + mapFormSchema({ + label: '表描述', + field: 'tableTxt', + component: 'Input', + required: true, + // update-begin--author:liaozhiyang---date:20240603---for:【TV360X-631】表名字段名表描述字段备注长度校验 + dynamicRules: ({ model, schema }) => { + return [ + { + validator: (_, value) => { + return new Promise((resolve, reject) => { + if (value.length > 200) { + reject('表描述最长200个字'); + } + resolve(); + }); + }, + }, + ]; + }, + // update-end--author:liaozhiyang---date:20240603---for:【TV360X-631】表名字段名表描述字段备注长度校验 + }), + mapFormSchema({ + label: '表类型', + field: 'tableType', + component: 'Select', + defaultValue: 1, + componentProps: { + options: [ + { label: '单表', value: 1 }, + { label: '主表', value: 2 }, + { label: '附表', value: 3 }, + ], + onChange: handlers.onTableTypeChange, + allowClear: false, + }, + }), + // 此处为占位符,用于将 relationType 顶到最右边 + { + label: '', + field: 'relationType', + component: 'InputNumber', + render: () => '', + colProps: { xs: 0, sm: 17 }, + ifShow: fieldIfShow, + }, + mapFormSchema({ + label: '', + field: 'relationType', + component: 'RadioGroup', + defaultValue: 0, + componentProps: { + options: [ + { label: '一对多', value: 0 }, + { label: '一对一', value: 1 }, + ], + allowClear: false, + onChange: handlers.onRelationTypeChange, + }, + colProps: { xs: 24, sm: 4 }, + itemProps: { + colon: false, + labelCol: { xs: 0, sm: 0 }, + wrapperCol: { xs: 24, sm: 24 }, + }, + ifShow: fieldIfShow, + }), + mapFormSchema({ + label: '序号', + field: 'tabOrderNum', + component: 'InputNumber', + componentProps: { + style: { + width: '100%', + }, + }, + colProps: { xs: 24, sm: 3 }, + itemProps: { + labelCol: { xs: 24, sm: 7 }, + wrapperCol: { xs: 24, sm: 24 - 7 }, + }, + ifShow: fieldIfShow, + }), + mapFormSchema({ + label: '表单分类', + field: 'formCategory', + component: 'JDictSelectTag', + defaultValue: 'temp', + componentProps: { + dictCode: 'ol_form_biz_type', + allowClear: false, + }, + }), + mapFormSchema({ + label: '主键策略', + field: 'idType', + component: 'Select', + defaultValue: 'UUID', + componentProps: { + options: [ + { label: 'ID_WORKER(分布式自增)', value: 'UUID' }, + // { label: 'NATIVE(自增长方式)', value: 'NATIVE' }, + // { label: 'SEQUENCE(序列方式)', value: 'SEQUENCE' }, + ], + allowClear: false, + }, + }), + mapFormSchema({ + label: '序号名称', + field: 'idSequence', + component: 'Input', + componentProps: {}, + ifShow: fieldIfShow, + }), + mapFormSchema({ + label: '显示复选框', + field: 'isCheckbox', + component: 'Select', + defaultValue: 'Y', + componentProps: { + options: [ + { label: '是', value: 'Y' }, + { label: '否', value: 'N' }, + ], + allowClear: false, + }, + }), + mapFormSchema({ + label: '主题模板', + field: 'themeTemplate', + component: 'Select', + defaultValue: 'normal', + componentProps: { + options: [ + { label: '默认主题', value: 'normal' }, + { label: 'ERP主题(一对多)', value: 'erp' }, + { label: '内嵌子表主题(一对多)', value: 'innerTable' }, + { label: 'TAB主题(一对多)', value: 'tab' }, + ], + allowClear: false, + }, + // 单表时禁用该字段 + dynamicDisabled: ({ model }) => model.tableType === 1, + // update-begin--author:liaozhiyang---date:20231123---for:【QQYUN-7073】提示ERP、内嵌子表不支持联合查询 + dynamicRules() { + return [ + { + validator({}, value) { + const data = expandingConfig.value; + if (value === 'erp') { + if (data.joinQuery) { + return Promise.reject('ERP不支持联合查询功能'); + } + } else if (value === 'innerTable') { + if (data.joinQuery) { + return Promise.reject('内嵌子表不支持联合查询功能'); + } + } + return Promise.resolve(); + }, + }, + ]; + }, + // update-end--author:liaozhiyang---date:20231123---for:【QQYUN-7073】提示ERP、内嵌子表不支持联合查询 + }), + mapFormSchema({ + label: '表单风格', + field: 'formTemplate', + component: 'Select', + defaultValue: '1', + componentProps: { + options: [ + { label: '一列', value: '1' }, + { label: '两列', value: '2' }, + { label: '三列', value: '3' }, + { label: '四列', value: '4' }, + ], + placeholder: '请选择PC表单风格', + allowClear: false, + }, + }), + mapFormSchema({ + label: '移动表单风格', + field: 'formTemplateMobile', + component: 'Select', + defaultValue: '1', + componentProps: { + options: [ + { label: 'AntDesign模板', value: '1' }, + { label: 'Bootstrap模板', value: '2' }, + ], + placeholder: '请选择移动表单风格', + }, + // 暂时先隐藏 + ifShow: false, + }), + mapFormSchema({ + label: '滚动条', + field: 'scroll', + component: 'Select', + defaultValue: 1, + componentProps: { + options: [ + { label: '有', value: 1 }, + { label: '无', value: 0 }, + ], + allowClear: false, + }, + }), + mapFormSchema({ + label: '是否分页', + field: 'isPage', + component: 'Select', + defaultValue: 'Y', + componentProps: { + options: [ + { label: '是', value: 'Y' }, + { label: '否', value: 'N' }, + ], + allowClear: false, + }, + }), + mapFormSchema({ + label: '是否树', + field: 'isTree', + component: 'Select', + defaultValue: 'N', + componentProps: { + options: [ + { label: '是', value: 'Y' }, + { label: '否', value: 'N' }, + ], + onChange: handlers.onIsTreeChange, + allowClear: false, + }, + dynamicRules({ model }) { + return [ + { + validator({}, value) { + if (value === 'Y' && (model.tableType == 2 || model.tableType == 3)) { + return Promise.reject('主表和附表不支持树类型!'); + } else { + } + return Promise.resolve(); + }, + }, + ]; + }, + // update-begin--author:liaozhiyang---date:20240604---for:【TV360X-125】选择主表和附表时隐藏树表配置 + show({ model, values }) { + if (model.tableType == 2 || model.tableType == 3) { + model.isTree = 'N'; + handlers.onIsTreeChange('N'); + return false; + } + return true; + }, + // update-end--author:liaozhiyang---date:20240604---for:【TV360X-125】选择主表和附表时隐藏树表配置 + }), + mapFormSchema({ + // 空格不要删,否则布局会乱 + label: ' ', // 扩展配置 + field: 'extConfigJson', + component: 'Input', + slot: 'extConfigButton', + itemProps: { colon: false }, + // 一对多子表时隐藏扩展配置按钮 + ifShow: ({ model }) => !(model.tableType === 3 && model.relationType === 0), + }), + mapFormSchema({ + label: '树表单父ID', + field: 'treeParentIdField', + component: 'Input', + ifShow: fieldIfShow, + }), + mapFormSchema({ + label: '是否有子节点字段', + field: 'treeIdField', + component: 'Input', + show: false, + }), + mapFormSchema({ + label: '树开表单列', + field: 'treeFieldname', + required: true, + component: 'Input', + ifShow: fieldIfShow, + }), + mapFormSchema( + { + label: '附表', + field: 'subTableStr', + component: 'Input', + componentProps: { + disabled: true, + placeholder: ' ', + allowClear: false, + }, + ifShow: handlers.ifShowOfSubTableStr, + }, + 'one' + ), + ]; + + // 控制字段是否显示 + function fieldIfShow({ field, model }: RenderCallbackParams) { + switch (field) { + case 'relationType': + case 'tabOrderNum': + return model.tableType === 3; + case 'treeParentIdField': + case 'treeIdField': + case 'treeFieldname': + return model.isTree === 'Y'; + case 'idSequence': + return model.idType === 'SEQUENCE'; + } + return true; + } + + return { formSchemas }; +} + +/** 获取 扩展参数 FormSchemas */ +export function useExtendConfigFormSchemas(_props, handlers) { + type SpanType = 'left' | 'right'; + // formItem 的绑定值,统一布局 + const mapFormSchema = bindMapFormSchema( + { + left: { + colProps: { xs: 24, sm: 7 }, + itemProps: { + labelCol: { xs: 24, sm: 11 }, + wrapperCol: { xs: 24, sm: 13 }, + }, + style: { width: '100%' }, + }, + right: { + colProps: { xs: 24, sm: 17 }, + itemProps: { + labelCol: { xs: 24, sm: 3 }, + wrapperCol: { xs: 24, sm: 20 }, + }, + style: { width: '100%' }, + }, + }, + 'left' + ); + + // 一对一子表时只显示固定操作列、列宽拖动、表单Label长度 + function isNotOneToOneSub() { + const { tableType, relationType } = _props.parentForm.getFieldsValue(['tableType', 'relationType']); + return !(tableType === 3 && relationType === 1); + } + + const formSchemas: FormSchema[] = [ + // 弹窗 + mapFormSchema( + { + label: '弹窗默认全屏', + field: 'modelFullscreen', + ifShow: isNotOneToOneSub, + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + buttonStyle: 'solid', + }, + }, + 'left' + ), + mapFormSchema( + { + label: '弹窗宽度', + field: 'modalMinWidth', + component: 'InputNumber', + ifShow: isNotOneToOneSub, + componentProps: { + style: 'width: 80%', + placeholder: '弹窗最小宽度(单位:px)', + }, + // update-begin--author:liaozhiyang---date:20240520---for:【TV360X-77】弹窗全屏后,输入宽度框禁用 + dynamicDisabled: ({ model }) => model.modelFullscreen, + // update-end--author:liaozhiyang---date:20240520---for:【TV360X-77】弹窗全屏后,输入宽度框禁用 + }, + 'right' + ), + //----------------------------表单评论 begin----------------------------------------- + mapFormSchema( + { + label: '开启表单评论', + field: 'commentStatus', + component: 'RadioButtonGroup', + ifShow: isNotOneToOneSub, + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + buttonStyle: 'solid', + }, + }, + 'left' + ), + // 此处为占位符 + mapFormSchema( + { + label: '', + field: 'commentStatus', + component: 'InputNumber', + ifShow: isNotOneToOneSub, + render: () => '', + }, + 'right' + ), + // 启用联合查询 + mapFormSchema( + { + label: '启用联合查询', + field: 'joinQuery', + component: 'RadioButtonGroup', + ifShow: isNotOneToOneSub, + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + buttonStyle: 'solid', + onChange: handlers.onJoinQueryChange, + }, + }, + 'left' + ), + // 此处为占位符 + mapFormSchema( + { + label: '', + field: 'joinQuery', + component: 'InputNumber', + ifShow: isNotOneToOneSub, + render: () => '', + }, + 'right' + ), + // 积木报表打印 + mapFormSchema( + { + label: '集成积木报表', + field: 'reportPrintShow', + component: 'RadioButtonGroup', + ifShow: isNotOneToOneSub, + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + buttonStyle: 'solid', + onChange: handlers.onReportPrintShowChange, + }, + }, + 'left' + ), + mapFormSchema( + { + label: '报表地址', + field: 'reportPrintUrl', + component: 'Input', + ifShow: isNotOneToOneSub, + componentProps: { + style: 'width: 80%', + }, + dynamicDisabled: ({ model }) => !model.reportPrintShow, + dynamicRules: ({ model }) => { + return [ + { required: !!model.reportPrintShow, message: '请输入报表地址!' }, + { + validator({}, value) { + if (/\/jmreport\/view\/{积木报表ID}/.test(value)) { + return Promise.reject('请将{积木报表ID}替换为真实的积木报表ID!'); + } + return Promise.resolve(); + }, + }, + ]; + }, + }, + 'right' + ), + // update-begin--author:liaozhiyang---date:20231213---for:【QQYUN-7421】vue3先注释集成设计表单功能 + // mapFormSchema( + // { + // label: '集成设计表单', + // field: 'isDesForm', + // component: 'RadioButtonGroup', + // componentProps: { + // options: [ + // { label: '开启', value: 'Y' }, + // { label: '关闭', value: 'N' }, + // ], + // buttonStyle: 'solid', + // onChange: handlers.onIsDesformChange, + // }, + // }, + // 'left' + // ), + // mapFormSchema( + // { + // label: '表单编码', + // field: 'desFormCode', + // component: 'Input', + // componentProps: { + // style: 'width: 80%', + // }, + // dynamicDisabled: ({ model }) => model.isDesForm !== 'Y', + // dynamicRules: ({ model }) => { + // return [{ required: model.isDesForm === 'Y', message: '请输入表单编码!' }]; + // }, + // }, + // 'right' + // ), + // update-end--author:liaozhiyang---date:20231213---for:【QQYUN-7421】vue3先注释集成设计表单功能 + // 列表操作列 + mapFormSchema( + { + label: '固定操作列', + field: 'tableFixedAction', + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + buttonStyle: 'solid', + }, + defaultValue: 1, + }, + 'left' + ), + mapFormSchema( + { + label: '固定方式', + field: 'tableFixedActionType', + component: 'Select', + componentProps: { + options: [ + { label: '固定到右侧', value: 'right' }, + { label: '固定到左侧', value: 'left' }, + ], + style: 'width: 80%', + }, + defaultValue: 'right', + dynamicDisabled: ({ model }) => !model.tableFixedAction, + dynamicRules: ({ model }) => { + return [{ required: !!model.tableFixedAction, message: '请选择固定方式!' }]; + }, + }, + 'right' + ), + // 列宽拖动调整 + mapFormSchema( + { + label: '列宽拖动', + field: 'canResizeColumn', + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + buttonStyle: 'solid', + }, + defaultValue: 0, + }, + 'left' + ), + // 此处为占位符 + mapFormSchema( + { + label: '', + field: 'canResizeColumn', + component: 'InputNumber', + render: () => '', + }, + 'right' + ), + //--------------------------表单评论 end----------------------------------------- + // update-begin--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + mapFormSchema( + { + label: '表单Label长度', + field: 'formLabelLengthShow', + component: 'RadioButtonGroup', + componentProps: { + options: [ + { label: '开启', value: 1 }, + { label: '关闭', value: 0 }, + ], + buttonStyle: 'solid', + onChange: handlers.onFormLabelLengthShow, + }, + }, + 'left' + ), + mapFormSchema( + { + label: 'Label长度', + field: 'formLabelLength', + component: 'InputNumber', + componentProps: { + style: 'width: 80%', + placeholder: '自定义表单Label长度', + }, + dynamicDisabled: ({ model }) => !model.formLabelLengthShow, + dynamicRules: ({ model }) => { + return [{ required: !!model.formLabelLengthShow, message: '请填写表单label长度' }]; + }, + }, + 'right' + ), + // update-end--author:liaozhiyang---date:20240329---for:【QQYUN-7872】online表单label较长优化 + mapFormSchema( + { + label: '启用外部链接', + field: 'enableExternalLink', + component: 'RadioButtonGroup', + ifShow: isNotOneToOneSub, + componentProps: { + options: [ + {label: '开启', value: 1}, + {label: '关闭', value: 0}, + ], + buttonStyle: 'solid', + defaultValue: 0, + // onChange: handlers.onFormLabelLengthShow, + }, + }, + 'left' + ), + mapFormSchema( + { + label: '允许的操作', + field: 'externalLinkActions', + component: 'JCheckbox', + ifShow: isNotOneToOneSub, + componentProps: { + options: [ + {label: '新增', value: 'add'}, + {label: '编辑', value: 'edit'}, + {label: '详情', value: 'detail'}, + ], + }, + dynamicDisabled: ({model}) => !model.enableExternalLink, + }, + 'right' + ), + ]; + + return { formSchemas }; +} + +/** 获取 代码生成 FormSchemas */ +export function useCodeGeneratorFormSchemas(_, handlers, single: Ref) { + type SpanType = 'one' | 'tow' | 'towOne'; + // 动态布局 + const mapFormSchema = bindMapFormSchema( + { + // 一列 + one: { + colProps: { xs: 24, sm: 24 }, + itemProps: { + labelCol: { xs: 24, sm: 5 }, + wrapperCol: { xs: 24, sm: 16 }, + }, + }, + // 两列中的一列 + towOne: { + colProps: { xs: 24, sm: 24 }, + itemProps: { + labelCol: { xs: 24, sm: 3 }, + wrapperCol: { xs: 24, sm: 20 }, + }, + }, + // 两列 + tow: { + colProps: { xs: 24, sm: 12 }, + itemProps: { + labelCol: { xs: 24, sm: 6 }, + wrapperCol: { xs: 24, sm: 16 }, + }, + }, + }, + 'one' + ); + const getColSize = computed(() => (single.value ? 'one' : 'tow')); + // 由于需要动态改变布局,所以使用 computed + // e3e3NcxzbUiGa53YYVXxWc8ADo5ISgQGx/gaZwERF91oAryDlivjqBv3wqRArgChupi+Y/Gg/swwGEyL0PuVFg== + const formSchemas = computed(() => [ + mapFormSchema( + { + label: '代码生成目录', + field: 'projectPath', + render: ({ model, field }) => + h( + Input.Search, + { + value: model[field], + onChange: (e) => { + model[field] = e.target.value; + handlers.onProjectPathChange(e); + }, + onSearch: handlers.onProjectPathSearch, + disabled: isDisabledAuth('online:codeGenerate:projectPath'), + }, + { + enterButton: () => + h( + Button, + { + preIcon: 'ant-design:folder-open', + disabled: isDisabledAuth('online:codeGenerate:projectPath'), + }, + { + default: () => '浏览', + icon: () => h(FolderOpenOutlined), + } + ), + } + ), + component: 'InputSearch', + required: true, + // 如果版本号为1 表示未曾修改 未曾同步 可以修改表名 + }, + single.value ? 'one' : 'towOne' + ), + mapFormSchema( + { + label: '页面风格', + field: 'jspMode', + component: 'Select', + componentProps: { + options: handlers.jspModeOptions.value, + // update-begin--author:liaozhiyang---date:20240603---for:【TV360X-895】页面风格去掉x + allowClear: false, + // update-end--author:liaozhiyang---date:20240603---for:【TV360X-895】页面风格去掉x + }, + }, + getColSize.value + ), + mapFormSchema( + { + label: '功能说明', + field: 'ftlDescription', + component: 'Input', + }, + getColSize.value + ), + { label: '数据模型', field: 'jformType', component: 'Input', show: false }, + mapFormSchema( + { + label: '表名', + field: 'tableName_tmp', + required: true, + dynamicDisabled: true, + component: 'Input', + }, + getColSize.value + ), + mapFormSchema( + { + label: '实体类名', + field: 'entityName', + required: true, + component: 'Input', + componentProps: { + placeholder: '请输入实体类名(首字母大写)', + }, + }, + getColSize.value + ), + mapFormSchema( + { + label: '包名(小写)', + field: 'entityPackage', + component: 'Input', + rules: [{ required: true, pattern: /^[a-zA-Z0-9._]*$/, message: '包名必填,且只允许字母、数字、下划线、小数点组合' }], + }, + getColSize.value + ), + mapFormSchema( + { + label: '代码分层样式', + field: 'packageStyle', + component: 'Select', + componentProps: { + disabled: true, + options: [ + { label: '业务分层', value: 'service' }, + { label: '代码分层', value: 'project' }, + ], + }, + }, + getColSize.value + ), + mapFormSchema( + { + label: '页面代码', + field: 'vueStyle', + required: true, + component: 'Input', + defaultValue: 'vue3', + slot: 'pageCode', + // componentProps: { + // options: [ + // { label: 'Vue3(BasicForm)', value: 'vue3' }, + // { label: 'Vue3原生(a-form)', value: 'vue3Native' }, + // { label: 'Vue2', value: 'vue' }, + // ], + // }, + // update-begin--author:liaozhiyang---date:20240612---for:【TV360X-1057】代码生成页面代码鼠标移入给说明 + // dynamicPropskey: 'options', + // dynamicPropsVal: ({ model }) => { + // if (model.jspMode && (model.jspMode == 'innerTable' || model.jspMode == 'tab')) { + // return [ + // { value: 'vue3', label: '封装表单(BasicForm)' }, + // ]; + // } else { + // return [ + // { value: 'vue3', label: '封装表单(BasicForm)' }, + // { value: 'vue3Native', label: '原生表单(a-form)' }, + // ]; + // } + // }, + // update-end--author:liaozhiyang---date:20240612---for:【TV360X-1057】代码生成页面代码鼠标移入给说明 + }, + getColSize.value + ), + { label: '需要生成的代码', field: 'codeTypes', component: 'Input', show: false }, + ]); + + return { formSchemas }; +} diff --git a/src/views/super/online/cgform/hooks/useTableSync.ts b/src/views/super/online/cgform/hooks/useTableSync.ts new file mode 100644 index 0000000..aee2f5a --- /dev/null +++ b/src/views/super/online/cgform/hooks/useTableSync.ts @@ -0,0 +1,167 @@ +import type { Ref, ComputedRef } from 'vue'; +import { ref, computed, nextTick, inject } from 'vue'; +import { CgformModal } from '../types'; +import { JVxeColumn, JVxeTableInstance } from '/@/components/jeecg/JVxeTable/types'; +import { VALIDATE_FAILED } from '../cgform.data'; +import { pick } from 'lodash-es'; + +export function useTableSync(columns: Ref) { + const tables = inject('tables'); + const fullScreenRef = inject>('fullScreenRef'); + const vxetableHeight = inject>('vxetableHeight'); + const tableRef = ref(); + const loading = ref(false); + const dataSource = ref([]); + // 表格动态高度 + const tableHeight = computed(() => ({ + // 正常表格高度 + normal: fullScreenRef?.value ? vxetableHeight?.value : 260, + // 没有 toolbar 的表格高度 + noToolbar: fullScreenRef?.value ? vxetableHeight?.value : 320, + })); + + // 当前表的所有列字段key + const columnKeys = computed(() => ['id'].concat(columns.value.map((col) => col.key))); + + // 表格其他props + const tableProps = computed(() => { + return { + // 针对Online表单对虚拟滚动做出优化 + // 虚拟滚动配置,y轴(行数)大于xx条数据时启用虚拟滚动 + // update-begin--author:liaozhiyang---date:20231025---for:【QQYUN-6808】online编辑字段多了卡顿 + scrollY: { + enabled: true, + gt: 15, + }, + // 列数 + scrollX: { + enabled: true, + gt: 20, + }, + // update-begin--author:liaozhiyang---date:20231025---for:【QQYUN-6808】online编辑字段多了卡顿 + }; + }); + + // 校验并获取表格数据 + async function validateData(activeKey: string) { + let instance = tableRef.value!; + let errMap = await instance.fullValidateTable(); + if (errMap) { + throw { code: VALIDATE_FAILED, activeKey }; + } + // 过滤掉当前表中不存在的字段,以防止多个表冲突 + let tableData = instance.getTableData().map((data) => pick(data, columnKeys.value)); + // 获取被删除的ID + let deleteIds = instance.getDeleteData().map((d) => d.id); + return { tableData, deleteIds }; + } + + /** + * 设置数据源 + * @param data + * @param insert + */ + async function setDataSource(data, insert = false) { + if (insert) { + dataSource.value = []; + await nextTick(); + await tableRef.value!.addOrInsert(data, 0, null, { setActive: false }); + await nextTick(); + tableRef.value!.recalcDisableRows(); + } else { + dataSource.value = data; + // update-begin--author:liaozhiyang---date:20240705---for:【TV360X-1762】解决编辑时id可删除 + await nextTick(); + tableRef.value!.recalcDisableRows(); + // update-end--author:liaozhiyang---date:20240705---for:【TV360X-1762】解决编辑时id可删除 + } + } + + /** + * 同步列表,可以同步新增、修改、删除 + * @param dbTable + */ + function syncTable(dbTable: Ref) { + let targetTable = tableRef.value!; + let sourceTable = dbTable.value!.tableRef!; + + let removeIds = dbTable.value!.getRemoveIds(); + let sourceData = sourceTable.getXTable().internalData.tableFullData; + let targetData = targetTable.getXTable().internalData.tableFullData; + // update-begin--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + // 用 Map 索引 targetData,将查找从 O(N) 降到 O(1) + const targetMap = new Map(); + targetData.forEach((targetValue) => { + if (targetValue.id) { + targetMap.set(targetValue.id, targetValue); + } + }); + // 用 Set 索引 removeIds,将查找从 O(K) 降到 O(1) + const removeIdSet = new Set(removeIds); + // update-begin--author:liaozhiyang---date:20240724---for:【TV360X-1852】新增时删除所有字段再新增一个字段,保存报错 + // 先收集需要删除的ID,最后统一删除,避免在遍历中修改数组 + const toRemoveIds: string[] = []; + // update-end--author:liaozhiyang---date:20240724---for:【TV360X-1852】新增时删除所有字段再新增一个字段,保存报错 + // 批量收集需要修改的值,最后一次性调用 setValues + const batchSetValues: { rowKey: string; values: Recordable }[] = []; + // update-begin--author:liaozhiyang---date:20250407---for:【QQYUN-11801】ai建表字段 + // 提前缓存列默认值映射,避免新增每行时重复遍历 columns + const columnDefaults: { key: string; defaultValue: any }[] = []; + columns.value.forEach((column) => { + if (column.key !== 'dbFieldName' && column.key !== 'dbFieldTxt') { + columnDefaults.push({ key: column.key, defaultValue: column.defaultValue }); + } + }); + // update-end--author:liaozhiyang---date:20250407---for:【QQYUN-11801】ai建表字段 + sourceData.forEach((sourceValue) => { + const targetValue = targetMap.get(sourceValue.id); + if (targetValue) { + // 判断是否修改了值 + let dbFieldName = targetValue['dbFieldName']; + let dbFieldTxt = targetValue['dbFieldTxt']; + if (sourceValue.dbFieldName !== dbFieldName || sourceValue.dbFieldTxt !== dbFieldTxt) { + // 收集修改字段,稍后批量同步 + batchSetValues.push({ + rowKey: targetValue.id, + values: { + dbFieldName: sourceValue.dbFieldName, + dbFieldTxt: sourceValue.dbFieldTxt, + }, + }); + } + } else { + // target中不存在,说明是新增的 + let record = Object.assign({}, sourceValue); + // update-begin--author:liaozhiyang---date:20250407---for:【QQYUN-11801】ai建表字段 + for (const { key, defaultValue } of columnDefaults) { + if (record[key] == undefined) { + record[key] = defaultValue; + } + } + // update-end--author:liaozhiyang---date:20250407---for:【QQYUN-11801】ai建表字段 + targetTable.addRows(record); + } + }); + // 批量同步修改 + if (batchSetValues.length > 0) { + targetTable.setValues(batchSetValues); + } + // 处理删除:target中存在但已被删除的行 + // update-begin--author:liaozhiyang---date:20240724---for:【TV360X-1852】新增时删除所有字段再新增一个字段,保存报错 + targetData.forEach((targetValue) => { + if (targetValue.id && removeIdSet.has(targetValue.id)) { + toRemoveIds.push(targetValue.id); + } + }); + if (toRemoveIds.length > 0) { + setTimeout(() => { + toRemoveIds.forEach((id) => targetTable.removeRowsById(id)); + }, 0); + } + // update-end--author:liaozhiyang---date:20240724---for:【TV360X-1852】新增时删除所有字段再新增一个字段,保存报错 + // update-end--author:liaozhiyang---date:20260316---for:【QQYUN-13751】jVxetable优化 + return nextTick(); + } + + return { tables, tableRef, loading, dataSource, columnKeys, tableHeight, tableProps, syncTable, validateData, setDataSource }; +} diff --git a/src/views/super/online/cgform/index.vue b/src/views/super/online/cgform/index.vue new file mode 100644 index 0000000..bbbbd07 --- /dev/null +++ b/src/views/super/online/cgform/index.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/src/views/super/online/cgform/router/cgformRouter.ts b/src/views/super/online/cgform/router/cgformRouter.ts new file mode 100644 index 0000000..a919a0c --- /dev/null +++ b/src/views/super/online/cgform/router/cgformRouter.ts @@ -0,0 +1,48 @@ +import {router} from '/@/router'; +import {LAYOUT} from '/@/router/constant'; + +export function registerCgformRouter() { + router.addRoute({ + path: '/online-auto-cgform-router', + name: 'onl-auto-cgform-router', + component: LAYOUT, + redirect: '/online/cgform', + meta: { + title: 'OnlCgformAuto', + hideMenu: true, + hideBreadcrumb: true, + }, + children: [ + { + path: '/online/cgformList/:id', + name: 'OnlineAutoList', + component: () => import('../auto/default/OnlineAutoList.vue'), + meta: {title: 'AUTO在线表单'}, + }, + { + path: '/online/cgformTreeList/:id', + name: 'DefaultOnlineList', + component: () => import('../auto/tree/OnlineAutoTreeList.vue'), + meta: {title: 'AUTO在线树表单'}, + }, + { + path: '/online/cgformErpList/:id', + name: 'CgformErpList', + component: () => import('../auto/erp/OnlCgformErpList.vue'), + meta: {title: 'AUTO在线ERP表单'}, + }, + { + path: '/online/cgformInnerTableList/:id', + name: 'OnlCgformInnerTableList', + component: () => import('../auto/innerTable/OnlCgformInnerTableList.vue'), + meta: {title: 'AUTO在线一对多内嵌'}, + }, + { + path: '/online/cgformTabList/:id', + name: 'OnlCgformTabList', + component: () => import('../auto/tab/OnlCgformTabList.vue'), + meta: {title: 'AUTO在线Tab风格'}, + }, + ], + }) +} diff --git a/src/views/super/online/cgform/share/README.md b/src/views/super/online/cgform/share/README.md new file mode 100644 index 0000000..c8b3b41 --- /dev/null +++ b/src/views/super/online/cgform/share/README.md @@ -0,0 +1,3 @@ +# 目录说明 + +本目录为 Online表单 的外部链接功能目录 diff --git a/src/views/super/online/cgform/share/components/ShareView.vue b/src/views/super/online/cgform/share/components/ShareView.vue new file mode 100644 index 0000000..54a0486 --- /dev/null +++ b/src/views/super/online/cgform/share/components/ShareView.vue @@ -0,0 +1,157 @@ + + + + + \ No newline at end of file diff --git a/src/views/super/online/cgform/share/components/SingleView.vue b/src/views/super/online/cgform/share/components/SingleView.vue new file mode 100644 index 0000000..ea6ecb6 --- /dev/null +++ b/src/views/super/online/cgform/share/components/SingleView.vue @@ -0,0 +1,200 @@ + + + + + diff --git a/src/views/super/online/cgform/share/components/add/ShareAddView.vue b/src/views/super/online/cgform/share/components/add/ShareAddView.vue new file mode 100644 index 0000000..afa21ab --- /dev/null +++ b/src/views/super/online/cgform/share/components/add/ShareAddView.vue @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/src/views/super/online/cgform/share/components/edit/ShareEditView.vue b/src/views/super/online/cgform/share/components/edit/ShareEditView.vue new file mode 100644 index 0000000..0b90205 --- /dev/null +++ b/src/views/super/online/cgform/share/components/edit/ShareEditView.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/src/views/super/online/cgform/share/hooks/useCgformShare.ts b/src/views/super/online/cgform/share/hooks/useCgformShare.ts new file mode 100644 index 0000000..a6d6c5d --- /dev/null +++ b/src/views/super/online/cgform/share/hooks/useCgformShare.ts @@ -0,0 +1,120 @@ +import {ref} from 'vue'; +import {router} from "@/router"; + +import { + SHARE_ADD_ROUTER_NAME, + SHARE_DETAIL_ROUTER_NAME, + SHARE_LOGIN__ROUTER_NAME, + SHARE_UPDATE_ROUTER_NAME +} from "../route"; +import {useShareStore} from "../store/shareStore"; +import {useUserStore} from "@/store/modules/user"; +import {getCgformById, getCgformRecordById} from "../share.api"; +import {parseExtConfigJson} from "../../util/utils"; + +export function useCgformShare() { + const userStore = useUserStore() + const shareStore = useShareStore() + + const pageLoading = ref(true) + const pageErrorTip = ref('') + + async function initCgformShare() { + try { + // 检查url中的token + await shareStore.checkUrlToken() + const route = router.currentRoute.value + // 检查缓存中的token + if (!userStore.getToken) { + // 跳转到登录页 + router.push({ + name: SHARE_LOGIN__ROUTER_NAME, + query: { + redirect: encodeURIComponent(route.path), + } + }); + return + } + // 获取 Online表单 的信息 + const {id: formId} = route.params + if (!formId) { + pageErrorTip.value = '参数错误' + return + } + let res = await getCgformById(formId as string); + if (!res.success) { + pageErrorTip.value = res.message + return + } + const record = res.result + // 判断是否开启了外部链接 + const extJson = parseExtConfigJson(record); + if (!extJson?.enableExternalLink) { + pageErrorTip.value = '当前表单未开启外部链接' + return + } + // 判断是否支持当前操作 + let externalLinkActions = extJson.externalLinkActions.split(','); + if (route.name === SHARE_ADD_ROUTER_NAME) { + if (!externalLinkActions.includes('add')) { + pageErrorTip.value = '当前表单不支持外部新增' + return + } + } else if (route.name === SHARE_UPDATE_ROUTER_NAME) { + if (!externalLinkActions.includes('edit')) { + pageErrorTip.value = '当前表单不支持外部编辑' + return + } + } else if (route.name === SHARE_DETAIL_ROUTER_NAME) { + if (!externalLinkActions.includes('detail')) { + pageErrorTip.value = '当前表单不支持外部详情' + return + } + } else { + pageErrorTip.value = '未知的页面'; + return; + } + + // 判断表单类型 + if (record.tableType == 3) { + pageErrorTip.value = '不支持附表外部链接'; + return; + } + + shareStore.setCgformRecord(record); + + // 查询数据 + if (route.name === SHARE_UPDATE_ROUTER_NAME || route.name === SHARE_DETAIL_ROUTER_NAME) { + const {dataId} = route.params; + if (!dataId) { + pageErrorTip.value = '参数错误' + return + } + res = await getCgformRecordById(formId as string, dataId as string); + if (!res.success) { + pageErrorTip.value = res.message + return + } + const dataRecord = res.result + if (dataRecord?.id !== dataId) { + pageErrorTip.value = '数据不存在或已删除' + return + } + shareStore.setDataRecord(dataRecord); + } + + } catch (e: any) { + pageErrorTip.value = e?.message || e + console.error(e) + } finally { + pageLoading.value = false + } + } + + return { + pageLoading, + pageErrorTip, + + initCgformShare, + } +} \ No newline at end of file diff --git a/src/views/super/online/cgform/share/index.ts b/src/views/super/online/cgform/share/index.ts new file mode 100644 index 0000000..9c30024 --- /dev/null +++ b/src/views/super/online/cgform/share/index.ts @@ -0,0 +1,10 @@ +import {router} from "@/router"; + +import {routerBeforeEach, SHARE_LOGIN_ROUTE, SHARE_ROUTE} from "./route"; + +export function register() { + router.addRoute(SHARE_LOGIN_ROUTE); + router.addRoute(SHARE_ROUTE); + + router.beforeEach(routerBeforeEach); +} diff --git a/src/views/super/online/cgform/share/layouts/default/components/ErrorTip.vue b/src/views/super/online/cgform/share/layouts/default/components/ErrorTip.vue new file mode 100644 index 0000000..24c1f97 --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/default/components/ErrorTip.vue @@ -0,0 +1,44 @@ + + + + + \ No newline at end of file diff --git a/src/views/super/online/cgform/share/layouts/default/content/index.vue b/src/views/super/online/cgform/share/layouts/default/content/index.vue new file mode 100644 index 0000000..a411712 --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/default/content/index.vue @@ -0,0 +1,51 @@ + + + diff --git a/src/views/super/online/cgform/share/layouts/default/content/useContentContext.ts b/src/views/super/online/cgform/share/layouts/default/content/useContentContext.ts new file mode 100644 index 0000000..f12e77b --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/default/content/useContentContext.ts @@ -0,0 +1,17 @@ +import type { InjectionKey, ComputedRef } from 'vue'; +import { createContext, useContext } from '/@/hooks/core/useContext'; + +export interface ContentContextProps { + contentHeight: ComputedRef; + setPageHeight: (height: number) => Promise; +} + +const key: InjectionKey = Symbol(); + +export function createContentContext(context: ContentContextProps) { + return createContext(context, key, { native: true }); +} + +export function useContentContext() { + return useContext(key); +} diff --git a/src/views/super/online/cgform/share/layouts/default/content/useContentViewHeight.ts b/src/views/super/online/cgform/share/layouts/default/content/useContentViewHeight.ts new file mode 100644 index 0000000..b55b7e8 --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/default/content/useContentViewHeight.ts @@ -0,0 +1,42 @@ +import { ref, computed, unref } from 'vue'; +import { createPageContext } from '/@/hooks/component/usePageContext'; +import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; + +const headerHeightRef = ref(0); +const footerHeightRef = ref(0); + +export function useLayoutHeight() { + function setHeaderHeight(val) { + headerHeightRef.value = val; + } + function setFooterHeight(val) { + footerHeightRef.value = val; + } + return { headerHeightRef, footerHeightRef, setHeaderHeight, setFooterHeight }; +} + +export function useContentViewHeight() { + const contentHeight = ref(window.innerHeight); + const pageHeight = ref(window.innerHeight); + const getViewHeight = computed(() => { + return unref(contentHeight) - unref(headerHeightRef) - unref(footerHeightRef) || 0; + }); + + useWindowSizeFn( + () => { + contentHeight.value = window.innerHeight; + }, + 100, + { immediate: true } + ); + + async function setPageHeight(height: number) { + pageHeight.value = height; + } + + createPageContext({ + contentHeight: getViewHeight, + setPageHeight, + pageHeight, + }); +} diff --git a/src/views/super/online/cgform/share/layouts/default/header/components/index.ts b/src/views/super/online/cgform/share/layouts/default/header/components/index.ts new file mode 100644 index 0000000..4d3daef --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/default/header/components/index.ts @@ -0,0 +1,5 @@ +import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; + +export const UserDropDown = createAsyncComponent(() => import('./user-dropdown/index.vue'), { + loading: true, +}); diff --git a/src/views/super/online/cgform/share/layouts/default/header/components/user-dropdown/DropMenuItem.vue b/src/views/super/online/cgform/share/layouts/default/header/components/user-dropdown/DropMenuItem.vue new file mode 100644 index 0000000..7bc9d76 --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/default/header/components/user-dropdown/DropMenuItem.vue @@ -0,0 +1,34 @@ + + diff --git a/src/views/super/online/cgform/share/layouts/default/header/components/user-dropdown/index.vue b/src/views/super/online/cgform/share/layouts/default/header/components/user-dropdown/index.vue new file mode 100644 index 0000000..15ced9d --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/default/header/components/user-dropdown/index.vue @@ -0,0 +1,183 @@ + + + diff --git a/src/views/super/online/cgform/share/layouts/default/header/index.less b/src/views/super/online/cgform/share/layouts/default/header/index.less new file mode 100644 index 0000000..f3e15f9 --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/default/header/index.less @@ -0,0 +1,200 @@ +@header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger'; +@header-prefix-cls: ~'@{namespace}-online-share-layout-header'; +@breadcrumb-prefix-cls: ~'@{namespace}-layout-breadcrumb'; +@logo-prefix-cls: ~'@{namespace}-app-logo'; + +.@{header-prefix-cls} { + display: flex; + height: @header-height; + padding: 0; + margin-left: -1px; + line-height: @header-height; + color: @white; + background-color: @white; + align-items: center; + justify-content: space-between; + + &--mobile { + .@{breadcrumb-prefix-cls}, + .error-action, + .notify-item, + .lock-item, + .fullscreen-item { + display: none; + } + + .@{logo-prefix-cls} { + min-width: unset; + padding-right: 0; + + &__title { + display: none; + } + } + + .@{header-trigger-prefix-cls} { + padding: 0 4px 0 8px !important; + } + + .@{header-prefix-cls}-action { + padding-right: 4px; + } + } + + &--fixed { + position: fixed; + top: 0; + left: 0; + z-index: @layout-header-fixed-z-index; + width: 100%; + } + + &-logo { + height: @header-height; + min-width: 192px; + padding: 0 10px; + font-size: 14px; + + img { + width: @logo-width; + height: @logo-width; + margin-right: 2px; + } + } + + &-left { + display: flex; + height: 100%; + align-items: center; + + .@{header-trigger-prefix-cls} { + display: flex; + height: 100%; + padding: 1px 10px 0 10px; + cursor: pointer; + align-items: center; + + .anticon { + font-size: 22px; + } + + &.light { + &:hover { + background-color: @header-light-bg-hover-color; + } + + svg { + fill: #000; + } + } + + &.dark { + &:hover { + background-color: @header-dark-bg-hover-color; + } + } + } + } + + &-menu { + height: 100%; + min-width: 0; + flex: 1; + align-items: center; + } + + &-action { + display: flex; + min-width: 180px; + // padding-right: 12px; + align-items: center; + + &__item { + display: flex !important; + height: @header-height; + padding: 0 2px; + font-size: 1.2em; + cursor: pointer; + align-items: center; + + .ant-badge { + height: @header-height; + line-height: @header-height; + } + + .ant-badge-dot { + top: 10px; + right: 2px; + } + } + + span[role='img'] { + padding: 0 8px; + } + } + + &--light { + background-color: @white !important; + border-bottom: 1px solid @header-light-bottom-border-color; + border-left: 1px solid @header-light-bottom-border-color; + + .@{header-prefix-cls}-logo { + color: @text-color-base; + + &:hover { + background-color: @header-light-bg-hover-color; + } + } + + .@{header-prefix-cls}-action { + &__item { + color: @text-color-base; + + .app-iconify { + padding: 0 10px; + font-size: 16px !important; + } + + &:hover { + background-color: @header-light-bg-hover-color; + } + } + + &-icon, + span[role='img'] { + color: @text-color-base; + } + } + } + + &--dark { + background-color: @header-dark-bg-color !important; + // border-bottom: 1px solid @border-color-base; + border-left: 1px solid @border-color-base; + + .@{header-prefix-cls}-logo { + &:hover { + background-color: @header-dark-bg-hover-color; + } + } + + .@{header-prefix-cls}-action { + &__item { + .app-iconify { + padding: 0 10px; + font-size: 16px !important; + } + + .ant-badge { + span { + color: @white; + } + } + + &:hover { + background-color: @header-dark-bg-hover-color; + } + } + } + } +} diff --git a/src/views/super/online/cgform/share/layouts/default/header/index.vue b/src/views/super/online/cgform/share/layouts/default/header/index.vue new file mode 100644 index 0000000..f83a297 --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/default/header/index.vue @@ -0,0 +1,167 @@ + + + + diff --git a/src/views/super/online/cgform/share/layouts/default/index.vue b/src/views/super/online/cgform/share/layouts/default/index.vue new file mode 100644 index 0000000..26b74cd --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/default/index.vue @@ -0,0 +1,105 @@ + + + + diff --git a/src/views/super/online/cgform/share/layouts/login/AppLogin.api.ts b/src/views/super/online/cgform/share/layouts/login/AppLogin.api.ts new file mode 100644 index 0000000..ba60229 --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/login/AppLogin.api.ts @@ -0,0 +1,22 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + saveTenantJoinUser = '/sys/tenant/saveTenantJoinUser', + joinTenantByHouseNumber = '/sys/tenant/joinTenantByHouseNumber', +} + +/** + * 保存租户 + * @param params + */ +export const saveTenantJoinUser = (params) => { + return defHttp.post({ url: Api.saveTenantJoinUser, params }, { isTransformResponse: false }); +}; + +/** + * 加入租户 + * @param params + */ +export const joinTenantByHouseNumber = (params) => { + return defHttp.post({ url: Api.joinTenantByHouseNumber, params }, { isTransformResponse: false }); +}; diff --git a/src/views/super/online/cgform/share/layouts/login/AppLogin.vue b/src/views/super/online/cgform/share/layouts/login/AppLogin.vue new file mode 100644 index 0000000..72f611c --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/login/AppLogin.vue @@ -0,0 +1,396 @@ + + + + + diff --git a/src/views/super/online/cgform/share/layouts/login/AppThirdLogin.ts b/src/views/super/online/cgform/share/layouts/login/AppThirdLogin.ts new file mode 100644 index 0000000..c8b1644 --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/login/AppThirdLogin.ts @@ -0,0 +1,196 @@ +import { ref, unref, defineEmits } from 'vue'; +import { defHttp } from '/@/utils/http/axios'; +import { useGlobSetting } from '/@/hooks/setting'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { useUserStore } from '/@/store/modules/user'; +import { setThirdCaptcha, getCaptcha } from '/@/api/sys/user'; +import { useI18n } from '/@/hooks/web/useI18n'; + +export function useThirdLogin(emit) { + const { createMessage, notification } = useMessage(); + const { t } = useI18n(); + const glob = useGlobSetting(); + const userStore = useUserStore(); + //第三方类型 + const thirdType = ref(''); + //第三方登录相关信息 + const thirdLoginInfo = ref({}); + //状态 + const thirdLoginState = ref(false); + //注册或者绑定账户 + const bindingAccount = ref(false); + //第三方用户UUID + const thirdUserUuid = ref(''); + //提示窗 + const thirdConfirmShow = ref(false); + //绑定手机号 + const thirdPhone = ref(''); + //验证码 + const thirdCaptcha = ref(''); + //第三方登录 + function onThirdLogin(source) { + let url = `${glob.uploadUrl}/sys/thirdLogin/render/${source}`; + window.open( + url, + `login ${source}`, + 'height=500, width=500, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, resizable=no,location=n o, status=no' + ); + thirdType.value = source; + thirdLoginInfo.value = {}; + thirdLoginState.value = false; + let receiveMessage = function (event) { + let token = event.data; + if (typeof token === 'string') { + //如果是字符串类型 说明是token信息 + if (token === '登录失败') { + createMessage.warning(token); + } else if (token.includes('绑定手机号')) { + bindingAccount.value = true; + let strings = token.split(','); + thirdUserUuid.value = strings[1]; + emit('type', { loginType: 'thirdLogin' }); + } else { + doThirdLogin(token); + } + } else if (typeof token === 'object') { + //对象类型 说明需要提示是否绑定现有账号 + if (token['isObj'] === true) { + thirdConfirmShow.value = true; + thirdLoginInfo.value = { ...token }; + } + } else { + createMessage.warning('不识别的信息传递'); + } + //update-begin---author:wangshuai---date:2024-02-20---for:【QQYUN-8156】连续登录失败,导致失败提醒累加--- + window.removeEventListener('message', unref(receiveMessage),false); + //update-end---author:wangshuai---date:2024-02-20---for:【QQYUN-8156】连续登录失败,导致失败提醒累加--- + }; + window.addEventListener('message', receiveMessage, false); + } + // 根据token执行登录 + function doThirdLogin(token) { + if (unref(thirdLoginState) === false) { + thirdLoginState.value = true; + userStore.ThirdLogin({ token, thirdType: unref(thirdType) }).then((res) => { + console.log('res====>doThirdLogin', res); + if (res && res.userInfo) { + notification.success({ + message: t('sys.login.loginSuccessTitle'), + description: `${t('sys.login.loginSuccessDesc')}: ${res.userInfo.realname}`, + duration: 3, + }); + } else { + requestFailed(res); + } + }); + } + } + + function requestFailed(err) { + notification.error({ + message: '登录失败', + description: ((err.response || {}).data || {}).message || err.message || '请求出现错误,请稍后再试', + duration: 4, + }); + } + + //绑定手机号点击确定按钮 + function thirdHandleOk() { + if (!unref(thirdPhone)) { + cmsFailed('请输入手机号'); + } + if (!unref(thirdCaptcha)) { + cmsFailed('请输入验证码'); + } + let params = { + mobile: unref(thirdPhone), + captcha: unref(thirdCaptcha), + thirdUserUuid: unref(thirdUserUuid), + }; + defHttp.post({ url: '/sys/thirdLogin/bindingThirdPhone', params }, { isTransformResponse: false }).then((res) => { + if (res.success) { + bindingAccount.value = false; + doThirdLogin(res.result); + } else { + createMessage.warning(res.message); + } + }).catch((e)=>{ + createMessage.warning(e.message); + }); + } + function cmsFailed(err) { + notification.error({ + message: '登录失败', + description: err, + duration: 4, + }); + return; + } + + /** + * 登录并绑定 + */ + function loginAccountClick() { + emit('type', { loginType: 'login' }); + } + + /** + * 创建新账号 + */ + function registerAccountClick() { + emit('type', { loginType: 'register' }); + } + + /** + * 隐藏绑定页面 + */ + function hideBindThirdAccount() { + bindingAccount.value = false; + } + + /** + * 注册用户并且绑定第三方 + */ + async function createAccountBindThird(values) { + let params = { ...values }; + params.thirdUserUuid = unref(thirdUserUuid); + await defHttp + .put({ url: '/sys/thirdLogin/registerBindThirdAccount', params }, { isTransformResponse: false }) + .then((res) => { + if (res.success) { + bindingAccount.value = false; + doThirdLogin(res.result); + } else { + createMessage.warning(res.message); + } + }) + .catch((e) => { + createMessage.warning(e.message); + }) + } + + /** + * 绑定手机号 + * @param values + */ + function bindThirdAccount(values) { + thirdPhone.value = values.mobile; + thirdCaptcha.value = values.sms; + thirdHandleOk(); + } + + //返回数据和方法 + return { + thirdConfirmShow, + bindingAccount, + thirdHandleOk, + thirdPhone, + thirdCaptcha, + onThirdLogin, + loginAccountClick, + registerAccountClick, + hideBindThirdAccount, + bindThirdAccount, + createAccountBindThird, + }; +} diff --git a/src/views/super/online/cgform/share/layouts/login/component/AccountLoginForm.vue b/src/views/super/online/cgform/share/layouts/login/component/AccountLoginForm.vue new file mode 100644 index 0000000..b459218 --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/login/component/AccountLoginForm.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/src/views/super/online/cgform/share/layouts/login/component/AppForgetPassword.vue b/src/views/super/online/cgform/share/layouts/login/component/AppForgetPassword.vue new file mode 100644 index 0000000..52ceef9 --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/login/component/AppForgetPassword.vue @@ -0,0 +1,477 @@ + + + + + diff --git a/src/views/super/online/cgform/share/layouts/login/component/AppLoginHeader.vue b/src/views/super/online/cgform/share/layouts/login/component/AppLoginHeader.vue new file mode 100644 index 0000000..f33f983 --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/login/component/AppLoginHeader.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/src/views/super/online/cgform/share/layouts/login/component/AppNameEmail.vue b/src/views/super/online/cgform/share/layouts/login/component/AppNameEmail.vue new file mode 100644 index 0000000..113e899 --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/login/component/AppNameEmail.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/src/views/super/online/cgform/share/layouts/login/component/AppRegister.vue b/src/views/super/online/cgform/share/layouts/login/component/AppRegister.vue new file mode 100644 index 0000000..642a592 --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/login/component/AppRegister.vue @@ -0,0 +1,445 @@ + + + + + diff --git a/src/views/super/online/cgform/share/layouts/login/component/AppTenant.vue b/src/views/super/online/cgform/share/layouts/login/component/AppTenant.vue new file mode 100644 index 0000000..5f49c41 --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/login/component/AppTenant.vue @@ -0,0 +1,462 @@ + + + + + diff --git a/src/views/super/online/cgform/share/layouts/login/component/AppThirdForm.vue b/src/views/super/online/cgform/share/layouts/login/component/AppThirdForm.vue new file mode 100644 index 0000000..1ab047e --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/login/component/AppThirdForm.vue @@ -0,0 +1,75 @@ + + + diff --git a/src/views/super/online/cgform/share/layouts/login/component/PhoneLoginForm.vue b/src/views/super/online/cgform/share/layouts/login/component/PhoneLoginForm.vue new file mode 100644 index 0000000..1bf6c6a --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/login/component/PhoneLoginForm.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/src/views/super/online/cgform/share/layouts/login/index.vue b/src/views/super/online/cgform/share/layouts/login/index.vue new file mode 100644 index 0000000..2ad97ba --- /dev/null +++ b/src/views/super/online/cgform/share/layouts/login/index.vue @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/src/views/super/online/cgform/share/route/index.ts b/src/views/super/online/cgform/share/route/index.ts new file mode 100644 index 0000000..705fcb6 --- /dev/null +++ b/src/views/super/online/cgform/share/route/index.ts @@ -0,0 +1,99 @@ +import type {RouteRecordRaw} from 'vue-router'; +import {PageEnum} from "@/enums/pageEnum"; + +// 外部链接路由名称(新增) +export const SHARE_ADD_ROUTER_NAME = 'online-cgform-@formId-share-add' + +// 外部链接路由名称(编辑) +export const SHARE_UPDATE_ROUTER_NAME = 'online-cgform-@formId-share-u-@dataId' + +// 外部链接路由名称(详情) +export const SHARE_DETAIL_ROUTER_NAME = 'online-cgform-@formId-share-d-@dataId' + +export const SHARE_ROUTE: RouteRecordRaw = { + path: "/online/cgform/share", + name: "online-cgform-share", + component: () => import("../layouts/default/index.vue"), + meta: { + title: 'Online表单外部页面', + ignoreAuth: true, + }, + children: [ + { + path: ':id/add', + name: SHARE_ADD_ROUTER_NAME, + component: () => import("../components/add/ShareAddView.vue"), + meta: { + title: 'Online表单外部新增页面', + }, + }, + { + path: ':id/u/:dataId', + name: SHARE_UPDATE_ROUTER_NAME, + component: () => import("../components/edit/ShareEditView.vue"), + meta: { + title: 'Online表单外部编辑页面', + }, + }, + { + path: ':id/d/:dataId', + name: SHARE_DETAIL_ROUTER_NAME, + component: () => import("../components/edit/ShareEditView.vue"), + meta: { + title: 'Online表单外部详情页面', + }, + }, + ] +} + + +// 外部链接路由名称(详情) +export const SHARE_LOGIN__ROUTER_NAME = 'online-cgform-share-login' + +export const SHARE_LOGIN_ROUTE: RouteRecordRaw = { + path: "/online/cgform/share/login", + name: SHARE_LOGIN__ROUTER_NAME, + // component: () => import("../layouts/login/index.vue"), + component: () => import("../layouts/login/AppLogin.vue"), + meta: { + title: '登录 · Online表单', + ignoreAuth: true, + }, +} + +// +// Online表单外部链接页面 +const ONLINE_CGFORM_SHARE = '/online/cgform/share'; + +export async function routerBeforeEach(to: any, _from: any, next: any) { + // 如果是登录路由 + if (to.path === PageEnum.BASE_LOGIN) { + // 获取 redirect + let redirect = ((redirect: string) => { + if (!redirect) { + return ''; + } + // 判断 redirect 是否是 online表单 外部页面 + if (redirect.startsWith(ONLINE_CGFORM_SHARE)) { + return redirect; + } + redirect = decodeURIComponent(redirect) + if (redirect.startsWith(ONLINE_CGFORM_SHARE)) { + return redirect; + } + return ''; + })(to.query.redirect as string) + // 如果是则跳转到 online表单 外部专属登录页面 + if (redirect) { + redirect = redirect.split('?')[0]; + next({ + name: SHARE_LOGIN__ROUTER_NAME, + query: { + redirect: encodeURIComponent(redirect), + } + }); + return; + } + } + next(); +} diff --git a/src/views/super/online/cgform/share/share.api.ts b/src/views/super/online/cgform/share/share.api.ts new file mode 100644 index 0000000..e8aee7a --- /dev/null +++ b/src/views/super/online/cgform/share/share.api.ts @@ -0,0 +1,16 @@ +import {defHttp} from "@/utils/http/axios"; + +const enum Api { + getCgformById = '/online/cgform/head/queryById', + getCgformRecordById = '/online/cgform/api/form/{formId}/{recordId}', +} + +export const getCgformById = (id: string) => defHttp.get({ + url: Api.getCgformById, + params: {id} +}, {isTransformResponse: false}); + +export const getCgformRecordById = (formId: string, dataId: string) => defHttp.get({ + url: Api.getCgformRecordById.replace('{formId}', formId).replace('{recordId}', dataId), + params: {} +}, {isTransformResponse: false}); diff --git a/src/views/super/online/cgform/share/store/shareStore.ts b/src/views/super/online/cgform/share/store/shareStore.ts new file mode 100644 index 0000000..f84a72f --- /dev/null +++ b/src/views/super/online/cgform/share/store/shareStore.ts @@ -0,0 +1,69 @@ +import {defineStore} from 'pinia'; +import {useUserStoreWithOut} from "@/store/modules/user"; +import {removeCacheByDynKey} from "@/utils/auth"; +import {TOKEN_KEY} from "@/enums/cacheEnum"; +import {getUserInfo} from "@/api/sys/user"; + +interface StateType { + cgformRecord: Nullable, + dataRecord: Nullable, +} + +const userStore = useUserStoreWithOut() + +export const useShareStore = defineStore({ + id: 'online-cgform-share', + state: (): StateType => ({ + cgformRecord: null, + dataRecord: null, + }), + getters: { + + getCgformRecord(): Nullable { + return this.cgformRecord; + }, + getDataRecord(): Nullable { + return this.dataRecord; + }, + + }, + actions: { + + // 检查 url 参数是否携带token + async checkUrlToken(): Promise { + const token = new URLSearchParams(window.location.search).get('token'); + const flag = token != null && token.length > 0; + if (flag) { + userStore.setToken(token); + // 检查 token 是否有效 + try { + const res = await getUserInfo(); + if (!res?.userInfo) { + throw new Error('token无效'); + } else { + userStore.setUserInfo(res.userInfo) + if (res.sysAllDictItems) { + userStore.setAllDictItems(res.sysAllDictItems); + } + } + return true; + } catch (e) { + userStore.setToken(''); + removeCacheByDynKey(TOKEN_KEY) + return false; + } + } + return flag; + }, + + setCgformRecord(value: Nullable) { + this.cgformRecord = value; + }, + + setDataRecord(value: Nullable) { + this.dataRecord = value; + }, + + } +}); + diff --git a/src/views/super/online/cgform/store/cgformState.ts b/src/views/super/online/cgform/store/cgformState.ts new file mode 100644 index 0000000..0d36e2e --- /dev/null +++ b/src/views/super/online/cgform/store/cgformState.ts @@ -0,0 +1,38 @@ +import {defineStore} from 'pinia'; +import {store} from '/@/store'; + +interface CgformState { + // 近期被修改过的表,存储表id + changedTables: string[]; +} + +export const useCgformStore = defineStore({ + id: 'cgform-state', + state: (): CgformState => ({ + changedTables: [], + }), + getters: {}, + actions: { + /** + * 检查是否同步过数据库 + * @param tableId 表id + */ + checkIsChanged(tableId: string) { + return this.changedTables.includes(tableId); + }, + addChangedTable(tableId: string) { + this.changedTables.push(tableId); + }, + removeChangedTable(tableId: string) { + const index = this.changedTables.findIndex((item) => item === tableId); + if (index !== -1) { + this.changedTables.splice(index, 1); + } + }, + }, +}); + +// 在 setup 之外使用 +export function useCgformStoreWithOut() { + return useCgformStore(store); +} diff --git a/src/views/super/online/cgform/store/enhance.ts b/src/views/super/online/cgform/store/enhance.ts new file mode 100644 index 0000000..aecfe74 --- /dev/null +++ b/src/views/super/online/cgform/store/enhance.ts @@ -0,0 +1,41 @@ +import { store } from '/@/store'; +import { defineStore } from 'pinia'; +import { createLocalStorage } from '/@/utils/cache'; + +const ls = createLocalStorage(); +const ENHANCE_PRE = 'enhance_'; + +interface EnhanceStore { + enhanceJs: Recordable; +} + +export const useEnhanceStore = defineStore({ + id: 'online-cgform-enhance', + state: (): EnhanceStore => ({ + enhanceJs: {}, + }), + getters: {}, + actions: { + getEnhanceJs(code: string): Recordable[] { + this.enhanceJs[code] = ls.get(ENHANCE_PRE + code); + return this.enhanceJs[code]; + }, + addEnhanceJs(record: Recordable) { + if (!this.enhanceJs[record.code]) { + this.enhanceJs[record.code] = [{ ...record }]; + } else { + this.enhanceJs[record.code].push({ ...record }); + } + let enhanceJsArray = this.enhanceJs[record.code]; + while (enhanceJsArray.length > 16) { + enhanceJsArray.shift(); + } + ls.set(ENHANCE_PRE + record.code, enhanceJsArray); + }, + }, +}); + +// 在setup函数之外使用 +export function useEnhanceStoreWithOut() { + return useEnhanceStore(store); +} diff --git a/src/views/super/online/cgform/types/index.ts b/src/views/super/online/cgform/types/index.ts new file mode 100644 index 0000000..5e70224 --- /dev/null +++ b/src/views/super/online/cgform/types/index.ts @@ -0,0 +1,54 @@ +import { Ref } from 'vue'; +import DBAttributeTable from '../components/tables/DBAttributeTable.vue'; +import PageAttributeTable from '../components/tables/PageAttributeTable.vue'; +import CheckDictTable from '../components/tables/CheckDictTable.vue'; +import ForeignKeyTable from '../components/tables/ForeignKeyTable.vue'; +import IndexTable from '../components/tables/IndexTable.vue'; +import QueryTable from '../components/tables/QueryTable.vue'; + +// 定义弹窗form的类型 +export namespace CgformModal { + export type DBAttributeTableType = InstanceType; + export type PageAttributeTableType = InstanceType; + export type CheckDictTableType = InstanceType; + export type ForeignKeyTableType = InstanceType; + export type IndexTableType = InstanceType; + export type QueryTableType = InstanceType; + + export type TablesRef = { + dbTable: Ref; + pageTable: Ref; + checkTable: Ref; + fkTable: Ref; + idxTable: Ref; + queryTable: Ref; + }; +} + +// 定义页面类型,normal = 普通页面,copy=视图页面 +export enum CgformPageType { + normal, + copy, +} + +/** + * Online扩展配置类型 + */ +export type ExtConfigType = Partial<{ + // 是否启用积木报表打印(0否,1是) + reportPrintShow: number, + // 积木报表地址 + reportPrintUrl: string, + // 是否启用联合查询(0否,1是) + joinQuery: number, + // 弹窗是否默认全屏(0否,1是) + modelFullscreen: number, + // 弹窗的最小宽度(px) + modalMinWidth: string, + // 是否固定操作列(0否,1是) + tableFixedAction: number, + // 操作列固定方式 + tableFixedActionType: 'left' | 'right', + // 是否允许调整列表列宽 + canResizeColumn?: number, +}> diff --git a/src/views/super/online/cgform/types/onlineRender.ts b/src/views/super/online/cgform/types/onlineRender.ts new file mode 100644 index 0000000..f2f6ae3 --- /dev/null +++ b/src/views/super/online/cgform/types/onlineRender.ts @@ -0,0 +1,157 @@ +/** + * 因为online列表渲染 公用一个list 所以一些独有的属性配置 需要单独放到map中存放 避免切换路由冲突 + */ +interface SpecialConfig { + // 排序字段 + sortField: string; + // 排序方式 + sortType: 'asc' | 'desc'; + // 当前页数 + currentPage: number; + // 当前每页数目 + pageSize: number; + // 总页数 + total: number; + // 选中的行key + selectedRowKeys: string[]; + // 查询条件 + queryParam: object; + // href跳转至online列表页 会携带参数 + acceptHrefParams: object; + // 路由是否被缓存 + cache: boolean; + // 表描述 + description: string; + // 表名 + currentTableName: string; + // 是否启用表单设计器表单 + isDesForm: boolean; + // 表单设计器表单编码 + desFormCode: string; + isTree: boolean; + hasChildrenField?: string; +} + +interface Page { + current?: number; + pageSize?: number; + pageSizeOptions?: string[]; + showTotal?: Function; + showQuickJumper?: boolean; + showSizeChanger?: boolean; + total?: number; +} + +interface CgFormButton { + buttonCode?: string; + buttonName?: string; + buttonStyle?: string; + optType?: 'js' | 'bus'; + exp?: string; + buttonIcon?: string; +} + +/*** + * 表单页面的扩展配置 + */ +interface ExtendConfig { + modalMinWidth: number; +} + +/*** + * 表单字段的扩展配置解析结果 + */ +interface FieldExtends { + //上传数量 + uploadnum?: number | string; + + //限制大文本在列表页面的展示长度 + showLength?: number | string; + + //popup是否支持多选 + popupMulti?: boolean; + + //部门、用户组件 用于存储的字段名 + store?: string; + + //部门、用户组件 用于展示的字段名 + text?: string; + + //部门、用户组件 是否多选 + multiSelect?: boolean; + + //查询排序规则 + orderRule?: 'asc' | 'desc'; + + // 关联记录展示风格 card/select + showType?: string; + // 关联记录封面图 + imageField?: string; + // label长度 + labelLength?: number; +} + +// online提交表单和流程标识 默认只提交表单 +const SUBMIT_FLOW_KEY = 'jeecg_submit_form_and_flow'; +// 表单提交成功后回传表单数据的id key用于提交流程取id +const SUBMIT_FLOW_ID = 'flow_submit_id'; +// 表单提交成功后 在formData中添加设置表名的属性 +const ONL_FORM_TABLE_NAME = 'online_form_table_name'; +// 校验失败 +const VALIDATE_FAILED = 'validate-failed'; + +/* + * 查询表单的样式-label + * */ +const ONL_QUERY_LABEL_COL = { xs: { span: 24 }, sm: { span: 6 } }; + +/* + * 查询表单的样式-wrapper + * */ +const ONL_QUERY_WRAPPER_COL = { xs: { span: 24 }, sm: { span: 18 } }; + +/**setup*/ +const SETUP = 'setup'; + +/**EnhanceJS*/ +const ENHANCEJS = 'EnhanceJS'; + +/** + * 表单类型转换成查询类型 + * 普通查询和高级查询组件区别 :高级查询不支持联动组件 + */ +const FORM_VIEW_TO_QUERY_VIEW = { + password: 'text', + file: 'text', + image: 'text', + textarea: 'text', + umeditor: 'text', + markdown: 'text', + checkbox: 'list_multi', + radio: 'list', +}; + +/**下拉组件PopupContainer类选择器*/ +const POP_CONTAINER = '.jeecg-online-modal .ant-modal-content'; + +/**online权限前缀-现主要用于子表button*/ +const ONL_AUTH_PRE = 'online_'; + +export { + SpecialConfig, + Page, + CgFormButton, + ExtendConfig, + FieldExtends, + SUBMIT_FLOW_KEY, + SUBMIT_FLOW_ID, + VALIDATE_FAILED, + ONL_QUERY_LABEL_COL, + ONL_QUERY_WRAPPER_COL, + SETUP, + ENHANCEJS, + FORM_VIEW_TO_QUERY_VIEW, + POP_CONTAINER, + ONL_AUTH_PRE, + ONL_FORM_TABLE_NAME +}; diff --git a/src/views/super/online/cgform/util/FieldDefVal.ts b/src/views/super/online/cgform/util/FieldDefVal.ts new file mode 100644 index 0000000..ae18fd0 --- /dev/null +++ b/src/views/super/online/cgform/util/FieldDefVal.ts @@ -0,0 +1,452 @@ +import dayjs from 'dayjs'; +import { useUserStore } from '/@/store/modules/user'; +import { defHttp } from '/@/utils/http/axios'; +import { replaceAll, _eval } from '/@/utils'; +import * as CustomExpression from '/@/utils/desform/customExpression'; +import weekOfYear from 'dayjs/plugin/weekOfYear'; +import quarterOfYear from 'dayjs/plugin/quarterOfYear'; +dayjs.extend(weekOfYear); +dayjs.extend(quarterOfYear); +// 获取所有用户自定义表达式的Key +const ceKeys = Object.keys(CustomExpression); +// 将key用逗号拼接,可以拼接成方法参数,例:a,b,c --> function(a,b,c){} +const ceJoin = ceKeys.join(','); +// 将用户自定义的表达式按key的顺序放到数组中,可以使用 apply 传递给方法直接调用 +const $CE$ = ceKeys.map((key) => CustomExpression[key]); + +/** 普通规则表达式 #{...} */ +const normalRegExp = /#{([^}]+)?}/g; +/** 用户自定义规则表达式 {{...}} */ +const customRegExp = /{{([^}]+)?}}/g; +/** 填值规则表达式 ${...} */ +const fillRuleRegExp = /\${([^}]+)?}/g; + +/** action 类型 */ +export const ACTION_TYPES = { ADD: 'add', EDIT: 'edit', DETAIL: 'detail', RELOAD: 'reload' }; + +/** + * 将主表/一对一子表 默认值配置信息暂存 + * @param field + * @param item + * @param config + */ +export function initDefValueConfig(field, item, config) { + if (hasEffectiveValue(item.defVal)) { + const obj = { field: field, type: item.type, value: item.defVal, view: item.view, fieldExtendJson: item.fieldExtendJson } + // 避免重复添加 + const index = config.findIndex((c) => c.field === field); + if (index === -1) { + config.push(obj); + } else { + config[index] = obj; + } + } +} + +/** + * 将一对多子表 默认值配置信息暂存 + * @param item + * @param config + */ +export function initSubTableDefValueConfig(item, config) { + if (hasEffectiveValue(item.fieldDefaultValue)) { + config.push({ field: item.key, type: item.type, value: item.fieldDefaultValue }); + } +} + +/** + * 加载form组件默认值-仅用于新增页面 + * @param properties 字段配置 + * @param callback 回调传值 + * @param formData 表单值 + */ +export async function loadFormFieldsDefVal(properties, callback, formData?) { + if (Array.isArray(properties) && properties.length > 0) { + let formValues = {}; + for (let prop of properties) { + let { value, type, field } = prop; + value = await handleDefaultValue(value, ACTION_TYPES.ADD, formData||{}); + // 处理数字类型,如果type=number并且value有值 + if ('number' === type && value) { + // parseFloat() 可以直接处理字符串、整数、小数、null和undefined, + // 非数字类型直接返回NaN,不必担心报错 + value = Number.parseFloat(value); + } + // update-begin--author:liaozhiyang---date:20240517---for:【TV360X-321】日期组件(date)中设置了年,年月,年周,年季度等格式的默认值需要转化成YYYY-MM-DD + value = transformDefValDate(prop, value); + // update-end--author:liaozhiyang---date:20240517---for:【TV360X-321】日期组件(date)中设置了年,年月,年周,年季度等格式的默认值需要转化成YYYY-MM-DD + formValues[field] = value; + } + callback(formValues); + } +} + +/** + * 2024-05-22 + * liaozhiyang + * 日期组件(date)中设置了年,年月,年周,年季度等格式的默认值需要转化成YYYY-MM-DD + */ +function transformDefValDate(prop, value) { + const { type, field, view, fieldExtendJson } = prop; + if (view == 'date' && fieldExtendJson) { + const extendJson = JSON.parse(fieldExtendJson); + const { picker } = extendJson; + if (picker && picker != 'default' && value) { + let result; + try { + // 年 (2020) + if (picker === 'year') { + // update-begin--author:liaozhiyang---date:20240717---for:【TV360X-1790】年默认值设置YYYY-MM-DD格式,出现invaild Date + const data = value.split('-'); + const y = data[0]; + // update-end--author:liaozhiyang---date:20240717---for:【TV360X-1790】年默认值设置YYYY-MM-DD格式,出现invaild Date + result = dayjs().year(y).format('YYYY-MM-DD'); + } + // 年 - 月 (2024-02) + if (picker === 'month') { + const data = value.split('-'); + const y = data[0]; + const m = +data[1] + 1; + result = dayjs().year(y).month(m).format('YYYY-MM-DD'); + } + // 年 - 周 (2024-14周) + if (picker === 'week') { + const data = value.split('-'); + const y = data[0]; + const w = data[1].match(/^(\d+)周$/)[1]; + result = dayjs().year(y).week(w).format('YYYY-MM-DD'); + } + // 年 - 季度 (2024-Q4) + if (picker === 'quarter') { + const data = value.split('-'); + const y = data[0]; + const q = data[1].match(/^[Qq](\d)$/)[1]; + result = dayjs().year(y).quarter(q).format('YYYY-MM-DD'); + } + } catch (error) { + result = value; + } + return result; + } + return value; + } + return value; +} + +export async function loadOneFieldDefVal(field, item, formValues) { + let { defVal, type } = item; + if (hasEffectiveValue(defVal)) { + let value = await handleDefaultValue(defVal, ACTION_TYPES.ADD, {}); + if ('number' === type && value) { + // update-begin--author:liaozhiyang---date:20240618---for:online普通查询默认值范围查询不好使 + if (item.mode == 'group' && typeof value === 'string' && value.indexOf(',') != -1) { + const arr = value.split(','); + value = []; + if (arr[0]) { + value.push(Number.parseFloat(arr[0])); + } + if (arr[1]) { + value.push(Number.parseFloat(arr[1])); + } + } else { + value = Number.parseFloat(value); + } + // update-end--author:liaozhiyang---date:20240618---for:online普通查询默认值范围查询不好使 + } + formValues[field] = value; + } +} + +/** + * 判断给定的值是不是有效的 + */ +function hasEffectiveValue(val) { + if (val || val === 0) { + return true; + } + return false; +} + +/** 加载JEditableTable组件默认值 */ +export function loadFieldDefValForSubTable({ subForms, subTable, row, action, getFormData }) { + if (subTable && Array.isArray(subTable.columns) && subTable.columns.length > 0) { + subTable.columns.forEach(async (column) => { + let { key, fieldDefaultValue: defVal } = column; + eachHandler( + defVal, + action, + (value) => { + if (subForms.form) { + subForms.form.setFieldsValue({ [key]: value }); + } else { + // update-begin---author:sunjianlei Date:20200725 for:online功能测试,行操作切换成新的行编辑----------- + let v = [{ rowKey: row.id, values: { [key]: value } }]; + (subForms.jvt || subForms.jet).setValues(v); + // update-end---author:sunjianlei Date:20200725 for:online功能测试,行操作切换成新的行编辑------------ + } + }, + getFormData + ); + }); + } +} + +async function eachHandler(defVal, action, callback, getFormData) { + if (defVal != null) { + // 检查类型,如果类型错误则不继续运行 + if (checkExpressionType(defVal)) { + let value = await getDefaultValue(defVal, action, getFormData); + if (value != null) { + callback(value); + return value; + } + } else { + // 不合法的表达式直接返回不解析 + callback(defVal); + } + } +} + +/** + * 处理默认值 + * @param defVal + * @param action + * @param getFormData + */ +async function handleDefaultValue(defVal, action, getFormData) { + if (defVal != null) { + // 检查类型,如果类型错误则不继续运行 + if (checkExpressionType(defVal)) { + let value = await getDefaultValue(defVal, action, getFormData); + if (value != null) { + return value; + } + } + } + return defVal; +} + +/** + * 检查表达式类型是否合法,规则: + * 1、填值规则表达式不能和其他表达式混用 + * 2、每次只能填写一个填值规则表达式 + * 3、普通表达式和用户自定义表达式可以混用 + */ +export function checkExpressionType(defVal) { + // 获取各个表达式的数量 + let normalCount = 0, + customCount = 0, + fillRuleCount = 0; + defVal.replace(fillRuleRegExp, () => fillRuleCount++); + if (fillRuleCount > 1) { + logWarn(`表达式[${defVal}]不合法:只能同时填写一个填值规则表达式!`); + return false; + } + defVal.replace(normalRegExp, () => normalCount++); + defVal.replace(customRegExp, () => customCount++); + // 除填值规则外其他规则的数量 + let fillRuleOtherCount = normalCount + customCount; + if (fillRuleCount > 0 && fillRuleOtherCount > 0) { + logWarn(`表达式[${defVal}]不合法:填值规则表达式不能和其他表达式混用!`); + return false; + } + return true; +} + +/** 获取所有匹配的表达式 */ +function getRegExpMap(text, exp) { + let map = new Map(); + text.replace(exp, function (match, param) { + map.set(match, param.trim()); + return match; + }); + return map; +} + +/** 获取默认值,可以执行表达式,可以执行用户自定义方法,可以异步获取用户信息等 */ +async function getDefaultValue(defVal, action, getFormData) { + // 只有在 add 和 reload 模式下才执行填值规则 + if (action === ACTION_TYPES.ADD || action === ACTION_TYPES.RELOAD) { + // 判断是否是填值规则表达式,如果是就执行填值规则 + if (fillRuleRegExp.test(defVal)) { + let arr: any[] = [getFormData]; + return await executeRegExp(defVal, fillRuleRegExp, executeFillRuleExpression, arr); + } + } + // 只有在 add 模式下才执行其他表达式 + if (action === ACTION_TYPES.ADD) { + // 获取并替换所有常规表达式 + defVal = await executeRegExp(defVal, normalRegExp, executeNormalExpression); + // 获取并替换所有用户自定义表达式 + defVal = await executeRegExp(defVal, customRegExp, executeCustomExpression); + return defVal; + } + return null; +} + +async function executeRegExp(defVal, regExp, execFun, otherParams: any[] = []) { + let map = getRegExpMap(defVal, regExp); + for (let origin of map.keys()) { + let exp = map.get(origin); + let result = await execFun.apply(null, [exp, origin, ...otherParams]); + // 如果只有一个表达式,那么就不替换(因为一旦替换,类型就会被转成String),直接返回执行结果,保证返回的类型不变 + if (origin === defVal) { + return result; + } + defVal = replaceAll(defVal, origin, result); + } + return defVal; +} + +/** 执行【普通表达式】#{xxx} */ +async function executeNormalExpression(expression, origin) { + switch (expression) { + case 'date': + return dayjs().format('YYYY-MM-DD'); + case 'time': + return dayjs().format('HH:mm:ss'); + case 'datetime': + return dayjs().format('YYYY-MM-DD HH:mm:ss'); + default: + // 获取当前登录用户的信息 + let result = getUserInfoByExpression(expression); + if (result != null) { + return result; + } + // 没有符合条件的表达式,返回原始值 + return origin; + } +} + +/** 根据表达式获取相应的用户信息 */ +function getUserInfoByExpression(expression) { + const userStore = useUserStore(); + let userInfo = userStore.getUserInfo; + if (userInfo) { + switch (expression) { + case 'sysUserId': + return userInfo.id; + // 当前登录用户登录账号 + case 'sysUserCode': + case 'sys_user_code': + return userInfo.username; + // 当前登录用户真实名称 + case 'sysUserName': + return userInfo.realname; + // 当前登录用户部门编号 + case 'sysOrgCode': + case 'sys_org_code': + return userInfo.orgCode; + } + } + return null; +} + +/** 执行【用户自定义表达式】 {{xxx}} */ +async function executeCustomExpression(expression, origin) { + // update-begin--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + // 利用 eval 生成一个方法,这个方法的参数就是用户自定义的所有的表达式 + let fn = _eval(`(function (${ceJoin}){ return ${expression} })`); + // update-end--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + try { + // 然后调用这个方法,并把表达式传递进去,从而完成表达式的执行 + return fn.apply(null, $CE$); + } catch (e) { + // 执行失败,输出错误并返回原始值 + logError(e); + return origin; + } +} + +/** 执行【填值规则表达式】 ${xxx} */ +async function executeFillRuleExpression(expression, origin, getFormData) { + let formData = {}; + if (typeof getFormData === 'function') { + formData = getFormData(); + }else if(getFormData){ + formData = {...getFormData} + } + // 解析 url 参数 + expression = handleFillRuleQueryString(expression).exp; + let url = `/sys/fillRule/executeRuleByCode/${expression}`; + let { success, message, result } = await defHttp.put({ url, params: formData }, { isTransformResponse: false }); + if (success) { + return result; + } else { + logError(`填值规则(${expression})执行失败:${message}`); + return origin; + } +} + +// 处理填值规则 queryString 参数 +export function handleFillRuleQueryString(expression: string) { + let arr = expression.split('?'); + if (arr.length > 1) { + let queryString = ''; + let watchFields: string[] = []; + let str = arr[1]; + let pairs = str.split('&'); + pairs.forEach((pair, idx) => { + let [key, value] = pair.split('='); + value = value.trim(); + // 取出监听的字段,多个用逗号分隔 + if (key === 'onl_watch') { + watchFields = value.split(','); + } else { + queryString += `${key}=${value}`; + if (idx < pairs.length - 1) { + queryString += '&'; + } + } + }); + return { + exp: arr[0] + (queryString === '' ? '' : ('?' + queryString)), + watchFields: watchFields, + }; + } + return {exp: expression, watchFields: []}; +} + +export function handleFillRuleWatchKeysMap(properties: Recordable[]) { + const watchKeyMap = new Map(); + if (Array.isArray(properties) && properties.length > 0) { + for (let prop of properties) { + let {value: defVal, field} = prop; + if (defVal == null || defVal == '') { + continue; + } + // 检查类型,如果类型错误则不继续运行 + if (!checkExpressionType(defVal)) { + continue; + } + // 判断是否是填值规则,如果是就解析填值规则 + if (fillRuleRegExp.test(defVal)) { + let map = getRegExpMap(defVal, fillRuleRegExp); + for (let origin of map.keys()) { + let exp = map.get(origin); + const {watchFields} = handleFillRuleQueryString(exp); + for (const watchField of watchFields) { + let arr = watchKeyMap.get(watchField) + if (!Array.isArray(arr)) { + arr = [] + watchKeyMap.set(watchField, arr) + } + if (arr.includes(field)) { + continue; + } + arr.push(field); + } + } + } + } + } + return watchKeyMap; +} + +function logWarn(message) { + console.warn('[loadFieldDefVal]:', message); +} + +function logError(message) { + console.error('[loadFieldDefVal]:', message); +} diff --git a/src/views/super/online/cgform/util/constant.ts b/src/views/super/online/cgform/util/constant.ts new file mode 100644 index 0000000..d4079a6 --- /dev/null +++ b/src/views/super/online/cgform/util/constant.ts @@ -0,0 +1,9 @@ + + +export const ERP = 'erp'; +export const Tree = 'tree'; +export const NORMAL = 'normal'; +export const INNER_TABLE = 'innerTable'; +export const TAB = 'tab'; +export const LABELLENGTH = 6; +export const ERPSUBTABLE = 'erpSubTable'; diff --git a/src/views/super/online/cgform/util/utils.ts b/src/views/super/online/cgform/util/utils.ts new file mode 100644 index 0000000..b111c5d --- /dev/null +++ b/src/views/super/online/cgform/util/utils.ts @@ -0,0 +1,19 @@ +import {ExtConfigDefaultJson} from "../cgform.data"; + +// 初始化扩展JSON +export function parseExtConfigJson(record: Recordable) { + // 解析扩展JSON + let parseJSON = {}; + if (record.extConfigJson) { + try { + parseJSON = JSON.parse(record.extConfigJson); + } catch (e) { + console.error('online扩展JSON转换失败:', e); + } + } + // 从数据库中取值,并合并 + return Object.assign({}, ExtConfigDefaultJson, parseJSON, { + isDesForm: record.isDesForm || 'N', + desFormCode: record.desFormCode || '', + }); +} diff --git a/src/views/super/online/cgreport/auto/OnlCgReportList.vue b/src/views/super/online/cgreport/auto/OnlCgReportList.vue new file mode 100644 index 0000000..0adad11 --- /dev/null +++ b/src/views/super/online/cgreport/auto/OnlCgReportList.vue @@ -0,0 +1,28 @@ + + + diff --git a/src/views/super/online/cgreport/cgreport.api.ts b/src/views/super/online/cgreport/cgreport.api.ts new file mode 100644 index 0000000..8345bc8 --- /dev/null +++ b/src/views/super/online/cgreport/cgreport.api.ts @@ -0,0 +1,96 @@ +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; +const { createConfirm } = useMessage(); + +enum Api { + list = '/online/cgreport/head/list', + save = '/online/cgreport/head/add', + edit = '/online/cgreport/head/editAll', + deleteOne = '/online/cgreport/head/delete', + deleteBatch = '/online/cgreport/head/deleteBatch', + onlCgreportParamList = '/online/cgreport/param/listByHeadId', + onlCgreportItemList = '/online/cgreport/item/listByHeadId', + getDataSourceList = '/sys/dataSource/options', + getParamsInfo = '/online/cgreport/api/getParamsInfo/', + analyzeSql = '/online/cgreport/head/parseSql', +} + +/** + * 查询子表数据 + * @param params + */ +export const onlCgreportParamList = Api.onlCgreportParamList; +/** + * 查询子表数据 + * @param params + */ +export const onlCgreportItemList = Api.onlCgreportItemList; +/** + * 列表接口 + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); + +/** + * 删除单个 + */ +export const deleteOne = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 批量删除 + * e3e3NcxzbUiGa53YYVXxWc8ADo5ISgQGx/gaZwERF91oAryDlivjqBv3wqRArgChupi+Y/Gg/swwGEyL0PuVFg== + * @param params + */ +export const batchDelete = (params, handleSuccess) => { + createConfirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + iconType: 'warning', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; +/** + * 保存或者更新 + * @param params + */ +export const saveOrUpdate = (params, isUpdate) => { + if (isUpdate) { + return defHttp.put({ url: Api.edit, params }); + } else { + return defHttp.post({ url: Api.save, params }); + } +}; + +/** + * 获取参数地址 + * @param params + */ +export const getReportParam = (id) => { + return defHttp.get({ url: Api.getParamsInfo + id }); +}; + +/** + * 获取数据源列表 + */ +export const getDataSourceList = () => { + return defHttp.get({ url: Api.getDataSourceList }); +}; + +/** + * 解析sql + * @param params + */ +export const analyzeSql = (params) => { + return defHttp.get({ + url: Api.analyzeSql + '?' + params, + }); +}; diff --git a/src/views/super/online/cgreport/cgreport.data.ts b/src/views/super/online/cgreport/cgreport.data.ts new file mode 100644 index 0000000..e73af55 --- /dev/null +++ b/src/views/super/online/cgreport/cgreport.data.ts @@ -0,0 +1,345 @@ +import { BasicColumn } from '/@/components/Table'; +import { FormSchema } from '/@/components/Table'; +import { JVxeTypes, JVxeColumn } from '/@/components/jeecg/JVxeTable/types'; +import { duplicateCheckDelay } from '/@/views/system/user/user.api'; +import { getDataSourceList } from './cgreport.api'; +import {usePermissionStore} from "/@/store/modules/permission"; +const permissionStore = usePermissionStore(); +//列表数据 +export const columns: BasicColumn[] = [ + { + title: '报表名字', + align: 'center', + dataIndex: 'name', + width: 120, + }, + { + title: '报表编码', + align: 'center', + dataIndex: 'code', + width: 120, + }, + { + title: '报表SQL', + align: 'center', + dataIndex: 'cgrSql', + width: 360, + }, + { + title: '数据源', + align: 'center', + dataIndex: 'dbSource', + customRender: ({ text, record }) => { + return record["dbSource_dictText"] ? record["dbSource_dictText"] : text + }, + width: 120, + }, + { + title: '创建时间', + align: 'center', + dataIndex: 'createTime', + width: 120, + }, +]; +//查询数据 +export const searchFormSchema: FormSchema[] = [ + { + label: '报表名称', + field: 'name', + component: 'JInput', + }, + { + label: '报表编码', + field: 'code', + component: 'JInput', + }, +]; + +// 编码校验 仅online报表用 +const codePattern = /^[a-z|A-Z][a-z|A-Z|\d|_|-]{0,}$/; +//表单数据 +export const formSchema: FormSchema[] = [ + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '报表编码', + field: 'code', + component: 'Input', + colProps: { + sm: 24, + xs: 24, + md: 12, + lg: 8, + xl: 8, + xxl: 8, + }, + dynamicRules: ({ values, model }) => { + console.log('values:', values); + return [ + { + required: true, + validator: (_, value) => { + return new Promise((resolve, reject) => { + if (!value) { + return reject('请输入报表编码!'); + } + if (!codePattern.test(value)) { + return reject('编码必须以字母开头,可包含数字、下划线、横杠!'); + } + let params = { + tableName: 'onl_cgreport_head', + fieldName: 'code', + fieldVal: value, + dataId: model.id, + }; + duplicateCheckDelay(params) + .then((res) => { + res.success ? resolve() : reject('报表编码已存在!'); + }) + .catch((err) => { + reject(err.message || '校验失败'); + }); + }); + }, + }, + ]; + }, + }, + { + label: '报表名字', + field: 'name', + component: 'Input', + colProps: { + sm: 24, + xs: 24, + md: 12, + lg: 8, + xl: 8, + xxl: 8, + }, + dynamicRules: () => { + return [{ required: true, message: '请输入报表名字!' }]; + }, + }, + { + label: '动态数据源', + field: 'dbSource', + colProps: { + sm: 24, + xs: 24, + md: 12, + lg: 8, + xl: 8, + xxl: 8, + }, + component: 'ApiSelect', + rules: [{ required: permissionStore.sysSafeMode, message: '请选择数据源!' }], + componentProps: { + api: getDataSourceList, + }, + }, + /* { + label: ' ', + field: 'line1', + component: 'Input', + slot: 'line1', + colProps: { + span: 24 + }, + itemProps:{ + labelCol: { xs: 1, sm: 1 }, + wrapperCol: { xs: 23, sm: 23 }, + colon: false + }, + },*/ + { + label: '报表SQL', + field: 'cgrSql', + component: 'JCodeEditor', + rules: [{ required: true, message: '请填写报表SQL' }], + // update-begin--author:liaozhiyang---date:20240509---for:【QQYUN-9230】报表图表弹窗样式调整 + // itemProps: { + // labelCol: { xs: 24, sm: 4, md: 2, lg: 2, xl: 3, xxl: 2 }, + // wrapperCol: { xs: { span: 24 }, sm: { span: 18 }, md: { span: 24 } }, + // }, + // update-end--author:liaozhiyang---date:20240509---for:【QQYUN-9230】报表图表弹窗样式调整 + componentProps: { + height: '200px', + fullScreen: true, + }, + colProps: { + sm: 24, + xs: 24, + md: 18, + lg: 16, + xl: 16, + xxl: 16, + }, + }, + { + label: ' ', + field: 'analyseButton', + component: 'Input', + slot: 'analyseButton', + colProps: { + xs: 24, + sm: 24, + md: 6, + lg: 8, + xl: 8, + xxl: 8, + }, + itemProps: { + labelCol: { xs: 1, sm: 1 }, + wrapperCol: { xs: 23, sm: 23 }, + colon: false, + }, + }, +]; +//子表表格配置 +export const onlCgreportParamColumns: JVxeColumn[] = [ + { + title: '参数字段', + key: 'paramName', + type: JVxeTypes.input, + width: '150px', + placeholder: '请输入${title}', + defaultValue: '', + validateRules: [{ required: true, message: '${title}不能为空' }], + }, + { + title: '参数文本', + key: 'paramTxt', + type: JVxeTypes.input, + width: '150px', + placeholder: '请输入${title}', + defaultValue: '', + validateRules: [{ required: true, message: '${title}不能为空' }], + }, + { + title: '默认值', + key: 'paramValue', + type: JVxeTypes.input, + width: '150px', + placeholder: '请输入${title}', + defaultValue: '', + }, +]; +export const onlCgreportItemColumns: JVxeColumn[] = [ + { + title: '字段名字', + key: 'fieldName', + type: JVxeTypes.input, + width: '160px', + placeholder: '请输入${title}', + defaultValue: '', + validateRules: [{ required: true, message: '${title}不能为空' }], + }, + { + title: '字段文本', + key: 'fieldTxt', + type: JVxeTypes.input, + width: '160px', + placeholder: '请输入${title}', + defaultValue: '', + validateRules: [{ required: true, message: '${title}不能为空' }], + }, + { + title: '宽度', + key: 'fieldWidth', + type: JVxeTypes.input, + width: '80px', + defaultValue: '', + }, + { + title: '类型', + key: 'fieldType', + width: '120px', + placeholder: '请输入${title}', + defaultValue: '', + validateRules: [{ required: true, message: '${title}不能为空' }], + type: JVxeTypes.select, + options: [ + { title: '数值类型', value: 'Integer' }, + { title: '字符类型', value: 'String' }, + { title: '日期类型', value: 'Date' }, + { title: '时间类型', value: 'Datetime' }, + { title: '长整型', value: 'Long' }, + { title: '图片类型', value: 'Image' }, + ], + }, + { + title: '列显示', + key: 'isShow', + width: '80px', + align: 'center', + type: JVxeTypes.checkbox, + customValue: [1, 0], + defaultChecked: true, + }, + { + title: '字段href', + key: 'fieldHref', + type: JVxeTypes.input, + width: '120px', + placeholder: '请输入${title}', + defaultValue: '', + }, + { + title: '查询', + key: 'isSearch', + type: JVxeTypes.checkbox, + customValue: ['1', '0'], + width: '80px', + align: 'center', + defaultChecked: false, + }, + { + title: '查询模式', + key: 'searchMode', + type: JVxeTypes.select, + width: '120px', + placeholder: '请选择${title}', + options: [ + { title: '单值查询', value: 'single' }, + { title: '范围查询', value: 'group' }, + ], + }, + { + title: '取值表达式', + key: 'replaceVal', + type: JVxeTypes.input, + width: '120px', + placeholder: '请输入${title}', + defaultValue: '', + }, + { + title: '字典code', + key: 'dictCode', + type: JVxeTypes.input, + width: '120px', + placeholder: '请输入${title}', + defaultValue: '', + }, + { + title: '分组标题', + key: 'groupTitle', + type: JVxeTypes.input, + width: '120px', + placeholder: '请输入${title}', + defaultValue: '', + }, + { + title: '合计列', + align: 'center', + key: 'isTotal', + type: JVxeTypes.checkbox, + customValue: ['1', '0'], + width: '80px', + defaultChecked: false, + }, +]; diff --git a/src/views/super/online/cgreport/components/CgreportAigcModal.vue b/src/views/super/online/cgreport/components/CgreportAigcModal.vue new file mode 100644 index 0000000..f520748 --- /dev/null +++ b/src/views/super/online/cgreport/components/CgreportAigcModal.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/src/views/super/online/cgreport/components/CgreportModal.vue b/src/views/super/online/cgreport/components/CgreportModal.vue new file mode 100644 index 0000000..545e22f --- /dev/null +++ b/src/views/super/online/cgreport/components/CgreportModal.vue @@ -0,0 +1,332 @@ + + + + diff --git a/src/views/super/online/cgreport/demo/ModalFormDemo.vue b/src/views/super/online/cgreport/demo/ModalFormDemo.vue new file mode 100644 index 0000000..def058a --- /dev/null +++ b/src/views/super/online/cgreport/demo/ModalFormDemo.vue @@ -0,0 +1,79 @@ + + diff --git a/src/views/super/online/cgreport/index.vue b/src/views/super/online/cgreport/index.vue new file mode 100644 index 0000000..8bcd465 --- /dev/null +++ b/src/views/super/online/cgreport/index.vue @@ -0,0 +1,259 @@ + + + diff --git a/src/views/super/online/cgreport/router/cgreportRouter.ts b/src/views/super/online/cgreport/router/cgreportRouter.ts new file mode 100644 index 0000000..bda2b7c --- /dev/null +++ b/src/views/super/online/cgreport/router/cgreportRouter.ts @@ -0,0 +1,24 @@ +import {router} from '/@/router'; +import {LAYOUT} from '/@/router/constant'; + +export function registerCgreportRouter() { + router.addRoute({ + path: '/online-auto-cgreport-router', + name: 'onl-auto-cgreport-router', + component: LAYOUT, + redirect: '/online/cgreport', + meta: { + title: 'OnlCgreportAuto', + hideMenu: true, + hideBreadcrumb: true, + }, + children: [ + { + path: '/online/cgreport/:id', + name: 'OnlCgReportList', + component: () => import('../auto/OnlCgReportList.vue'), + meta: {title: 'AUTO在线报表'}, + }, + ], + }) +} diff --git a/src/views/super/online/graphreport/GraphreportList.vue b/src/views/super/online/graphreport/GraphreportList.vue new file mode 100644 index 0000000..4837000 --- /dev/null +++ b/src/views/super/online/graphreport/GraphreportList.vue @@ -0,0 +1,206 @@ + + + + diff --git a/src/views/super/online/graphreport/auto/GraphreportAutoChart.vue b/src/views/super/online/graphreport/auto/GraphreportAutoChart.vue new file mode 100644 index 0000000..86b8fba --- /dev/null +++ b/src/views/super/online/graphreport/auto/GraphreportAutoChart.vue @@ -0,0 +1,277 @@ + + + + + diff --git a/src/views/super/online/graphreport/auto/components/ErrorTip.vue b/src/views/super/online/graphreport/auto/components/ErrorTip.vue new file mode 100644 index 0000000..2f6d588 --- /dev/null +++ b/src/views/super/online/graphreport/auto/components/ErrorTip.vue @@ -0,0 +1,31 @@ + + + \ No newline at end of file diff --git a/src/views/super/online/graphreport/auto/components/render/ChartAutoRender.vue b/src/views/super/online/graphreport/auto/components/render/ChartAutoRender.vue new file mode 100644 index 0000000..33447aa --- /dev/null +++ b/src/views/super/online/graphreport/auto/components/render/ChartAutoRender.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/views/super/online/graphreport/auto/components/render/ChartDoubleRender.vue b/src/views/super/online/graphreport/auto/components/render/ChartDoubleRender.vue new file mode 100644 index 0000000..ee39013 --- /dev/null +++ b/src/views/super/online/graphreport/auto/components/render/ChartDoubleRender.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/views/super/online/graphreport/auto/components/render/ChartSingleRender.vue b/src/views/super/online/graphreport/auto/components/render/ChartSingleRender.vue new file mode 100644 index 0000000..6ec9adb --- /dev/null +++ b/src/views/super/online/graphreport/auto/components/render/ChartSingleRender.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/src/views/super/online/graphreport/auto/components/render/ChartTabsRender.vue b/src/views/super/online/graphreport/auto/components/render/ChartTabsRender.vue new file mode 100644 index 0000000..4548bde --- /dev/null +++ b/src/views/super/online/graphreport/auto/components/render/ChartTabsRender.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/src/views/super/online/graphreport/auto/hooks/useChartRender.ts b/src/views/super/online/graphreport/auto/hooks/useChartRender.ts new file mode 100644 index 0000000..cf22c8c --- /dev/null +++ b/src/views/super/online/graphreport/auto/hooks/useChartRender.ts @@ -0,0 +1,499 @@ +import { ref, watch, computed, reactive, ExtractPropTypes } from 'vue'; +import { router } from '/@/router'; +import { cloneDeep } from 'lodash-es'; +import { propTypes } from '/@/utils/propTypes'; +import { printJS } from '/@/hooks/web/usePrintJS'; +import { downloadByData } from '/@/utils/file/download'; +import { filterDictText } from '/@/utils/dict/JDictSelectUtil'; +import Bar from '/@/components/chart/Bar.vue'; +import Pie from '/@/components/chart/Pie.vue'; +import BarMulti from '/@/components/chart/BarMulti.vue'; +import LineMulti from '/@/components/chart/LineMulti.vue'; +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; +import { isFunction } from '/@/utils/is'; + +export const ChartRenderProps = { + // 图表标题 + title: propTypes.string, + // 图表数据 + chartsData: propTypes.object, + // 是否运行在组件模式 + asComponent: propTypes.bool.def(false), +}; +type PropsType = ExtractPropTypes; + +export const ChartRenderEmits = ['error']; +export const ChartRenderComponents = { + LineMulti, + BarMulti, + Pie, + Bar, +}; + +export const ChartRenderCommon = { + components: ChartRenderComponents, + props: ChartRenderProps, + emits: ChartRenderEmits, +}; + +const errorText = { + jsonFormattingFailed: 'JSON字符串格式化失败', +}; + +export function useChartRender(props: PropsType, { emit }) { + const { + createMessage: $message, + createConfirm: $confirm, + createInfoModal: $info, + createErrorModal: $error, + createSuccessModal: $success, + createWarningModal: $warning, + } = useMessage(); + const headId = ref(null); + // 图表的高度 + const height = ref('400px'); + // 当前显示的图表 + const activeKey = ref('bar'); + // 图表类型 + const chartTypes = ref([]); + // 是否开启分页 + const pageSwitch = ref(true); + // 打印ID + const printId = computed(() => `print-content-${headId.value}`); + // 曲线图参数配置 + const lineParams = reactive({ + chartData: [] as Recordable[], + }); + // 柱状图参数配置 + const barParams = reactive({ + chartData: [] as Recordable[], + }); + // 饼图参数配置 + const pieParams = reactive({ + chartData: [] as Recordable[], + }); + // 折柱图参数配置 + const barLineParams = reactive({ + dataSource: [] as Recordable[], + }); + // 表格参数配置 + const tableParams = reactive({ + // 固定的列数据 + fixedColumns: [ + { + title: '#', + key: 'rowIndex', + width: '10%', + align: 'center', + customRender: function ({ record, index }) { + if (record.isTotal === true) { + return '总计'; + } else { + return parseInt(index) + 1; + } + }, + }, + ], + columns: [] as Recordable[], + dataSource: [] as Recordable[], + }); + // 用户JS增强的事件暂存处(通过headId隔离) + const extendJsHandlerIsolation = reactive({}); + // 当前图表的JS增强 + const extendJsHandler = computed>({ + get() { + if (headId.value == null) { + return null; + } else { + return extendJsHandlerIsolation[headId.value]; + } + }, + set(obj) { + if (headId.value != null) { + extendJsHandlerIsolation[headId.value] = obj; + } + }, + }); + // 是否包含曲线图 + const hasLine = computed(() => chartTypes.value.includes('line')); + // 是否包含柱状图 + const hasBar = computed(() => chartTypes.value.includes('bar')); + // 是否包含饼图 + const hasPie = computed(() => chartTypes.value.includes('pie')); + // 是否包含数据表格 + const hasTable = ref(false); + // 曲线图参数 + const lineProps = computed(() => { + return { + type: 'line', + height: height.value, + chartData: lineParams.chartData, + onClick(params) { + // console.debug('lineProps-click: ', arguments) + emitExtendJsEvent(params); + }, + }; + }); + // 柱状图参数 + const barProps = computed(() => { + return { + height: height.value, + chartData: barParams.chartData, + onClick(params) { + // console.debug('barProps-click: ', arguments) + emitExtendJsEvent(params); + }, + }; + }); + // 饼图参数 + const pieProps = computed(() => { + return { + height: height.value, + chartData: pieParams.chartData, + onClick(params) { + // console.debug('pieProps-click: ', arguments) + emitExtendJsEvent(params); + }, + }; + }); + // 折柱图参数 + const barLineProps = computed(() => { + return { + height: height.value, + dataSource: barLineParams.dataSource, + onClick(_event, _chart) { + console.debug('barLineProps-click: ', arguments); + }, + }; + }); + // 图表区域 ACard 标签的固定属性 + const chartCardProps = computed(() => { + return { + title: props.title, + headStyle: { paddingLeft: '20px' }, + bodyStyle: { padding: '10px' }, + bordered: !props.asComponent, + }; + }); + // 数据表格区域 ACard 标签的固定属性 + const tableCardProps = computed(() => { + return { + title: '数据明细', + headStyle: { paddingLeft: '20px' }, + bodyStyle: { padding: '0' }, + style: { marginTop: '20px' }, + bordered: !props.asComponent, + }; + }); + // 导出按钮固定属性 + const exportButtonProps = computed(() => { + return { + type: 'primary', + preIcon: 'ant-design:download', + text: '导出', + style: { margin: '12px' }, + }; + }); + /** 分页开关固定属性 */ + const pageSwitchProps = computed(() => { + return { + checkedChildren: '分页', + unCheckedChildren: '分页', + style: { + position: 'absolute', + top: '17px', + right: '12px', + }, + }; + }); + // 数据表格的固定属性 + const tableProps = computed(() => { + return { + size: 'middle', + rowKey: 'id', + // bordered: true, + pagination: pageSwitch.value ? { pageSize: 10 } : false, + columns: tableParams.columns, + dataSource: tableParams.dataSource, + style: { borderTop: '1px solid #e8e8e8' }, + }; + }); + // 是否显示打印按钮 + const showPrint = computed(() => !props.asComponent); + // 是否显示详情按钮 + const showDetail = computed(() => props.asComponent); + + watch( + () => props.chartsData, + (data) => parseChartsData(data), + { immediate: true } + ); + + /** 执行JS扩展 */ + function executeExtendJs(headId, jsCode) { + if (!jsCode || !headId) { + return; + } + let onClick = { line: null, bar: null, pie: null }; + // update-begin--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + // 执行JS增强 + new Function('onClick', 'headId', `${jsCode}`)(onClick, headId); + // update-end--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + if (extendJsHandler.value == null) { + extendJsHandler.value = { click: onClick }; + } else { + extendJsHandler.value.click = onClick; + } + } + + // click 事件的 this 指向 + const onClickThis = { + $router: router, + $http: defHttp, + $message, + $confirm, + $info, + $error, + $success, + $warning, + }; + + /** 触发JS增强里定义的事件 */ + function emitExtendJsEvent(params) { + if (extendJsHandler.value != null) { + let clickType = params.seriesType; + let fn: Fn = extendJsHandler.value.click[clickType]; + if (isFunction(fn)) { + fn.call(onClickThis, params); + } + } + } + + /** 解析 chartData */ + function parseChartsData(chartsData) { + if (chartsData == null) return null; + let { head, data, items, dictOptions } = chartsData; + if (head == null) return; + let { id, xaxisField, yaxisField, dataType, cgrSql, graphType, extendJs } = head; + headId.value = id; + executeExtendJs(id, extendJs); + try { + data = dataType === 'sql' || dataType === 'api' ? data : JSON.parse(cgrSql); + } catch { + emit('error', errorText.jsonFormattingFailed); + return; + } + let dictList = dictOptions[xaxisField]; + let graphTypes = graphType.split(','); + activeKey.value = graphTypes[0]; + if (activeKey.value == 'table') { + activeKey.value = graphTypes[1]; + } + chartTypes.value = graphTypes; + let yaxisFields: string[] = yaxisField.split(','); + let fieldMap = new Map(); + items.forEach((item) => fieldMap.set(item.fieldName, item)); + // 判断是否定义了数据表格,如果定义了则删除该项,不参与动态渲染,只显示在最底部 + let index = graphTypes.indexOf('table'); + hasTable.value = index !== -1; + if (hasTable.value) { + graphTypes.splice(index, 1); + } + let parseOption = { graphTypes, data, items, fieldMap, xaxisField, yaxisFields, dictList, dictOptions }; + parseLineData(parseOption); + parseBarData(parseOption); + parsePicData(parseOption); + parseTableData(parseOption); + } + + type ParseDataOption = { + graphTypes: string[]; + data: Recordable[]; + items: Recordable[]; + fieldMap: Map; + xaxisField: string; + yaxisFields: string[]; + dictList; + dictOptions; + }; + + // 根据用户配置构造出通用数据 + function parseCommonData(option: ParseDataOption) { + let { data, fieldMap, xaxisField, yaxisFields, dictList } = option; + let chartData: Recordable[] = []; + for (let yField of yaxisFields) { + for (let item of data) { + let name = item[xaxisField]; + // 判断是否有字典 + if (dictList) { + name = filterDictText(dictList, name); + } + chartData.push({ + name: name, + value: item[yField], + type: fieldMap.get(yField)?.fieldTxt || yField, + }); + } + } + return chartData; + } + + // 根据用户配置构造出 lineChartData + function parseLineData(option: ParseDataOption) { + let { graphTypes } = option; + if (graphTypes.includes('line')) { + lineParams.chartData = parseCommonData(option); + } + } + + // 根据用户配置构造出 barChartData + function parseBarData(option: ParseDataOption) { + let { graphTypes } = option; + if (graphTypes.includes('bar')) { + barParams.chartData = parseCommonData(option); + } + } + + // 根据用户配置构造出 pieChartData + function parsePicData(option: ParseDataOption) { + let { graphTypes, data, xaxisField, yaxisFields, dictList } = option; + let yField = yaxisFields[0]; + if (graphTypes.includes('pie')) { + let chartData: Recordable[] = []; + for (let item of data) { + let name = item[xaxisField]; + // 判断是否有字典 + if (dictList) { + name = filterDictText(dictList, name); + } + chartData.push({ + name: name, + value: item[yField], + }); + } + pieParams.chartData = chartData; + } + } + + // 根据用户配置构造出 tableData + function parseTableData(option: ParseDataOption) { + let { data, items, xaxisField, yaxisFields, dictList, dictOptions } = option; + if (hasTable.value) { + tableParams.dataSource = data.map((item, index) => { + item.id = index; + let pieData = { + item: item[xaxisField], + count: item[yaxisFields[0]], + }; + // 判断是否有字典 + if (dictList) { + pieData.item = filterDictText(dictList, pieData.item); + } + return item; + }); + // 根据用户配置构造出 tableColumns + let tableColumns: Recordable[] = cloneDeep(tableParams.fixedColumns); + let isTotals: string[] = []; + items.forEach((item) => { + if (item.isShow === 'Y') { + let column: Recordable = { + align: 'center', + width: '10%', + title: item.fieldTxt, + dataIndex: item.fieldName, + }; + if (item.dictCode) { + column.customRender = ({ text }) => filterDictText(dictOptions[item.fieldName], text); + } + tableColumns.push(column); + // 判断是否计算总数 + if (item.isTotal === 'Y') isTotals.push(item.fieldName); + } + }); + tableParams.columns = tableColumns; + // 如果有计算需要的值就进行计算 + if (isTotals.length > 0) { + let totalRow = { id: tableParams.dataSource.length, isTotal: true }; + isTotals.forEach((column) => { + let count = 0; + tableParams.dataSource.forEach((row) => { + count += parseFloat(row[column]); + }); + totalRow[column] = isNaN(count) ? '包含非数字内容' : count.toFixed(2); + }); + tableParams.dataSource.push(totalRow); + } + } + } + + // 导出Excel + function onExportXls() { + let fileName = props.title; + defHttp + .get( + { + url: '/online/graphreport/api/exportXlsById', + params: { + id: headId.value, + name: fileName, + }, + responseType: 'blob', + }, + { isTransformResponse: false } + ) + .then((data) => { + if (!data || data.size == 0) { + $message.warning('导出失败!'); + return; + } + downloadByData(data, fileName + '.xls'); + }); + } + + // 打印 + function onPrint() { + printJS({ + type: 'html', + printable: '#' + printId.value, + }); + } + + /** 跳转到详情页 */ + function onGoToDetail() { + goToInfo(props.chartsData); + } + + function goToInfo(data) { + let url = `/online/graphreport/chart/${data.head.id}`; + router.push({ path: url }); + } + + return { + headId, + printId, + height, + activeKey, + chartTypes, + pageSwitch, + showPrint, + showDetail, + hasLine, + hasBar, + hasPie, + hasTable, + lineProps, + barProps, + pieProps, + tableProps, + barLineProps, + chartCardProps, + tableCardProps, + exportButtonProps, + pageSwitchProps, + extendJsHandlerIsolation, + onPrint, + onGoToDetail, + onExportXls, + }; +} diff --git a/src/views/super/online/graphreport/auto/hooks/useParseFormSchemas.ts b/src/views/super/online/graphreport/auto/hooks/useParseFormSchemas.ts new file mode 100644 index 0000000..2a32c3f --- /dev/null +++ b/src/views/super/online/graphreport/auto/hooks/useParseFormSchemas.ts @@ -0,0 +1,101 @@ +import { nextTick, Ref, h } from 'vue'; +import { FormSchema } from '/@/components/Form'; +import { InputNumber, Input, DatePicker } from 'ant-design-vue'; + +type PSchema = Partial; + +export function useParseFormSchemas(chartsData: Ref, showSearchField: Ref) { + // 解析查询条件 FormSchemas + async function parseFormSchemas() { + let { head, items, dictOptions } = chartsData.value; + if (head.dataType === 'sql') { + let formSchemas: FormSchema[] = []; + items.forEach((field) => { + // 判断是否查询 + if (field.searchFlag !== 'Y') return; + let isRange = field.searchMode === 'group'; + let schema: PSchema = {}; + let schemas: FormSchema[] = []; + // 判断是否有字典,字典组件不能范围查询 + if (field.dictCode && dictOptions[field.dictCode]) { + schema.component = 'Select'; + schema.componentProps = { + options: dictOptions[field.dictCode], + }; + } else if (['Integer', 'Long'].includes(field.fieldType)) { + // 数字输入框 + schema.component = 'InputNumber'; + if (isRange) { + schema.render = getRangeRender(schemas, field, InputNumber); + } + } else if (field.fieldType === 'Date') { + // 日期选择组件 + schema.component = 'DatePicker'; + schema.componentProps = { + format: 'YYYY-MM-DD', + }; + if (isRange) { + schema.render = getRangeRender(schemas, field, DatePicker); + } + } else { + // 普通文本框 + schema.component = 'Input'; + if (isRange) { + schema.render = getRangeRender(schemas, field, Input); + } + } + formSchemas = formSchemas + .concat({ + label: field.fieldTxt, + field: field.fieldName, + component: 'Input', + itemProps: { + class: { 'range-query': isRange }, + } as any, + ...schema, + }) + .concat(schemas); + }); + showSearchField.value = formSchemas.length > 0; + await nextTick(); + return formSchemas; + } else { + showSearchField.value = false; + return null; + } + } + + return { parseFormSchemas }; +} + +/** + * 获取范围渲染方法 + * + * @param schemas 显示名 + * @param fieldItem 字段 + * @param component vue 组件 + */ +function getRangeRender(schemas: FormSchema[], fieldItem, component: any) { + let { fieldTxt: label, fieldName: beginField } = fieldItem; + let endField = beginField + '_end'; + // 添加占位符 + schemas.push({ label: '', field: endField, component: 'Input', show: false }); + return function ({ model }) { + return [ + h(component, { + value: model[beginField], + 'onUpdate:value': (v) => (model[beginField] = v), + placeholder: '请输入开始' + label, + + format: 'YYYY-MM-DD', + }), + h('span', { class: 'range-span' }, '~'), + h(component, { + value: model[endField], + 'onUpdate:value': (v) => (model[endField] = v), + placeholder: '请输入结束' + label, + format: 'YYYY-MM-DD', + }), + ]; + }; +} diff --git a/src/views/super/online/graphreport/components/GraphreportAigcModal.vue b/src/views/super/online/graphreport/components/GraphreportAigcModal.vue new file mode 100644 index 0000000..796a1c5 --- /dev/null +++ b/src/views/super/online/graphreport/components/GraphreportAigcModal.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/src/views/super/online/graphreport/components/GraphreportModal.vue b/src/views/super/online/graphreport/components/GraphreportModal.vue new file mode 100644 index 0000000..7a22507 --- /dev/null +++ b/src/views/super/online/graphreport/components/GraphreportModal.vue @@ -0,0 +1,346 @@ + + + + + diff --git a/src/views/super/online/graphreport/components/tables/FieldTable.vue b/src/views/super/online/graphreport/components/tables/FieldTable.vue new file mode 100644 index 0000000..77468cf --- /dev/null +++ b/src/views/super/online/graphreport/components/tables/FieldTable.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/src/views/super/online/graphreport/components/tables/ParamsTable.vue b/src/views/super/online/graphreport/components/tables/ParamsTable.vue new file mode 100644 index 0000000..c0839bf --- /dev/null +++ b/src/views/super/online/graphreport/components/tables/ParamsTable.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/src/views/super/online/graphreport/graphreport.api.ts b/src/views/super/online/graphreport/graphreport.api.ts new file mode 100644 index 0000000..7ea45b0 --- /dev/null +++ b/src/views/super/online/graphreport/graphreport.api.ts @@ -0,0 +1,39 @@ +import { defHttp } from '/@/utils/http/axios'; + +export enum Api { + list = '/online/graphreport/head/list', + delete = '/online/graphreport/head/delete', + deleteBatch = '/online/graphreport/head/deleteBatch', + exportXls = '/online/graphreport/head/exportXls', + importXls = '/online/graphreport/head/importExcel', + parseField = '/online/graphreport/head/parseField', + paramsList = '/online/graphreport/params/listByHeadId', + getChartsData = '/online/graphreport/api/getChartsData', + getParamsInfo = '/online/graphreport/params/listByHeadId', +} + +/** + * 列表接口 + * e3e3NcxzbUiGa53YYVXxWc8ADo5ISgQGx/gaZwERF91oAryDlivjqBv3wqRArgChupi+Y/Gg/swwGEyL0PuVFg== + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); + +// 批量删除 +export function doBatchDelete(idList: string[]) { + return defHttp.delete( + { + url: Api.deleteBatch, + params: { ids: idList.join(',') }, + }, + { joinParamsToUrl: true } + ); +} + +export const queryParamsList = (headId: string) => defHttp.get({ url: Api.paramsList, params: { headId } }); + +// 查询图表数据 +export const getChartsData = (params) => defHttp.get({ url: Api.getChartsData, params: params }); +export const getParamsInfo = (params) => defHttp.get({ url: Api.getParamsInfo, params: params }); + +export const parseField = (type, data, params = {}) => defHttp.post({ url: Api.parseField, params: { type, data, ...params } }); diff --git a/src/views/super/online/graphreport/graphreport.data.ts b/src/views/super/online/graphreport/graphreport.data.ts new file mode 100644 index 0000000..1cce46d --- /dev/null +++ b/src/views/super/online/graphreport/graphreport.data.ts @@ -0,0 +1,241 @@ +import { FormSchema } from '/@/components/Form'; +import { duplicateCheckDelay } from '/@/views/system/user/user.api'; +import { bindMapFormSchema } from '/@/utils/common/compUtils'; +import { computed, ref } from 'vue'; +import { usePermissionStore } from '/@/store/modules/permission'; + +// 弹窗表单 +export function useFormSchemas(_, handler) { + const permissionStore = usePermissionStore(); + // 由于需要动态改变布局,所以使用 computed + type SpanType = 'one' | 'tow' | 'threeTow' | 'three'; + // 动态布局 + const mapFormSchema = bindMapFormSchema( + { + // 一列 + one: { + colProps: { xs: 24, sm: 24 }, + itemProps: { + labelCol: { xs: 24, sm: 2 }, + wrapperCol: { xs: 24, sm: 22 }, + }, + }, + // 两列 + tow: { + colProps: { xs: 24, sm: 12 }, + itemProps: { + labelCol: { xs: 24, sm: 4 }, + wrapperCol: { xs: 24, sm: 20 }, + }, + }, + // 三分之二列 + threeTow: { + colProps: { xs: 24, sm: 16 }, + itemProps: { + labelCol: { xs: 24, sm: 3 }, + wrapperCol: { xs: 24, sm: 21 }, + }, + }, + // 三列 + three: { + colProps: { xs: 24, sm: 8 }, + itemProps: { + labelCol: { xs: 24, sm: 6 }, + wrapperCol: { xs: 24, sm: 18 }, + }, + }, + }, + 'three' + ); + + const dataType = ref('sql'); + const isCombination = ref('combination'); + // 根据不同的值展示不同的form + const formInfo = { + cgrSql: { + sql: { label: '查询SQL', placeholder: '请输入查询SQL', language: 'sql' }, + json: { label: '数据JSON', placeholder: '请输入数据JSON', language: 'javascript' }, + api: { label: 'API接口', placeholder: '请输入API接口', language: 'javascript' }, + }, + }; + const cgrSqlFormInfo = computed(() => { + return formInfo.cgrSql[dataType.value]; + }); + + const formSchemas = computed(() => [ + { label: 'ID', field: 'id', component: 'Input', show: false }, + mapFormSchema({ + label: '图表名称', + field: 'name', + component: 'Input', + required: true, + }), + mapFormSchema({ + label: '编码', + field: 'code', + component: 'Input', + dynamicRules({ model }) { + return [ + { required: true, message: '请输入编码!' }, + { + async validator({}, value) { + if (/[\u4E00-\u9FA5]/g.test(value)) { + return Promise.reject('编码不能为汉字'); + } + let { success, message } = await duplicateCheckDelay({ + tableName: 'onl_graphreport_head', + fieldName: 'code', + fieldVal: value, + dataId: model.id, + }); + if (!success) { + return Promise.reject(message); + } + }, + }, + ]; + }, + }), + mapFormSchema({ + label: '展示模板', + field: 'displayTemplate', + component: 'Select', + componentProps: { + options: [ + { label: 'Tab风格', value: 'tab' }, + { label: '单排布局', value: 'single' }, + { label: '双排布局', value: 'double' }, + ], + }, + defaultValue: 'tab', + }), + mapFormSchema({ + label: 'X轴字段', + field: 'xaxisField', + component: 'Input', + required: true, + }), + mapFormSchema( + { + label: 'Y轴字段', + field: 'yaxisField', + component: 'JDictSelectTag', + componentProps: { + mode: 'tags', + open: false, + dictCode: 'online_graph_display_template', + }, + required: true, + }, + 'threeTow' + ), + mapFormSchema({ + label: '数据类型', + field: 'dataType', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'online_graph_data_type', + showChooseOption: false, + onChange: (value) => (dataType.value = value), + }, + defaultValue: 'sql', + }), + mapFormSchema({ + label: '数据源', + field: 'dbSource', + component: 'Select', + componentProps: { + options: handler.dbSourceOptions.value, + }, + rules: [{ required: permissionStore.sysSafeMode, message: '请选择数据源!' }], + ifShow: ({ model }) => model.dataType === 'sql', + }), + mapFormSchema( + { + label: '图表类型', + field: 'graphType', + component: 'JDictSelectTag', + componentProps: { + mode: isCombination.value === 'single' ? 'default' : 'multiple', + dictCode: 'online_graph_type', + showChooseOption: false, + }, + defaultValue: ['bar'], + }, + dataType.value === 'sql' ? 'three' : 'threeTow' + ), + mapFormSchema( + { + label: '描述', + field: 'content', + component: 'Input', + }, + 'one' + ), + mapFormSchema( + { + label: cgrSqlFormInfo.value?.label, + field: 'cgrSql', + required: true, + component: 'JCodeEditor', + componentProps: { + placeholder: cgrSqlFormInfo.value?.placeholder, + language: cgrSqlFormInfo.value?.language, + fullScreen: true, + autoHeight: '!ie', + height: '100px', + }, + dynamicRules() { + return [ + { + required: true, + // 根据数据类型的不同显示不同的错误信息 + message: cgrSqlFormInfo.value?.placeholder, + }, + { + // 自定义校验:校验JSON字符串格式是否正确 + async validator({}, value) { + if (value && dataType.value === 'json') { + try { + JSON.parse(value); + } catch { + return Promise.reject('JSON格式不正确!'); + } + } + }, + }, + ]; + }, + }, + 'one' + ), + mapFormSchema( + { + label: ' ', // SQL解析 + field: 'cgrSql', + component: 'Input', + slot: 'SQLAnalyzeButton', + itemProps: { colon: false }, + // ifShow: ({ model }) => model.dataType === 'sql', + }, + 'one' + ), + mapFormSchema( + { + label: 'JS增强', + field: 'extendJs', + component: 'JCodeEditor', + componentProps: { + placeholder: 'JS增强', + language: 'javascript', + fullScreen: true, + autoHeight: '!ie', + height: '100px', + }, + }, + 'one' + ), + ]); + + return { formSchemas, dataType, isCombination }; +} diff --git a/src/views/super/online/graphreport/router/graphreportRouter.ts b/src/views/super/online/graphreport/router/graphreportRouter.ts new file mode 100644 index 0000000..5bad015 --- /dev/null +++ b/src/views/super/online/graphreport/router/graphreportRouter.ts @@ -0,0 +1,24 @@ +import {router} from '/@/router'; +import {LAYOUT} from '/@/router/constant'; + +export function registerGraphreportRouter() { + router.addRoute({ + path: '/online-auto-graphreport-router', + name: 'onl-auto-graphreport-router', + component: LAYOUT, + redirect: '/online/graphreport', + meta: { + title: 'OnlGraphreportAuto', + hideMenu: true, + hideBreadcrumb: true, + }, + children: [ + { + path: '/online/graphreport/chart/:code', + name: 'GraphreportAutoChart', + component: () => import('../auto/GraphreportAutoChart.vue'), + meta: {title: 'AUTO在线图表'}, + }, + ], + }) +} diff --git a/src/views/super/online/register.ts b/src/views/super/online/register.ts new file mode 100644 index 0000000..843a479 --- /dev/null +++ b/src/views/super/online/register.ts @@ -0,0 +1,47 @@ +import type {App} from 'vue'; +import {createAsyncComponent} from "@/utils/factory/createAsyncComponent"; +import {registerCgformRouter} from "./cgform/router/cgformRouter"; +import {registerCgreportRouter} from "./cgreport/router/cgreportRouter"; +import {registerGraphreportRouter} from "./graphreport/router/graphreportRouter"; + +/** 注册 online */ +export async function register(app: App) { + + // 注册Online弹窗 + const OnlineAutoModalAsync = createAsyncComponent(() => import('./cgform/auto/default/OnlineAutoModal.vue'), {loading: true}); + app.component('OnlineAutoModal', OnlineAutoModalAsync); + + // 注册Online表单路由 + registerCgformRouter(); + // 注册Online报表路由 + registerCgreportRouter(); + // 注册Online图表路由 + registerGraphreportRouter(); + + // 注册外部链接页面(如果有) + await registerCgformShareView(); + + console.log('[online] 注册完成!'); +} + +// 注册外部链接页面,自动判断是否存在 +async function registerCgformShareView() { + try { + const globModels = import.meta.glob('./cgform/share/index.ts'); + if (!globModels) { + return + } + const models = Object.values(globModels); + if (models.length == 0) { + return + } + const shareModel = await models[0]() as Recordable; + if (typeof shareModel?.register !== 'function') { + return + } + await shareModel.register(); + + console.debug('[online] Online表单外部链接路由注册完成!'); + } catch (e) { + } +} diff --git a/src/views/super/registerSuper.ts b/src/views/super/registerSuper.ts new file mode 100644 index 0000000..d00198e --- /dev/null +++ b/src/views/super/registerSuper.ts @@ -0,0 +1,16 @@ +import type { App } from 'vue'; + +/** + * 动态引入 super 下的组件 + */ +export async function registerSuper(app: App) { + const modules = import.meta.glob('./**/register.ts'); + for (let [url, module] of Object.entries(modules)) { + let { register } = await module(); + if (typeof register === 'function') { + await register(app); + } else { + console.error(`${url} 没有导出 register 函数,无法完成注册!`); + } + } +} \ No newline at end of file diff --git a/src/views/sys/about/index.vue b/src/views/sys/about/index.vue new file mode 100644 index 0000000..3d6dd07 --- /dev/null +++ b/src/views/sys/about/index.vue @@ -0,0 +1,97 @@ + + diff --git a/src/views/sys/error-log/DetailModal.vue b/src/views/sys/error-log/DetailModal.vue new file mode 100644 index 0000000..2047707 --- /dev/null +++ b/src/views/sys/error-log/DetailModal.vue @@ -0,0 +1,27 @@ + + diff --git a/src/views/sys/error-log/data.tsx b/src/views/sys/error-log/data.tsx new file mode 100644 index 0000000..3ffc2f4 --- /dev/null +++ b/src/views/sys/error-log/data.tsx @@ -0,0 +1,67 @@ +import { Tag } from 'ant-design-vue'; +import { BasicColumn } from '/@/components/Table/index'; +import { ErrorTypeEnum } from '/@/enums/exceptionEnum'; +import { useI18n } from '/@/hooks/web/useI18n'; + +const { t } = useI18n(); + +export function getColumns(): BasicColumn[] { + return [ + { + dataIndex: 'type', + title: t('sys.errorLog.tableColumnType'), + width: 80, + customRender: ({ text }) => { + const color = + text === ErrorTypeEnum.VUE + ? 'green' + : text === ErrorTypeEnum.RESOURCE + ? 'cyan' + : text === ErrorTypeEnum.PROMISE + ? 'blue' + : ErrorTypeEnum.AJAX + ? 'red' + : 'purple'; + return {() => text}; + }, + }, + { + dataIndex: 'url', + title: 'URL', + width: 200, + }, + { + dataIndex: 'time', + title: t('sys.errorLog.tableColumnDate'), + width: 160, + }, + { + dataIndex: 'file', + title: t('sys.errorLog.tableColumnFile'), + width: 200, + }, + { + dataIndex: 'name', + title: 'Name', + width: 200, + }, + { + dataIndex: 'message', + title: t('sys.errorLog.tableColumnMsg'), + width: 300, + }, + { + dataIndex: 'stack', + title: t('sys.errorLog.tableColumnStackMsg'), + }, + ]; +} + +export function getDescSchema(): any { + return getColumns().map((column) => { + return { + field: column.dataIndex!, + label: column.title, + }; + }); +} diff --git a/src/views/sys/error-log/index.vue b/src/views/sys/error-log/index.vue new file mode 100644 index 0000000..1895524 --- /dev/null +++ b/src/views/sys/error-log/index.vue @@ -0,0 +1,88 @@ + + + diff --git a/src/views/sys/exception/Exception.vue b/src/views/sys/exception/Exception.vue new file mode 100644 index 0000000..c3db956 --- /dev/null +++ b/src/views/sys/exception/Exception.vue @@ -0,0 +1,143 @@ + + diff --git a/src/views/sys/exception/NetworkErrorException.vue b/src/views/sys/exception/NetworkErrorException.vue new file mode 100644 index 0000000..e4ce2b9 --- /dev/null +++ b/src/views/sys/exception/NetworkErrorException.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/views/sys/exception/NotAccessException.vue b/src/views/sys/exception/NotAccessException.vue new file mode 100644 index 0000000..a5b2a5f --- /dev/null +++ b/src/views/sys/exception/NotAccessException.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/views/sys/exception/NotDataErrorException.vue b/src/views/sys/exception/NotDataErrorException.vue new file mode 100644 index 0000000..9a09dd0 --- /dev/null +++ b/src/views/sys/exception/NotDataErrorException.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/views/sys/exception/ServerErrorException.vue b/src/views/sys/exception/ServerErrorException.vue new file mode 100644 index 0000000..9742f55 --- /dev/null +++ b/src/views/sys/exception/ServerErrorException.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/views/sys/exception/index.ts b/src/views/sys/exception/index.ts new file mode 100644 index 0000000..fb57528 --- /dev/null +++ b/src/views/sys/exception/index.ts @@ -0,0 +1,5 @@ +export { default as Exception } from './Exception.vue'; +export { default as NotAccessException } from './NotAccessException.vue'; +export { default as NetworkErrorException } from './NetworkErrorException.vue'; +export { default as NotDataErrorException } from './NotDataErrorException.vue'; +export { default as ServerErrorException } from './ServerErrorException.vue'; diff --git a/src/views/sys/forget-password/step1.vue b/src/views/sys/forget-password/step1.vue new file mode 100644 index 0000000..7a7892f --- /dev/null +++ b/src/views/sys/forget-password/step1.vue @@ -0,0 +1,96 @@ + + diff --git a/src/views/sys/forget-password/step2.vue b/src/views/sys/forget-password/step2.vue new file mode 100644 index 0000000..b81d49e --- /dev/null +++ b/src/views/sys/forget-password/step2.vue @@ -0,0 +1,103 @@ + + diff --git a/src/views/sys/forget-password/step3.vue b/src/views/sys/forget-password/step3.vue new file mode 100644 index 0000000..77ab02b --- /dev/null +++ b/src/views/sys/forget-password/step3.vue @@ -0,0 +1,71 @@ + + diff --git a/src/views/sys/iframe/FrameBlank.vue b/src/views/sys/iframe/FrameBlank.vue new file mode 100644 index 0000000..a8a61f5 --- /dev/null +++ b/src/views/sys/iframe/FrameBlank.vue @@ -0,0 +1,9 @@ + + diff --git a/src/views/sys/iframe/index.vue b/src/views/sys/iframe/index.vue new file mode 100644 index 0000000..e73bef3 --- /dev/null +++ b/src/views/sys/iframe/index.vue @@ -0,0 +1,85 @@ + + + diff --git a/src/views/sys/lock/LockPage.vue b/src/views/sys/lock/LockPage.vue new file mode 100644 index 0000000..b43df99 --- /dev/null +++ b/src/views/sys/lock/LockPage.vue @@ -0,0 +1,241 @@ + + + diff --git a/src/views/sys/lock/index.vue b/src/views/sys/lock/index.vue new file mode 100644 index 0000000..e8c4d55 --- /dev/null +++ b/src/views/sys/lock/index.vue @@ -0,0 +1,13 @@ + + diff --git a/src/views/sys/lock/useNow.ts b/src/views/sys/lock/useNow.ts new file mode 100644 index 0000000..ee461fc --- /dev/null +++ b/src/views/sys/lock/useNow.ts @@ -0,0 +1,60 @@ +import { dateUtil } from '/@/utils/dateUtil'; +import { reactive, toRefs } from 'vue'; +import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'; + +export function useNow(immediate = true) { + let timer: IntervalHandle; + + const state = reactive({ + year: 0, + month: 0, + week: '', + day: 0, + hour: '', + minute: '', + second: 0, + meridiem: '', + }); + + const update = () => { + const now = dateUtil(); + + const h = now.format('HH'); + const m = now.format('mm'); + const s = now.get('s'); + + state.year = now.get('y'); + state.month = now.get('M') + 1; + state.week = '星期' + ['日', '一', '二', '三', '四', '五', '六'][now.day()]; + state.day = now.get('date'); + state.hour = h; + state.minute = m; + state.second = s; + + state.meridiem = now.format('A'); + }; + + function start() { + update(); + clearInterval(timer); + timer = setInterval(() => update(), 1000); + } + + function stop() { + clearInterval(timer); + } + + tryOnMounted(() => { + immediate && start(); + }); + + tryOnUnmounted(() => { + stop(); + }); + + return { + ...toRefs(state), + start, + stop, + }; +} diff --git a/src/views/sys/login/LoginForm.vue b/src/views/sys/login/LoginForm.vue new file mode 100644 index 0000000..1c9596d --- /dev/null +++ b/src/views/sys/login/LoginForm.vue @@ -0,0 +1,194 @@ + + diff --git a/src/views/sys/login/LoginFormTitle.vue b/src/views/sys/login/LoginFormTitle.vue new file mode 100644 index 0000000..a673636 --- /dev/null +++ b/src/views/sys/login/LoginFormTitle.vue @@ -0,0 +1,25 @@ + + diff --git a/src/views/sys/login/LoginSelect.vue b/src/views/sys/login/LoginSelect.vue new file mode 100644 index 0000000..486d0a2 --- /dev/null +++ b/src/views/sys/login/LoginSelect.vue @@ -0,0 +1,332 @@ + + + + + diff --git a/src/views/sys/login/MobileForm.vue b/src/views/sys/login/MobileForm.vue new file mode 100644 index 0000000..d9b386f --- /dev/null +++ b/src/views/sys/login/MobileForm.vue @@ -0,0 +1,90 @@ + + diff --git a/src/views/sys/login/OAuth2Login.vue b/src/views/sys/login/OAuth2Login.vue new file mode 100644 index 0000000..c36d2b4 --- /dev/null +++ b/src/views/sys/login/OAuth2Login.vue @@ -0,0 +1,129 @@ + + + diff --git a/src/views/sys/login/SessionTimeoutLogin.vue b/src/views/sys/login/SessionTimeoutLogin.vue new file mode 100644 index 0000000..d78a2f3 --- /dev/null +++ b/src/views/sys/login/SessionTimeoutLogin.vue @@ -0,0 +1,53 @@ + + + diff --git a/src/views/sys/login/ThirdModal.vue b/src/views/sys/login/ThirdModal.vue new file mode 100644 index 0000000..607bdfa --- /dev/null +++ b/src/views/sys/login/ThirdModal.vue @@ -0,0 +1,64 @@ + + diff --git a/src/views/sys/login/TokenLoginPage.vue b/src/views/sys/login/TokenLoginPage.vue new file mode 100644 index 0000000..66acab7 --- /dev/null +++ b/src/views/sys/login/TokenLoginPage.vue @@ -0,0 +1,217 @@ + + + + + + \ No newline at end of file diff --git a/src/views/sys/login/useLogin.ts b/src/views/sys/login/useLogin.ts new file mode 100644 index 0000000..5a20bbe --- /dev/null +++ b/src/views/sys/login/useLogin.ts @@ -0,0 +1,211 @@ +import type { ValidationRule } from 'ant-design-vue/lib/form/Form'; +import type { RuleObject } from 'ant-design-vue/lib/form/interface'; +import { ref, computed, unref, Ref } from 'vue'; +import { useI18n } from '/@/hooks/web/useI18n'; +import { checkOnlyUser } from '/@/api/sys/user'; +import { defHttp } from '/@/utils/http/axios'; +import { OAUTH2_THIRD_LOGIN_TENANT_ID } from "/@/enums/cacheEnum"; +import { getAuthCache } from "/@/utils/auth"; + +export enum LoginStateEnum { + LOGIN, + REGISTER, + RESET_PASSWORD, + MOBILE, + QR_CODE, +} + +export enum SmsEnum { + LOGIN = '0', + REGISTER = '1', + FORGET_PASSWORD = '2', +} +const currentState = ref(LoginStateEnum.LOGIN); + +export function useLoginState() { + function setLoginState(state: LoginStateEnum) { + currentState.value = state; + } + + const getLoginState = computed(() => currentState.value); + + function handleBackLogin() { + setLoginState(LoginStateEnum.LOGIN); + } + + return { setLoginState, getLoginState, handleBackLogin }; +} + +export function useFormValid(formRef: Ref) { + async function validForm() { + const form = unref(formRef); + if (!form) return; + const data = await form.validate(); + return data as T; + } + + return { validForm }; +} + +export function useFormRules(formData?: Recordable) { + const { t } = useI18n(); + + const getAccountFormRule = computed(() => createRule(t('sys.login.accountPlaceholder'))); + const getPasswordFormRule = computed(() => createRule(t('sys.login.passwordPlaceholder'))); + const getSmsFormRule = computed(() => createRule(t('sys.login.smsPlaceholder'))); + const getMobileFormRule = computed(() => createRule(t('sys.login.mobilePlaceholder'))); + + const getRegisterAccountRule = computed(() => createRegisterAccountRule('account')); + const getRegisterMobileRule = computed(() => createRegisterAccountRule('mobile')); + + const validatePolicy = async (_: RuleObject, value: boolean) => { + return !value ? Promise.reject(t('sys.login.policyPlaceholder')) : Promise.resolve(); + }; + + const validateConfirmPassword = (password: string) => { + return async (_: RuleObject, value: string) => { + if (!value) { + return Promise.reject(t('sys.login.passwordPlaceholder')); + } + if (value !== password) { + return Promise.reject(t('sys.login.diffPwd')); + } + return Promise.resolve(); + }; + }; + + const getFormRules = computed((): { [k: string]: ValidationRule | ValidationRule[] } => { + const accountFormRule = unref(getAccountFormRule); + const passwordFormRule = unref(getPasswordFormRule); + const smsFormRule = unref(getSmsFormRule); + const mobileFormRule = unref(getMobileFormRule); + + const registerAccountRule = unref(getRegisterAccountRule); + const registerMobileRule = unref(getRegisterMobileRule); + + const mobileRule = { + sms: smsFormRule, + mobile: mobileFormRule, + }; + switch (unref(currentState)) { + // register form rules + case LoginStateEnum.REGISTER: + return { + account: registerAccountRule, + password: passwordFormRule, + mobile: registerMobileRule, + sms: smsFormRule, + confirmPassword: [{ validator: validateConfirmPassword(formData?.password), trigger: 'change' }], + policy: [{ validator: validatePolicy, trigger: 'change' }], + }; + + // reset password form rules + case LoginStateEnum.RESET_PASSWORD: + return { + username: accountFormRule, + confirmPassword: [{ validator: validateConfirmPassword(formData?.password), trigger: 'change' }], + ...mobileRule, + }; + + // mobile form rules + case LoginStateEnum.MOBILE: + return mobileRule; + + // login form rules + default: + return { + account: accountFormRule, + password: passwordFormRule, + }; + } + }); + return { getFormRules }; +} + +function createRule(message: string) { + return [ + { + required: true, + message, + trigger: 'change', + }, + ]; +} +function createRegisterAccountRule(type) { + return [ + { + validator: type == 'account' ? checkUsername : checkPhone, + trigger: 'change', + }, + ]; +} + +function checkUsername(rule, value, callback) { + const { t } = useI18n(); + if (!value) { + return Promise.reject(t('sys.login.accountPlaceholder')); + } else { + return new Promise((resolve, reject) => { + checkOnlyUser({ username: value }).then((res) => { + res.success ? resolve() : reject('用户名已存在!'); + }); + }); + } +} +async function checkPhone(rule, value, callback) { + const { t } = useI18n(); + var reg = /^1[3456789]\d{9}$/; + if (!reg.test(value)) { + return Promise.reject(new Error('请输入正确手机号')); + } else { + return new Promise((resolve, reject) => { + checkOnlyUser({ phone: value }).then((res) => { + res.success ? resolve() : reject('手机号已存在!'); + }); + }); + } +} + +/** + * 判断是否是OAuth2APP环境 + */ +export function isOAuth2AppEnv() { + return /wxwork|dingtalk/i.test(navigator.userAgent); +} + +/** + * 判断是否是钉钉环境 + */ +export function isOAuth2DingAppEnv() { + return /dingtalk/i.test(navigator.userAgent); +} + +/** + * 后台构造oauth2登录地址 + * @param source + * @param tenantId + */ +export function sysOAuth2Login(source) { + let url = `${window._CONFIG['domianURL']}/sys/thirdLogin/oauth2/${source}/login`; + url += `?state=${encodeURIComponent(window.location.origin)}`; + // 代码逻辑说明: [QQYUN-3440]新建企业微信和钉钉配置表,通过租户模式隔离------------ + let tenantId = getAuthCache(OAUTH2_THIRD_LOGIN_TENANT_ID); + if(tenantId){ + url += `&tenantId=${tenantId}`; + } + window.location.href = url; +} + +/** + * 后台callBack + * @param code + */ +export function sysOAuth2Callback(code:string) { + let url = `${window._CONFIG['domianURL']}/sys/thirdLogin/oauth2/dingding/login`; + url += `?state=${encodeURIComponent(window.location.origin)}&authCode=${code}`; + let tenantId = getAuthCache(OAUTH2_THIRD_LOGIN_TENANT_ID); + if(tenantId){ + url += `&tenantId=${tenantId}`; + } + window.location.href = url; +} diff --git a/src/views/sys/redirect/index.vue b/src/views/sys/redirect/index.vue new file mode 100644 index 0000000..1cdec5d --- /dev/null +++ b/src/views/sys/redirect/index.vue @@ -0,0 +1,29 @@ + + diff --git a/src/views/system/address/address.api.ts b/src/views/system/address/address.api.ts new file mode 100644 index 0000000..12b482e --- /dev/null +++ b/src/views/system/address/address.api.ts @@ -0,0 +1,19 @@ +import { defHttp } from '/@/utils/http/axios'; + +export enum Api { + list = '/sys/user/queryDepartUserByOrgCode', + positionList = '/sys/position/list', + queryDepartTreeSync = '/sys/sysDepart/queryDepartTreeSync', +} +/** + * 获取部门树列表 + */ +export const queryDepartTreeSync = (params?) => defHttp.get({ url: Api.queryDepartTreeSync, params }); +/** + * 部门用户信息 + */ +export const list = (params?) => defHttp.get({ url: Api.list, params }); +/** + * 职务list + */ +export const positionList = (params?) => defHttp.get({ url: Api.positionList, params }); diff --git a/src/views/system/address/address.data.ts b/src/views/system/address/address.data.ts new file mode 100644 index 0000000..32efb1e --- /dev/null +++ b/src/views/system/address/address.data.ts @@ -0,0 +1,69 @@ +import { FormSchema } from '/@/components/Form'; +import { BasicColumn } from '/@/components/Table'; +import { getDepartName } from "@/utils/common/compUtils"; + +export const columns: BasicColumn[] = [ + { + title: '姓名', + dataIndex: 'realname', + width: 100, + }, + { + title: '部门', + dataIndex: 'departName', + customRender:({ text })=>{ + return getDepartName(text); + } + }, + { + title: '主岗位', + dataIndex: 'postName', + customRender:({ text })=>{ + return getDepartName(text); + } + }, + { + title: '兼职岗位', + dataIndex: 'otherPostName', + customRender:({ text })=>{ + return getDepartName(text); + } + }, +/* { + title: '职务', + dataIndex: 'post', + width: 150, + slots: { customRender: 'post' }, + },*/ + { + title: '手机', + width: 110, + dataIndex: 'phone', + customRender:( { record, text })=>{ + if(record.izHideContact && record.izHideContact === '1'){ + return '/'; + } + return text; + } + }, + { + title: '邮箱', + width: 180, + dataIndex: 'email', + customRender:( { record, text })=>{ + if(record.izHideContact && record.izHideContact === '1'){ + return text?'/':''; + } + return text; + } + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + label: '姓名', + field: 'realname', + component: 'Input', + colProps: { span: 6 }, + }, +]; diff --git a/src/views/system/address/components/DepartLeftTree.vue b/src/views/system/address/components/DepartLeftTree.vue new file mode 100644 index 0000000..9b82646 --- /dev/null +++ b/src/views/system/address/components/DepartLeftTree.vue @@ -0,0 +1,190 @@ + + + diff --git a/src/views/system/address/index.less b/src/views/system/address/index.less new file mode 100644 index 0000000..61b73d4 --- /dev/null +++ b/src/views/system/address/index.less @@ -0,0 +1,13 @@ +//noinspection LessUnresolvedVariable +@prefix-cls: ~'@{namespace}-address-list'; + +.@{prefix-cls} { + // update-begin-author:liusq date:20230625 for: [issues/563]暗色主题部分失效 + background-color: @component-background; + // update-end-author:liusq date:20230625 for: [issues/563]暗色主题部分失效 + &--box { + .ant-tabs-nav { + padding: 0 20px; + } + } +} diff --git a/src/views/system/address/index.vue b/src/views/system/address/index.vue new file mode 100644 index 0000000..da77b31 --- /dev/null +++ b/src/views/system/address/index.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/src/views/system/appVersion/SysAppVersion.vue b/src/views/system/appVersion/SysAppVersion.vue new file mode 100644 index 0000000..b9afc8e --- /dev/null +++ b/src/views/system/appVersion/SysAppVersion.vue @@ -0,0 +1,215 @@ + + + + + diff --git a/src/views/system/appVersion/appVersion.api.ts b/src/views/system/appVersion/appVersion.api.ts new file mode 100644 index 0000000..855ada6 --- /dev/null +++ b/src/views/system/appVersion/appVersion.api.ts @@ -0,0 +1,20 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + //查询app版本 + queryAppVersion = '/sys/version/app3version', + //保存app版本 + saveAppVersion = '/sys/version/saveVersion', +} +/** + * 查询APP版本 + * @param params + */ +export const queryAppVersion = (params) => defHttp.get({ url: Api.queryAppVersion, params }); +/** + * 保存APP版本 + * @param params + */ +export const saveAppVersion = (params) => { + return defHttp.post({ url: Api.saveAppVersion, params }); +}; diff --git a/src/views/system/appconfig/ThirdApp.api.ts b/src/views/system/appconfig/ThirdApp.api.ts new file mode 100644 index 0000000..8db4ccd --- /dev/null +++ b/src/views/system/appconfig/ThirdApp.api.ts @@ -0,0 +1,81 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + //第三方登录配置 + addThirdAppConfig = '/sys/thirdApp/addThirdAppConfig', + editThirdAppConfig = '/sys/thirdApp/editThirdAppConfig', + getThirdConfigByTenantId = '/sys/thirdApp/getThirdConfigByTenantId', + syncDingTalkDepartUserToLocal = '/sys/thirdApp/sync/dingtalk/departAndUser/toLocal', + getThirdUserByWechat = '/sys/thirdApp/getThirdUserByWechat', + wechatEnterpriseToLocal = '/sys/thirdApp/sync/wechatEnterprise/departAndUser/toLocal', + getThirdUserBindByWechat = '/sys/thirdApp/getThirdUserBindByWechat', + deleteThirdAccount = '/sys/thirdApp/deleteThirdAccount', + deleteThirdAppConfig = '/sys/thirdApp/deleteThirdAppConfig', +} + +/** + * 第三方配置保存或者更新 + */ +export const saveOrUpdateThirdConfig = (params, isUpdate) => { + let url = isUpdate ? Api.editThirdAppConfig : Api.addThirdAppConfig; + return defHttp.post({ url: url, params }, { joinParamsToUrl: true }); +}; + +/** + * 获取第三方配置 + * @param params + */ +export const getThirdConfigByTenantId = (params) => { + return defHttp.get({ url: Api.getThirdConfigByTenantId, params }); +}; + +/** + * 同步钉钉部门用户到本地 + * @param params + */ +export const syncDingTalkDepartUserToLocal = () => { + return defHttp.get({ url: Api.syncDingTalkDepartUserToLocal, timeout: 60000 }, { isTransformResponse: false }); +}; + +/** + * 获取企业微信绑定的用户信息 + * @param params + */ +export const getThirdUserByWechat = () => { + return defHttp.get({ url: Api.getThirdUserByWechat }, { isTransformResponse: false }); +}; + +/** + * 同步企业微信用户部门到本地 + * @param params + */ +export const wechatEnterpriseToLocal = (params) => { + return defHttp.get({ url: Api.wechatEnterpriseToLocal, params }, { isTransformResponse: false }); +}; + +/** + * 获取绑定企业微信的用户 + * @param params + */ +export const getThirdUserBindByWechat = () => { + return defHttp.get({ url: Api.getThirdUserBindByWechat }, { isTransformResponse: false }); +}; + +/** + * 根据第三方账号表的id解绑账号 + * @param params + */ +export const deleteThirdAccount = (params) => { + return defHttp.delete({ url: Api.deleteThirdAccount, params }, { isTransformResponse:false, joinParamsToUrl: true }); +}; + +/** + * 根据配置表的id删除第三方配置 + * @param params + * @param handleSuccess + */ +export const deleteThirdAppConfig = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteThirdAppConfig, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; \ No newline at end of file diff --git a/src/views/system/appconfig/ThirdApp.data.ts b/src/views/system/appconfig/ThirdApp.data.ts new file mode 100644 index 0000000..f2b207c --- /dev/null +++ b/src/views/system/appconfig/ThirdApp.data.ts @@ -0,0 +1,61 @@ +//第三方app配置表单 +import { FormSchema } from '/@/components/Form'; + +//第三方app表单 +export const thirdAppFormSchema: FormSchema[] = [ + { + label: 'id', + field: 'id', + component: 'Input', + show: false, + }, + { + label: 'thirdType', + field: 'thirdType', + component: 'Input', + show: false, + }, + { + label: 'CorpId', + field: 'corpId', + component: 'Input', + ifShow: ({ values }) => { + return values.thirdType === 'dingtalk'; + }, + required: true, + }, + { + label: 'Agentld', + field: 'agentId', + component: 'Input', + required: true, + }, + { + label: 'AppKey', + field: 'clientId', + component: 'Input', + required: true, + }, + { + label: 'AppSecret', + field: 'clientSecret', + component: 'Input', + required: true, + },{ + label: '启用', + field: 'status', + component: 'Switch', + componentProps:{ + checkedChildren:'关闭', + checkedValue:1, + unCheckedChildren:'开启', + unCheckedValue: 0 + }, + defaultValue: 1 + },{ + label: '租户id', + field: 'tenantId', + component: 'Input', + show: false, + }, +]; diff --git a/src/views/system/appconfig/ThirdAppBindWeEnterpriseModal.vue b/src/views/system/appconfig/ThirdAppBindWeEnterpriseModal.vue new file mode 100644 index 0000000..9293ae7 --- /dev/null +++ b/src/views/system/appconfig/ThirdAppBindWeEnterpriseModal.vue @@ -0,0 +1,316 @@ + + + + + + diff --git a/src/views/system/appconfig/ThirdAppConfigList.vue b/src/views/system/appconfig/ThirdAppConfigList.vue new file mode 100644 index 0000000..6d55f97 --- /dev/null +++ b/src/views/system/appconfig/ThirdAppConfigList.vue @@ -0,0 +1,140 @@ + + + + + + + diff --git a/src/views/system/appconfig/ThirdAppConfigModal.vue b/src/views/system/appconfig/ThirdAppConfigModal.vue new file mode 100644 index 0000000..12a6b48 --- /dev/null +++ b/src/views/system/appconfig/ThirdAppConfigModal.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/src/views/system/appconfig/ThirdAppDingTalkConfigForm.vue b/src/views/system/appconfig/ThirdAppDingTalkConfigForm.vue new file mode 100644 index 0000000..94e30c7 --- /dev/null +++ b/src/views/system/appconfig/ThirdAppDingTalkConfigForm.vue @@ -0,0 +1,326 @@ + + + + + diff --git a/src/views/system/appconfig/ThirdAppWeEnterpriseConfigForm.vue b/src/views/system/appconfig/ThirdAppWeEnterpriseConfigForm.vue new file mode 100644 index 0000000..410d2c6 --- /dev/null +++ b/src/views/system/appconfig/ThirdAppWeEnterpriseConfigForm.vue @@ -0,0 +1,272 @@ + + + + + diff --git a/src/views/system/category/category.api.ts b/src/views/system/category/category.api.ts new file mode 100644 index 0000000..9295745 --- /dev/null +++ b/src/views/system/category/category.api.ts @@ -0,0 +1,78 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +enum Api { + list = '/sys/category/rootList', + save = '/sys/category/add', + edit = '/sys/category/edit', + deleteCategory = '/sys/category/delete', + deleteBatch = '/sys/category/deleteBatch', + importExcel = '/sys/category/importExcel', + exportXls = '/sys/category/exportXls', + loadTreeData = '/sys/category/loadTreeRoot', + getChildList = '/sys/category/childList', + getChildListBatch = '/sys/category/getChildListBatch', +} +/** + * 导出api + * @param params + */ +export const getExportUrl = Api.exportXls; +/** + * 导入api + * @param params + */ +export const getImportUrl = Api.importExcel; +/** + * 列表接口 + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); +/** + * 删除 + */ +export const deleteCategory = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteCategory, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 批量删除 + * @param params + */ +export const batchDeleteCategory = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; +/** + * 保存或者更新 + * @param params + */ +export const saveOrUpdateDict = (params, isUpdate) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url: url, params }); +}; +/** + * 查询全部树形节点数据 + * @param params + */ +export const loadTreeData = (params) => defHttp.get({ url: Api.loadTreeData, params }); +/** + * 查询子节点数据 + * @param params + */ +export const getChildList = (params) => defHttp.get({ url: Api.getChildList, params }); +/** + * 批量查询子节点数据 + * @param params + */ +export const getChildListBatch = (params) => defHttp.get({ url: Api.getChildListBatch, params }, { isTransformResponse: false }); diff --git a/src/views/system/category/category.data.ts b/src/views/system/category/category.data.ts new file mode 100644 index 0000000..31e0b5a --- /dev/null +++ b/src/views/system/category/category.data.ts @@ -0,0 +1,66 @@ +import { BasicColumn } from '/@/components/Table'; +import { FormSchema } from '/@/components/Table'; + +export const columns: BasicColumn[] = [ + { + title: '分类名称', + dataIndex: 'name', + width: 350, + align: 'left', + }, + { + title: '分类编码', + dataIndex: 'code', + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + label: '名称', + field: 'name', + component: 'JInput', + colProps: { span: 6 }, + }, + { + label: '编码', + field: 'code', + component: 'JInput', + colProps: { span: 6 }, + }, +]; + +export const formSchema: FormSchema[] = [ + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '父级节点', + field: 'pid', + component: 'TreeSelect', + componentProps: { + // 代码逻辑说明: replaceFields已过期,使用fieldNames代替------------ + fieldNames: { + value: 'key', + }, + dropdownStyle: { + maxHeight: '50vh', + }, + getPopupContainer: () => document.body, + }, + show: ({ values }) => { + return values.pid !== '0'; + }, + dynamicDisabled: ({ values }) => { + return !!values.id; + }, + }, + { + label: '分类名称', + field: 'name', + required: true, + component: 'Input', + }, +]; diff --git a/src/views/system/category/components/CategoryModal.vue b/src/views/system/category/components/CategoryModal.vue new file mode 100644 index 0000000..ee9671e --- /dev/null +++ b/src/views/system/category/components/CategoryModal.vue @@ -0,0 +1,106 @@ + + diff --git a/src/views/system/category/index.vue b/src/views/system/category/index.vue new file mode 100644 index 0000000..dbae42d --- /dev/null +++ b/src/views/system/category/index.vue @@ -0,0 +1,293 @@ + + + + + diff --git a/src/views/system/checkRule/CheckRuleModal.vue b/src/views/system/checkRule/CheckRuleModal.vue new file mode 100644 index 0000000..466c3ba --- /dev/null +++ b/src/views/system/checkRule/CheckRuleModal.vue @@ -0,0 +1,247 @@ + + + diff --git a/src/views/system/checkRule/CheckRuleTestModal.vue b/src/views/system/checkRule/CheckRuleTestModal.vue new file mode 100644 index 0000000..06dd10c --- /dev/null +++ b/src/views/system/checkRule/CheckRuleTestModal.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/src/views/system/checkRule/check.rule.api.ts b/src/views/system/checkRule/check.rule.api.ts new file mode 100644 index 0000000..c4b5391 --- /dev/null +++ b/src/views/system/checkRule/check.rule.api.ts @@ -0,0 +1,86 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +enum Api { + list = '/sys/checkRule/list', + delete = '/sys/checkRule/delete', + deleteBatch = '/sys/checkRule/deleteBatch', + exportXls = 'sys/checkRule/exportXls', + importXls = 'sys/checkRule/importExcel', + checkByCode = '/sys/checkRule/checkByCode', + save = '/sys/checkRule/add', + edit = '/sys/checkRule/edit', +} + +/** + * 导出地址 + */ +export const exportUrl = Api.exportXls; +/** + * 导入地址 + */ +export const importUrl = Api.importXls; + +/** + * 列表查询 + * @param params + */ +export const getCheckRuleList = (params) => { + return defHttp.get({ url: Api.list, params }); +}; + +/** + * 删除 + * @param params + * @param handleSuccess + */ +export const deleteCheckRule = (params, handleSuccess) => { + return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +/** + * 批量删除 + * @param params + */ +export const batchDeleteCheckRule = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; + +/** + * 根据编码校验规则code,校验传入的值是否合法 + * @param ruleCode + * @param value + */ +export const validateCheckRule = (ruleCode, value) => { + value = encodeURIComponent(value); + let params = { ruleCode, value }; + return defHttp.get({ url: Api.checkByCode, params }, { isTransformResponse: false }); +}; + +/** + * 保存 + * @param params + */ +export const saveCheckRule = (params) => { + return defHttp.post({ url: Api.save, params }); +}; + +/** + * 更新 + * @param params + */ +export const updateCheckRule = (params) => { + return defHttp.put({ url: Api.edit, params }); +}; diff --git a/src/views/system/checkRule/check.rule.data.ts b/src/views/system/checkRule/check.rule.data.ts new file mode 100644 index 0000000..b7da750 --- /dev/null +++ b/src/views/system/checkRule/check.rule.data.ts @@ -0,0 +1,152 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { render } from '/@/utils/common/renderUtils'; +import { duplicateCheckDelay } from '/@/views/system/user/user.api'; +import { validateCheckRule } from '/@/views/system/checkRule/check.rule.api'; +import { array } from 'vue-types'; + +export const columns: BasicColumn[] = [ + { + title: '规则名称', + dataIndex: 'ruleName', + width: 200, + align: 'center', + }, + { + title: '规则编码', + dataIndex: 'ruleCode', + width: 200, + align: 'center', + }, + { + title: '规则描述', + dataIndex: 'ruleDescription', + width: 300, + align: 'center', + customRender: function ({ text }) { + return render.renderTip(text, 30); + }, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'ruleName', + label: '规则名称', + component: 'Input', + colProps: { span: 6 }, + }, + { + field: 'ruleCode', + label: '规则编码', + component: 'Input', + colProps: { span: 6 }, + }, +]; + +export const formSchema: FormSchema[] = [ + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, + { + field: 'ruleName', + label: '规则名称', + component: 'Input', + required: true, + colProps: { span: 24 }, + }, + { + field: 'ruleCode', + label: '规则编码', + component: 'Input', + colProps: { span: 24 }, + dynamicDisabled: ({ values }) => { + return !!values.id; + }, + dynamicRules: ({ model }) => { + return [ + { + required: true, + validator: (_, value) => { + return new Promise((resolve, reject) => { + if (!value) { + return reject('请输入规则编码!'); + } + let params = { + tableName: 'sys_check_rule', + fieldName: 'rule_code', + fieldVal: value, + dataId: model.id, + }; + duplicateCheckDelay(params) + .then((res) => { + res.success ? resolve() : reject('规则编码已存在!'); + }) + .catch((err) => { + reject(err.message || '校验失败'); + }); + }); + }, + }, + ]; + }, + }, + { + field: 'ruleDescription', + label: '规则描述', + colProps: { span: 24 }, + component: 'InputTextArea', + componentProps: { + placeholder: '请输入规则描述', + rows: 2, + }, + }, +]; + +export const checkRuleInput: FormSchema[] = [ + { + label: '123', + field: 'ruleCode', + component: 'Input', + show: false, + }, + { + field: 'testValue', + label: '需要测试的值:', + component: 'Input', + componentProps: ({ formModel }) => { + return { + onChange: (e) => { + formModel.testValue = e.target.value; + }, + }; + }, + dynamicRules: ({ model }) => { + const { ruleCode } = model; + return [ + { + required: false, + validator: (_, value) => { + return new Promise((resolve, reject) => { + if (ruleCode && value) { + /*console.log({ruleCode,value})*/ + validateCheckRule(ruleCode, value) + .then((res) => { + //console.log(1233, res) + res['success'] ? resolve() : reject(res['message']); + }) + .catch((err) => { + reject(err.message || err); + }); + } else { + resolve(); + } + }); + }, + }, + ]; + }, + }, +]; diff --git a/src/views/system/checkRule/index.vue b/src/views/system/checkRule/index.vue new file mode 100644 index 0000000..9dbf249 --- /dev/null +++ b/src/views/system/checkRule/index.vue @@ -0,0 +1,150 @@ + + + diff --git a/src/views/system/depart/TenantDepartList.vue b/src/views/system/depart/TenantDepartList.vue new file mode 100644 index 0000000..a60bcd6 --- /dev/null +++ b/src/views/system/depart/TenantDepartList.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/views/system/depart/components/DepartDataRuleDrawer.vue b/src/views/system/depart/components/DepartDataRuleDrawer.vue new file mode 100644 index 0000000..3427872 --- /dev/null +++ b/src/views/system/depart/components/DepartDataRuleDrawer.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/views/system/depart/components/DepartFormModal.vue b/src/views/system/depart/components/DepartFormModal.vue new file mode 100644 index 0000000..2458c23 --- /dev/null +++ b/src/views/system/depart/components/DepartFormModal.vue @@ -0,0 +1,154 @@ + + + + + \ No newline at end of file diff --git a/src/views/system/depart/components/DepartFormTab.vue b/src/views/system/depart/components/DepartFormTab.vue new file mode 100644 index 0000000..e0199de --- /dev/null +++ b/src/views/system/depart/components/DepartFormTab.vue @@ -0,0 +1,177 @@ + + + + + \ No newline at end of file diff --git a/src/views/system/depart/components/DepartLeftTree.vue b/src/views/system/depart/components/DepartLeftTree.vue new file mode 100644 index 0000000..2343884 --- /dev/null +++ b/src/views/system/depart/components/DepartLeftTree.vue @@ -0,0 +1,481 @@ + + + + + diff --git a/src/views/system/depart/components/DepartRankRelation.vue b/src/views/system/depart/components/DepartRankRelation.vue new file mode 100644 index 0000000..3b09b2f --- /dev/null +++ b/src/views/system/depart/components/DepartRankRelation.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/views/system/depart/components/DepartRuleTab.vue b/src/views/system/depart/components/DepartRuleTab.vue new file mode 100644 index 0000000..f16b755 --- /dev/null +++ b/src/views/system/depart/components/DepartRuleTab.vue @@ -0,0 +1,267 @@ + + + + + diff --git a/src/views/system/depart/components/DepartUserList.vue b/src/views/system/depart/components/DepartUserList.vue new file mode 100644 index 0000000..ec80c1b --- /dev/null +++ b/src/views/system/depart/components/DepartUserList.vue @@ -0,0 +1,182 @@ + + + diff --git a/src/views/system/depart/components/DepartmentHeadList.vue b/src/views/system/depart/components/DepartmentHeadList.vue new file mode 100644 index 0000000..879d785 --- /dev/null +++ b/src/views/system/depart/components/DepartmentHeadList.vue @@ -0,0 +1,44 @@ + + + + + + diff --git a/src/views/system/depart/depart.api.ts b/src/views/system/depart/depart.api.ts new file mode 100644 index 0000000..d8d8510 --- /dev/null +++ b/src/views/system/depart/depart.api.ts @@ -0,0 +1,171 @@ +import { unref } from 'vue'; +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; + +const { createConfirm } = useMessage(); + +export enum Api { + queryDepartTreeSync = '/sys/sysDepart/queryDepartTreeSync', + save = '/sys/sysDepart/add', + edit = '/sys/sysDepart/edit', + delete = '/sys/sysDepart/delete', + deleteBatch = '/sys/sysDepart/deleteBatch', + exportXlsUrl = '/sys/sysDepart/exportXls', + importExcelUrl = '/sys/sysDepart/importExcel', + + roleQueryTreeList = '/sys/role/queryTreeList', + queryDepartPermission = '/sys/permission/queryDepartPermission', + saveDepartPermission = '/sys/permission/saveDepartPermission', + + dataRule = '/sys/sysDepartPermission/datarule', + + getCurrentUserDeparts = '/sys/user/getCurrentUserDeparts', + selectDepart = '/sys/selectDepart', + getUpdateDepartInfo = '/sys/user/getUpdateDepartInfo', + doUpdateDepartInfo = '/sys/user/doUpdateDepartInfo', + changeDepartChargePerson = '/sys/user/changeDepartChargePerson', + //根据部门id获取岗位信息 + getPositionByDepartId = '/sys/sysDepart/getPositionByDepartId', + //根据部门id获取岗位上下级关系 + getRankRelation = '/sys/sysDepart/getRankRelation', + //异步获取部门和岗位 + queryDepartAndPostTreeSync = '/sys/sysDepart/queryDepartAndPostTreeSync', + //获取部门和岗位下的成员 + queryDepartPostByOrgCode = '/sys/user/queryDepartPostByOrgCode', + //更新拖拽部门后的位置 + updateChangeDepart = '/sys/sysDepart/updateChangeDepart', + //获取负责部门 + getDepartmentHead = '/sys/sysDepart/getDepartmentHead', +} + +/** + * 获取部门树列表 + */ +export const queryDepartTreeSync = (params?) => defHttp.get({ url: Api.queryDepartTreeSync, params }); + +/** + * 获取部门和岗位树列表 + */ +export const queryDepartAndPostTreeSync = (params?) => defHttp.get({ url: Api.queryDepartAndPostTreeSync, params }); + +/** + * 保存或者更新部门角色 + */ +export const saveOrUpdateDepart = (params, isUpdate) => { + if (isUpdate) { + return defHttp.put({ url: Api.edit, params }); + } else { + return defHttp.post({ url: Api.save, params }); + } +}; + +/** + * 批量删除部门角色 + */ +export const deleteBatchDepart = (params, confirm = false) => { + return new Promise((resolve, reject) => { + const doDelete = () => { + resolve(defHttp.delete({ url: Api.deleteBatch, params }, { joinParamsToUrl: true })); + }; + if (confirm) { + createConfirm({ + iconType: 'warning', + title: '删除', + content: '确定要删除吗?', + onOk: () => doDelete(), + onCancel: () => reject(), + }); + } else { + doDelete(); + } + }); +}; + +/** + * 获取权限树列表 + */ +export const queryRoleTreeList = (params?) => defHttp.get({ url: Api.roleQueryTreeList, params }); +/** + * 查询部门权限 + */ +export const queryDepartPermission = (params?) => defHttp.get({ url: Api.queryDepartPermission, params }); +/** + * 保存部门权限 + */ +export const saveDepartPermission = (params) => defHttp.post({ url: Api.saveDepartPermission, params }); + +/** + * 查询部门数据权限列表 + */ +export const queryDepartDataRule = (functionId, departId, params?) => { + let url = `${Api.dataRule}/${unref(functionId)}/${unref(departId)}`; + return defHttp.get({ url, params }); +}; +/** + * 保存部门数据权限 + */ +export const saveDepartDataRule = (params) => defHttp.post({ url: Api.dataRule, params }); +/** + * 获取登录用户部门信息 + */ +export const getUserDeparts = (params?) => defHttp.get({ url: Api.getCurrentUserDeparts, params }); +/** + * 切换选择部门 + */ +export const selectDepart = (params?) => defHttp.put({ url: Api.selectDepart, params }); + +/** + * 编辑部门前获取部门相关信息 + * @param id + */ +export const getUpdateDepartInfo = (id) => defHttp.get({ url: Api.getUpdateDepartInfo, params: {id} }); + +/** + * 编辑部门 + * @param params + */ +export const doUpdateDepartInfo = (params) => defHttp.put({ url: Api.doUpdateDepartInfo, params }); + +/** + * 删除部门 + * @param id + */ +export const deleteDepart = (id) => defHttp.delete({ url: Api.delete, params:{ id } }, { joinParamsToUrl: true }); + +/** + * 设置负责人 取消负责人 + * @param params + */ +export const changeDepartChargePerson = (params) => defHttp.put({ url: Api.changeDepartChargePerson, params }); + +/** + * 根据部门id获取岗位信息 + */ +export const getPositionByDepartId = (params) => defHttp.get({ url: Api.getPositionByDepartId, params }, { isTransformResponse: false }); + +/** + * 根据部门id获取岗位上下级关系 + * @param params + */ +export const getRankRelation = (params) => defHttp.get({ url: Api.getRankRelation, params,timeout: 2 * 60 * 1000 }, { isTransformResponse: false }); + +/** + * 根据部门或岗位编码获取通讯录成员 + * + * @param params + */ +export const queryDepartPostByOrgCode = (params) => defHttp.get({ url: Api.queryDepartPostByOrgCode, params }); + +/** + * 更新拖拽部门后的位置 + * + * @param params + */ +export const updateChangeDepart = (params) => defHttp.put({ url: Api.updateChangeDepart, params },{ isTransformResponse: false }); + +/** + * 获取负责部门 + * + * @param params + */ +export const getDepartmentHead = (params) => defHttp.get({ url: Api.getDepartmentHead, params }); diff --git a/src/views/system/depart/depart.data.ts b/src/views/system/depart/depart.data.ts new file mode 100644 index 0000000..dd77194 --- /dev/null +++ b/src/views/system/depart/depart.data.ts @@ -0,0 +1,247 @@ +import { FormSchema } from '/@/components/Form'; +import { getPositionByDepartId } from "./depart.api"; +import { useMessage } from "@/hooks/web/useMessage"; +import { BasicColumn } from "@/components/Table"; +import { + getDepartName, + getDepartPathName, + getDepartPathNameByOrgCode, + getMultiDepartPathName +} from '@/utils/common/compUtils'; +import { h, ref } from 'vue'; + +const { createMessage: $message } = useMessage(); +//部门名称 +const departNamePath = ref>({}); + +// 部门基础表单 +export function useBasicFormSchema(treeData) { + const basicFormSchema: FormSchema[] = [ + { + field: 'departName', + label: '机构名称', + component: 'Input', + componentProps: { + placeholder: '请输入机构/部门名称', + }, + rules: [{ required: true, message: '机构名称不能为空' }], + }, + { + field: 'departNameAbbr', + label: '机构简称', + component: 'Input', + componentProps: { + placeholder: '请输入机构/部门简称', + } + }, + { + field: 'parentId', + label: '上级部门', + component: 'TreeSelect', + componentProps: { + treeData: [], + placeholder: '无', + treeCheckAble: true, + multiple: true, + dropdownStyle: { maxHeight: '200px', overflow: 'auto' }, + tagRender: (options) => { + const { value, label, option } = options; + if (departNamePath.value[value]) { + return h( + 'span', { style: { marginLeft: '10px' } }, + departNamePath.value[value] + ); + } + getDepartPathNameByOrgCode('', label, option.id).then((data) => { + departNamePath.value[value] = data; + }); + }, + }, + }, + { + field: 'orgCode', + label: '机构编码', + component: 'Input', + componentProps: { + placeholder: '请输入机构编码', + }, + }, + { + field: 'orgCategory', + label: '机构类型', + component: 'RadioButtonGroup', + componentProps: { options: [] }, + }, + { + field: 'positionId', + label: '职务级别', + component: 'JDictSelectTag', + componentProps: ({ formModel, formActionType }) => { + return { + dictCode: "sys_position,name,id, 1=1 order by post_level asc", + getPopupContainer: ()=> document.body, + onChange: (value) => { + formModel.depPostParentId = ""; + return positionChange(value, formModel, treeData); + }, + } + }, + ifShow:({ values })=>{ + return values.orgCategory === '3' + }, + required: true, + }, + { + field: 'depPostParentId', + label: '上级岗位', + component: 'TreeSelect', + ifShow:({ values })=>{ + return values.orgCategory === '3' + }, + slot: 'depPostParentId', + }, + { + field: 'departOrder', + label: '排序', + component: 'InputNumber', + componentProps: {}, + }, + { + field: 'mobile', + label: '电话', + component: 'Input', + componentProps: { + placeholder: '请输入电话', + }, + ifShow:({ values })=>{ + return values.orgCategory !== '3' + }, + }, + { + field: 'fax', + label: '传真', + component: 'Input', + componentProps: { + placeholder: '请输入传真', + }, + ifShow:({ values })=>{ + return values.orgCategory !== '3' + }, + }, + { + field: 'address', + label: '地址', + component: 'Input', + componentProps: { + placeholder: '请输入地址', + }, + ifShow:({ values })=>{ + return values.orgCategory !== '3' + }, + }, + { + field: 'memo', + label: '备注', + component: 'InputTextArea', + componentProps: { + placeholder: '请输入备注', + }, + ifShow:({ values })=>{ + return values.orgCategory !== '3' + }, + }, + { + field: 'id', + label: 'ID', + component: 'Input', + show: false, + }, + ]; + return { basicFormSchema }; +} + +// 机构类型选项 +export const orgCategoryOptions = { + // 一级部门 + root: [{ value: '1', label: '公司' }], + // 子级部门 + child: [ + { value: '4', label: '子公司' }, + { value: '2', label: '部门' }, + { value: '3', label: '岗位' }, + ], + //部门岗位 + childDepartPost: [ + { value: '2', label: '部门' }, + { value: '3', label: '岗位' }, + ], + //岗位 + childPost: [ + { value: '3', label: '岗位' }, + ] +}; + +/** + * 用户列表 + */ +export const userColumns: BasicColumn[] = [ + { + title: '姓名', + dataIndex: 'realname', + width: 150, + }, + { + title: '手机', + width: 150, + dataIndex: 'phone', + customRender:( { record, text })=>{ + if(record.izHideContact && record.izHideContact === '1'){ + return '/'; + } + return text; + } + }, + { + title: '主岗位', + dataIndex: 'mainDepPostId', + customRender: ({ record, text })=>{ + if(!text){ + return ''; + } + return getDepartName(getDepartPathName(record.mainDepPostId_dictText,text,false)); + }, + width: 200, + }, + { + title: '兼职岗位', + dataIndex: 'otherDepPostId', + customRender: ({ record, text })=>{ + if(!text){ + return ''; + } + return getDepartName(getMultiDepartPathName(record.otherDepPostId_dictText,text)); + }, + width: 200, + }, +]; + +/** + * 职位改变事件 + * @param value + * @param model + * @param treeData + */ +export function positionChange(value, model, treeData) { + if(value && model.parentId){ + getPositionByDepartId({ parentId: model.parentId, departId: model.id ? model.id:'', positionId: value }).then((res) =>{ + if(res.success){ + treeData.value = res.result; + }else{ + treeData.value = []; + $message.warning(res.message); + } + }); + } else { + treeData.value = []; + } +} diff --git a/src/views/system/depart/index.less b/src/views/system/depart/index.less new file mode 100644 index 0000000..67e0e08 --- /dev/null +++ b/src/views/system/depart/index.less @@ -0,0 +1,14 @@ +//noinspection LessUnresolvedVariable +@prefix-cls: ~'@{namespace}-depart-manage'; + +.@{prefix-cls} { + // update-begin-author:liusq date:20230625 for: [issues/563]暗色主题部分失效 + background: @component-background; + // update-end-author:liusq date:20230625 for: [issues/563]暗色主题部分失效 + + &--box { + .ant-tabs-nav { + padding: 0 20px; + } + } +} diff --git a/src/views/system/depart/index.vue b/src/views/system/depart/index.vue new file mode 100644 index 0000000..4a7b11e --- /dev/null +++ b/src/views/system/depart/index.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/src/views/system/departUser/components/DepartBaseInfoTab.vue b/src/views/system/departUser/components/DepartBaseInfoTab.vue new file mode 100644 index 0000000..911319f --- /dev/null +++ b/src/views/system/departUser/components/DepartBaseInfoTab.vue @@ -0,0 +1,40 @@ + + + diff --git a/src/views/system/departUser/components/DepartRoleAuthDrawer.vue b/src/views/system/departUser/components/DepartRoleAuthDrawer.vue new file mode 100644 index 0000000..3762d8e --- /dev/null +++ b/src/views/system/departUser/components/DepartRoleAuthDrawer.vue @@ -0,0 +1,294 @@ + + + + diff --git a/src/views/system/departUser/components/DepartRoleDataRuleDrawer.vue b/src/views/system/departUser/components/DepartRoleDataRuleDrawer.vue new file mode 100644 index 0000000..04f2e45 --- /dev/null +++ b/src/views/system/departUser/components/DepartRoleDataRuleDrawer.vue @@ -0,0 +1,82 @@ + + + diff --git a/src/views/system/departUser/components/DepartRoleInfoTab.vue b/src/views/system/departUser/components/DepartRoleInfoTab.vue new file mode 100644 index 0000000..8725a6b --- /dev/null +++ b/src/views/system/departUser/components/DepartRoleInfoTab.vue @@ -0,0 +1,202 @@ + + + diff --git a/src/views/system/departUser/components/DepartRoleModal.vue b/src/views/system/departUser/components/DepartRoleModal.vue new file mode 100644 index 0000000..4eec504 --- /dev/null +++ b/src/views/system/departUser/components/DepartRoleModal.vue @@ -0,0 +1,63 @@ + + + diff --git a/src/views/system/departUser/components/DepartRoleUserAuthDrawer.vue b/src/views/system/departUser/components/DepartRoleUserAuthDrawer.vue new file mode 100644 index 0000000..78dbc1b --- /dev/null +++ b/src/views/system/departUser/components/DepartRoleUserAuthDrawer.vue @@ -0,0 +1,91 @@ + + + diff --git a/src/views/system/departUser/components/DepartTree.vue b/src/views/system/departUser/components/DepartTree.vue new file mode 100644 index 0000000..5a150a8 --- /dev/null +++ b/src/views/system/departUser/components/DepartTree.vue @@ -0,0 +1,259 @@ + + + + diff --git a/src/views/system/departUser/components/DepartUserInfoTab.vue b/src/views/system/departUser/components/DepartUserInfoTab.vue new file mode 100644 index 0000000..b910bf4 --- /dev/null +++ b/src/views/system/departUser/components/DepartUserInfoTab.vue @@ -0,0 +1,238 @@ + + + diff --git a/src/views/system/departUser/depart.user.api.ts b/src/views/system/departUser/depart.user.api.ts new file mode 100644 index 0000000..d5a37ed --- /dev/null +++ b/src/views/system/departUser/depart.user.api.ts @@ -0,0 +1,159 @@ +import { unref } from 'vue'; +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; + +const { createConfirm } = useMessage(); + +enum Api { + treeList = '/sys/sysDepart/queryMyDeptTreeList', + queryIdTree = '/sys/sysDepart/queryIdTree', + searchBy = '/sys/sysDepart/searchBy', +} + +// 部门用户API +enum DepartUserApi { + list = '/sys/user/departUserList', + link = '/sys/user/editSysDepartWithUser', + unlink = '/sys/user/deleteUserInDepartBatch', +} + +// 部门角色API +enum DepartRoleApi { + list = '/sys/sysDepartRole/list', + deleteBatch = '/sys/sysDepartRole/deleteBatch', + save = '/sys/sysDepartRole/add', + edit = '/sys/sysDepartRole/edit', + queryTreeListForDeptRole = '/sys/sysDepartPermission/queryTreeListForDeptRole', + queryDeptRolePermission = '/sys/sysDepartPermission/queryDeptRolePermission', + saveDeptRolePermission = '/sys/sysDepartPermission/saveDeptRolePermission', + dataRule = '/sys/sysDepartRole/datarule', + getDeptRoleList = '/sys/sysDepartRole/getDeptRoleList', + getDeptRoleByUserId = '/sys/sysDepartRole/getDeptRoleByUserId', + saveDeptRoleUser = '/sys/sysDepartRole/deptRoleUserAdd', +} + +/** + * 获取部门树列表 + */ +export const queryMyDepartTreeList = (params?) => defHttp.get({ url: Api.treeList, params }, { isTransformResponse: false }); + +/** + * 查询数据,以树结构形式加载所有部门的名称 + */ +export const queryIdTree = (params?) => defHttp.get({ url: Api.queryIdTree, params }); + +/** + * 根据关键字搜索部门 + */ +export const searchByKeywords = (params) => defHttp.get({ url: Api.searchBy, params }); + +/** + * 查询部门下的用户信息 + */ +export const departUserList = (params) => defHttp.get({ url: DepartUserApi.list, params }); + +/** + * 批量添加部门和用户的关联关系 + * + * @param departId 部门ID + * @param userIdList 用户ID列表 + */ +export const linkDepartUserBatch = (departId: string, userIdList: string[]) => + defHttp.post({ url: DepartUserApi.link, params: { depId: departId, userIdList } }); + +/** + * 批量取消部门和用户的关联关系 + */ +export const unlinkDepartUserBatch = (params, confirm = false) => { + return new Promise((resolve, reject) => { + const doDelete = () => { + resolve(defHttp.delete({ url: DepartUserApi.unlink, params }, { joinParamsToUrl: true })); + }; + if (confirm) { + createConfirm({ + iconType: 'warning', + title: '取消关联', + content: '确定要取消关联吗?', + onOk: () => doDelete(), + onCancel: () => reject(), + }); + } else { + doDelete(); + } + }); +}; + +/** + * 查询部门角色信息 + */ +export const departRoleList = (params) => defHttp.get({ url: DepartRoleApi.list, params }); + +/** + * 保存或者更新部门角色 + */ +export const saveOrUpdateDepartRole = (params, isUpdate) => { + if (isUpdate) { + return defHttp.put({ url: DepartRoleApi.edit, params }); + } else { + return defHttp.post({ url: DepartRoleApi.save, params }); + } +}; + +/** + * 批量删除部门角色 + */ +export const deleteBatchDepartRole = (params, confirm = false) => { + return new Promise((resolve, reject) => { + const doDelete = () => { + resolve(defHttp.delete({ url: DepartRoleApi.deleteBatch, params }, { joinParamsToUrl: true })); + }; + if (confirm) { + createConfirm({ + iconType: 'warning', + title: '删除', + content: '确定要删除吗?', + onOk: () => doDelete(), + onCancel: () => reject(), + }); + } else { + doDelete(); + } + }); +}; + +/** + * 用户角色授权功能,查询菜单权限树 + */ +export const queryTreeListForDeptRole = (params) => defHttp.get({ url: DepartRoleApi.queryTreeListForDeptRole, params }); +/** + * 查询角色授权 + */ +export const queryDeptRolePermission = (params) => defHttp.get({ url: DepartRoleApi.queryDeptRolePermission, params }); +/** + * 保存角色授权 + */ +export const saveDeptRolePermission = (params) => defHttp.post({ url: DepartRoleApi.saveDeptRolePermission, params }); + +/** + * 查询部门角色数据权限列表 + */ +export const queryDepartRoleDataRule = (functionId, departId, roleId, params?) => { + let url = `${DepartRoleApi.dataRule}/${unref(functionId)}/${unref(departId)}/${unref(roleId)}`; + return defHttp.get({ url, params }); +}; +/** + * 保存部门角色数据权限 + */ +export const saveDepartRoleDataRule = (params) => defHttp.post({ url: DepartRoleApi.dataRule, params }); +/** + * 查询部门角色用户授权 + */ +export const queryDepartRoleUserList = (params) => defHttp.get({ url: DepartRoleApi.getDeptRoleList, params }); +/** + * 根据 userId 查询部门角色用户授权 + */ +export const queryDepartRoleByUserId = (params) => defHttp.get({ url: DepartRoleApi.getDeptRoleByUserId, params }); +/** + * 保存部门角色用户授权 + */ +export const saveDepartRoleUser = (params) => defHttp.post({ url: DepartRoleApi.saveDeptRoleUser, params }); diff --git a/src/views/system/departUser/depart.user.data.ts b/src/views/system/departUser/depart.user.data.ts new file mode 100644 index 0000000..79e78e8 --- /dev/null +++ b/src/views/system/departUser/depart.user.data.ts @@ -0,0 +1,197 @@ +import { Ref } from 'vue'; +import { duplicateCheckDelay } from '/@/views/system/user/user.api'; +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { DescItem } from '/@/components/Description'; +import { findTree } from '/@/utils/common/compUtils'; + +// 用户信息 columns +export const userInfoColumns: BasicColumn[] = [ + { + title: '用户账号', + dataIndex: 'username', + width: 150, + }, + { + title: '用户名称', + dataIndex: 'realname', + width: 180, + }, + { + title: '部门', + dataIndex: 'orgCode', + width: 200, + }, + { + title: '性别', + dataIndex: 'sex_dictText', + width: 80, + }, + { + title: '电话', + dataIndex: 'phone', + width: 120, + }, +]; + +// 用户信息查询条件表单 +export const userInfoSearchFormSchema: FormSchema[] = [ + { + field: 'username', + label: '用户账号', + component: 'Input', + }, +]; + +// 部门角色 columns +export const departRoleColumns: BasicColumn[] = [ + { + title: '部门角色名称', + dataIndex: 'roleName', + width: 100, + }, + { + title: '部门角色编码', + dataIndex: 'roleCode', + width: 100, + }, + { + title: '部门', + dataIndex: 'departId_dictText', + width: 100, + }, + { + title: '备注', + dataIndex: 'description', + width: 100, + }, +]; + +// 部门角色查询条件表单 +export const departRoleSearchFormSchema: FormSchema[] = [ + { + field: 'roleName', + label: '部门角色名称', + component: 'Input', + }, +]; + +// 部门角色弹窗form表单 +export const departRoleModalFormSchema: FormSchema[] = [ + { + label: 'id', + field: 'id', + component: 'Input', + show: false, + }, + { + field: 'roleName', + label: '部门角色名称', + component: 'Input', + rules: [ + { required: true, message: '部门角色名称不能为空!' }, + { min: 2, max: 30, message: '长度在 2 到 30 个字符', trigger: 'blur' }, + ], + }, + { + field: 'roleCode', + label: '部门角色编码', + component: 'Input', + dynamicDisabled: ({ values }) => { + return !!values.id; + }, + dynamicRules: ({ model }) => { + return [ + { required: true, message: '部门角色编码不能为空!' }, + { min: 0, max: 64, message: '长度不能超过 64 个字符', trigger: 'blur' }, + { + validator: (_, value) => { + if (/[\u4E00-\u9FA5]/g.test(value)) { + return Promise.reject('部门角色编码不可输入汉字!'); + } + return new Promise((resolve, reject) => { + let params = { + tableName: 'sys_depart_role', + fieldName: 'role_code', + fieldVal: value, + dataId: model.id, + }; + duplicateCheckDelay(params) + .then((res) => { + res.success ? resolve() : reject(res.message || '校验失败'); + }) + .catch((err) => { + reject(err.message || '验证失败'); + }); + }); + }, + }, + ]; + }, + }, + { + field: 'description', + label: '描述', + component: 'Input', + rules: [{ min: 0, max: 126, message: '长度不能超过 126 个字符', trigger: 'blur' }], + }, +]; + +// 基本信息form +export function useBaseInfoForm(treeData: Ref) { + const descItems: DescItem[] = [ + { + field: 'departName', + label: '机构名称', + }, + { + field: 'parentId', + label: '上级部门', + render(val) { + if (val) { + let data = findTree(treeData.value, (item) => item.key == val); + return data?.title ?? val; + } + return val; + }, + }, + { + field: 'orgCode', + label: '机构编码', + }, + { + field: 'orgCategory', + label: '机构类型', + render(val) { + if (val === '1') { + return '公司'; + } else if (val === '2') { + return '部门'; + } else if (val === '3') { + return '岗位'; + } else if(val === '4'){ + return '子公司'; + } + return val; + }, + }, + { + field: 'departOrder', + label: '排序', + }, + + { + field: 'mobile', + label: '手机号', + }, + { + field: 'address', + label: '地址', + }, + { + field: 'memo', + label: '备注', + }, + ]; + + return { descItems }; +} diff --git a/src/views/system/departUser/index.less b/src/views/system/departUser/index.less new file mode 100644 index 0000000..df2d981 --- /dev/null +++ b/src/views/system/departUser/index.less @@ -0,0 +1,48 @@ +@prefix-cls: ~'@{namespace}-depart-user'; + +.@{prefix-cls} { + &--tree-search { + width: 100%; + margin: 10px 0 20px; + } + + &--base-info-form { + @media (min-width: 576px) { + .no-border { + border: 0; + box-shadow: none; + } + + .ant-select.ant-select-disabled { + .ant-select-selector { + border: 0; + color: black; + background-color: transparent; + } + + .ant-select-selector, + .ant-select-selection-item { + cursor: text !important; + user-select: initial !important; + } + + .ant-select-selection-search, + .ant-select-arrow { + display: none; + } + } + } + } +} + +// 夜间模式样式兼容 +[data-theme='dark'] .@{prefix-cls} { + &--base-info-form { + .ant-select.ant-select-disabled { + .ant-select-selector { + color: #c9d1d9; + background-color: transparent; + } + } + } +} diff --git a/src/views/system/departUser/index.vue b/src/views/system/departUser/index.vue new file mode 100644 index 0000000..675d10b --- /dev/null +++ b/src/views/system/departUser/index.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/src/views/system/dict/components/DictItemList.vue b/src/views/system/dict/components/DictItemList.vue new file mode 100644 index 0000000..1ea5e43 --- /dev/null +++ b/src/views/system/dict/components/DictItemList.vue @@ -0,0 +1,140 @@ + + + diff --git a/src/views/system/dict/components/DictItemModal.vue b/src/views/system/dict/components/DictItemModal.vue new file mode 100644 index 0000000..5638da9 --- /dev/null +++ b/src/views/system/dict/components/DictItemModal.vue @@ -0,0 +1,124 @@ + + + diff --git a/src/views/system/dict/components/DictModal.vue b/src/views/system/dict/components/DictModal.vue new file mode 100644 index 0000000..e8f0808 --- /dev/null +++ b/src/views/system/dict/components/DictModal.vue @@ -0,0 +1,52 @@ + + diff --git a/src/views/system/dict/components/DictRecycleBinModal.vue b/src/views/system/dict/components/DictRecycleBinModal.vue new file mode 100644 index 0000000..c32aafe --- /dev/null +++ b/src/views/system/dict/components/DictRecycleBinModal.vue @@ -0,0 +1,137 @@ + + diff --git a/src/views/system/dict/dict.api.ts b/src/views/system/dict/dict.api.ts new file mode 100644 index 0000000..36fe729 --- /dev/null +++ b/src/views/system/dict/dict.api.ts @@ -0,0 +1,156 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; +enum Api { + list = '/sys/dict/list', + save = '/sys/dict/add', + edit = '/sys/dict/edit', + duplicateCheck = '/sys/duplicate/check', + deleteDict = '/sys/dict/delete', + deleteBatch = '/sys/dict/deleteBatch', + importExcel = '/sys/dict/importExcel', + exportXls = '/sys/dict/exportXls', + recycleBinList = '/sys/dict/deleteList', + putRecycleBin = '/sys/dict/back', + batchPutRecycleBin = '/sys/dict/putRecycleBin', + batchDeleteRecycleBin = '/sys/dict/deleteRecycleBin', + deleteRecycleBin = '/sys/dict/deletePhysic', + itemList = '/sys/dictItem/list', + deleteItem = '/sys/dictItem/delete', + itemSave = '/sys/dictItem/add', + itemEdit = '/sys/dictItem/edit', + dictItemCheck = '/sys/dictItem/dictItemCheck', + refreshCache = '/sys/dict/refleshCache', + queryAllDictItems = '/sys/dict/queryAllDictItems', +} +/** + * 导出api + * @param params + */ +export const getExportUrl = Api.exportXls; +/** + * 导入api + * @param params + */ +export const getImportUrl = Api.importExcel; +/** + * 字典列表接口 + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); +/** + * 删除字典 + */ +export const deleteDict = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteDict, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 批量删除字典 + * @param params + */ +export const batchDeleteDict = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; +/** + * 保存或者更新字典 + * @param params + */ +export const saveOrUpdateDict = (params, isUpdate) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url: url, params }); +}; +/** + * 唯一校验 + * @param params + */ +export const duplicateCheck = (params) => defHttp.get({ url: Api.duplicateCheck, params }, { isTransformResponse: false }); +/** + * 字典回收站列表 + * @param params + */ +export const getRecycleBinList = (params) => defHttp.get({ url: Api.recycleBinList, params }); + +/** + * 回收站批量还原 + * @param params + */ +export const batchPutRecycleBin = (params, handleSuccess) => { + return defHttp.put({ url: Api.batchPutRecycleBin, params}).then(() => { + handleSuccess(); + }); +}; +/** + * 回收站还原 + * @param params + */ +export const putRecycleBin = (id, handleSuccess) => { + return defHttp.put({ url: Api.putRecycleBin + `/${id}` }).then(() => { + handleSuccess(); + }); +}; +/** + * 回收站批量删除 + * @param params + */ +export const batchDeleteRecycleBin = (params, handleSuccess) => { + return defHttp.delete({ url: `${Api.batchDeleteRecycleBin}?ids=${params.ids}`}).then(() => { + handleSuccess(); + }); +}; +/** + * 回收站删除 + * @param params + */ +export const deleteRecycleBin = (id, handleSuccess) => { + return defHttp.delete({ url: Api.deleteRecycleBin + `/${id}` }).then(() => { + handleSuccess(); + }); +}; +/** + * 字典配置列表 + * @param params + */ +export const itemList = (params) => defHttp.get({ url: Api.itemList, params }); +/** + * 字典配置删除 + * @param params + */ +export const deleteItem = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteItem, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 保存或者更新字典配置 + * @param params + */ +export const saveOrUpdateDictItem = (params, isUpdate) => { + let url = isUpdate ? Api.itemEdit : Api.itemSave; + return defHttp.post({ url: url, params }); +}; +/** + * 校验字典数据值 + * @param params + */ +export const dictItemCheck = (params) => defHttp.get({ url: Api.dictItemCheck, params }, { isTransformResponse: false }); +/** + * 刷新字典 + * @param params + */ +export const refreshCache = () => defHttp.get({ url: Api.refreshCache }, { isTransformResponse: false }); +/** + * 获取所有字典项 + * @param params + */ +export const queryAllDictItems = () => defHttp.get({ url: Api.queryAllDictItems }, { isTransformResponse: false }); diff --git a/src/views/system/dict/dict.data.ts b/src/views/system/dict/dict.data.ts new file mode 100644 index 0000000..8e7ea97 --- /dev/null +++ b/src/views/system/dict/dict.data.ts @@ -0,0 +1,203 @@ +import { BasicColumn } from '/@/components/Table'; +import { FormSchema } from '/@/components/Table'; +import { dictItemCheck } from './dict.api'; +import { rules } from '/@/utils/helper/validator'; +import { h } from "vue"; + +export const columns: BasicColumn[] = [ + { + title: '字典名称', + dataIndex: 'dictName', + width: 240, + }, + { + title: '字典编码', + dataIndex: 'dictCode', + width: 240, + }, + { + title: '描述', + dataIndex: 'description', + // width: 120 + }, +]; + +export const recycleBincolumns: BasicColumn[] = [ + { + title: '字典名称', + dataIndex: 'dictName', + width: 120, + }, + { + title: '字典编码', + dataIndex: 'dictCode', + width: 120, + }, + { + title: '描述', + dataIndex: 'description', + width: 120, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + label: '字典名称', + field: 'dictName', + component: 'JInput', + colProps: { span: 6 }, + }, + { + label: '字典编码', + field: 'dictCode', + component: 'JInput', + colProps: { span: 6 }, + }, +]; + +export const formSchema: FormSchema[] = [ + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '字典名称', + field: 'dictName', + required: true, + component: 'Input', + }, + { + label: '字典编码', + field: 'dictCode', + component: 'Input', + dynamicDisabled: ({ values }) => { + return !!values.id; + }, + dynamicRules: ({ model, schema }) => rules.duplicateCheckRule('sys_dict', 'dict_code', model, schema, true), + }, + { + label: '描述', + field: 'description', + component: 'Input', + }, +]; + +export const dictItemColumns: BasicColumn[] = [ + { + title: '名称', + dataIndex: 'itemText', + width: 80, + }, + { + title: '数据值', + dataIndex: 'itemValue', + width: 80, + }, + { + title: '字典颜色', + dataIndex: 'itemColor', + width: 80, + align:'center', + customRender:({ text }) => { + return h('div', { + style: {"background": text, "width":"18px","height":"18px","border-radius":"50%","margin":"0 auto"} + }) + } + }, +]; + +export const dictItemSearchFormSchema: FormSchema[] = [ + { + label: '名称', + field: 'itemText', + component: 'Input', + }, + { + label: '状态', + field: 'status', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'dict_item_status', + stringToNumber: true, + }, + }, +]; + +export const itemFormSchema: FormSchema[] = [ + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '名称', + field: 'itemText', + required: true, + component: 'Input', + }, + { + label: '数据值', + field: 'itemValue', + component: 'Input', + dynamicRules: ({ values, model }) => { + return [ + { + required: true, + validator: (_, value) => { + if (!value) { + return Promise.reject('请输入数据值'); + } + if (new RegExp("[`~!@#$^&*()=|{}'.<>《》/?!¥()—【】‘;:”“。,、?]").test(value)) { + return Promise.reject('数据值不能包含特殊字符!'); + } + return new Promise((resolve, reject) => { + let params = { + dictId: values.dictId, + id: model.id, + itemValue: value, + }; + dictItemCheck(params) + .then((res) => { + res.success ? resolve() : reject(res.message || '校验失败'); + }) + .catch((err) => { + reject(err.message || '验证失败'); + }); + }); + }, + }, + ]; + }, + }, + { + label: '颜色值', + field: 'itemColor', + component: 'Input', + slot:'itemColor' + }, + { + label: '描述', + field: 'description', + component: 'Input', + }, + { + field: 'sortOrder', + label: '排序', + component: 'InputNumber', + defaultValue: 1, + }, + { + field: 'status', + label: '是否启用', + defaultValue: 1, + component: 'JDictSelectTag', + componentProps: { + type: 'radioButton', + dictCode: 'dict_item_status', + stringToNumber: true, + }, + }, +]; diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue new file mode 100644 index 0000000..29361b2 --- /dev/null +++ b/src/views/system/dict/index.vue @@ -0,0 +1,194 @@ + + + + + diff --git a/src/views/system/examples/demo/DemoModal.vue b/src/views/system/examples/demo/DemoModal.vue new file mode 100644 index 0000000..53121c6 --- /dev/null +++ b/src/views/system/examples/demo/DemoModal.vue @@ -0,0 +1,69 @@ + + diff --git a/src/views/system/examples/demo/demo.api.ts b/src/views/system/examples/demo/demo.api.ts new file mode 100644 index 0000000..1cde92b --- /dev/null +++ b/src/views/system/examples/demo/demo.api.ts @@ -0,0 +1,73 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +enum Api { + list = '/test/jeecgDemo/list', + save = '/test/jeecgDemo/add', + edit = '/test/jeecgDemo/edit', + get = '/test/jeecgDemo/queryById', + delete = '/test/jeecgDemo/delete', + deleteBatch = '/test/jeecgDemo/deleteBatch', + exportXls = '/test/jeecgDemo/exportXls', + importExcel = '/test/jeecgDemo/importExcel', +} +/** + * 导出api + */ +export const getExportUrl = Api.exportXls; +/** + * 导入api + */ +export const getImportUrl = Api.importExcel; +/** + * 查询示例列表 + * @param params + */ +export const getDemoList = (params) => { + return defHttp.get({ url: Api.list, params }); +}; + +/** + * 保存或者更新示例 + * @param params + */ +export const saveOrUpdateDemo = (params, isUpdate) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url: url, params }); +}; + +/** + * 查询示例详情 + * @param params + */ +export const getDemoById = (params) => { + return defHttp.get({ url: Api.get, params }); +}; + +/** + * 删除示例 + * @param params + */ +export const deleteDemo = (params, handleSuccess) => { + return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +/** + * 批量删除示例 + * @param params + */ +export const batchDeleteDemo = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; diff --git a/src/views/system/examples/demo/demo.data.ts b/src/views/system/examples/demo/demo.data.ts new file mode 100644 index 0000000..e4b00f4 --- /dev/null +++ b/src/views/system/examples/demo/demo.data.ts @@ -0,0 +1,223 @@ +import { BasicColumn } from '/@/components/Table'; +import { FormSchema } from '/@/components/Table'; +import { render } from '/@/utils/common/renderUtils'; + +export const columns: BasicColumn[] = [ + { + title: '姓名', + dataIndex: 'name', + width: 170, + align: 'left', + resizable: true, + sorter: { + multiple:1 + } + }, + { + title: '关键词', + dataIndex: 'keyWord', + width: 130, + resizable: true, + }, + { + title: '打卡时间', + dataIndex: 'punchTime', + width: 140, + resizable: true, + }, + { + title: '工资', + dataIndex: 'salaryMoney', + width: 140, + resizable: true, + sorter: { + multiple: 2 + } + }, + { + title: '奖金', + dataIndex: 'bonusMoney', + width: 140, + resizable: true, + }, + { + title: '性别', + dataIndex: 'sex', + sorter: { + multiple: 3 + }, + customRender: ({ record }) => { + return render.renderDict(record.sex, 'sex', true); + // let v = record.sex ? (record.sex == '1' ? '男' : '女') : ''; + // return h('span', v); + }, + width: 120, + resizable: true, + }, + { + title: '生日', + dataIndex: 'birthday', + width: 120, + resizable: true, + }, + { + title: '邮箱', + dataIndex: 'email', + width: 120, + resizable: true, + }, + { + title: '个人简介', + dataIndex: 'content', + width: 120, + resizable: true, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'name', + label: '姓名', + component: 'Input', + componentProps: { + trim: true, + }, + colProps: { span: 8 }, + }, + { + field: 'birthday', + label: '生日', + component: 'RangePicker', + componentProps: { + valueType: 'Date' + }, + colProps: { span: 8 }, + }, + { + field: 'age', + label: '年龄', + component: 'Input', + slot: 'age', + colProps: { span: 8 }, + }, + { + field: 'sex', + label: '性别', + colProps: { span: 8 }, + component: 'JDictSelectTag', + componentProps: { + dictCode: 'sex', + placeholder: '请选择性别', + }, + }, +]; + +export const formSchema: FormSchema[] = [ + { + field: 'id', + label: 'id', + component: 'Input', + show: false, + }, + { + field: 'createBy', + label: 'createBy', + component: 'Input', + show: false, + }, + { + field: 'createTime', + label: 'createTime', + component: 'Input', + show: false, + }, + { + field: 'name', + label: '名字', + component: 'Input', + required: true, + componentProps: { + placeholder: '请输入名字', + }, + }, + { + field: 'keyWord', + label: '关键词', + component: 'Input', + componentProps: { + placeholder: '请输入关键词', + }, + }, + { + field: 'punchTime', + label: '打卡时间', + component: 'DatePicker', + componentProps: { + showTime: true, + valueFormat: 'YYYY-MM-DD HH:mm:ss', + placeholder: '请选择打卡时间', + }, + }, + { + field: 'salaryMoney', + label: '工资', + component: 'Input', + componentProps: { + placeholder: '请输入工资', + }, + }, + { + field: 'sex', + label: '性别', + component: 'JDictSelectTag', + defaultValue: '1', + componentProps: { + type: 'radio', + dictCode: 'sex', + placeholder: '请选择性别', + }, + }, + { + field: 'age', + label: '年龄', + component: 'InputNumber', + defaultValue: 1, + componentProps: { + placeholder: '请输入年龄', + }, + }, + { + field: 'birthday', + label: '生日', + component: 'DatePicker', + defaultValue: '', + componentProps: { + valueFormat: 'YYYY-MM-DD', + placeholder: '请选择生日', + }, + }, + { + field: 'email', + label: '邮箱', + component: 'Input', + rules: [{ required: false, type: 'email', message: '邮箱格式不正确', trigger: 'blur' }], + componentProps: { + placeholder: '请输入邮箱', + }, + }, + { + field: 'content', + label: '个人简介 - To introduce myself', + component: 'InputTextArea', + labelLength: 4, + componentProps: { + placeholder: '请输入个人简介', + }, + }, + { + field: 'updateCount', + label: '乐观锁', + show: false, + component: 'Input', + }, +]; diff --git a/src/views/system/examples/demo/index.vue b/src/views/system/examples/demo/index.vue new file mode 100644 index 0000000..43367f5 --- /dev/null +++ b/src/views/system/examples/demo/index.vue @@ -0,0 +1,317 @@ + + + diff --git a/src/views/system/fillRule/FillRuleModal.vue b/src/views/system/fillRule/FillRuleModal.vue new file mode 100644 index 0000000..81f824d --- /dev/null +++ b/src/views/system/fillRule/FillRuleModal.vue @@ -0,0 +1,82 @@ + + + diff --git a/src/views/system/fillRule/fill.rule.api.ts b/src/views/system/fillRule/fill.rule.api.ts new file mode 100644 index 0000000..1348a12 --- /dev/null +++ b/src/views/system/fillRule/fill.rule.api.ts @@ -0,0 +1,83 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +enum Api { + list = '/sys/fillRule/list', + test = '/sys/fillRule/testFillRule', + save = '/sys/fillRule/add', + edit = '/sys/fillRule/edit', + delete = '/sys/fillRule/delete', + deleteBatch = '/sys/fillRule/deleteBatch', + exportXls = '/sys/fillRule/exportXls', + importExcel = '/sys/fillRule/importExcel', +} + +/** + * 导出地址 + */ +export const exportUrl = Api.exportXls; +/** + * 导入地址 + */ +export const importUrl = Api.importExcel; + +/** + * 列表查询 + * @param params + */ +export const getFillRuleList = (params) => { + return defHttp.get({ url: Api.list, params }); +}; + +/** + * 删除 + * @param params + * @param handleSuccess + */ +export const deleteFillRule = (params, handleSuccess) => { + return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +/** + * 批量删除 + * @param params + */ +export const batchDeleteFillRule = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; + +/** + * 规则功能测试 + * @param params + */ +export const handleTest = (params) => { + return defHttp.get({ url: Api.test, params }, { isTransformResponse: false }); +}; + +/** + * 保存 + * @param params + */ +export const saveFillRule = (params) => { + return defHttp.post({ url: Api.save, params }); +}; + +/** + * 更新 + * @param params + */ +export const updateFillRule = (params) => { + return defHttp.put({ url: Api.edit, params }); +}; diff --git a/src/views/system/fillRule/fill.rule.data.ts b/src/views/system/fillRule/fill.rule.data.ts new file mode 100644 index 0000000..f249163 --- /dev/null +++ b/src/views/system/fillRule/fill.rule.data.ts @@ -0,0 +1,112 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { duplicateCheckDelay } from '/@/views/system/user/user.api'; + +export const columns: BasicColumn[] = [ + { + title: '规则名称', + dataIndex: 'ruleName', + width: 200, + align: 'center', + }, + { + title: '规则编码', + dataIndex: 'ruleCode', + width: 200, + align: 'center', + }, + { + title: '规则实现类', + dataIndex: 'ruleClass', + width: 300, + align: 'center', + }, + { + title: '规则参数', + dataIndex: 'ruleParams', + width: 200, + align: 'center', + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'ruleName', + label: '规则名称', + component: 'Input', + colProps: { span: 6 }, + }, + { + field: 'ruleCode', + label: '规则编码', + component: 'Input', + colProps: { span: 6 }, + }, +]; + +export const formSchema: FormSchema[] = [ + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, + { + field: 'ruleName', + label: '规则名称', + component: 'Input', + required: true, + colProps: { span: 24 }, + }, + { + field: 'ruleCode', + label: '规则编码', + component: 'Input', + colProps: { span: 24 }, + dynamicDisabled: ({ values }) => { + return !!values.id; + }, + dynamicRules: ({ model }) => { + return [ + { + required: true, + validator: (_, value) => { + return new Promise((resolve, reject) => { + if (!value) { + return reject('请输入规则编码!'); + } + let params = { + tableName: 'sys_fill_rule', + fieldName: 'rule_code', + fieldVal: value, + dataId: model.id, + }; + duplicateCheckDelay(params) + .then((res) => { + res.success ? resolve() : reject('规则编码已存在!'); + }) + .catch((err) => { + reject(err.message || '校验失败'); + }); + }); + }, + }, + ]; + }, + }, + { + field: 'ruleClass', + label: '规则实现类', + component: 'Input', + required: true, + colProps: { span: 24 }, + }, + { + field: 'ruleParams', + label: '规则参数', + colProps: { span: 24 }, + component: 'JAddInput', + componentProps: { + min: 0, + }, + }, +]; diff --git a/src/views/system/fillRule/index.vue b/src/views/system/fillRule/index.vue new file mode 100644 index 0000000..93d5e1c --- /dev/null +++ b/src/views/system/fillRule/index.vue @@ -0,0 +1,146 @@ + + + diff --git a/src/views/system/homeConfig/components/HomeConfigModal.vue b/src/views/system/homeConfig/components/HomeConfigModal.vue new file mode 100644 index 0000000..c33b974 --- /dev/null +++ b/src/views/system/homeConfig/components/HomeConfigModal.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/views/system/homeConfig/home.api.ts b/src/views/system/homeConfig/home.api.ts new file mode 100644 index 0000000..0f9600a --- /dev/null +++ b/src/views/system/homeConfig/home.api.ts @@ -0,0 +1,55 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +enum Api { + list = '/sys/sysRoleIndex/list', + save = '/sys/sysRoleIndex/add', + edit = '/sys/sysRoleIndex/edit', + deleteIndex = '/sys/sysRoleIndex/delete', + deleteBatch = '/sys/sysRoleIndex/deleteBatch', + queryIndexByCode = '/sys/sysRoleIndex/queryByCode', +} +/** + * 系统角色列表 + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); + +/** + * 删除角色 + */ +export const deleteIndex = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteIndex, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 批量删除角色 + * @param params + */ +export const batchDelete = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; +/** + * 保存或者更新首页配置 + * @param params + */ +export const saveOrUpdate = (params, isUpdate) => { + const url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url: url, params }); +}; +/** + * 查询首页配置 + * @param params + */ +export const queryIndexByCode = (params) => defHttp.get({ url: Api.queryIndexByCode, params }, { isTransformResponse: false }); diff --git a/src/views/system/homeConfig/home.data.ts b/src/views/system/homeConfig/home.data.ts new file mode 100644 index 0000000..86562ad --- /dev/null +++ b/src/views/system/homeConfig/home.data.ts @@ -0,0 +1,129 @@ +import { FormSchema } from '/@/components/Table'; + +//列配置 +export const columns = [ + { + title: '关联类型(用户/角色)', + dataIndex: 'relationType_dictText', + width: 80, + slots: { customRender: 'relationType' }, + }, + { + title: '用户/角色编码', + dataIndex: 'roleCode', + width: 80, + slots: { customRender: 'roleCode' }, + }, + { + title: '首页路由', + dataIndex: 'url', + width: 100, + }, + { + title: '组件地址', + dataIndex: 'component', + width: 100, + }, + { + title: '是否开启', + dataIndex: 'status', + slots: { customRender: 'status' }, + width: 60, + }, +]; +//查询配置 +export const searchFormSchema: FormSchema[] = [ + { + field: 'relationType', + label: '关联类型', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'relation_type', + }, + }, + { + field: 'route', + label: '是否路由菜单', + helpMessage: '非路由菜单设置成首页,需开启', + component: 'Switch', + show: false, + }, +]; + +export const formSchema: FormSchema[] = [ + { + field: 'id', + label: '', + component: 'Input', + show: false, + }, + { + field: 'relationType', + label: '关联类型', + component: 'JDictSelectTag', + required: true, + defaultValue: 'ROLE', + componentProps: { + dictCode: 'relation_type', + type: 'radioButton', + }, + }, + { + label: '角色编码', + field: 'roleCode', + component: 'JSelectRole', + required: true, + componentProps: { + rowKey: 'roleCode', + isRadioSelection: true, + }, + ifShow: ({ values }) => values.relationType == 'ROLE', + }, + { + label: '用户编码', + field: 'userCode', + component: 'JSelectUser', + required: true, + componentProps: { + isRadioSelection: true, + }, + ifShow: ({ values }) => values.relationType == 'USER', + }, + { + label: '首页路由', + field: 'url', + component: 'Input', + required: true, + }, + { + label: '组件地址', + field: 'component', + component: 'Input', + componentProps: { + placeholder: '请输入前端组件', + }, + required: true, + }, + { + label: '优先级', + field: 'priority', + component: 'InputNumber', + }, + { + field: 'route', + label: '是否路由菜单', + helpMessage: '非路由菜单设置成首页,需开启', + component: 'Switch', + defaultValue: true, + show: false, + }, + { + label: '是否开启', + field: 'status', + component: 'JSwitch', + defaultValue: '1', + componentProps: { + options: ['1', '0'], + }, + }, +]; diff --git a/src/views/system/homeConfig/index.vue b/src/views/system/homeConfig/index.vue new file mode 100644 index 0000000..93cfbdb --- /dev/null +++ b/src/views/system/homeConfig/index.vue @@ -0,0 +1,126 @@ + + diff --git a/src/views/system/loginmini/MiniCodelogin.vue b/src/views/system/loginmini/MiniCodelogin.vue new file mode 100644 index 0000000..bb9989e --- /dev/null +++ b/src/views/system/loginmini/MiniCodelogin.vue @@ -0,0 +1,163 @@ + + + + diff --git a/src/views/system/loginmini/MiniForgotpad.vue b/src/views/system/loginmini/MiniForgotpad.vue new file mode 100644 index 0000000..6a02b93 --- /dev/null +++ b/src/views/system/loginmini/MiniForgotpad.vue @@ -0,0 +1,293 @@ + + + diff --git a/src/views/system/loginmini/MiniLogin.vue b/src/views/system/loginmini/MiniLogin.vue new file mode 100644 index 0000000..25d0394 --- /dev/null +++ b/src/views/system/loginmini/MiniLogin.vue @@ -0,0 +1,722 @@ + + + + + + diff --git a/src/views/system/loginmini/MiniRegister.vue b/src/views/system/loginmini/MiniRegister.vue new file mode 100644 index 0000000..20c60cc --- /dev/null +++ b/src/views/system/loginmini/MiniRegister.vue @@ -0,0 +1,277 @@ + + + + diff --git a/src/views/system/loginmini/OAuth2Login.vue b/src/views/system/loginmini/OAuth2Login.vue new file mode 100644 index 0000000..baa671e --- /dev/null +++ b/src/views/system/loginmini/OAuth2Login.vue @@ -0,0 +1,129 @@ + + + diff --git a/src/views/system/menu/DataRuleList.vue b/src/views/system/menu/DataRuleList.vue new file mode 100644 index 0000000..97ed574 --- /dev/null +++ b/src/views/system/menu/DataRuleList.vue @@ -0,0 +1,144 @@ + + + diff --git a/src/views/system/menu/DataRuleModal.vue b/src/views/system/menu/DataRuleModal.vue new file mode 100644 index 0000000..3c94a6b --- /dev/null +++ b/src/views/system/menu/DataRuleModal.vue @@ -0,0 +1,54 @@ + + diff --git a/src/views/system/menu/MenuDrawer.vue b/src/views/system/menu/MenuDrawer.vue new file mode 100644 index 0000000..aecf915 --- /dev/null +++ b/src/views/system/menu/MenuDrawer.vue @@ -0,0 +1,139 @@ + + diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue new file mode 100644 index 0000000..024e90e --- /dev/null +++ b/src/views/system/menu/index.vue @@ -0,0 +1,272 @@ + + diff --git a/src/views/system/menu/menu.api.ts b/src/views/system/menu/menu.api.ts new file mode 100644 index 0000000..ce51569 --- /dev/null +++ b/src/views/system/menu/menu.api.ts @@ -0,0 +1,122 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +enum Api { + list = '/sys/permission/list', + save = '/sys/permission/add', + edit = '/sys/permission/edit', + delete = '/sys/permission/delete', + deleteBatch = '/sys/permission/deleteBatch', + ruleList = '/sys/permission/queryPermissionRule', + ruleSave = '/sys/permission/addPermissionRule', + ruleEdit = '/sys/permission/editPermissionRule', + ruleDelete = '/sys/permission/deletePermissionRule', + checkPermDuplication = '/sys/permission/checkPermDuplication', +} + +/** + * 列表接口 + * @param params + */ +export const list = (params) => { + return defHttp.get({ url: Api.list, params }); +} + +/** + * 删除菜单 + */ +export const deleteMenu = (params, handleSuccess) => { + return defHttp.delete({ url: Api.delete, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 批量删除菜单 + * @param params + */ +export const batchDeleteMenu = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; +/** + * 保存或者更新菜单 + * @param params + */ +export const saveOrUpdateMenu = (params, isUpdate) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url: url, params }); +}; +/** + * 菜单数据权限列表接口 + * @param params + */ +export const dataRuleList = (params) => defHttp.get({ url: Api.ruleList, params }); +/** + * 保存或者更新数据规则 + * @param params + */ +export const saveOrUpdateRule = (params, isUpdate) => { + let url = isUpdate ? Api.ruleEdit : Api.ruleSave; + return defHttp.post({ url: url, params }); +}; + +/** + * 删除数据权限 + */ +export const deleteRule = (params, handleSuccess) => { + return defHttp.delete({ url: Api.ruleDelete, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 根据code获取字典数值 + * @param params + */ +export const ajaxGetDictItems = (params) => defHttp.get({ url: `/sys/dict/getDictItems/${params.code}` }); + +/** + * 唯一校验 + * @param params + */ +export const getCheckPermDuplication = (params) => defHttp.get({ url: Api.checkPermDuplication, params }, { isTransformResponse: false }); + +/** + * 校验菜单是否存在 + * @param model + * @param schema + * @param required + */ +export const checkPermDuplication=(model, schema, required?)=>{ + return [ + { + validator: (_, value) => { + if (!required) { + return Promise.resolve(); + } + if (!value && required) { + return Promise.reject(`请输入${schema.label}`); + } + return new Promise((resolve, reject) => { + getCheckPermDuplication({ + id: model.id, + url:model.url, + alwaysShow:model.alwaysShow + }).then((res) => { + res.success ? resolve() : reject(res.message || '校验失败'); + }).catch((err) => { + reject(err.message || '验证失败'); + }); + }); + }, + }, + ]; +} diff --git a/src/views/system/menu/menu.data.ts b/src/views/system/menu/menu.data.ts new file mode 100644 index 0000000..d7f21ab --- /dev/null +++ b/src/views/system/menu/menu.data.ts @@ -0,0 +1,456 @@ +import { BasicColumn } from '/@/components/Table'; +import { FormSchema } from '/@/components/Table'; +import { h } from 'vue'; +import { Icon } from '/@/components/Icon'; +import { duplicateCheck } from '../user/user.api'; +import { ajaxGetDictItems ,checkPermDuplication } from './menu.api'; +import { render } from '/@/utils/common/renderUtils'; + +const isDir = (type) => type === 0; +const isMenu = (type) => type === 1; +const isButton = (type) => type === 2; + +// 定义可选择的组件类型 +export enum ComponentTypes { + Default = 'layouts/default/index', + IFrame = 'sys/iframe/FrameBlank', +} + +export const columns: BasicColumn[] = [ + { + title: '菜单名称', + dataIndex: 'name', + width: 200, + align: 'left', + }, + { + title: '菜单类型', + dataIndex: 'menuType', + width: 150, + customRender: ({ text }) => { + return render.renderDict(text, 'menu_type'); + }, + }, + { + title: '图标', + dataIndex: 'icon', + width: 50, + customRender: ({ record }) => { + return h(Icon, { icon: record.icon }); + }, + }, + { + title: '组件', + dataIndex: 'component', + align: 'left', + width: 150, + }, + { + title: '路径', + dataIndex: 'url', + align: 'left', + width: 150, + }, + { + title: '排序', + dataIndex: 'sortNo', + width: 50, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'name', + label: '菜单名称', + component: 'Input', + colProps: { span: 8 }, + }, +]; + +export const formSchema: FormSchema[] = [ + { + label: 'id', + field: 'id', + component: 'Input', + show: false, + }, + { + field: 'menuType', + label: '菜单类型', + component: 'RadioButtonGroup', + defaultValue: 0, + componentProps: ({ formActionType, formModel }) => { + return { + options: [ + { label: '一级菜单', value: 0 }, + { label: '子菜单', value: 1 }, + { label: '按钮/权限', value: 2 }, + ], + onChange: (e) => { + const { updateSchema, clearValidate } = formActionType; + const label = isButton(e) ? '按钮/权限' : '菜单名称'; + //清除校验 + clearValidate(); + updateSchema([ + { + field: 'name', + label: label, + }, + { + field: 'url', + required: !isButton(e), + }, + ]); + // 代码逻辑说明: [VUEN-1834]只有一级菜单,才默认值,子菜单的时候,清空------------ + if (isMenu(e) && !formModel.id && (formModel.component=='layouts/default/index' || formModel.component=='layouts/RouteView')) { + formModel.component = ''; + } + }, + }; + }, + }, + { + field: 'name', + label: '菜单名称', + component: 'Input', + required: true, + }, + { + field: 'parentId', + label: '上级菜单', + component: 'TreeSelect', + required: true, + componentProps: { + // 代码逻辑说明: replaceFields已过期,使用fieldNames代替------------ + fieldNames: { + label: 'name', + key: 'id', + value: 'id', + }, + dropdownStyle: { + maxHeight: '50vh', + }, + getPopupContainer: (node) => node?.parentNode, + }, + ifShow: ({ values }) => !isDir(values.menuType), + }, + { + field: 'url', + label: '访问路径', + component: 'Input', + required: true, + // 代码逻辑说明: [issues/5008]子表数据权限设置不生效 + ifShow: ({ values }) => !(values.component === ComponentTypes.IFrame && values.internalOrExternal), + // 代码逻辑说明: 聚合路由允许路径重复 + dynamicRules: ({ model, schema,values }) => { + return checkPermDuplication(model, schema, values.menuType !== 2?true:false); + }, + }, + { + field: 'component', + label: '前端组件', + component: 'Input', + componentProps: { + placeholder: '请输入前端组件', + }, + defaultValue:'layouts/default/index', + required: true, + ifShow: ({ values }) => !isButton(values.menuType), + }, + { + field: 'componentName', + label: '组件名称', + component: 'Input', + componentProps: { + placeholder: '请输入组件名称', + }, + helpMessage: [ + '此处名称应和vue组件的name属性保持一致。', + '组件名称不能重复,主要用于路由缓存功能。', + '如果组件名称和vue组件的name属性不一致,则会导致路由缓存失效。', + '非必填,留空则会根据访问路径自动生成。', + ], + defaultValue: '', + ifShow: ({ values }) => !isButton(values.menuType), + }, + { + field: 'frameSrc', + label: 'Iframe地址', + component: 'Input', + rules: [ + { required: true, message: '请输入Iframe地址' }, + { type: 'url', message: '请输入正确的url地址' }, + ], + ifShow: ({ values }) => !isButton(values.menuType) && values.component === ComponentTypes.IFrame, + }, + { + field: 'redirect', + label: '默认跳转地址', + component: 'Input', + ifShow: ({ values }) => isDir(values.menuType), + }, + { + field: 'perms', + label: '授权标识', + component: 'Input', + ifShow: ({ values }) => isButton(values.menuType), + // dynamicRules: ({ model }) => { + // return [ + // { + // required: false, + // validator: (_, value) => { + // return new Promise((resolve, reject) => { + // let params = { + // tableName: 'sys_permission', + // fieldName: 'perms', + // fieldVal: value, + // dataId: model.id, + // }; + // duplicateCheck(params) + // .then((res) => { + // res.success ? resolve() : reject(res.message || '校验失败'); + // }) + // .catch((err) => { + // reject(err.message || '校验失败'); + // }); + // }); + // }, + // }, + // ]; + // }, + }, + { + field: 'permsType', + label: '授权策略', + component: 'RadioGroup', + defaultValue: '1', + helpMessage: ['可见/可访问(授权后可见/可访问)', '可编辑(未授权时禁用)'], + componentProps: { + options: [ + { label: '可见/可访问', value: '1' }, + { label: '可编辑', value: '2' }, + ], + }, + ifShow: ({ values }) => isButton(values.menuType), + }, + { + field: 'status', + label: '状态', + component: 'RadioGroup', + defaultValue: '1', + componentProps: { + options: [ + { label: '有效', value: '1' }, + { label: '无效', value: '0' }, + ], + }, + ifShow: ({ values }) => isButton(values.menuType), + }, + { + field: 'icon', + label: '菜单图标', + component: 'IconPicker', + ifShow: ({ values }) => !isButton(values.menuType), + componentProps: { + allowClear: true + }, + }, + { + field: 'sortNo', + label: '排序', + component: 'InputNumber', + defaultValue: 1, + ifShow: ({ values }) => !isButton(values.menuType), + }, + { + field: 'route', + label: '是否路由菜单', + component: 'Switch', + defaultValue: true, + componentProps: { + checkedChildren: '是', + unCheckedChildren: '否', + }, + ifShow: ({ values }) => !isButton(values.menuType), + }, + { + field: 'hidden', + label: '隐藏路由', + component: 'Switch', + defaultValue: 0, + componentProps: { + checkedChildren: '是', + unCheckedChildren: '否', + }, + ifShow: ({ values }) => !isButton(values.menuType), + }, + { + field: 'hideTab', + label: '隐藏Tab', + component: 'Switch', + defaultValue: 0, + componentProps: { + checkedChildren: '是', + unCheckedChildren: '否', + }, + ifShow: ({ values }) => !isButton(values.menuType), + }, + { + field: 'keepAlive', + label: '是否缓存路由', + component: 'Switch', + defaultValue: false, + componentProps: { + checkedChildren: '是', + unCheckedChildren: '否', + }, + ifShow: ({ values }) => !isButton(values.menuType), + }, + { + field: 'alwaysShow', + label: '聚合路由', + component: 'Switch', + defaultValue: false, + componentProps: { + checkedChildren: '是', + unCheckedChildren: '否', + }, + ifShow: ({ values }) => !isButton(values.menuType), + }, + { + field: 'internalOrExternal', + label: '打开方式', + component: 'Switch', + defaultValue: false, + componentProps: { + checkedChildren: '外部', + unCheckedChildren: '内部', + }, + ifShow: ({ values }) => !isButton(values.menuType), + }, +]; + +export const dataRuleColumns: BasicColumn[] = [ + { + title: '规则名称', + dataIndex: 'ruleName', + width: 150, + }, + { + title: '规则字段', + dataIndex: 'ruleColumn', + width: 100, + }, + { + title: '规则值', + dataIndex: 'ruleValue', + width: 100, + }, +]; + +export const dataRuleSearchFormSchema: FormSchema[] = [ + { + field: 'ruleName', + label: '规则名称', + component: 'Input', + // colProps: { span: 6 }, + }, + { + field: 'ruleValue', + label: '规则值', + component: 'Input', + // colProps: { span: 6 }, + }, +]; + +export const dataRuleFormSchema: FormSchema[] = [ + { + label: 'id', + field: 'id', + component: 'Input', + show: false, + }, + { + field: 'ruleName', + label: '规则名称', + component: 'Input', + required: true, + }, + { + field: 'ruleColumn', + label: '规则字段', + component: 'Input', + ifShow: ({ values }) => { + const ruleConditions = Array.isArray(values.ruleConditions) ? values.ruleConditions[0] : values.ruleConditions; + return ruleConditions !== 'USE_SQL_RULES'; + }, + }, + { + field: 'ruleConditions', + label: '条件规则', + required: true, + component: 'ApiSelect', + componentProps: { + api: ajaxGetDictItems, + params: { code: 'rule_conditions' }, + labelField: 'text', + valueField: 'value', + getPopupContainer: (node) => document.body, + }, + }, + // 代码逻辑说明: 【TV360X-1864】添加系统变量 + { + field: 'ruleValue', + component: 'JInputSelect', + label: '规则值', + required: true, + componentProps: { + selectPlaceholder: '可选择系统变量', + inputPlaceholder: '请输入', + getPopupContainer: () => document.body, + selectWidth: '200px', + options: [ + { + label: '登录用户账号', + value: '#{sys_user_code}', + }, + { + label: '登录用户名称', + value: '#{sys_user_name}', + }, + { + label: '当前日期', + value: '#{sys_date}', + }, + { + label: '当前时间', + value: '#{sys_time}', + }, + { + label: '登录用户部门', + value: '#{sys_org_code}', + }, + { + label: '用户拥有部门', + value: '#{sys_multi_org_code}', + }, + { + label: '登录用户租户', + value: '#{tenant_id}', + }, + ], + }, + }, + { + field: 'status', + label: '状态', + component: 'RadioButtonGroup', + defaultValue: '1', + componentProps: { + options: [ + { label: '无效', value: '0' }, + { label: '有效', value: '1' }, + ], + }, + }, +]; diff --git a/src/views/system/message/components/SysMessageList.vue b/src/views/system/message/components/SysMessageList.vue new file mode 100644 index 0000000..d983f26 --- /dev/null +++ b/src/views/system/message/components/SysMessageList.vue @@ -0,0 +1,317 @@ + + + + + diff --git a/src/views/system/message/components/SysMessageModal.vue b/src/views/system/message/components/SysMessageModal.vue new file mode 100644 index 0000000..e14c0a7 --- /dev/null +++ b/src/views/system/message/components/SysMessageModal.vue @@ -0,0 +1,648 @@ + + + + + + diff --git a/src/views/system/message/components/useSysMessage.ts b/src/views/system/message/components/useSysMessage.ts new file mode 100644 index 0000000..f43765d --- /dev/null +++ b/src/views/system/message/components/useSysMessage.ts @@ -0,0 +1,591 @@ +import { ref, reactive, nextTick } from 'vue'; +import { defHttp } from '/@/utils/http/axios'; +import { getDictItemsByCode } from '/@/utils/dict/index'; +import { useRouter, useRoute } from 'vue-router' +import { useAppStore } from '/@/store/modules/app'; +import { useTabs } from '/@/hooks/web/useTabs'; +import { useModal } from '/@/components/Modal'; +import {useMessage} from "/@/hooks/web/useMessage"; + +/** + * 列表接口 + * @param params + */ +const queryMessageList = (params) => { + const url = '/sys/annountCement/vue3List'; + return defHttp.get({ url, params }); +}; + +/** + * 获取消息列表数据 + * + * setLocaleText 设置未读消息 + */ +export function useSysMessage(setLocaleText) { + const { createMessage } = useMessage(); + const rangeDateArray = getDictItemsByCode('rangeDate'); + console.log('+++++++++++++++++++++'); + console.log('rangeDateArray', rangeDateArray); + console.log('+++++++++++++++++++++'); + + const messageList = ref([]); + const pageNo = ref(1) + let pageSize = 10; + + const searchParams = reactive({ + fromUser: '', + rangeDateKey: '', + rangeDate: [], + starFlag: '', + noticeType: '' + }); + + + function getQueryParams() { + let { fromUser, rangeDateKey, rangeDate, starFlag, noticeType } = searchParams; + let params = { + fromUser, + starFlag, + rangeDateKey, + beginDate: '', + endDate: '', + pageNo: pageNo.value, + pageSize, + noticeType + }; + if (rangeDateKey == 'zdy') { + params.beginDate = rangeDate[0]+' 00:00:00'; + params.endDate = rangeDate[1]+' 23:59:59'; + } + return params; + } + + // 数据是否加载完了 + const loadEndStatus = ref(false); + + //请求数据 + async function loadData() { + if(loadEndStatus.value === true){ + return; + } + let params = getQueryParams(); + const data = await queryMessageList(params); + console.log('获取结果', data); + if(!data || data.length<=0){ + loadEndStatus.value = true; + setLocaleText(); + return; + } + if(data.length(null); + //当前表单参数 + const modalParams = ref({}); + //表单注册缓存 + const modalRegCache = ref({}); + //组件绑定参数 + const bindParams = ref({}); + + /** + * 根据类型打开不同弹窗 + * @param type + * @param params + */ + async function handleOpenType(type, params) { + currentModal.value = null; + modalParams.value = { ...params }; + switch (type) { + case 'task': + //流程办理 + bindParams.value = { actionType: 'todo' }; + currentModal.value = 'ProcessTaskHandleModal'; + break; + case 'history': + bindParams.value = {}; + //历史流程 + currentModal.value = 'MyTaskHandleModal'; + break; + case 'design': + //表单设计 + currentModal.value = 'DesformViewModal'; + bindParams.value = { + showRecordCopy: false, + showRecordShare: false, + showRecordSysPrint: false, + showDesignFormBtn: false, + }; + break; + case 'cgform': + //Online表单 + currentModal.value = 'OnlineAutoModal'; + bindParams.value = { + id: params.formId, + } + break; + default: + currentModal.value = null; + break; + } + //注册表单弹窗 + initModalRegister(); + await nextTick(() => { + if (modalRegCache.value[currentModal.value!]?.isRegister) { + console.log('已注冊,走缓存'); + modalRegCache.value[currentModal.value!].modalMethods.openModal(true, modalParams.value); + } + }); + } + + /** + * 初始化弹窗注册 + */ + function initModalRegister() { + //如果当前选择表单为null,就不处理 + if (!currentModal.value) { + return; + } + //判断缓存中是否存在,不存在就走缓存逻辑 + if (!modalRegCache.value[currentModal.value]) { + const [registerModal, modalMethods] = useModal(); + modalRegCache.value[currentModal.value] = { + isRegister: false, + register: bindRegisterModal(registerModal, modalMethods), + modalMethods, + }; + } + } + + /** + * 绑定注册弹窗 + * @param regFn + * @param modalMethod + */ + function bindRegisterModal(regFn, modalMethod) { + return async (...args) => { + console.log('开始注册:', currentModal.value); + await regFn(...args); + console.log('注册完成:', currentModal.value); + //打开弹窗 + modalMethod.openModal(true, modalParams.value); + //设置缓存标识 + modalRegCache.value[currentModal.value!].isRegister = true; + }; + } + //*************************************[QQYUN-6713]系统通知打开弹窗修改,动态设置弹窗end********************************************* + // const defaultPath = '/monitor/mynews'; + //const bpmPath = '/task/handle/' + + async function goPage(record, openModalFun?){ + if(!record.busType || record.busType == 'msg_node'){ + if(!openModalFun){ + // 从首页的消息通知跳转 + await goPageFromOuter(record); + }else{ + // 从消息页面列表点击详情查看 直接打开modal + openModalFun() + } + // 代码逻辑说明: QQYUN-4744【系统通知】6、系统通知@人后,对方看不到是哪个表单@的,没有超链接 + }else if(record.busType == 'comment'){ + // de + let msgAbstract = record.msgAbstract; + if(msgAbstract){ + try { + let data = JSON.parse(msgAbstract.toString()); + if(data.type == 'designForm'){ + showDesignFormModal(data); + } else { + showOnlineCgformModal(data); + } + }catch (e) { + console.error('打开评论表单,但是msgAbstract参数不是JSON格式', msgAbstract) + if(openModalFun){ + openModalFun(); + } + } + } + }else if(record.busType == 'tenant_invite'){ + if(props.isLowApp===true){ + router.push({ name:"myapps-settings-user", query:{ page:'tenantSetting' }}) + }else{ + router.push({ name:"system-usersetting", query:{ page:'tenantSetting' }}) + } + }else{ + if(props && props.isLowApp===true){ + openLowAppFlowModal(record) + }else{ + await goPageWithBusType(record) + } + } +/* busId: "1562035005173587970" + busType: "email" + openPage: "modules/eoa/email/modals/EoaEmailInForm" + openType: "component"*/ + } + + /** + * 打开表单设计器 表单弹窗 + * @param data + */ + function showDesignFormModal(data) { + handleOpenType('design', { + mode: 'detail', + desformCode: data.code, + dataId: data.dataId, + isOnline: false, + }); + } + + /** + * 打开Online表单 弹窗 + * @param data + */ + function showOnlineCgformModal(data) { + handleOpenType('cgform', { + formId: data.formId, + isUpdate: true, + disableSubmit: true, + record: { + id: data.dataId, + }, + }); + } + + /** + * 判断是不是表单的评论消息 + * @param record + */ + function isFormComment(record) { + if(record.busType == 'comment'){ + let msgAbstract = record.msgAbstract; + if(msgAbstract){ + try { + let data = JSON.parse(msgAbstract); + if(['cgform', 'designForm'].includes(data.type)){ + return true + } + }catch (e) { + console.error('打开评论表单,但是msgAbstract参数不是JSON格式', msgAbstract) + } + } + } + return false + } + + /** + * 如果是工作流任务 在lowApp中 直接打开modal + */ + function openLowAppFlowModal(record){ + const { busType, busId, msgAbstract } = record; + let temp = messageHrefArray.filter(item=>item.value === busType); + if(!temp || temp.length==0){ + console.error('当前业务类型不识别', busType); + return; + } + if(busType.indexOf('bpm')<0){ + console.error('low-app不支持跳转邮箱', busType); + return; + } + //固定参数 detailId 用于查询表单数据 + let query:any = { + detailId: busId + }; + // 额外参数处理 + if(msgAbstract){ + try { + let json = JSON.parse(msgAbstract); + Object.keys(json).map(k=>{ + query[k] = json[k] + }); + }catch (e) { + console.error('msgAbstract参数不是JSON格式', msgAbstract) + } + } + console.log("busType = ", busType) + handleOpenType('task', { + record: { + id: busId, + procInsId: query.procInsId, + processDefinitionId: query.processDefinitionId, + isDetail: query.taskDetail || 'bpm_cc' == busType + } + }) + } + + /** + * 根据busType不同跳转不同页面 + * @param record + */ + async function goPageWithBusType(record){ + const { busType, busId, msgAbstract } = record; + let temp = messageHrefArray.filter(item=>item.value === busType); + if(!temp || temp.length==0){ + console.error('当前业务类型不识别', busType); + return; + } + let path = temp[0].text; + if (['eoa_co_notify', 'eoa_co_remind'].includes(busType)) { + if (busId.startsWith('coId-')) { + path = temp[0].url; + } else if (busId.startsWith('nodeId-')) { + path = temp[0].text; + } + } + path = path.replace('{DETAIL_ID}', busId) + //固定参数 detailId 用于查询表单数据 + let query:any = { + detailId: busId + }; + // 额外参数处理 + if(msgAbstract){ + try { + let json = JSON.parse(msgAbstract); + Object.keys(json).map(k=>{ + query[k] = json[k] + }); + }catch (e) { + console.error('msgAbstract参数不是JSON格式', msgAbstract) + } + } + if(query.taskDetail){ + // 查看任务详情的弹窗 + await showHistory(query.procInsId, {taskOriginalId:query.taskId,busType,id:busId,readFlag:record.readFlag}) + }else{ + // 跳转路由 + appStore.setMessageHrefParams(query); + if(rt.path.indexOf(path)>=0){ + await closeTab(); + await router.replace({ path: path, query:{ time: new Date().getTime() } }); + }else{ + closeSameRoute(path) + await router.push({ path: path }); + } + } + } + + /** + * 从首页的消息通知跳转消息列表打开modal + * @param record + */ + async function goPageFromOuter(record){ + //没有定义业务类型 直接跳转我的消息页面 + emit('detail', record) + } + + //=============================================================================================================== + // 代码逻辑说明: QQYUN-3485 【查看流程】做一个查看页面,非办理页面,只通过流程实例参数即可 + async function showHistory(processInstanceId, data?) { + let { formData, formUrl } = await getTaskInfoForHistory({ processInstanceId }); + formData['PROCESS_TAB_TYPE'] = 'history'; + handleOpenType('history', { + formData, + formUrl, + isCc: data && data.busType == 'bpm_cc', + record: data, + title: '流程历史', + }); + } + + const nodeInfoUrl = '/act/process/extActProcessNode/getHisProcessNodeInfo' + const taskNodeInfo = (params) => defHttp.get({ url: nodeInfoUrl, params }); + + async function getTaskInfoForHistory(record) { + //查询条件 + let params = { procInstId: record.processInstanceId }; + const result = await taskNodeInfo(params); + console.log('获取历史任务信息', result); + let formData: any = { + dataId: result.dataId, + taskId: record.id, + taskDefKey: record.taskId, + procInsId: record.processInstanceId, + tableName: result.tableName, + vars: result.records, + }; + let tempFormUrl = result.formUrl; + console.log('获取流程节点表单URL', tempFormUrl); + //节点配置表单URL,VUE组件类型对应的拓展参数 + if (tempFormUrl && tempFormUrl.indexOf('?') != -1 && !isURL(tempFormUrl) && tempFormUrl.indexOf('{{DOMAIN_URL}}') == -1) { + tempFormUrl = result.formUrl.split('?')[0]; + console.log('获取流程节点表单URL(去掉参数)', tempFormUrl); + formData.extendUrlParams = getQueryVariable(result.formUrl); + } + return { + formData, + formUrl: tempFormUrl, + }; + } + + /** + * 获取URL上参数 + * @param url + */ + function getQueryVariable(url) { + if (!url) return; + + let t, + n, + r, + i = url.split('?')[1], + s = {}; + (t = i.split('&')), (r = null), (n = null); + for (let o in t) { + let u = t[o].indexOf('='); + u !== -1 && ((r = t[o].substr(0, u)), (n = t[o].substr(u + 1)), (s[r] = n)); + } + return s; + } + + /** + * URL地址 + * @param {*} s + */ + function isURL(s) { + return /^http[s]?:\/\/.*/.test(s); + } + //=============================================================================================================== + + return { + goPage, + isFormComment, + modalRegCache, + currentModal, + bindParams, + } +} diff --git a/src/views/system/message/manage/ManageDrawer.vue b/src/views/system/message/manage/ManageDrawer.vue new file mode 100644 index 0000000..407888c --- /dev/null +++ b/src/views/system/message/manage/ManageDrawer.vue @@ -0,0 +1,24 @@ + + + diff --git a/src/views/system/message/manage/index.less b/src/views/system/message/manage/index.less new file mode 100644 index 0000000..63b7bd0 --- /dev/null +++ b/src/views/system/message/manage/index.less @@ -0,0 +1,5 @@ +//noinspection LessUnresolvedVariable +@prefix-cls: ~'@{namespace}-message-manage'; + +.@{prefix-cls} { +} diff --git a/src/views/system/message/manage/index.vue b/src/views/system/message/manage/index.vue new file mode 100644 index 0000000..2602be1 --- /dev/null +++ b/src/views/system/message/manage/index.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/src/views/system/message/manage/manage.api.ts b/src/views/system/message/manage/manage.api.ts new file mode 100644 index 0000000..b83d6c2 --- /dev/null +++ b/src/views/system/message/manage/manage.api.ts @@ -0,0 +1,52 @@ +import { unref } from 'vue'; +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; + +const { createConfirm } = useMessage(); + +export enum Api { + list = '/sys/message/sysMessage/list', + delete = '/sys/message/sysMessage/delete', + deleteBatch = '/sys/message/sysMessage/deleteBatch', + exportXls = 'sys/message/sysMessage/exportXls', + importXls = 'sys/message/sysMessage/importExcel', + save = '/sys/message/sysMessage/add', + edit = '/sys/message/sysMessage/edit', +} + +export const list = (params) => defHttp.get({ url: Api.list, params }); + +/** + * 批量删除 + * @param params + * @param confirm + */ +export const deleteBatch = (params, confirm = false) => { + return new Promise((resolve, reject) => { + const doDelete = () => { + resolve(defHttp.delete({ url: Api.deleteBatch, params }, { joinParamsToUrl: true })); + }; + if (confirm) { + createConfirm({ + iconType: 'warning', + title: '删除', + content: '确定要删除吗?', + onOk: () => doDelete(), + onCancel: () => reject(), + }); + } else { + doDelete(); + } + }); +}; + +/** + * 保存或者更改消息模板 + */ +export const saveOrUpdate = (params, isUpdate) => { + if (unref(isUpdate)) { + return defHttp.put({ url: Api.edit, params }); + } else { + return defHttp.post({ url: Api.save, params }); + } +}; diff --git a/src/views/system/message/manage/manage.data.ts b/src/views/system/message/manage/manage.data.ts new file mode 100644 index 0000000..3806fda --- /dev/null +++ b/src/views/system/message/manage/manage.data.ts @@ -0,0 +1,134 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; + +export const columns: BasicColumn[] = [ + { + title: '消息标题', + dataIndex: 'esTitle', + width: 140, + }, + { + title: '发送内容', + dataIndex: 'esContent', + width: 200, + // slots: { customRender: 'esContent' }, + }, + { + title: '接收人', + dataIndex: 'esReceiver', + width: 140, + }, + { + title: '发送次数', + dataIndex: 'esSendNum', + width: 120, + }, + { + title: '发送状态', + dataIndex: 'esSendStatus_dictText', + width: 120, + }, + { + title: '发送时间', + dataIndex: 'esSendTime', + width: 140, + }, + { + title: '发送方式', + dataIndex: 'esType_dictText', + width: 120, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + label: '消息标题', + field: 'esTitle', + component: 'Input', + }, + { + label: '发送状态', + field: 'esSendStatus', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'msgSendStatus', + }, + }, + { + label: '发送方式', + field: 'esType', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'messageType', + }, + }, +]; + +export const formSchemas: FormSchema[] = [ + { + label: 'ID', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '消息标题', + field: 'esTitle', + component: 'Input', + componentProps: { readOnly: true }, + }, + { + label: '发送内容', + field: 'esContent', + component: 'InputTextArea', + componentProps: { readOnly: true }, + }, + { + label: '发送参数', + field: 'esParam', + component: 'Input', + componentProps: { readOnly: true }, + }, + + { + label: '接收人', + field: 'esReceiver', + component: 'Input', + componentProps: { readOnly: true }, + }, + { + label: '发送方式', + field: 'esType', + component: 'JDictSelectTag', + componentProps: { disabled: true, dictCode: 'messageType' }, + }, + { + label: '发送时间', + field: 'esSendTime', + component: 'Input', + componentProps: { readOnly: true }, + }, + { + label: '发送状态', + field: 'esSendStatus', + component: 'JDictSelectTag', + componentProps: { disabled: true, dictCode: 'msgSendStatus' }, + }, + { + label: '发送次数', + field: 'esSendNum', + component: 'Input', + componentProps: { readOnly: true }, + }, + { + label: '发送失败原因', + field: 'esResult', + component: 'Input', + componentProps: { readOnly: true }, + }, + { + label: '备注', + field: 'remark', + component: 'InputTextArea', + componentProps: { readOnly: true }, + }, +]; diff --git a/src/views/system/message/template/TemplateModal.vue b/src/views/system/message/template/TemplateModal.vue new file mode 100644 index 0000000..9daad11 --- /dev/null +++ b/src/views/system/message/template/TemplateModal.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/views/system/message/template/TemplateTestModal.vue b/src/views/system/message/template/TemplateTestModal.vue new file mode 100644 index 0000000..eeea2f5 --- /dev/null +++ b/src/views/system/message/template/TemplateTestModal.vue @@ -0,0 +1,40 @@ + + + diff --git a/src/views/system/message/template/index.less b/src/views/system/message/template/index.less new file mode 100644 index 0000000..15e8d49 --- /dev/null +++ b/src/views/system/message/template/index.less @@ -0,0 +1,5 @@ +//noinspection LessUnresolvedVariable +@prefix-cls: ~'@{namespace}-message-template'; + +.@{prefix-cls} { +} diff --git a/src/views/system/message/template/index.vue b/src/views/system/message/template/index.vue new file mode 100644 index 0000000..94d827d --- /dev/null +++ b/src/views/system/message/template/index.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/src/views/system/message/template/template.api.ts b/src/views/system/message/template/template.api.ts new file mode 100644 index 0000000..fe4f03c --- /dev/null +++ b/src/views/system/message/template/template.api.ts @@ -0,0 +1,60 @@ +import { unref } from 'vue'; +import { defHttp } from '/@/utils/http/axios'; +import { useMessage } from '/@/hooks/web/useMessage'; + +const { createConfirm } = useMessage(); + +export enum Api { + list = '/sys/message/sysMessageTemplate/list', + delete = '/sys/message/sysMessageTemplate/delete', + deleteBatch = '/sys/message/sysMessageTemplate/deleteBatch', + exportXls = 'sys/message/sysMessageTemplate/exportXls', + importXls = 'sys/message/sysMessageTemplate/importExcel', + save = '/sys/message/sysMessageTemplate/add', + edit = '/sys/message/sysMessageTemplate/edit', + // 发送测试 + send = '/sys/message/sysMessageTemplate/sendMsg', +} + +export const list = (params) => defHttp.get({ url: Api.list, params }); + +/** + * 批量删除 + * @param params + * @param confirm + */ +export const deleteBatch = (params, confirm = false) => { + return new Promise((resolve, reject) => { + const doDelete = () => { + resolve(defHttp.delete({ url: Api.deleteBatch, params }, { joinParamsToUrl: true })); + }; + if (confirm) { + createConfirm({ + iconType: 'warning', + title: '删除', + content: '确定要删除吗?', + onOk: () => doDelete(), + onCancel: () => reject(), + }); + } else { + doDelete(); + } + }); +}; + +/** + * 保存或者更改消息模板 + */ +export const saveOrUpdate = (params, isUpdate) => { + if (unref(isUpdate)) { + return defHttp.put({ url: Api.edit, params }); + } else { + return defHttp.post({ url: Api.save, params }); + } +}; + +/** + * 发送消息测试 + * @param params + */ +export const sendMessageTest = (params) => defHttp.post({ url: Api.send, params }); diff --git a/src/views/system/message/template/template.data.ts b/src/views/system/message/template/template.data.ts new file mode 100644 index 0000000..1b779bb --- /dev/null +++ b/src/views/system/message/template/template.data.ts @@ -0,0 +1,197 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { rules } from '/@/utils/helper/validator'; +import { filterDictTextByCache } from '/@/utils/dict/JDictSelectUtil'; + +export const columns: BasicColumn[] = [ + { + title: '模板标题', + dataIndex: 'templateName', + width: 80, + }, + { + title: '模板编码', + dataIndex: 'templateCode', + width: 100, + }, + { + title: '通知模板', + dataIndex: 'templateContent', + width: 150, + }, + { + title: '模板类型', + dataIndex: 'templateType', + width: 100, + customRender: ({ text }) => filterDictTextByCache('msgType', text), + }, + { + title: '是否应用', + dataIndex: 'useStatus', + width: 90, + customRender: function ({ text }) { + if (text == '1') { + return '是'; + } else { + return '否'; + } + }, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + label: '模板标题', + field: 'templateName', + component: 'Input', + }, + { + label: '模板编码', + field: 'templateCode', + component: 'Input', + }, + { + label: '模板类型', + field: 'templateType', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'msgType', + }, + }, +]; + +export const formSchemas: FormSchema[] = [ + { + label: 'ID', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '模板标题', + field: 'templateName', + component: 'Input', + required: true, + }, + { + label: '模板编码', + field: 'templateCode', + component: 'Input', + required: true, + dynamicRules: ({ model, schema }) => { + return [ ...rules.duplicateCheckRule('sys_sms_template', 'template_code', model, schema, true)]; + }, + // 编辑模式下不可修改编码 + dynamicDisabled: (params) => !!params.values.id, + }, + { + label: '模板类型', + field: 'templateType', + component: 'JDictSelectTag', + defaultValue: '1', + componentProps: { + dictCode: 'msgType', + type: 'radio', + placeholder: '请选择模板类型', + }, + required: true, + }, + { + label: '模板分类', + field: 'templateCategory', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'msgCategory', + placeholder: '请选择模板分类', + } + }, + { + label: '是否应用', + field: 'useStatus', + component: 'JSwitch', + componentProps: { + options: ['1', '0'], + }, + }, + { + label: '模板内容', + field: 'templateContent', + component: 'InputTextArea', + componentProps: { + autoSize: { + minRows: 8, + maxRows: 8, + }, + }, + ifShow: ({ values }) => { + return !['2', '4', '5'].includes(values.templateType); + }, + }, + + { + label: '模板内容', + field: 'templateContent', + component: 'JEditor', + ifShow: ({ values }) => { + return ['2', '4'].includes(values.templateType); + }, + }, + { + label: '模板内容', + field: 'templateContent', + component: 'JMarkdownEditor', + ifShow: ({ values }) => { + return ['5'].includes(values.templateType); + }, + }, +]; + +export const sendTestFormSchemas: FormSchema[] = [ + { + label: '模板编码', + field: 'templateCode', + component: 'Input', + show: false, + }, + { + label: '模板标题', + field: 'templateName', + component: 'Input', + componentProps: { disabled: true }, + }, + { + label: '模板内容', + field: 'templateContent', + component: 'InputTextArea', + componentProps: { disabled: true, rows: 5 }, + }, + { + label: '测试数据', + field: 'testData', + component: 'InputTextArea', + required: true, + helpMessage: 'JSON数据', + defaultValue: '{}', + componentProps: { + placeholder: '请输入JSON格式测试数据', + rows: 5, + }, + }, + { + label: '消息类型', + field: 'msgType', + component: 'JDictSelectTag', + required: true, + defaultValue:'system', + componentProps: { dictCode: 'messageType',type:'radio' }, + }, + { + label: '消息接收方', + field: 'receiver', + required: true, + component: 'JSelectUser', + componentProps: { + labelKey: 'username', + rowKey: 'username', + }, + }, +]; diff --git a/src/views/system/notice/DetailModal.vue b/src/views/system/notice/DetailModal.vue new file mode 100644 index 0000000..3568a47 --- /dev/null +++ b/src/views/system/notice/DetailModal.vue @@ -0,0 +1,176 @@ + + + + diff --git a/src/views/system/notice/NoticeForm.vue b/src/views/system/notice/NoticeForm.vue new file mode 100644 index 0000000..750fc7c --- /dev/null +++ b/src/views/system/notice/NoticeForm.vue @@ -0,0 +1,116 @@ + + + diff --git a/src/views/system/notice/NoticeModal.vue b/src/views/system/notice/NoticeModal.vue new file mode 100644 index 0000000..ddaa596 --- /dev/null +++ b/src/views/system/notice/NoticeModal.vue @@ -0,0 +1,120 @@ + + + diff --git a/src/views/system/notice/index.vue b/src/views/system/notice/index.vue new file mode 100644 index 0000000..848176b --- /dev/null +++ b/src/views/system/notice/index.vue @@ -0,0 +1,210 @@ + + diff --git a/src/views/system/notice/notice.api.ts b/src/views/system/notice/notice.api.ts new file mode 100644 index 0000000..c8a6eb0 --- /dev/null +++ b/src/views/system/notice/notice.api.ts @@ -0,0 +1,95 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + list = '/sys/annountCement/list', + save = '/sys/annountCement/add', + edit = '/sys/annountCement/edit', + delete = '/sys/annountCement/delete', + queryById = '/sys/annountCement/queryById', + deleteBatch = '/sys/annountCement/deleteBatch', + exportXls = '/sys/annountCement/exportXls', + importExcel = '/sys/annountCement/importExcel', + releaseData = '/sys/annountCement/doReleaseData', + reovkeData = '/sys/annountCement/doReovkeData', + editIzTop = '/sys/annountCement/editIzTop', + addVisitsNum = '/sys/annountCement/addVisitsNumber', + tempList = '/sys/message/sysMessageTemplate/list', +} + +/** + * 导出url + */ +export const getExportUrl = Api.exportXls; +/** + * 导入url + */ +export const getImportUrl = Api.importExcel; +/** + * 查询消息列表 + * @param params + */ +export const getList = (params) => { + return defHttp.get({ url: Api.list, params }); +}; + +/** + * 保存或者更新通告 + * @param params + */ +export const saveOrUpdate = (params, isUpdate) => { + const url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url: url, params }); +}; + +/** + * 删除通告 + * @param params + */ +export const deleteNotice = (params, handleSuccess) => { + return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 置顶编辑 + * @param params + */ +export const editIzTop = (params, handleSuccess) => { + return defHttp.post({ url: Api.editIzTop, data: params }).then(() => { + handleSuccess(); + }); +}; + +/** + * 批量消息公告 + * @param params + */ +export const batchDeleteNotice = (params) => defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }); + +/** + * 发布 + * @param id + */ +export const doReleaseData = (params) => defHttp.get({ url: Api.releaseData, params }); +/** + * 撤销 + * @param id + */ +export const doReovkeData = (params) => defHttp.get({ url: Api.reovkeData, params }); +/** + * 新增访问量 + * @param id + */ +export const addVisitsNum = (params) => defHttp.get({ url: Api.addVisitsNum, params }, { successMessageMode: 'none' }); +/** + * 根据ID查询数据 + * @param id + */ +export const queryById = (params) => defHttp.get({ url: Api.queryById, params }, { isTransformResponse: false }); +/** + * 查询模板列表 + * @param params + */ +export const getTempList = (params) => { + return defHttp.get({ url: Api.tempList, params }); +}; diff --git a/src/views/system/notice/notice.data.ts b/src/views/system/notice/notice.data.ts new file mode 100644 index 0000000..e99ba30 --- /dev/null +++ b/src/views/system/notice/notice.data.ts @@ -0,0 +1,434 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { render } from '/@/utils/common/renderUtils'; +import { h } from 'vue'; +import { Tinymce } from '@/components/Tinymce'; + +export const columns: BasicColumn[] = [ + { + title: '标题', + width: 150, + dataIndex: 'titile', + }, + { + title: '消息类型', + dataIndex: 'msgCategory', + width: 100, + customRender: ({ text }) => { + return render.renderDict(text, 'msg_category'); + }, + }, + { + title: '发布人', + width: 100, + dataIndex: 'sender_dictText', + }, + { + title: '优先级', + dataIndex: 'priority', + width: 70, + customRender: ({ text }) => { + const color = text == 'L' ? 'blue' : text == 'M' ? 'yellow' : 'red'; + return render.renderTag(render.renderDict(text, 'priority'), color); + }, + }, + { + title: '通告对象', + dataIndex: 'msgType', + width: 100, + customRender: ({ text }) => { + return render.renderDict(text, 'msg_type'); + }, + }, + { + title: '发布状态', + dataIndex: 'sendStatus', + width: 70, + customRender: ({ text }) => { + const color = text == '0' ? 'red' : text == '1' ? 'green' : 'gray'; + return render.renderTag(render.renderDict(text, 'send_status'), color); + }, + }, + { + title: '发布时间', + width: 100, + dataIndex: 'sendTime', + }, + { + title: '撤销时间', + width: 100, + dataIndex: 'cancelTime', + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'titile', + label: '标题', + component: 'JInput', + colProps: { span: 6 }, + }, + { + field: 'msgCategory', + label: '消息类型', + component: 'JDictSelectTag', + defaultValue: '1', + componentProps: { + dictCode: 'msg_category', + placeholder: '请选择类型', + }, + colProps: { span: 6 }, + }, + { + field: 'msgClassify', + label: '公告分类', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'notice_type', + placeholder: '请选择公告分类', + }, + colProps: { span: 6 }, + }, + { + field: 'sendTime', + label: '发布时间', + component: 'RangePicker', + componentProps: { + valueType: 'Date', + }, + colProps: { span: 6 }, + }, +]; + +export const formSchema: FormSchema[] = [ + { + field: 'id', + label: 'id', + component: 'Input', + show: false, + }, + { + field: 'msgCategory', + label: '消息类型', + required: true, + component: 'JDictSelectTag', + defaultValue: '1', + componentProps: { + type: 'radio', + dictCode: 'msg_category', + placeholder: '请选择类型', + }, + }, + { + field: 'izTop', + label: '是否置顶', + defaultValue: '0', + component: 'JSwitch', + componentProps: { + //取值 options + options: ['1', '0'], + //文本option + labelOptions: ['是', '否'], + placeholder: '是否置顶', + checkedChildren: '是', + unCheckedChildren: '否', + }, + }, + { + field: 'titile', + label: '通告标题', + component: 'Input', + required: true, + componentProps: { + placeholder: '请输入标题', + }, + // 代码逻辑说明: 【TV360X-1632】标题过长保存报错,长度校验 + dynamicRules() { + return [ + { + validator: (_, value) => { + return new Promise((resolve, reject) => { + if (value.length > 100) { + reject('最长100个字符'); + } + resolve(); + }); + }, + }, + ]; + }, + }, + { + field: 'msgAbstract', + label: '通告摘要', + component: 'InputTextArea', + componentProps: { + allowClear: true, + autoSize: { + minRows: 2, + maxRows: 5, + }, + }, + required: true, + }, + // { + // field: 'endTime', + // label: '截至日期', + // component: 'DatePicker', + // componentProps: { + // showTime: true, + // valueFormat: 'YYYY-MM-DD HH:mm:ss', + // placeholder: '请选择截至日期', + // }, + // dynamicRules: ({ model }) => rules.endTime(model.startTime, true), + // }, + { + field: 'msgType', + label: '接收用户', + defaultValue: 'ALL', + component: 'JDictSelectTag', + required: true, + componentProps: { + type: 'radio', + dictCode: 'msg_type', + placeholder: '请选择发布范围', + }, + }, + { + field: 'userIds', + label: '指定用户', + component: 'JSelectUserByDepartment', + required: true, + componentProps: { + rowKey: 'id', + // 代码逻辑说明: 【TV360X-1627】通知公告用户选择组件没翻译 + labelKey: 'realname', + }, + ifShow: ({ values }) => values.msgType == 'USER', + }, + { + field: 'msgClassify', + label: '公告分类', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'notice_type', + placeholder: '请选择公告分类', + }, + }, + { + field: 'priority', + label: '优先级别', + defaultValue: 'H', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'priority', + type: 'radio', + placeholder: '请选择优先级', + }, + }, + { + field: 'izApproval', + label: '是否审批', + component: 'RadioGroup', + defaultValue: '0', + componentProps: { + options: [ + { + label: '是', + value: '1', + }, + { + label: '否', + value: '0', + }, + ], + }, + }, + { + field: 'msgTemplate', + label: '公告模版', + component: 'Input', + slot: 'msgTemplate', + }, + { + field: 'files', + label: '通告附件', + component: 'JUpload', + componentProps: { + //是否显示选择按钮 + text: '文件上传', + //最大上传数 + maxCount: 20, + //是否显示下载按钮 + download: true, + }, + }, + { + field: 'msgContent', + label: '通告内容', + component: 'Input', + colProps: { span: 24 }, + render: render.renderTinymce, + }, +]; + +/** + * 流程表单调用这个方法获取formSchema + * @param param + */ +export function getBpmFormSchema(_formData): FormSchema[] { + // 默认和原始表单保持一致 如果流程中配置了权限数据,这里需要单独处理formSchema + return [ + { + field: 'id', + label: 'id', + component: 'Input', + show: false, + }, + { + field: 'msgCategory', + label: '消息类型', + required: true, + component: 'JDictSelectTag', + defaultValue: '1', + componentProps: { + type: 'radio', + dictCode: 'msg_category', + placeholder: '请选择类型', + }, + }, + { + field: 'izTop', + label: '是否置顶', + defaultValue: '0', + component: 'JSwitch', + componentProps: { + //取值 options + options: ['1', '0'], + //文本option + labelOptions: ['是', '否'], + placeholder: '是否置顶', + checkedChildren: '是', + unCheckedChildren: '否', + }, + }, + { + field: 'titile', + label: '通告标题', + component: 'Input', + required: true, + componentProps: { + placeholder: '请输入标题', + }, + // 代码逻辑说明: 【TV360X-1632】标题过长保存报错,长度校验 + dynamicRules() { + return [ + { + validator: (_, value) => { + return new Promise((resolve, reject) => { + if (value.length > 100) { + reject('最长100个字符'); + } + resolve(); + }); + }, + }, + ]; + }, + }, + { + field: 'msgAbstract', + label: '通告摘要', + component: 'InputTextArea', + required: true, + }, + { + field: 'msgType', + label: '接收用户', + defaultValue: 'ALL', + component: 'JDictSelectTag', + required: true, + componentProps: { + type: 'radio', + dictCode: 'msg_type', + placeholder: '请选择发布范围', + }, + }, + { + field: 'userIds', + label: '指定用户', + component: 'JSelectUserByDepartment', + required: true, + componentProps: { + rowKey: 'id', + // 代码逻辑说明: 【TV360X-1627】通知公告用户选择组件没翻译 + labelKey: 'realname', + }, + ifShow: ({ values }) => values.msgType == 'USER', + }, + { + field: 'msgClassify', + label: '公告分类', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'notice_type', + placeholder: '请选择公告分类', + }, + }, + { + field: 'priority', + label: '优先级别', + defaultValue: 'H', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'priority', + type: 'radio', + placeholder: '请选择优先级', + }, + }, + { + field: 'msgTemplate', + label: '公告模版', + component: 'Input', + slot: 'msgTemplate', + }, + { + field: 'files', + label: '通告附件', + component: 'JUpload', + componentProps: { + //是否显示选择按钮 + text: '文件上传', + //最大上传数 + maxCount: 2, + //是否显示下载按钮 + download: true, + }, + }, + { + field: 'msgContent', + label: '通告内容', + component: 'Input', + colProps: { span: 24 }, + ifShow: ({}) => _formData.disabled == false, + render: ({ model, field }) => { + return h(Tinymce, { + showImageUpload: false, + disabled: _formData.disabled !== false, + height: 300, + value: model[field], + onChange: (value: string) => { + model[field] = value; + }, + }); + }, + }, + { + field: 'msgContent', + label: '通告内容', + component: 'Input', + colProps: { span: 24 }, + ifShow: ({}) => _formData.disabled !== false, + slot: 'msgContent', + }, + ]; +} diff --git a/src/views/system/onlineuser/OnlineUser.api.ts b/src/views/system/onlineuser/OnlineUser.api.ts new file mode 100644 index 0000000..e7438dc --- /dev/null +++ b/src/views/system/onlineuser/OnlineUser.api.ts @@ -0,0 +1,20 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + list = '/sys/online/list', + forceLogout = '/sys/online/forceLogout' +} + +/** + * 列表 + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); + +/** + * 批量删除角色 + * @param params + */ +export const forceLogout = (params) => { + return defHttp.post({url:Api.forceLogout,params},{isTransformResponse:false}) +}; diff --git a/src/views/system/onlineuser/OnlineUser.data.ts b/src/views/system/onlineuser/OnlineUser.data.ts new file mode 100644 index 0000000..bf6f8eb --- /dev/null +++ b/src/views/system/onlineuser/OnlineUser.data.ts @@ -0,0 +1,54 @@ +import { FormSchema } from '/@/components/Table'; +import { render } from "/@/utils/common/renderUtils"; +import { getToken } from '/@/utils/auth'; + +//列表 +export const columns = [ + { + title:'用户账号', + align:"center", + dataIndex: 'username', + customRender: ( {text,record} ) => { + let token = getToken(); + if(record.token === token) { + return text + '(我)' + } + return text + }, + },{ + title:'用户姓名', + align:"center", + dataIndex: 'realname' + },{ + title: '头像', + align: "center", + width: 120, + dataIndex: 'avatar', + customRender: render.renderAvatar, + },{ + title:'生日', + align:"center", + dataIndex: 'birthday' + },{ + title: '性别', + align: "center", + dataIndex: 'sex', + customRender: ({text}) => { + return render.renderDict(text, 'sex'); + } + },{ + title:'手机号', + align:"center", + dataIndex: 'phone' + } +]; + +//查询区域 +export const searchFormSchema: FormSchema[] = [ + { + field: 'username', + label: '用户账号', + component: 'Input', + colProps: { span: 6 }, + } +]; diff --git a/src/views/system/onlineuser/OnlineUserList.vue b/src/views/system/onlineuser/OnlineUserList.vue new file mode 100644 index 0000000..d68d68e --- /dev/null +++ b/src/views/system/onlineuser/OnlineUserList.vue @@ -0,0 +1,67 @@ + + + + + \ No newline at end of file diff --git a/src/views/system/ossfile/index.vue b/src/views/system/ossfile/index.vue new file mode 100644 index 0000000..1c63c3a --- /dev/null +++ b/src/views/system/ossfile/index.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/src/views/system/ossfile/ossfile.api.ts b/src/views/system/ossfile/ossfile.api.ts new file mode 100644 index 0000000..fe08466 --- /dev/null +++ b/src/views/system/ossfile/ossfile.api.ts @@ -0,0 +1,33 @@ +import { defHttp } from '/@/utils/http/axios'; + +enum Api { + list = '/sys/oss/file/list', + deleteFile = '/sys/oss/file/delete', + ossUpload = '/sys/oss/file/upload', + minioUpload = '/sys/upload/uploadMinio', +} + +/** + * oss上传 + * @param params + */ +export const getOssUrl = Api.ossUpload; +/** + * minio上传 + * @param params + */ +export const getMinioUrl = Api.minioUpload; +/** + * 列表接口 + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); + +/** + * 删除用户 + */ +export const deleteFile = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteFile, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; diff --git a/src/views/system/ossfile/ossfile.data.ts b/src/views/system/ossfile/ossfile.data.ts new file mode 100644 index 0000000..abd0839 --- /dev/null +++ b/src/views/system/ossfile/ossfile.data.ts @@ -0,0 +1,30 @@ +import { BasicColumn } from '/@/components/Table'; +import { FormSchema } from '/@/components/Table'; + +export const columns: BasicColumn[] = [ + { + title: '文件名称', + dataIndex: 'fileName', + width: 120, + }, + { + title: '文件地址', + dataIndex: 'url', + width: 100, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + label: '文件名称', + field: 'fileName', + component: 'Input', + colProps: { span: 6 }, + }, + { + label: '文件地址', + field: 'url', + component: 'Input', + colProps: { span: 6 }, + }, +]; diff --git a/src/views/system/position/PositionModal.vue b/src/views/system/position/PositionModal.vue new file mode 100644 index 0000000..4f7a55f --- /dev/null +++ b/src/views/system/position/PositionModal.vue @@ -0,0 +1,58 @@ + + + \ No newline at end of file diff --git a/src/views/system/position/TenantPositionList.vue b/src/views/system/position/TenantPositionList.vue new file mode 100644 index 0000000..8562cb5 --- /dev/null +++ b/src/views/system/position/TenantPositionList.vue @@ -0,0 +1,133 @@ + + diff --git a/src/views/system/position/index.vue b/src/views/system/position/index.vue new file mode 100644 index 0000000..482215d --- /dev/null +++ b/src/views/system/position/index.vue @@ -0,0 +1,128 @@ + + diff --git a/src/views/system/position/position.api.ts b/src/views/system/position/position.api.ts new file mode 100644 index 0000000..5e5e58b --- /dev/null +++ b/src/views/system/position/position.api.ts @@ -0,0 +1,79 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +enum Api { + list = '/sys/position/list', + save = '/sys/position/add', + edit = '/sys/position/edit', + get = '/sys/position/queryById', + delete = '/sys/position/delete', + importExcel = '/sys/position/importExcel', + exportXls = '/sys/position/exportXls', + deleteBatch = '/sys/position/deleteBatch', +} +/** + * 导出api + */ +export const getExportUrl = Api.exportXls; + +export const getImportUrl = Api.importExcel; +/** + * 查询列表 + * @param params + */ +export const getPositionList = (params) => { + return defHttp.get({ url: Api.list, params }); +}; + +/** + * 保存或者更新 + * @param params + */ +export const saveOrUpdatePosition = (params, isUpdate) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url: url, params }); +}; + +/** + * 查询详情 + * @param params + */ +export const getPositionById = (params) => { + return defHttp.get({ url: Api.get, params }); +}; + +/** + * 单条删除 + * @param params + */ +export const deletePosition = (params, handleSuccess) => { + return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +/** + * 批量删除 + * @param params + */ +export const batchDeletePosition = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; + +/** + * 自定义上传 + * @param customUpload + */ +export const customUpload = (params) => { + defHttp.uploadFile({ url: Api.importExcel }, params); +}; diff --git a/src/views/system/position/position.data.ts b/src/views/system/position/position.data.ts new file mode 100644 index 0000000..bf9e241 --- /dev/null +++ b/src/views/system/position/position.data.ts @@ -0,0 +1,70 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { rules } from '/@/utils/helper/validator'; + +export const columns: BasicColumn[] = [ + // { + // title: '职务编码', + // dataIndex: 'code', + // width: 200, + // align: 'left', + // }, + { + title: '职务级别名称', + dataIndex: 'name', + align: 'left' + // width: 200, + }, + { + title: '职务级别(越小级别越高)', + dataIndex: 'postLevel', + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'name', + label: '职务级别名称', + component: 'Input', + colProps: { span: 8 }, + }, +]; + +export const formSchema: FormSchema[] = [ + { + label: '主键', + field: 'id', + component: 'Input', + show: false, + }, + { + field: 'name', + label: '职务级别名称', + component: 'Input', + required: true, + }, + { + label: '职务级别', + field: 'postLevel', + component: 'InputNumber', + required: true, + componentProps: { + min: 1, + max: 99 + }, + dynamicRules: ({ model, schema }) => { + return [{ required: true, message: '请输入职务级别!' }]; + }, + }, + // { + // field: 'code', + // label: '职务编码', + // component: 'Input', + // required: true, + // dynamicDisabled: ({ values }) => { + // return !!values.id; + // }, + // dynamicRules: ({ model, schema }) => { + // return rules.duplicateCheckRule('sys_position', 'code', model, schema, true); + // }, + // }, +]; diff --git a/src/views/system/role/TenantRoleList.vue b/src/views/system/role/TenantRoleList.vue new file mode 100644 index 0000000..0408e32 --- /dev/null +++ b/src/views/system/role/TenantRoleList.vue @@ -0,0 +1,184 @@ + + + + diff --git a/src/views/system/role/components/RoleDataRuleDrawer.vue b/src/views/system/role/components/RoleDataRuleDrawer.vue new file mode 100644 index 0000000..d5b0596 --- /dev/null +++ b/src/views/system/role/components/RoleDataRuleDrawer.vue @@ -0,0 +1,85 @@ + + diff --git a/src/views/system/role/components/RoleDesc.vue b/src/views/system/role/components/RoleDesc.vue new file mode 100644 index 0000000..2a4edc6 --- /dev/null +++ b/src/views/system/role/components/RoleDesc.vue @@ -0,0 +1,18 @@ + + diff --git a/src/views/system/role/components/RoleDrawer.vue b/src/views/system/role/components/RoleDrawer.vue new file mode 100644 index 0000000..2d4c14e --- /dev/null +++ b/src/views/system/role/components/RoleDrawer.vue @@ -0,0 +1,53 @@ + + diff --git a/src/views/system/role/components/RoleIndexModal.vue b/src/views/system/role/components/RoleIndexModal.vue new file mode 100644 index 0000000..07b35d3 --- /dev/null +++ b/src/views/system/role/components/RoleIndexModal.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/src/views/system/role/components/RolePermissionDrawer.vue b/src/views/system/role/components/RolePermissionDrawer.vue new file mode 100644 index 0000000..7e7e2ea --- /dev/null +++ b/src/views/system/role/components/RolePermissionDrawer.vue @@ -0,0 +1,306 @@ + + + + diff --git a/src/views/system/role/components/RoleUserTable.vue b/src/views/system/role/components/RoleUserTable.vue new file mode 100644 index 0000000..f9d626c --- /dev/null +++ b/src/views/system/role/components/RoleUserTable.vue @@ -0,0 +1,198 @@ + + + + diff --git a/src/views/system/role/components/UseSelectModal.vue b/src/views/system/role/components/UseSelectModal.vue new file mode 100644 index 0000000..b43c34b --- /dev/null +++ b/src/views/system/role/components/UseSelectModal.vue @@ -0,0 +1,67 @@ + + diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue new file mode 100644 index 0000000..d73bc47 --- /dev/null +++ b/src/views/system/role/index.vue @@ -0,0 +1,188 @@ + + diff --git a/src/views/system/role/role.api.ts b/src/views/system/role/role.api.ts new file mode 100644 index 0000000..0972dbc --- /dev/null +++ b/src/views/system/role/role.api.ts @@ -0,0 +1,188 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; + +enum Api { + list = '/sys/role/list', + listByTenant = '/sys/role/listByTenant', + save = '/sys/role/add', + edit = '/sys/role/edit', + deleteRole = '/sys/role/delete', + deleteBatch = '/sys/role/deleteBatch', + exportXls = '/sys/role/exportXls', + importExcel = '/sys/role/importExcel', + isRoleExist = '/sys/role/checkRoleCode', + queryTreeListForRole = '/sys/role/queryTreeList', + queryRolePermission = '/sys/permission/queryRolePermission', + saveRolePermission = '/sys/permission/saveRolePermission', + queryDataRule = '/sys/role/datarule', + getParentDesignList = '/act/process/extActDesignFlowData/getDesFormFlows', + getRoleDegisnList = '/joa/designform/designFormCommuse/getRoleDegisnList', + saveRoleDesign = '/joa/designform/designFormCommuse/sysRoleDesignAdd', + userList = '/sys/user/userRoleList', + deleteUserRole = '/sys/user/deleteUserRole', + batchDeleteUserRole = '/sys/user/deleteUserRoleBatch', + addUserRole = '/sys/user/addSysUserRole', + saveRoleIndex = '/sys/sysRoleIndex/add', + editRoleIndex = '/sys/sysRoleIndex/edit', + queryIndexByCode = '/sys/sysRoleIndex/queryByCode', +} +/** + * 导出api + */ +export const getExportUrl = Api.exportXls; +/** + * 导入api + */ +export const getImportUrl = Api.importExcel; +/** + * 系统角色列表 + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); +/** + * 租户角色列表 + * @param params + */ +export const listByTenant = (params) => defHttp.get({ url: Api.listByTenant, params }); + +/** + * 删除角色 + */ +export const deleteRole = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteRole, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 批量删除角色 + * @param params + */ +export const batchDeleteRole = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; +/** + * 保存或者更新角色 + * @param params + */ +export const saveOrUpdateRole = (params, isUpdate) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url: url, params }); +}; +/** + * 编码校验 + * @param params + */ +// 代码逻辑说明: 【QQYUN-7415】表单调用接口进行校验的添加防抖 +let timer; +export const isRoleExist = (params) => { + return new Promise((resolve, rejected) => { + clearTimeout(timer); + timer = setTimeout(() => { + defHttp + .get({ url: Api.isRoleExist, params }, { isTransformResponse: false }) + .then((res) => { + resolve(res); + }) + .catch((error) => { + rejected(error); + }); + }, 500); + }); +}; +/** + * 根据角色查询树信息 + */ +export const queryTreeListForRole = () => defHttp.get({ url: Api.queryTreeListForRole }); +/** + * 查询角色权限 + */ +export const queryRolePermission = (params) => defHttp.get({ url: Api.queryRolePermission, params }); +/** + * 保存角色权限 + */ +export const saveRolePermission = (params) => defHttp.post({ url: Api.saveRolePermission, params }); +/** + * 查询角色数据规则 + */ +export const queryDataRule = (params) => + defHttp.get({ url: `${Api.queryDataRule}/${params.functionId}/${params.roleId}` }, { isTransformResponse: false }); +/** + * 保存角色数据规则 + */ +export const saveDataRule = (params) => defHttp.post({ url: Api.queryDataRule, params }); +/** + * 获取表单数据 + * @return List + */ +export const getParentDesignList = () => defHttp.get({ url: Api.getParentDesignList }); +/** + * 获取角色表单数据 + * @return List + */ +export const getRoleDegisnList = (params) => defHttp.get({ url: Api.getRoleDegisnList, params }); +/** + * 提交角色工单信息 + */ +export const saveRoleDesign = (params) => defHttp.post({ url: Api.saveRoleDesign, params }); +/** + * 角色列表接口 + * @param params + */ +export const userList = (params) => defHttp.get({ url: Api.userList, params }); +/** + * 删除角色用户 + */ +export const deleteUserRole = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteUserRole, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 批量删除角色用户 + * @param params + */ +export const batchDeleteUserRole = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.batchDeleteUserRole, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; +/** + * 添加已有用户 + */ +export const addUserRole = (params, handleSuccess) => { + return defHttp.post({ url: Api.addUserRole, params }).then(() => { + handleSuccess(); + }); +}; +/** + * 保存或者更新 + * @param params + * @param isUpdate 是否是更新数据 + */ +export const saveOrUpdateRoleIndex = (params, isUpdate) => { + let url = isUpdate ? Api.editRoleIndex : Api.saveRoleIndex; + return defHttp.post({ url: url, params }); +}; +/** + * 根据code查询首页配置 + * @param params + */ +export const queryIndexByCode = (params) => defHttp.get({ url: Api.queryIndexByCode, params }, { isTransformResponse: false }); diff --git a/src/views/system/role/role.data.ts b/src/views/system/role/role.data.ts new file mode 100644 index 0000000..9195102 --- /dev/null +++ b/src/views/system/role/role.data.ts @@ -0,0 +1,191 @@ +import { FormSchema } from '/@/components/Table'; +import { isRoleExist } from './role.api'; +export const columns = [ + { + title: '角色名称', + dataIndex: 'roleName', + width: 100, + }, + { + title: '角色编码', + dataIndex: 'roleCode', + width: 100, + }, + { + title: '创建时间', + dataIndex: 'createTime', + width: 100, + }, +]; +/** + * 角色用户Columns + */ +export const userColumns = [ + { + title: '用户账号', + dataIndex: 'username', + }, + { + title: '用户姓名', + dataIndex: 'realname', + }, + { + title: '状态', + dataIndex: 'status_dictText', + width: 80, + }, +]; +export const searchFormSchema: FormSchema[] = [ + { + field: 'roleName', + label: '角色名称', + component: 'Input', + colProps: { span: 6 }, + }, + { + field: 'roleCode', + label: '角色编码', + component: 'Input', + colProps: { span: 6 }, + }, +]; +/** + * 角色用户搜索form + */ +export const searchUserFormSchema: FormSchema[] = [ + { + field: 'username', + label: '用户账号', + component: 'JInput', + colProps: { span: 8 }, + labelWidth: 74, + }, + { + field: 'realname', + label: '用户名称', + component: 'JInput', + colProps: { span: 8 }, + labelWidth: 74, + }, +]; + +export const formSchema: FormSchema[] = [ + { + field: 'id', + label: '', + component: 'Input', + show: false, + }, + { + field: 'roleName', + label: '角色名称', + required: true, + component: 'Input', + }, + { + field: 'roleCode', + label: '角色编码', + required: true, + component: 'Input', + dynamicDisabled: ({ values }) => { + return !!values.id; + }, + dynamicRules: ({ values, model }) => { + console.log('values:', values); + return [ + { + required: true, + validator: (_, value) => { + if (!value) { + return Promise.reject('请输入角色编码'); + } + if (values) { + return new Promise((resolve, reject) => { + isRoleExist({ id: model.id, roleCode: value }) + .then((res) => { + res.success ? resolve() : reject(res.message || '校验失败'); + }) + .catch((err) => { + reject(err.message || '验证失败'); + }); + }); + } + return Promise.resolve(); + }, + }, + ]; + }, + }, + { + label: '备注', + field: 'description', + component: 'InputTextArea', + }, +]; + +export const formDescSchema = [ + { + field: 'roleName', + label: '角色名称', + }, + { + field: 'roleCode', + label: '角色编码', + }, + { + label: '备注', + field: 'description', + }, +]; + +export const roleIndexFormSchema: FormSchema[] = [ + { + field: 'id', + label: '', + component: 'Input', + show: false, + }, + { + label: '角色编码', + field: 'roleCode', + component: 'Input', + dynamicDisabled: true, + }, + { + label: '首页路由', + field: 'url', + component: 'Input', + required: true, + helpMessage: '首页路由的访问地址', + }, + { + label: '组件地址', + field: 'component', + component: 'Input', + helpMessage: '首页路由的组件地址', + componentProps: { + placeholder: '请输入前端组件', + }, + required: true, + }, + { + field: 'route', + label: '是否路由菜单', + helpMessage: '非路由菜单设置成首页,需开启', + component: 'Switch', + defaultValue: true + }, + { + label: '优先级', + field: 'priority', + component: 'InputNumber', + }, + { + label: '是否开启', + field: 'status', + component: 'JSwitch', + componentProps: { + options: ['1', '0'], + }, + }, +]; diff --git a/src/views/system/tableWhiteList/SysTableWhiteList.api.ts b/src/views/system/tableWhiteList/SysTableWhiteList.api.ts new file mode 100644 index 0000000..ee725e9 --- /dev/null +++ b/src/views/system/tableWhiteList/SysTableWhiteList.api.ts @@ -0,0 +1,69 @@ +import {defHttp} from '/@/utils/http/axios'; +import {Modal} from 'ant-design-vue'; + +enum Api { + list = '/sys/tableWhiteList/list', + save = '/sys/tableWhiteList/add', + edit = '/sys/tableWhiteList/edit', + deleteOne = '/sys/tableWhiteList/delete', + deleteBatch = '/sys/tableWhiteList/deleteBatch', + importExcel = '/sys/tableWhiteList/importExcel', + exportXls = '/sys/tableWhiteList/exportXls', +} + +/** + * 导出api + * @param params + */ +export const getExportUrl = Api.exportXls; +/** + * 导入api + */ +export const getImportUrl = Api.importExcel; +/** + * 列表接口 + * @param params + */ +export const list = (params) => + defHttp.get({url: Api.list, params}); + +/** + * 删除单个 + * @param params + * @param handleSuccess + */ +export const deleteOne = (params, handleSuccess) => { + return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => { + handleSuccess(); + }); +} +/** + * 批量删除 + * @param params + * @param handleSuccess + */ +export const batchDelete = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ + url: Api.deleteBatch, + data: params + }, {joinParamsToUrl: true}).then(() => { + handleSuccess(); + }); + } + }); +} +/** + * 保存或者更新 + * @param params + * @param isUpdate 是否是更新数据 + */ +export const saveOrUpdate = (params, isUpdate) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({url: url, params}); +} diff --git a/src/views/system/tableWhiteList/SysTableWhiteList.data.ts b/src/views/system/tableWhiteList/SysTableWhiteList.data.ts new file mode 100644 index 0000000..473fc46 --- /dev/null +++ b/src/views/system/tableWhiteList/SysTableWhiteList.data.ts @@ -0,0 +1,76 @@ +import {BasicColumn, FormSchema} from '/@/components/Table'; + +const statusOptions = [ + {label: '禁用', value: '0'}, + {label: '启用', value: '1'}, +] + +export const columns: BasicColumn[] = [ + { + title: '允许的表名', + dataIndex: 'tableName', + }, + { + title: '允许的字段名', + dataIndex: 'fieldName', + }, + { + title: '状态', + dataIndex: 'status', + customRender({text}) { + const find = statusOptions.find(opt => opt.value === text); + return find?.label || '未知'; + } + }, + { + title: '创建时间', + dataIndex: 'createTime', + } +]; + +export const searchFormSchema: FormSchema[] = [ + { + label: '允许的表名', + field: 'tableName', + component: 'Input', + }, + { + label: '允许的字段名', + field: 'fieldName', + component: 'Input', + }, + { + label: '状态', + field: 'status', + component: 'Select', + componentProps: { + options: statusOptions, + }, + }, +]; + +export const formSchema: FormSchema[] = [ + {label: '', field: 'id', component: 'Input', show: false}, + { + label: '允许的表名', + field: 'tableName', + component: 'Input', + required: true, + }, + { + label: '允许的字段名', + field: 'fieldName', + component: 'Input', + required: true, + helpMessage: '多个用逗号分割', + }, + { + label: '状态', + field: 'status', + component: 'Select', + defaultValue: '1', + componentProps: { + options: statusOptions, + }, + }, +]; diff --git a/src/views/system/tableWhiteList/SysTableWhiteListList.vue b/src/views/system/tableWhiteList/SysTableWhiteListList.vue new file mode 100644 index 0000000..65451f6 --- /dev/null +++ b/src/views/system/tableWhiteList/SysTableWhiteListList.vue @@ -0,0 +1,168 @@ + + + + diff --git a/src/views/system/tableWhiteList/modules/SysTableWhiteListModal.vue b/src/views/system/tableWhiteList/modules/SysTableWhiteListModal.vue new file mode 100644 index 0000000..d5d7c57 --- /dev/null +++ b/src/views/system/tableWhiteList/modules/SysTableWhiteListModal.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/src/views/system/tenant/TenantUserList.vue b/src/views/system/tenant/TenantUserList.vue new file mode 100644 index 0000000..23c9936 --- /dev/null +++ b/src/views/system/tenant/TenantUserList.vue @@ -0,0 +1,377 @@ + + + + + diff --git a/src/views/system/tenant/components/TenantInviteUserModal.vue b/src/views/system/tenant/components/TenantInviteUserModal.vue new file mode 100644 index 0000000..15e21cb --- /dev/null +++ b/src/views/system/tenant/components/TenantInviteUserModal.vue @@ -0,0 +1,86 @@ + + + + + + diff --git a/src/views/system/tenant/components/TenantModal.vue b/src/views/system/tenant/components/TenantModal.vue new file mode 100644 index 0000000..a828b94 --- /dev/null +++ b/src/views/system/tenant/components/TenantModal.vue @@ -0,0 +1,57 @@ + + diff --git a/src/views/system/tenant/components/TenantPackAllotModal.vue b/src/views/system/tenant/components/TenantPackAllotModal.vue new file mode 100644 index 0000000..01daf7b --- /dev/null +++ b/src/views/system/tenant/components/TenantPackAllotModal.vue @@ -0,0 +1,77 @@ + + + + + + diff --git a/src/views/system/tenant/components/TenantRecycleBinModal.vue b/src/views/system/tenant/components/TenantRecycleBinModal.vue new file mode 100644 index 0000000..942d5a1 --- /dev/null +++ b/src/views/system/tenant/components/TenantRecycleBinModal.vue @@ -0,0 +1,147 @@ + + + + + + diff --git a/src/views/system/tenant/components/TenantUserDrawer.vue b/src/views/system/tenant/components/TenantUserDrawer.vue new file mode 100644 index 0000000..63223c9 --- /dev/null +++ b/src/views/system/tenant/components/TenantUserDrawer.vue @@ -0,0 +1,100 @@ + + + diff --git a/src/views/system/tenant/components/TenantUserList.vue b/src/views/system/tenant/components/TenantUserList.vue new file mode 100644 index 0000000..1c23767 --- /dev/null +++ b/src/views/system/tenant/components/TenantUserList.vue @@ -0,0 +1,102 @@ + + + + diff --git a/src/views/system/tenant/components/TenantUserRightList.vue b/src/views/system/tenant/components/TenantUserRightList.vue new file mode 100644 index 0000000..e61d23e --- /dev/null +++ b/src/views/system/tenant/components/TenantUserRightList.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/src/views/system/tenant/components/TenantUserSelectModal.vue b/src/views/system/tenant/components/TenantUserSelectModal.vue new file mode 100644 index 0000000..ace70aa --- /dev/null +++ b/src/views/system/tenant/components/TenantUserSelectModal.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/src/views/system/tenant/index.vue b/src/views/system/tenant/index.vue new file mode 100644 index 0000000..7ff50ed --- /dev/null +++ b/src/views/system/tenant/index.vue @@ -0,0 +1,210 @@ + + diff --git a/src/views/system/tenant/my/MyTenantDetail.vue b/src/views/system/tenant/my/MyTenantDetail.vue new file mode 100644 index 0000000..8c5ce75 --- /dev/null +++ b/src/views/system/tenant/my/MyTenantDetail.vue @@ -0,0 +1,432 @@ + + + + diff --git a/src/views/system/tenant/my/MyTenantList.vue b/src/views/system/tenant/my/MyTenantList.vue new file mode 100644 index 0000000..1a526f3 --- /dev/null +++ b/src/views/system/tenant/my/MyTenantList.vue @@ -0,0 +1,170 @@ + + diff --git a/src/views/system/tenant/pack/PackPermissionDrawer.vue b/src/views/system/tenant/pack/PackPermissionDrawer.vue new file mode 100644 index 0000000..2eae4ff --- /dev/null +++ b/src/views/system/tenant/pack/PackPermissionDrawer.vue @@ -0,0 +1,272 @@ + + + + diff --git a/src/views/system/tenant/pack/TenantCurrentPackList.vue b/src/views/system/tenant/pack/TenantCurrentPackList.vue new file mode 100644 index 0000000..9965022 --- /dev/null +++ b/src/views/system/tenant/pack/TenantCurrentPackList.vue @@ -0,0 +1,188 @@ + + + + diff --git a/src/views/system/tenant/pack/TenantDefaultPackList.vue b/src/views/system/tenant/pack/TenantDefaultPackList.vue new file mode 100644 index 0000000..7aba735 --- /dev/null +++ b/src/views/system/tenant/pack/TenantDefaultPackList.vue @@ -0,0 +1,164 @@ + + diff --git a/src/views/system/tenant/pack/TenantPackList.vue b/src/views/system/tenant/pack/TenantPackList.vue new file mode 100644 index 0000000..23d41f7 --- /dev/null +++ b/src/views/system/tenant/pack/TenantPackList.vue @@ -0,0 +1,266 @@ + + diff --git a/src/views/system/tenant/pack/TenantPackMenuModal.vue b/src/views/system/tenant/pack/TenantPackMenuModal.vue new file mode 100644 index 0000000..f2f640b --- /dev/null +++ b/src/views/system/tenant/pack/TenantPackMenuModal.vue @@ -0,0 +1,70 @@ + + diff --git a/src/views/system/tenant/pack/TenantPackUserModal.vue b/src/views/system/tenant/pack/TenantPackUserModal.vue new file mode 100644 index 0000000..cffec08 --- /dev/null +++ b/src/views/system/tenant/pack/TenantPackUserModal.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/src/views/system/tenant/tenant.api.ts b/src/views/system/tenant/tenant.api.ts new file mode 100644 index 0000000..0844de8 --- /dev/null +++ b/src/views/system/tenant/tenant.api.ts @@ -0,0 +1,274 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; +import { getTenantId } from "/@/utils/auth"; + +enum Api { + list = '/sys/tenant/list', + save = '/sys/tenant/add', + edit = '/sys/tenant/edit', + get = '/sys/tenant/queryById', + delete = '/sys/tenant/delete', + deleteBatch = '/sys/tenant/deleteBatch', + getCurrentUserTenants = '/sys/tenant/getCurrentUserTenant', + invitationUserJoin = '/sys/tenant/invitationUserJoin', + getTenantUserList = '/sys/tenant/getTenantUserList', + leaveTenant = '/sys/tenant/leaveTenant', + packList = '/sys/tenant/packList', + addPackPermission = '/sys/tenant/addPackPermission', + editPackPermission = '/sys/tenant/editPackPermission', + deleteTenantPack = '/sys/tenant/deleteTenantPack', + recycleBinPageList = '/sys/tenant/recycleBinPageList', + deleteLogicDeleted = '/sys/tenant/deleteLogicDeleted', + revertTenantLogic = '/sys/tenant/revertTenantLogic', + syncDefaultPack = '/sys/tenant/syncDefaultPack', + //用户产品包关系api + queryTenantPackUserList = '/sys/tenant/queryTenantPackUserList', + deleteTenantPackUser = '/sys/tenant/deleteTenantPackUser', + addTenantPackUser = '/sys/tenant/addTenantPackUser', + //获取用户租户列表 + getTenantPageListByUserId = '/sys/tenant/getTenantPageListByUserId', + + //新增、编辑用户租户 + saveUser = '/sys/user/addTenantUser', + editUser = '/sys/user/editTenantUser', + //根据租户id和用户获取用户的产品包列表和当前用户下的产品包id + listPackByTenantUserId = '/sys/tenant/listPackByTenantUserId', + // 获取菜单权限 + queryPremTreeList = '/sys/role/queryTreeList', +} + +/** + * 查询租户列表 + * @param params + */ +export const getTenantList = (params) => { + return defHttp.get({ url: Api.list, params }); +}; + +/** + * 保存或者更新租户 + * @param params + */ +export const saveOrUpdateTenant = (params, isUpdate) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url: url, params }); +}; + +/** + * 查询租户详情 + * @param params + */ +export const getTenantById = (params) => { + return defHttp.get({ url: Api.get, params }); +}; + +/** + * 删除租户 + * @param params + */ +export const deleteTenant = (params, handleSuccess) => { + return defHttp.delete({ url: Api.delete, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +/** + * 批量删除租户 + * @param params + */ +export const batchDeleteTenant = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; + +/** + * 获取登录用户部门信息 + */ +export const getUserTenants = (params?) => defHttp.get({ url: Api.getCurrentUserTenants, params }); + +/** + * 邀请用户加入租户 + * @param params + */ +export const invitationUserJoin = (params) => defHttp.put({ url: Api.invitationUserJoin, params }, { joinParamsToUrl: true }); + +/** + * 通过租户id获取数据 + * @param params + */ +export const getTenantUserList = (params) => { + return defHttp.get({ url: Api.getTenantUserList, params }); +}; + +/** + * 用户离开租户 + * @param params + */ +export const leaveTenant = (params, handleSuccess) => { + Modal.confirm({ + title: '请离', + content: '是否将此用户请离当前租户', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.put({ url: Api.leaveTenant, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; + +/** + * 获取产品包列表 + * @param params + */ +export const packList = (params) => { + return defHttp.get({ url: Api.packList, params }); +}; + +/** + * 添加菜单 + * @param params + */ +export const addPackPermission = (params) => { + return defHttp.post({ url: Api.addPackPermission, params }); +}; + +/** + * 添加菜单 + * @param params + */ +export const editPackPermission = (params) => { + return defHttp.put({ url: Api.editPackPermission, params }); +}; + +/** + * 删除菜单 + * @param params + */ +export const deleteTenantPack = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteTenantPack, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +/** + * 初始化套餐包 + * @param params + * @param handleSuccess + */ +export const syncDefaultTenantPack = (params, handleSuccess) => { + return defHttp.post({ url: Api.syncDefaultPack, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +/** + * 获取租户回收站的列表 + * @param params + */ +export const recycleBinPageList = (params) => { + return defHttp.get({ url: Api.recycleBinPageList, params }); +}; + +/** + * 租户彻底删除 + * @param params + */ +export const deleteLogicDeleted = (params,handleSuccess) => { + return defHttp.delete({ url: Api.deleteLogicDeleted, params },{ joinParamsToUrl: true }).then(() => { + handleSuccess(); + }).catch(()=>{ + handleSuccess(); + }); +}; + +/** + * 租户还原 + * @param params + */ +export const revertTenantLogic = (params,handleSuccess) => { + return defHttp.put({ url: Api.revertTenantLogic, params },{ joinParamsToUrl: true }).then(() => { + handleSuccess(); + }) +}; + +/** + * 获取租户产品包下面的用户 + * @param params + */ +export const queryTenantPackUserList = (params) => { + return defHttp.get({ url: Api.queryTenantPackUserList, params }); +}; + +/** + * 移除用户和产品包的关系数据 + * @param params + */ +export const deleteTenantPackUser = (params)=>{ + return defHttp.put({ url: Api.deleteTenantPackUser, params }); +} + +/** + * 添加用户和产品包的关系数据 + * @param params + */ +export const addTenantPackUser = (params)=>{ + return defHttp.post({ url: Api.addTenantPackUser, params }); +} + +/** + * 查询用户租户列表 + * @param params + */ +export const getTenantPageListByUserId = (params) => { + return defHttp.get({ url: Api.getTenantPageListByUserId, params }); +}; + + +/** + * 获取当前登录租户名称 + */ +export async function getLoginTenantName() { + let tenantId = getTenantId(); + if(tenantId){ + let result = await getTenantById({ id:tenantId }); + if(result){ + return result.name; + } + } + return "空"; +} + +/** + * 保存或者更新用户 + * @param params + */ +export const saveOrUpdateTenantUser = (params, isUpdate) => { + let url = isUpdate ? Api.editUser : Api.saveUser; + return defHttp.post({ url: url, params },{ joinParamsToUrl: true }); +}; +/** + * 根据租户id和用户获取用户的产品包列表和当前用户下的产品包id + * + * @param params + */ +export const listPackByTenantUserId = (params) => { + return defHttp.get({ url: Api.listPackByTenantUserId, params }); +} + +/** + * 获取菜单树 + */ +export const queryPremTreeList = () =>{ + return defHttp.get({ url: Api.queryPremTreeList }); +} diff --git a/src/views/system/tenant/tenant.data.ts b/src/views/system/tenant/tenant.data.ts new file mode 100644 index 0000000..a91a0c0 --- /dev/null +++ b/src/views/system/tenant/tenant.data.ts @@ -0,0 +1,609 @@ +import { BasicColumn, FormSchema } from '/@/components/Table'; +import { getAutoScrollContainer } from '/@/utils/common/compUtils'; +import { render } from "/@/utils/common/renderUtils"; +import { rules } from "/@/utils/helper/validator"; + +export const columns: BasicColumn[] = [ + { + title: '租户名称', + dataIndex: 'name', + width: 200, + align: 'left', + }, + { + title: '租户编号(ID)', + dataIndex: 'id', + width: 180, + },{ + title: '组织LOGO', + dataIndex: 'companyLogo', + width: 100, + customRender: ({ text }) => { + if(!text){ + return text; + } + return render.renderImage({text}); + }, + }, + { + dataIndex: 'trade_dictText', + title: '所属行业', + width: 150 + }, + { + dataIndex: 'companySize_dictText', + title: '公司规模', + width: 100 + }, + { + dataIndex: 'houseNumber', + title: '门牌号', + width: 100, + }, + // { + // dataIndex: 'position_dictText', + // title: '职级', + // width: 150 + // }, + // { + // dataIndex: 'department_dictText', + // title: '部门', + // width: 150 + // }, + { + dataIndex: 'createBy_dictText', + title: '创建者(拥有)', + width: 150 + }, +/* { + title: '开始时间', + dataIndex: 'beginDate', + sorter: true, + width: 180, + }, + { + title: '结束时间', + dataIndex: 'endDate', + sorter: true, + width: 180, + },*/ + { + title: '状态', + dataIndex: 'status_dictText', + width: 100, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + field: 'name', + label: '租户名称', + component: 'Input', + colProps: { span: 8 }, + }, + { + field: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: [ + { label: '正常', value: 1 }, + { label: '冻结', value: 0 }, + ], + }, + colProps: { span: 8 }, + }, + // { + // field: 'fieldTime', + // component: 'RangePicker', + // label: '时间字段', + // componentProps: { + // valueType: 'Date', + // }, + // colProps: { + // span: 8, + // }, + // }, +]; + +export const formSchema: FormSchema[] = [ + { + field: 'name', + label: '租户名称', + component: 'Input', + required: true, + }, + { + field: 'id', + label: '租户编号(ID)', + component: 'InputNumber', + required: true, + ifShow: ({ values }) => { + return values.id!=null; + }, + }, + { + field: 'companyLogo', + label: '组织LOGO', + component: 'JImageUpload', + componentProps:{ + text:'logo' + } + }, + { + field: 'trade', + label: '所属行业', + component: 'JDictSelectTag', + componentProps: { + dictCode:'trade', + } + }, { + field: 'companySize', + label: '公司规模', + component: 'JDictSelectTag', + componentProps: { + dictCode:'company_size', + } + }, { + field: 'companyAddress', + label: '公司地址', + component: 'JAreaSelect', + componentProps: { + placeholder: '请输入公司地址', + rows: 4, + } + }, + { + field: 'workPlace', + label: '工作地点', + component: 'InputTextArea', + componentProps: { + placeholder: '请输入工作地点', + rows: 4, + } + }, +/* { + field: 'beginDate', + label: '开始时间', + component: 'DatePicker', + componentProps: { + showTime: true, + valueFormat: 'YYYY-MM-DD HH:mm:ss', + getPopupContainer: getAutoScrollContainer, + }, + }, + { + field: 'endDate', + label: '结束时间', + component: 'DatePicker', + componentProps: { + showTime: true, + valueFormat: 'YYYY-MM-DD HH:mm:ss', + getPopupContainer: getAutoScrollContainer, + }, + },*/ + { + field: 'houseNumber', + label: '门牌号', + component: 'Input', + dynamicDisabled: true, + ifShow: ({ values }) => { + return values.id!=null; + }, + }, + { + field: 'position', + label: '职级', + component: 'JDictSelectTag', + componentProps:{ + dictCode: 'company_rank' + } + }, + { + field: 'department', + label: '部门', + component: 'JDictSelectTag', + componentProps:{ + dictCode:'company_department' + } + }, + { + field: 'status', + label: '状态', + component: 'RadioButtonGroup', + defaultValue: 1, + componentProps: { + options: [ + { label: '正常', value: 1 }, + { label: '冻结', value: 0 }, + ], + }, + }, +]; + +//定义用户表格列 +export const userColumns: BasicColumn[] =[ + { + title: '用户账号', + dataIndex: 'username', + width: 100, + align: 'left', + }, + { + title: '用户姓名', + dataIndex: 'realname', + width: 100, + }, + { + title: '性别', + dataIndex: 'sex_dictText', + width: 100, + }, + { + title: '手机号码', + dataIndex: 'phone', + width: 100, + }, +]; + +//邀请用户搜索表单 +export const userSearchFormSchema: FormSchema[] = [ + { + field: 'username', + label: '账号', + component: 'Input', + }, + { + field: 'realname', + label: '姓名', + component: 'Input', + }, +]; + +//套餐包列表 +export const packColumns: BasicColumn[] = [ + { + title: '套餐包名称', + dataIndex: 'packName', + width: 100, + customRender: ( { record, text }) => { + if(record.packCode && record.packCode.indexOf('default') != -1) { + return text + '(默认产品包)'; + } else { + return text; + } + } + }, + { + title: '是否自动分配用户', + dataIndex: 'izSysn', + width: 100, + customRender: ( { text }) => { + if(text === '1') { + return '是'; + } else { + return '否'; + } + } + }, + { + title: '状态', + dataIndex: 'status', + width: 100, + customRender: ({ text }) => { + if (text === '1') { + return '开启'; + } else { + return '关闭'; + } + }, + }, + { + title: '备注说明', + dataIndex: 'remarks', + width: 150, + }, +]; + +//套餐包列表 +export const tenantPackColumns: BasicColumn[] = [ + { + title: '套餐包名称', + dataIndex: 'packName', + width: 100, + customRender: ( { record, text }) => { + if(record.packCode && record.packCode.indexOf('default') != -1) { + return text + '(默认产品包)'; + } else { + return text; + } + } + }, + { + title: '是否自动分配用户', + dataIndex: 'izSysn', + width: 100, + customRender: ( { text }) => { + if(text === '1') { + return '是'; + } else { + return '否'; + } + } + }, + { + title: '备注说明', + dataIndex: 'remarks', + width: 150, + }, +]; + +//套餐包列表 +export const defalutPackColumns: BasicColumn[] = [ + { + title: '默认套餐名称', + dataIndex: 'packName', + width: 100, + }, + { + title: '状态', + dataIndex: 'status', + width: 100, + customRender: ({ text }) => { + if (text === '1') { + return '开启'; + } else { + return '关闭'; + } + }, + }, + { + title: '备注说明', + dataIndex: 'remarks', + width: 150, + }, +]; + +//套餐包搜索表单 +export const packFormSchema: FormSchema[] = [ + { + field: 'packName', + label: '套餐包名', + component: 'JInput', + colProps: { xxl: 8 }, + }, +]; + +//套餐包搜索表单 +export const defaultPackFormSchema: FormSchema[] = [ + { + field: 'packName', + label: '默认套餐名', + component: 'JInput', + colProps: { xxl: 8 }, + }, +]; + +//套餐包表单 +export const packMenuFormSchema: FormSchema[] = [ + { + field: 'packName', + label: '套餐包名称', + component: 'Input', + }, +/* { + field: 'permissionIds', + label: '授权菜单', + component: 'JTreeSelect', + componentProps: { + dict: 'sys_permission,name,id', + pidField: 'parent_id', + hasChildField:'is_leaf', + multiple: true, + treeCheckAble:true, + treeCheckStrictly: true, + converIsLeafVal: 0, + getPopupContainer: () => document.body, + }, + },*/ + { + field: 'remarks', + label: '备注说明', + component: 'InputTextArea', + }, + { + field: 'izSysn', + label: '自动分配用户', + component: 'Switch', + componentProps: { + checkedValue: "1", + checkedChildren: '是', + unCheckedValue: "0", + unCheckedChildren: '否', + }, + defaultValue: "1", + helpMessage: "默认会自动分配给用户,个性高级套餐,需要租户管理员手工分配人员(拥有更灵活性权限控制)" + }, + { + field: 'status', + label: '开启状态', + component: 'Switch', + componentProps: { + checkedValue: '1', + checkedChildren: '开启', + unCheckedValue: '0', + unCheckedChildren: '关闭', + }, + defaultValue: '1', + }, + { + field: 'id', + label: '开启状态', + component: 'Input', + show: false + }, + { + field: 'packCode', + label: '产品包编码', + component: 'Input', + show: false + }, + { + field: 'packType', + label: '产品包类型', + component: 'Input', + show: false + }, +]; + +//回收站列表 +export const recycleColumns : BasicColumn[] = [ + { + title: '租户名称', + dataIndex: 'name', + width: 100, + align: 'left', + }, + { + title: '租户编号(ID)', + dataIndex: 'id', + width: 100, + }, + { + title: '组织LOGO', + dataIndex: 'companyLogo', + width: 100, + customRender: ({ text }) => { + if(!text){ + return text; + } + return render.renderImage({text}); + }, + }, + { + dataIndex: 'houseNumber', + title: '门牌号', + width: 100, + } +] + +//租户回收站搜索表单 +export const searchRecycleFormSchema : FormSchema[] = [ + { + field: 'name', + label: '租户名称', + component: 'Input', + }, + { + field: 'houseNumber', + label: '门牌号', + component: 'Input', + }, +] + +//套餐包用户列表 +export const tenantPackUserColumns: BasicColumn[] = [ + { + title: '用户', + dataIndex: 'realname', + width: 200, + }, + { + title: '部门', + dataIndex: 'departNames', + width: 200, + ellipsis: true, + slots: { customRender: 'departNames' } + }, + { + title: '职位', + dataIndex: 'positionNames', + ellipsis: true, + width: 200, + slots: { customRender: 'positionNames' } + } +] + +/** + * 用户租户新增编辑表单 + */ +export const tenantUserSchema: FormSchema[] = [ + { field: 'id', label: 'id', component: 'Input', show: false }, + { field: 'username', label: 'username', component: 'Input', show: false }, + { + field: 'realname', + label: '姓名', + component: 'Input', + dynamicDisabled: ({ values }) => { + return !!values.id; + }, + }, + { + field: 'phone', + label: '手机', + component: 'Input', + dynamicRules: ({ model, schema }) => { + if (model.id) { + return []; + } + return [{ ...rules.phone(true)[0] }, { ...rules.duplicateCheckRule('sys_user', 'phone', model, schema, false)[0] }]; + }, + dynamicDisabled: ({ values }) => { + return !!values.id; + }, + }, + { + field: 'email', + label: '邮箱', + component: 'Input', + dynamicRules: ({ model, schema }) => { + if (model.id) { + return []; + } + return [{ ...rules.email(true)[0] }, { ...rules.duplicateCheckRule('sys_user', 'email', model, schema, false)[0] }]; + }, + dynamicDisabled: ({ values }) => { + return !!values.id; + }, + }, + { field: 'selecteddeparts', label: '部门', component: 'JSelectDept', componentProps: { checkStrictly: true } }, + /* { + field: 'post', + label: '职位', + component: 'JSelectPosition', + }, + { + field: 'workNo', + label: '工号', + component: 'Input', + dynamicRules: ({ model, schema }) => { + return [{ required: false, message: '请输入工号' }, { ...rules.duplicateCheckRule('sys_user', 'work_no', model, schema, false)[0] }]; + }, + },*/ + { field: 'relTenantIds', label: '租户', component: 'Input',show:false }, + { field: 'selectedroles', label: '角色', component: 'Input',show:false }, +]; + +// 分配用户套餐 +export const packUserAllotSchemas: FormSchema[] = [ + { + field: 'userId', + label: '用户id', + component: 'Input', + show: false + }, + { + field: 'realname', + label: '用户姓名', + component: 'Input', + componentProps:{ + readonly : true + }, + }, + { + field: 'packId', + label: '套餐', + component: 'Select', + slot: 'packId' + } +]; \ No newline at end of file diff --git a/src/views/system/ugroup/SysUgroupList.vue b/src/views/system/ugroup/SysUgroupList.vue new file mode 100644 index 0000000..6ace530 --- /dev/null +++ b/src/views/system/ugroup/SysUgroupList.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/src/views/system/ugroup/components/GroupUserTable.vue b/src/views/system/ugroup/components/GroupUserTable.vue new file mode 100644 index 0000000..a37a5e9 --- /dev/null +++ b/src/views/system/ugroup/components/GroupUserTable.vue @@ -0,0 +1,176 @@ + + + + diff --git a/src/views/system/ugroup/components/SysUgroupModal.vue b/src/views/system/ugroup/components/SysUgroupModal.vue new file mode 100644 index 0000000..2daa432 --- /dev/null +++ b/src/views/system/ugroup/components/SysUgroupModal.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/views/system/ugroup/ugroup.api.ts b/src/views/system/ugroup/ugroup.api.ts new file mode 100644 index 0000000..2c2b85b --- /dev/null +++ b/src/views/system/ugroup/ugroup.api.ts @@ -0,0 +1,107 @@ +import {defHttp} from '/@/utils/http/axios'; +import { useMessage } from "/@/hooks/web/useMessage"; +import { Modal } from 'ant-design-vue'; +const { createConfirm } = useMessage(); + +enum Api { + list = '/sys/ugroup/list', + save='/sys/ugroup/add', + edit='/sys/ugroup/edit', + deleteOne = '/sys/ugroup/delete', + deleteBatch = '/sys/ugroup/deleteBatch', + importExcel = '/sys/ugroup/importExcel', + exportXls = '/sys/ugroup/exportXls', + + userList = '/sys/user/groupUserList', + deleteUserGroup = '/sys/user/deleteGroupUser', + batchDeleteUserGroup = '/sys/user/deleteUserGroupBatch', + addUserGroup = '/sys/user/addSysUserGroup', +} +/** + * 导出api + * @param params + */ +export const getExportUrl = Api.exportXls; +/** + * 导入api + */ +export const getImportUrl = Api.importExcel; +/** + * 列表接口 + * @param params + */ +export const list = (params) => + defHttp.get({url: Api.list, params}); + +/** + * 删除单个 + */ +export const deleteOne = (params,handleSuccess) => { + return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => { + handleSuccess(); + }); +} +/** + * 批量删除 + * @param params + */ +export const batchDelete = (params, handleSuccess) => { + createConfirm({ + iconType: 'warning', + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => { + handleSuccess(); + }); + } + }); +} +/** + * 保存或者更新 + * @param params + */ +export const saveOrUpdate = (params, isUpdate,showTip = true) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({url: url, params},{successMessageMode:showTip?'success':'none'}); +} +/** + * 角色列表接口 + * @param params + */ +export const userList = (params) => defHttp.get({ url: Api.userList, params }); +/** + * 删除角色用户 + */ +export const deleteUserGroup = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteUserGroup, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 批量删除角色用户 + * @param params + */ +export const batchDeleteUserGroup = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.batchDeleteUserGroup, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; +/** + * 添加已有用户 + */ +export const addUserGroup = (params, handleSuccess) => { + return defHttp.post({ url: Api.addUserGroup, params }).then(() => { + handleSuccess(); + }); +}; diff --git a/src/views/system/ugroup/ugroup.data.ts b/src/views/system/ugroup/ugroup.data.ts new file mode 100644 index 0000000..decfb7f --- /dev/null +++ b/src/views/system/ugroup/ugroup.data.ts @@ -0,0 +1,93 @@ +import { BasicColumn } from '/@/components/Table'; +import { FormSchema } from '/@/components/Table'; +// 名称最大长度 +export const NAME_MAX_LENGTH = 40; + +//列表数据 +export const columns: BasicColumn[] = [ + { + title: '用户组名称', + align: 'center', + dataIndex: 'groupName', + }, + { + title: '创建时间', + align: 'center', + dataIndex: 'createTime', + }, +]; +//查询数据 +export const searchFormSchema: FormSchema[] = [ + { + label: '用户组名称', + field: 'groupName', + component: 'JInput', + }, +]; + +//表单数据 +export const formSchema: FormSchema[] = [ + { + label: '用户组名称', + field: 'groupName', + component: 'Input', + dynamicRules() { + return [ + {required: true, message: '请输入用户组名称'}, + { + max: NAME_MAX_LENGTH, + message: `名称长度不能超过${NAME_MAX_LENGTH}个字符`, + }, + ]; + } + }, + { + label: '描述', + field: 'description', + component: 'InputTextArea', + }, + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, +]; + +/** + * 角色用户搜索form + */ +export const searchUserFormSchema: FormSchema[] = [ + { + field: 'username', + label: '用户账号', + component: 'Input', + colProps: { span: 8 }, + labelWidth: 74, + }, + { + field: 'realname', + label: '用户名称', + component: 'Input', + colProps: { span: 8 }, + labelWidth: 74, + }, +]; +/** + * 角色用户Columns + */ +export const userColumns = [ + { + title: '用户账号', + dataIndex: 'username', + }, + { + title: '用户姓名', + dataIndex: 'realname', + }, + { + title: '状态', + dataIndex: 'status_dictText', + width: 80, + }, +]; diff --git a/src/views/system/user/PasswordModal.vue b/src/views/system/user/PasswordModal.vue new file mode 100644 index 0000000..d638c86 --- /dev/null +++ b/src/views/system/user/PasswordModal.vue @@ -0,0 +1,42 @@ + + diff --git a/src/views/system/user/UserDrawer.vue b/src/views/system/user/UserDrawer.vue new file mode 100644 index 0000000..697df06 --- /dev/null +++ b/src/views/system/user/UserDrawer.vue @@ -0,0 +1,195 @@ + + + diff --git a/src/views/system/user/UserQuitModal.vue b/src/views/system/user/UserQuitModal.vue new file mode 100644 index 0000000..7822269 --- /dev/null +++ b/src/views/system/user/UserQuitModal.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/src/views/system/user/UserRecycleBinModal.vue b/src/views/system/user/UserRecycleBinModal.vue new file mode 100644 index 0000000..31a0f30 --- /dev/null +++ b/src/views/system/user/UserRecycleBinModal.vue @@ -0,0 +1,165 @@ + + diff --git a/src/views/system/user/components/ImportExcelProgress.vue b/src/views/system/user/components/ImportExcelProgress.vue new file mode 100644 index 0000000..e0b8950 --- /dev/null +++ b/src/views/system/user/components/ImportExcelProgress.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue new file mode 100644 index 0000000..d04d655 --- /dev/null +++ b/src/views/system/user/index.vue @@ -0,0 +1,309 @@ + + + + + diff --git a/src/views/system/user/user.api.ts b/src/views/system/user/user.api.ts new file mode 100644 index 0000000..f12d975 --- /dev/null +++ b/src/views/system/user/user.api.ts @@ -0,0 +1,249 @@ +import { defHttp } from '/@/utils/http/axios'; +import { Modal } from 'ant-design-vue'; +import { isObject } from '/@/utils/is'; +enum Api { + listNoCareTenant = '/sys/user/listAll', + list = '/sys/user/list', + save = '/sys/user/add', + edit = '/sys/user/edit', + getUserRole = '/sys/user/queryUserRole', + duplicateCheck = '/sys/duplicate/check', + deleteUser = '/sys/user/delete', + deleteBatch = '/sys/user/deleteBatch', + importExcel = '/sys/user/importExcel', + exportXls = '/sys/user/exportXls', + recycleBinList = '/sys/user/recycleBin', + putRecycleBin = '/sys/user/putRecycleBin', + deleteRecycleBin = '/sys/user/deleteRecycleBin', + allRolesList = '/sys/role/queryall', + allRolesListNoByTenant = '/sys/role/queryallNoByTenant', + allTenantList = '/sys/tenant/queryList', + allPostList = '/sys/position/list', + userDepartList = '/sys/user/userDepartList', + changePassword = '/sys/user/changePassword', + frozenBatch = '/sys/user/frozenBatch', + getUserAgent = '/sys/sysUserAgent/queryByUserName', + userQuitAgent = '/sys/user/userQuitAgent', + getQuitList = '/sys/user/getQuitList', + putCancelQuit = '/sys/user/putCancelQuit', + resetPassword = '/sys/user/resetPassword', + updateUserTenantStatus='/sys/tenant/updateUserTenantStatus', + getUserTenantPageList='/sys/tenant/getUserTenantPageList', + getDepPostIdByDepId = '/sys/sysDepart/getDepPostIdByDepId', +} +/** + * 导出api + * @param params + */ +export const getExportUrl = Api.exportXls; +/** + * 导入api + */ +export const getImportUrl = Api.importExcel; +/** + * 列表接口(查询用户,通过租户隔离) + * @param params + */ +export const list = (params) => defHttp.get({ url: Api.list, params }); + +/** + * 列表接口(查询全部用户,不通过租户隔离) + * @param params + */ +export const listNoCareTenant = (params) => defHttp.get({ url: Api.listNoCareTenant, params }); + +/** + * 用户角色接口 + * @param params + */ +export const getUserRoles = (params) => defHttp.get({ url: Api.getUserRole, params }, { errorMessageMode: 'none' }); + +/** + * 删除用户 + */ +export const deleteUser = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteUser, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 批量删除用户 + * @param params + */ +export const batchDeleteUser = (params, handleSuccess) => { + Modal.confirm({ + title: '确认删除', + content: '是否删除选中数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); + }, + }); +}; +/** + * 保存或者更新用户 + * @param params + */ +export const saveOrUpdateUser = (params, isUpdate) => { + let url = isUpdate ? Api.edit : Api.save; + return defHttp.post({ url: url, params }); +}; +/** + * 唯一校验 + * @param params + */ +export const duplicateCheck = (params) => defHttp.get({ url: Api.duplicateCheck, params }, { isTransformResponse: false }); + +/** + * 20231215 + * liaozhiyang + * 唯一校验( 延迟【防抖】) + * @param params + */ +const timer = {}; +export const duplicateCheckDelay = (params) => { + return new Promise((resove, rejected) => { + // -update-begin--author:liaozhiyang---date:20240619---for:【TV360X-1380】表单中使用多个duplicateCheckDelay,validate方法调用时会导致promise被挂起保存不了 + let key; + if (isObject(params)) { + key = `${params.tableName}_${params.fieldName}`; + } else { + key = params; + } + clearTimeout(timer[key]); + // -update-end--author:liaozhiyang---date:20240619---for:【TV360X-1380】表单中使用多个duplicateCheckDelay,validate方法调用时会导致promise被挂起保存不了 + timer[key] = setTimeout(() => { + defHttp + .get({ url: Api.duplicateCheck, params }, { isTransformResponse: false }) + .then((res: any) => { + resove(res as any); + }) + .catch((error) => { + rejected(error); + }); + delete timer[key]; + }, 500); + }); +}; +/** + * 获取全部角色(租户隔离) + * @param params + */ +export const getAllRolesList = (params) => defHttp.get({ url: Api.allRolesList, params }); +/** + * 获取全部角色(不租户隔离) + * @param params + */ +export const getAllRolesListNoByTenant = (params) => defHttp.get({ url: Api.allRolesListNoByTenant, params }); +/** + * 获取全部租户 + */ +export const getAllTenantList = (params) => defHttp.get({ url: Api.allTenantList, params }); +/** + * 获取指定用户负责部门 + */ +export const getUserDepartList = (params) => defHttp.get({ url: Api.userDepartList, params }, { successMessageMode: 'none' }); +/** + * 获取全部职务 + */ +export const getAllPostList = (params) => { + return new Promise((resolve) => { + defHttp.get({ url: Api.allPostList, params }).then((res) => { + resolve(res.records); + }); + }); +}; +/** + * 回收站列表 + * @param params + */ +export const getRecycleBinList = (params) => defHttp.get({ url: Api.recycleBinList, params }); +/** + * 回收站还原 + * @param params + */ +export const putRecycleBin = (params, handleSuccess) => { + return defHttp.put({ url: Api.putRecycleBin, params }).then(() => { + handleSuccess(); + }); +}; +/** + * 回收站删除 + * @param params + */ +export const deleteRecycleBin = (params, handleSuccess) => { + return defHttp.delete({ url: Api.deleteRecycleBin, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; +/** + * 修改密码 + * @param params + */ +export const changePassword = (params) => { + return defHttp.put({ url: Api.changePassword, params }); +}; +/** + * 冻结解冻 + * @param params + */ +export const frozenBatch = (params, handleSuccess) => { + return defHttp.put({ url: Api.frozenBatch, params }).then(() => { + handleSuccess(); + }); +}; +/** + * 重置密码 + * @param params + */ +export const resetPassword = (params, handleSuccess) => { + return defHttp.put({ url: Api.resetPassword, params },{joinParamsToUrl: true}).then(() => { + handleSuccess(); + }); +}; + + +/** + * 用户离职列表 + * @param params + */ +export const getQuitList = (params) => { + return defHttp.get({ url: Api.getQuitList, params }); +}; + +/** + * 取消离职 + * @param params + */ +export const putCancelQuit = (params, handleSuccess) => { + return defHttp.put({ url: Api.putCancelQuit, params }, { joinParamsToUrl: true }).then(() => { + handleSuccess(); + }); +}; + +/** + * 待审批获取列表数据 + */ +export const getUserTenantPageList = (params) => { + return defHttp.get({ url: Api.getUserTenantPageList, params }); +}; + +/** + * 更新租户状态 + * @param params + */ +export const updateUserTenantStatus = (params) => { + return defHttp.put({ url: Api.updateUserTenantStatus, params }, { joinParamsToUrl: true, isTransformResponse: false }); +}; + +/** + * 根据部门id和已选中的部门岗位id获取部门下的岗位id + * + * @param params + */ +export const getDepPostIdByDepId = (params) => { + return defHttp.get({ url: Api.getDepPostIdByDepId, params },{ isTransformResponse: false }); +}; diff --git a/src/views/system/user/user.data.ts b/src/views/system/user/user.data.ts new file mode 100644 index 0000000..cfbdee9 --- /dev/null +++ b/src/views/system/user/user.data.ts @@ -0,0 +1,704 @@ +import { BasicColumn } from '/@/components/Table'; +import { FormSchema } from '/@/components/Table'; +import { getAllRolesListNoByTenant, getDepPostIdByDepId } from './user.api'; +import { rules } from '/@/utils/helper/validator'; +import { render } from '/@/utils/common/renderUtils'; +import { getDepartPathNameByOrgCode, getDepartName, getMultiDepartPathName, getDepartPathName } from '@/utils/common/compUtils'; +import { h } from 'vue'; +import { Tag } from 'ant-design-vue'; +export const columns: BasicColumn[] = [ + { + title: '用户账号', + dataIndex: 'username', + width: 120, + resizable: true, + }, + { + title: '用户姓名', + dataIndex: 'realname', + width: 100, + resizable: true, + }, +/* { + title: '头像', + dataIndex: 'avatar', + width: 120, + customRender: render.renderAvatar, + },*/ + { + title: '性别', + dataIndex: 'sex', + width: 80, + resizable: true, + sorter: true, + customRender: ({ text }) => { + return render.renderDict(text, 'sex'); + }, + }, +/* { + title: '生日', + dataIndex: 'birthday', + width: 100, + },*/ + { + title: '手机号', + dataIndex: 'phone', + width: 100, + resizable: true, + customRender:( { record, text })=>{ + if(record.izHideContact && record.izHideContact === '1'){ + return '/'; + } + return text; + } + }, + { + title: '部门', + width: 150, + resizable: true, + dataIndex: 'belongDepIds', + customRender:( { record, text })=>{ + if(!text){ + return ''; + } + return getDepartName(getMultiDepartPathName(record.orgCodeTxt,text)); + } + }, + { + title: '负责部门', + width: 150, + resizable: true, + dataIndex: 'departIds', + customRender:( { record, text })=>{ + if(!text){ + return ''; + } + return getDepartName(getMultiDepartPathName(record.departIds_dictText,text)); + } + }, + { + title: '主岗位', + width: 150, + resizable: true, + dataIndex: 'mainDepPostId', + customRender: ({ record, text })=>{ + return getDepartName(getDepartPathName(record.mainDepPostId_dictText,text,false)); + } + }, + { + title: '兼职岗位', + width: 150, + resizable: true, + dataIndex: 'otherDepPostId', + customRender:({ record, text })=>{ + if(!text){ + return ''; + } + return getDepartName(getMultiDepartPathName(record.otherDepPostId_dictText,text)); + } + }, + { + title: '状态', + dataIndex: 'status_dictText', + width: 80, + resizable: true, + }, +]; + +export const recycleColumns: BasicColumn[] = [ + { + title: '用户账号', + dataIndex: 'username', + width: 100, + }, + { + title: '用户姓名', + dataIndex: 'realname', + width: 100, + }, + { + title: '头像', + dataIndex: 'avatar', + width: 80, + customRender: render.renderAvatar, + }, + { + title: '性别', + dataIndex: 'sex', + width: 80, + sorter: true, + customRender: ({ text }) => { + return render.renderDict(text, 'sex'); + }, + }, +]; + +export const searchFormSchema: FormSchema[] = [ + { + label: '账号', + field: 'username', + component: 'JInput', + //colProps: { span: 6 }, + }, + { + label: '名字', + field: 'realname', + component: 'JInput', + //colProps: { span: 6 }, + }, + { + label: '性别', + field: 'sex', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'sex', + placeholder: '请选择性别', + stringToNumber: true, + }, + //colProps: { span: 6 }, + }, + { + label: '手机号码', + field: 'phone', + component: 'Input', + //colProps: { span: 6 }, + }, + { + label: '用户状态', + field: 'status', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'user_status', + placeholder: '请选择状态', + stringToNumber: true, + }, + //colProps: { span: 6 }, + }, + { + label: '所属部门', + field: 'departId', + component: 'JSelectDept', + componentProps: { + placeholder: '请选择所属部门', + showButton: false, + checkStrictly: true + }, + }, +]; + +export const formSchema: FormSchema[] = [ + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, + { + label: '用户账号', + field: 'username', + component: 'Input', + required: true, + dynamicDisabled: ({ values }) => { + return !!values.id; + }, + dynamicRules: ({ model, schema }) => rules.duplicateCheckRule('sys_user', 'username', model, schema, true), + }, + { + label: '登录密码', + field: 'password', + component: 'StrengthMeter', + componentProps:{ + autocomplete: 'new-password', + }, + rules: [ + { + required: true, + message: '请输入登录密码', + }, + { + pattern: /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[~!@#$%^&*()_+`\-={}:";'<>?,./]).{8,}$/, + message: '密码由 8 位及以上数字、大小写字母和特殊符号组成!', + }, + ], + }, + { + label: '确认密码', + field: 'confirmPassword', + component: 'InputPassword', + dynamicRules: ({ values }) => rules.confirmPassword(values, true), + }, + { + label: '用户姓名', + field: 'realname', + required: true, + component: 'Input', + }, + { + label: '工号', + field: 'workNo', + required: false, + component: 'Input', + dynamicRules: ({ model, schema }) => rules.duplicateCheckRule('sys_user', 'work_no', model, schema, false), + }, +/* { + label: '职务', + field: 'post', + required: false, + component: 'JSelectPosition', + componentProps: { + labelKey: 'name', + }, + },*/ + { + label: '职务', + field: 'positionType', + required: false, + component: 'JDictSelectTag', + componentProps: { + dictCode: "user_position", + mode: 'multiple', + }, + }, + { + label: '角色', + field: 'selectedroles', + component: 'ApiSelect', + componentProps: { + mode: 'multiple', + api: getAllRolesListNoByTenant, + labelField: 'roleName', + valueField: 'id', + immediate: false, + }, + }, + { + label: '所属部门', + field: 'selecteddeparts', + component: 'JSelectDept', + componentProps: ({ formActionType, formModel }) => { + return { + sync: false, + checkStrictly: true, + defaultExpandLevel: 2, + + onSelect: (options, values) => { + const { updateSchema } = formActionType; + //所属部门修改后更新负责部门下拉框数据 + updateSchema([ + //修改主岗位和兼职岗位的参数 + { + field: 'mainDepPostId', + componentProps: { params: { departIds: values?values.value.join(","): "" } }, + }, + { + field: 'otherDepPostId', + componentProps: { params: { departIds: values?values.value.join(","): "" } }, + } + ]); + //更新负责部门的option + updateDepartOption(options, updateSchema); + if(!values){ + formModel.departIds = []; + formModel.mainDepPostId = ""; + formModel.otherDepPostId = ""; + return; + } + //所属部门修改后更新负责部门数据 + formModel.departIds && (formModel.departIds = formModel.departIds.filter((item) => values.value.indexOf(item) > -1)); + }, + onChange: async (values) => { + // 当所属部门发生改变时,需要取消主岗位和兼职岗位的选中值 + await removeDepPostByDepId(formModel, values, formActionType); + } + }; + }, + }, + { + label: '主岗位', + field: 'mainDepPostId', + component: 'JSelectDepartPost', + componentProps: { + rowKey: 'id', + multiple: false, + izShowDepPath: true, + }, + ifShow: ({ values }) => { + if(!values.selecteddeparts){ + return false; + } + return !(values.selecteddeparts instanceof Array && values.selecteddeparts.length == 0); + }, + }, + { + label: '兼职岗位', + field: 'otherDepPostId', + component: 'JSelectDepartPost', + componentProps: { + rowKey: 'id', + izShowDepPath: true, + }, + ifShow: ({ values }) => { + if(!values.selecteddeparts){ + return false; + } + return !(values.selecteddeparts instanceof Array && values.selecteddeparts.length == 0); + }, + }, + { + label: '租户', + field: 'relTenantIds', + component: 'JSearchSelect', + componentProps: { + dict:"sys_tenant,name,id", + async: true, + multiple: true + }, + }, + { + label: '身份', + field: 'userIdentity', + component: 'RadioGroup', + defaultValue: 1, + componentProps: ({ formModel }) => { + return { + options: [ + { label: '普通用户', value: 1, key: '1' }, + { label: '上级', value: 2, key: '2' }, + ], + onChange: () => { + formModel.userIdentity == 1 && (formModel.departIds = []); + }, + }; + }, + }, + { + label: '负责部门', + field: 'departIds', + component: 'Select', + componentProps: { + mode: 'multiple', + tagRender: ({ label, value, closable, onClose }) => { + // 计算显示文本:前面省略号 + 后面字符 + let displayLabel = label; + if(displayLabel && label.length >= 20) { + displayLabel = "..." + displayLabel.substring(label.length - 20); + } + return h(Tag, { + style: { + position: 'relative', + boxSizing: 'border-box', + height: '24px', + marginTop: '2px', + fontSize: '14px', + marginBottom: '2px', + lineHeight: '22px', + background: 'rgba(51, 51, 51, 0.06)', + border: '1px solid rgba(5, 5, 5, 0.06)', + borderRadius: '4px', + cursor: 'default' + }, + title: label, + closable, + onClose:(e)=>{ + e.stopPropagation(); + onClose(); + } + }, () => displayLabel); + } + }, + ifShow: ({ values }) => values.userIdentity == 2, + }, + { + label: '排序', + field: 'sort', + component: 'InputNumber', + defaultValue: 1000, + componentProps: { + min: 1, + max: 999999, + step: 1, + precision: 0 + } + }, + { + label: '头像', + field: 'avatar', + component: 'JImageUpload', + componentProps: { + fileMax: 1, + }, + }, + { + label: '生日', + field: 'birthday', + component: 'DatePicker', + }, + { + label: '性别', + field: 'sex', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'sex', + placeholder: '请选择性别', + stringToNumber: true, + }, + }, + { + label: '邮箱', + field: 'email', + component: 'Input', + required: false, + dynamicRules: ({ model, schema }) => { + return [ + { ...rules.duplicateCheckRule('sys_user', 'email', model, schema, false)[0], trigger: 'blur' }, + { ...rules.rule('email', false)[0], trigger: 'blur' }, + ]; + }, + }, + { + label: '手机号码', + field: 'phone', + component: 'Input', + required: true, + dynamicRules: ({ model, schema }) => { + return [ + { ...rules.duplicateCheckRule('sys_user', 'phone', model, schema, true)[0], trigger: 'blur' }, + { pattern: /^1[3456789]\d{9}$/, message: '手机号码格式有误', trigger: 'blur' }, + ]; + }, + }, + { + label: '座机', + field: 'telephone', + component: 'Input', + rules: [{ pattern: /^0\d{2,3}-[1-9]\d{6,7}$/, message: '请输入正确的座机号码' }], + }, + { + label: '工作流引擎', + field: 'activitiSync', + defaultValue: 1, + component: 'JDictSelectTag', + componentProps: { + dictCode: 'activiti_sync', + type: 'radio', + stringToNumber: true, + }, + }, + { + label: '隐藏联系方式', + field: 'izHideContact', + defaultValue: '0', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'yn', + type: 'radio', + }, + }, +]; + +export const formPasswordSchema: FormSchema[] = [ + { + label: '用户账号', + field: 'username', + component: 'Input', + componentProps: { readOnly: true }, + }, + { + label: '登录密码', + field: 'password', + component: 'StrengthMeter', + componentProps: { + placeholder: '请输入登录密码', + }, + rules: [ + { + required: true, + message: '请输入登录密码', + }, + { + pattern: /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[~!@#$%^&*()_+`\-={}:";'<>?,./]).{8,}$/, + message: '密码由 8 位及以上数字、大小写字母和特殊符号组成!', + }, + ], + }, + { + label: '确认密码', + field: 'confirmPassword', + component: 'InputPassword', + dynamicRules: ({ values }) => rules.confirmPassword(values, true), + }, +]; + + +//租户用户列表 +export const userTenantColumns: BasicColumn[] = [ + { + title: '用户账号', + dataIndex: 'username', + width: 120, + }, + { + title: '用户姓名', + dataIndex: 'realname', + width: 100, + }, + { + title: '头像', + dataIndex: 'avatar', + width: 120, + customRender: render.renderAvatar, + }, + { + title: '手机号', + dataIndex: 'phone', + width: 100, + }, + { + title: '部门', + width: 150, + dataIndex: 'orgCodeTxt', + }, + { + title: '状态', + dataIndex: 'status', + width: 80, + customRender: ({ text }) => { + if (text === '1') { + return '正常'; + } else if (text === '3') { + return '审批中'; + } else { + return '已拒绝'; + } + }, + }, +]; + +//用户租户搜索表单 +export const userTenantFormSchema: FormSchema[] = [ + { + label: '账号', + field: 'username', + component: 'Input', + colProps: { span: 6 }, + }, + { + label: '名字', + field: 'realname', + component: 'Input', + colProps: { span: 6 }, + }, + { + label: '性别', + field: 'sex', + component: 'JDictSelectTag', + componentProps: { + dictCode: 'sex', + placeholder: '请选择性别', + stringToNumber: true, + }, + colProps: { span: 6 }, + }, +]; + + +/** + * 删除非当前部门下的数据 + * 当所属部门发生改变时,取消主岗位和兼职岗位的选中值 + * + * @param formModel 表单模型 + * @param values 选中的部门值 + * @param formActionType 表单操作方法 + */ +async function removeDepPostByDepId(formModel, values, formActionType) { + const { setFieldsValue } = formActionType; + if (values) { + let departIds = ""; + if (values instanceof Array) { + departIds = values.join(","); + } else { + departIds = values; + } + if (departIds) { + try { + // 查询当前选中部门下的岗位ID + const { result } = await getDepPostIdByDepId({ depIds: departIds }); + const validPostIds = result.split(",") || []; + + // 检查主岗位是否在当前部门下 + if (formModel.mainDepPostId) { + const mainPostId = Array.isArray(formModel.mainDepPostId) + ? formModel.mainDepPostId[0] + : formModel.mainDepPostId; + + if (mainPostId && !validPostIds.includes(mainPostId)) { + // 主岗位不在当前部门下,清空主岗位 + setFieldsValue({ mainDepPostId: null }); + formModel.mainDepPostId = null; + } + } + + // 检查兼职岗位是否在当前部门下 + if(typeof formModel.otherDepPostId === "string"){ + formModel.otherDepPostId = formModel.otherDepPostId.split(","); + } + if (formModel.otherDepPostId && Array.isArray(formModel.otherDepPostId)) { + const validOtherPosts = formModel.otherDepPostId.filter(postId => + validPostIds.includes(postId) + ); + // 有兼职岗位不在当前部门下,更新兼职岗位 + setFieldsValue({ otherDepPostId: validOtherPosts }); + formModel.otherDepPostId = validOtherPosts; + } + } catch (error) { + console.error('查询部门岗位失败:', error); + // 查询失败时,清空所有岗位选择 + setFieldsValue({ + mainDepPostId: null, + otherDepPostId: [] + }); + formModel.mainDepPostId = null; + formModel.otherDepPostId = []; + } + } else { + // 没有选中部门时,清空所有岗位选择 + setFieldsValue({ + mainDepPostId: null, + otherDepPostId: [] + }); + formModel.mainDepPostId = null; + formModel.otherDepPostId = []; + } + } +} + +/** + * 更新负责部门的options + * + * @param options + * @param updateSchema + */ +async function updateDepartOption(options, updateSchema) { + if (options && options.length > 0) { + // 并行处理所有异步操作 + const updatedOptions = await Promise.all( + options.map(async (item) => { + const departPathName = await getDepartPathNameByOrgCode('', item.label, item.value); + return { ...item, label: departPathName }; + }) + ); + updateSchema([ + { + field: 'departIds', + componentProps: { options: updatedOptions }, + }, + ]); + } else { + updateSchema([ + { + field: 'departIds', + componentProps: { options: [] }, + }, + ]); + } +} diff --git a/src/views/system/user/userDetails.vue b/src/views/system/user/userDetails.vue new file mode 100644 index 0000000..c0a8cd4 --- /dev/null +++ b/src/views/system/user/userDetails.vue @@ -0,0 +1,54 @@ + + diff --git a/src/views/system/usersetting/AccountSetting.vue b/src/views/system/usersetting/AccountSetting.vue new file mode 100644 index 0000000..5911bb8 --- /dev/null +++ b/src/views/system/usersetting/AccountSetting.vue @@ -0,0 +1,221 @@ + + + diff --git a/src/views/system/usersetting/BaseSetting.vue b/src/views/system/usersetting/BaseSetting.vue new file mode 100644 index 0000000..2fa0132 --- /dev/null +++ b/src/views/system/usersetting/BaseSetting.vue @@ -0,0 +1,550 @@ + + + + diff --git a/src/views/system/usersetting/TenantSetting.vue b/src/views/system/usersetting/TenantSetting.vue new file mode 100644 index 0000000..03128b7 --- /dev/null +++ b/src/views/system/usersetting/TenantSetting.vue @@ -0,0 +1,772 @@ + + + + + + + diff --git a/src/views/system/usersetting/UserSetting.api.ts b/src/views/system/usersetting/UserSetting.api.ts new file mode 100644 index 0000000..4125bfb --- /dev/null +++ b/src/views/system/usersetting/UserSetting.api.ts @@ -0,0 +1,151 @@ +import { defHttp } from "/@/utils/http/axios"; + +enum Api { + userEdit='/sys/user/login/setting/userEdit', + getUserData='/sys/user/login/setting/getUserData', + queryNameByCodes='/sys/position/queryByCodes', + updateMobile='/sys/user/updateMobile', + updateUserPassword='/sys/user/passwordChange', + getTenantListByUserId = '/sys/tenant/getTenantListByUserId', + cancelApplyTenant = '/sys/tenant/cancelApplyTenant', + exitUserTenant = '/sys/tenant/exitUserTenant', + changeOwenUserTenant = '/sys/tenant/changeOwenUserTenant', + getThirdAccountByUserId = '/sys/thirdApp/getThirdAccountByUserId', + bindThirdAppAccount = '/sys/thirdApp/bindThirdAppAccount', + deleteThirdAccount = '/sys/thirdApp/deleteThirdAccount', + agreeOrRefuseJoinTenant = '/sys/tenant/agreeOrRefuseJoinTenant', + //更改手机号 + changePhone = '/sys/user/changePhone', + //用户注销 + userLogOff = '/sys/user/userLogOff', + //没有绑定手机号用的修改密码请求地址 + updatePasswordNotBindPhone = '/sys/user/updatePasswordNotBindPhone', +} + +/** + * 用户编辑 + * @param params + */ +export const userEdit = (params) => { + return defHttp.post({ url: Api.userEdit, params },{ isTransformResponse:false }); +} + +/** + * 获取用户信息 + * @param params + */ +export const getUserData = () => { + return defHttp.get({ url: Api.getUserData },{ isTransformResponse:false }); +} + +/** + * 获取多个职务信息 + * @param params + */ +export const queryNameByCodes = (params) => { + return defHttp.get({ url: Api.queryNameByCodes, params },{isTransformResponse:false}); +} + +/** + * 修改手机号 + * @param params + */ +export const updateMobile = (params) => { + return defHttp.put({ url: Api.updateMobile, params },{isTransformResponse:false}); +} + +/** + * 修改密码 + * @param params + */ +export const updateUserPassword = (params) => { + return defHttp.get({ url: Api.updateUserPassword, params },{isTransformResponse:false}); +} + +/** + * 修改密码 + * @param params + */ +export const updatePasswordNotBindPhone = (params) => { + return defHttp.put({ url: Api.updatePasswordNotBindPhone, params },{ isTransformResponse:false, joinParamsToUrl: true }); +} + +/** + * 通过用户id获取租户列表 + * @param params + */ +export const getTenantListByUserId = (params) => { + return defHttp.get({ url: Api.getTenantListByUserId, params }, { isTransformResponse: false }); +}; + +/** + * 取消申请 + * @param params + */ +export const cancelApplyTenant = (params) => { + return defHttp.put({ url: Api.cancelApplyTenant, data: params }, { joinParamsToUrl: true, isTransformResponse: false }); +}; + +/** + * 用户退出租户 + * @param params + */ +export const exitUserTenant = (params)=>{ + return defHttp.delete({ url: Api.exitUserTenant, params },{ isTransformResponse: false, joinParamsToUrl: true }); +} + +/** + * 变更租户拥有者 + * @param params + */ +export const changeOwenUserTenant = (params)=>{ + return defHttp.post({ url: Api.changeOwenUserTenant, params },{ isTransformResponse: false, joinParamsToUrl: true }); +} + +/** + * 获取账号第三方信息通过第三方类型 + * @param params + */ +export const getThirdAccountByUserId = (params) => { + return defHttp.get({ url: Api.getThirdAccountByUserId, params }, { isTransformResponse: false }); +}; + +/** + * 根据第三方uuid绑定账号 + * @param params + */ +export const bindThirdAppAccount = (params) => { + return defHttp.post({ url: Api.bindThirdAppAccount, params }, { isTransformResponse: false, joinParamsToUrl: true }); +}; + +/** + * 根据第三方uuid绑定账号 + * @param params + */ +export const deleteThirdAccount = (params) => { + return defHttp.delete({ url: Api.deleteThirdAccount, params }, { isTransformResponse:false, joinParamsToUrl: true }); +}; + +/** + * 同意和拒绝加入租户 + * @param params + */ +export const agreeOrRefuseJoinTenant = (params) => { + return defHttp.put({ url: Api.agreeOrRefuseJoinTenant, params },{ joinParamsToUrl: true }); +}; + +/** + * 更改手机号 + * @param params + */ +export const changePhone = (params) => { + return defHttp.put({ url: Api.changePhone, params },{ joinParamsToUrl: true, isTransformResponse: false }); +}; + +/** + * 用户注销 + * @param params + */ +export const userLogOff = (params) => { + return defHttp.put({ url: Api.userLogOff, params },{ isTransformResponse:false }); +} diff --git a/src/views/system/usersetting/UserSetting.data.ts b/src/views/system/usersetting/UserSetting.data.ts new file mode 100644 index 0000000..d94301e --- /dev/null +++ b/src/views/system/usersetting/UserSetting.data.ts @@ -0,0 +1,165 @@ +import { FormSchema } from '/@/components/Form/index'; +import { rules } from '/@/utils/helper/validator'; +import anquan1 from './icons/anquan1.png' +import anquan2 from './icons/anquan2.png' +import app1 from './icons/app1.png' +import app2 from './icons/app2.png' +import geren1 from './icons/geren1.png' +import geren2 from './icons/geren2.png' +import zuhu1 from './icons/zuhu1.png' +import zuhu2 from './icons/zuhu2.png' +import { calculateFileSize } from "/@/utils/common/compUtils"; +import { BasicColumn } from "@/components/Table"; + +export interface ListItem { + key: string; + title: string; + description: string; + extra?: string; + avatar?: string; + color?: string; +} + +// tab的list +export const settingList = [ + { + key: '1', + name: '个人信息', + component: 'BaseSetting', + icon:'ant-design:user-outlined', + img1: geren1, + img2: geren2, + }, + { + key: '2', + name: '我的组织', + component: 'TenantSetting', + isSlot:false, + icon:'ant-design:team-outlined', + img1: zuhu1, + img2: zuhu2, + }, + { + key: '3', + name: '账号安全', + component: 'AccountSetting', + icon:'ant-design:lock-outlined', + img1: anquan1, + img2: anquan2, + }, + { + key: '4', + name: '第三方APP', + component: 'WeChatDingSetting', + icon: 'ant-design:contacts-outlined', + img1: app1, + img2: app2, + }, +]; + + +/** + * 用户表单 + */ +export const formSchema: FormSchema[] = [ + { + field: 'realname', + component: 'Input', + label: '姓名', + colProps: { span: 24 }, + required:true + }, + { + field: 'birthday', + component: 'DatePicker', + label: '生日', + colProps: { span: 24 }, + componentProps:{ + showTime:false, + valueFormat:"YYYY-MM-DD", + getPopupContainer: () => document.body, + }, + }, + { + field: 'sex', + component: 'RadioGroup', + label: '性别', + colProps: { span: 24 }, + componentProps:{ + options: [ + { + label: '男', + value: 1, + }, + { + label: '女', + value: 2, + }, + ], + } + }, + { + field: 'relTenantIds', + component: 'JDictSelectTag', + label: '租户', + colProps: { span: 24 }, + componentProps:{ + mode:'multiple', + dictCode:'sys_tenant,name,id', + disabled:true + } + }, + { + field: 'post', + component: 'JDictSelectTag', + label: '职位', + colProps: { span: 24 }, + componentProps:{ + mode:'multiple', + dictCode:'sys_position,name,id', + disabled:true + } + }, + { + label: '', + field: 'id', + component: 'Input', + show: false, + }, +] + +//密码弹窗 +export const formPasswordSchema: FormSchema[] = [ + { + label: '用户账号', + field: 'username', + component: 'Input', + componentProps: { readOnly: true }, + }, + { + label: '旧密码', + field: 'oldpassword', + component: 'InputPassword', + required: true, + }, + { + label: '新密码', + field: 'password', + component: 'StrengthMeter', + componentProps: { + placeholder: '请输入新密码', + }, + rules: [ + { + required: true, + message: '请输入新密码', + }, + ], + }, + { + label: '确认新密码', + field: 'confirmpassword', + component: 'InputPassword', + dynamicRules: ({ values }) => rules.confirmPassword(values, true), + }, +]; diff --git a/src/views/system/usersetting/UserSetting.vue b/src/views/system/usersetting/UserSetting.vue new file mode 100644 index 0000000..588088b --- /dev/null +++ b/src/views/system/usersetting/UserSetting.vue @@ -0,0 +1,179 @@ + + + + + + diff --git a/src/views/system/usersetting/WeChatDingSetting.vue b/src/views/system/usersetting/WeChatDingSetting.vue new file mode 100644 index 0000000..5903268 --- /dev/null +++ b/src/views/system/usersetting/WeChatDingSetting.vue @@ -0,0 +1,306 @@ + + + diff --git a/src/views/system/usersetting/commponents/UserAccountModal.vue b/src/views/system/usersetting/commponents/UserAccountModal.vue new file mode 100644 index 0000000..1d187f4 --- /dev/null +++ b/src/views/system/usersetting/commponents/UserAccountModal.vue @@ -0,0 +1,69 @@ + + + diff --git a/src/views/system/usersetting/commponents/UserCancellationModal.vue b/src/views/system/usersetting/commponents/UserCancellationModal.vue new file mode 100644 index 0000000..87e13be --- /dev/null +++ b/src/views/system/usersetting/commponents/UserCancellationModal.vue @@ -0,0 +1,118 @@ + + + diff --git a/src/views/system/usersetting/commponents/UserEmailModal.vue b/src/views/system/usersetting/commponents/UserEmailModal.vue new file mode 100644 index 0000000..dd90b4c --- /dev/null +++ b/src/views/system/usersetting/commponents/UserEmailModal.vue @@ -0,0 +1,73 @@ + + + + + \ No newline at end of file diff --git a/src/views/system/usersetting/commponents/UserPasswordModal.vue b/src/views/system/usersetting/commponents/UserPasswordModal.vue new file mode 100644 index 0000000..05be344 --- /dev/null +++ b/src/views/system/usersetting/commponents/UserPasswordModal.vue @@ -0,0 +1,142 @@ + + + diff --git a/src/views/system/usersetting/commponents/UserPasswordNotBindPhone.vue b/src/views/system/usersetting/commponents/UserPasswordNotBindPhone.vue new file mode 100644 index 0000000..2ed2a47 --- /dev/null +++ b/src/views/system/usersetting/commponents/UserPasswordNotBindPhone.vue @@ -0,0 +1,115 @@ + + + diff --git a/src/views/system/usersetting/commponents/UserPhoneModal.vue b/src/views/system/usersetting/commponents/UserPhoneModal.vue new file mode 100644 index 0000000..b772511 --- /dev/null +++ b/src/views/system/usersetting/commponents/UserPhoneModal.vue @@ -0,0 +1,250 @@ + + + + \ No newline at end of file diff --git a/src/views/system/usersetting/icons/anquan1.png b/src/views/system/usersetting/icons/anquan1.png new file mode 100644 index 0000000..c41ee33 Binary files /dev/null and b/src/views/system/usersetting/icons/anquan1.png differ diff --git a/src/views/system/usersetting/icons/anquan2.png b/src/views/system/usersetting/icons/anquan2.png new file mode 100644 index 0000000..69b4bb9 Binary files /dev/null and b/src/views/system/usersetting/icons/anquan2.png differ diff --git a/src/views/system/usersetting/icons/app1.png b/src/views/system/usersetting/icons/app1.png new file mode 100644 index 0000000..157ef45 Binary files /dev/null and b/src/views/system/usersetting/icons/app1.png differ diff --git a/src/views/system/usersetting/icons/app2.png b/src/views/system/usersetting/icons/app2.png new file mode 100644 index 0000000..c1e2805 Binary files /dev/null and b/src/views/system/usersetting/icons/app2.png differ diff --git a/src/views/system/usersetting/icons/geren1.png b/src/views/system/usersetting/icons/geren1.png new file mode 100644 index 0000000..21c3a1d Binary files /dev/null and b/src/views/system/usersetting/icons/geren1.png differ diff --git a/src/views/system/usersetting/icons/geren2.png b/src/views/system/usersetting/icons/geren2.png new file mode 100644 index 0000000..18097fd Binary files /dev/null and b/src/views/system/usersetting/icons/geren2.png differ diff --git a/src/views/system/usersetting/icons/zuhu1.png b/src/views/system/usersetting/icons/zuhu1.png new file mode 100644 index 0000000..7e977e3 Binary files /dev/null and b/src/views/system/usersetting/icons/zuhu1.png differ diff --git a/src/views/system/usersetting/icons/zuhu2.png b/src/views/system/usersetting/icons/zuhu2.png new file mode 100644 index 0000000..75aecd6 Binary files /dev/null and b/src/views/system/usersetting/icons/zuhu2.png differ diff --git a/stylelint.config.js b/stylelint.config.js new file mode 100644 index 0000000..66baee5 --- /dev/null +++ b/stylelint.config.js @@ -0,0 +1,71 @@ +module.exports = { + root: true, + plugins: ['stylelint-order'], + extends: ['stylelint-config-standard', 'stylelint-config-prettier'], + rules: { + 'selector-pseudo-class-no-unknown': [ + true, + { + ignorePseudoClasses: ['global'], + }, + ], + 'selector-pseudo-element-no-unknown': [ + true, + { + ignorePseudoElements: ['v-deep'], + ignorePseudoElements: ['deep'], + }, + ], + 'at-rule-no-unknown': [ + true, + { + ignoreAtRules: [ + 'tailwind', + 'apply', + 'variants', + 'responsive', + 'screen', + 'function', + 'if', + 'each', + 'include', + 'mixin', + ], + }, + ], + 'no-empty-source': null, + 'named-grid-areas-no-invalid': null, + 'unicode-bom': 'never', + 'no-descending-specificity': null, + 'font-family-no-missing-generic-family-keyword': null, + 'declaration-colon-space-after': 'always-single-line', + 'declaration-colon-space-before': 'never', + // 'declaration-block-trailing-semicolon': 'always', + 'rule-empty-line-before': [ + 'always', + { + ignore: ['after-comment', 'first-nested'], + }, + ], + 'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }], + 'order/order': [ + [ + 'dollar-variables', + 'custom-properties', + 'at-rules', + 'declarations', + { + type: 'at-rule', + name: 'supports', + }, + { + type: 'at-rule', + name: 'media', + }, + 'rules', + ], + { severity: 'warning' }, + ], + }, + ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'], +}; diff --git a/tests/__mocks__/fileMock.ts b/tests/__mocks__/fileMock.ts new file mode 100644 index 0000000..08d725c --- /dev/null +++ b/tests/__mocks__/fileMock.ts @@ -0,0 +1 @@ +export default ''; diff --git a/tests/__mocks__/styleMock.ts b/tests/__mocks__/styleMock.ts new file mode 100644 index 0000000..ff8b4c5 --- /dev/null +++ b/tests/__mocks__/styleMock.ts @@ -0,0 +1 @@ +export default {}; diff --git a/tests/__mocks__/workerMock.ts b/tests/__mocks__/workerMock.ts new file mode 100644 index 0000000..0b8b671 --- /dev/null +++ b/tests/__mocks__/workerMock.ts @@ -0,0 +1,5 @@ +export default jest.fn().mockImplementation(() => ({ + postMessage: jest.fn(), + onmessage: jest.fn(), + onerror: jest.fn(), +})); diff --git a/tests/server/README.md b/tests/server/README.md new file mode 100644 index 0000000..33db6bf --- /dev/null +++ b/tests/server/README.md @@ -0,0 +1,15 @@ +# Test Server + +It is used to start the test interface service, which can test the upload, websocket, login and other interfaces. + +## Usage + +```bash + +cd ./test/server + +yarn + +yarn start + +``` diff --git a/tests/server/controller/FileController.ts b/tests/server/controller/FileController.ts new file mode 100644 index 0000000..cf6d90d --- /dev/null +++ b/tests/server/controller/FileController.ts @@ -0,0 +1,18 @@ +import FileService from '../service/FileService'; + +class FileController { + private service: FileService = new FileService(); + + upload = async (ctx) => { + const files = ctx.request.files.file; + console.log(files); + + if (files.length === undefined) { + this.service.upload(ctx, files, false); + } else { + this.service.upload(ctx, files, true); + } + }; +} + +export default new FileController(); diff --git a/tests/server/controller/UserController.ts b/tests/server/controller/UserController.ts new file mode 100644 index 0000000..db815c2 --- /dev/null +++ b/tests/server/controller/UserController.ts @@ -0,0 +1,15 @@ +import UserService from '../service/UserService'; + +class UserController { + private service: UserService = new UserService(); + + login = async (ctx) => { + ctx.body = await this.service.login(); + }; + + getUserInfoById = async (ctx) => { + ctx.body = await this.service.getUserInfoById(); + }; +} + +export default new UserController(); diff --git a/tests/server/ecosystem.config.js b/tests/server/ecosystem.config.js new file mode 100644 index 0000000..a31e457 --- /dev/null +++ b/tests/server/ecosystem.config.js @@ -0,0 +1,18 @@ +const { name } = require('./package.json'); +const path = require('path'); + +module.exports = { + apps: [ + { + name, + script: path.resolve(__dirname, './dist/index.js'), + instances: require('os').cpus().length, + autorestart: true, + watch: true, + env_production: { + NODE_ENV: 'production', + PORT: 8080, + }, + }, + ], +}; diff --git a/tests/server/index.ts b/tests/server/index.ts new file mode 100644 index 0000000..d0b0931 --- /dev/null +++ b/tests/server/index.ts @@ -0,0 +1,63 @@ +import Koa from 'koa'; +import path from 'path'; +import Router from 'koa-router'; +import body from 'koa-body'; +import cors from 'koa2-cors'; +import koaStatic from 'koa-static'; +import websockify from 'koa-websocket'; +import route from 'koa-route'; + +import AppRoutes from './routes'; + +const PORT = 3300; + +const app = websockify(new Koa()); + +app.ws.use(function (ctx, next) { + ctx.websocket.send('connection succeeded!'); + return next(ctx); +}); + +app.ws.use( + route.all('/test', function (ctx) { + // ctx.websocket.send('Hello World'); + ctx.websocket.on('message', function (message) { + // do something with the message from client + + if (message !== 'ping') { + const data = JSON.stringify({ + id: Math.ceil(Math.random() * 1000), + time: new Date().getTime(), + res: `${message}`, + }); + ctx.websocket.send(data); + } + console.log(message); + }); + }) +); + +const router = new Router(); + +// router +AppRoutes.forEach((route) => router[route.method](route.path, route.action)); + +app.use(cors()); +app.use( + body({ + encoding: 'gzip', + multipart: true, + formidable: { + // uploadDir: path.join(__dirname, '/upload/'), // 设置文件上传目录 + keepExtensions: true, + maxFieldsSize: 20 * 1024 * 1024, + }, + }) +); +app.use(router.routes()); +app.use(router.allowedMethods()); +app.use(koaStatic(path.join(__dirname))); + +app.listen(PORT, () => { + console.log(`Application started successfully: http://localhost:${PORT}`); +}); diff --git a/tests/server/nodemon.json b/tests/server/nodemon.json new file mode 100644 index 0000000..59fa5af --- /dev/null +++ b/tests/server/nodemon.json @@ -0,0 +1,8 @@ +{ + "watch": ["src"], + "ext": "ts", + "exec": "ts-node -r tsconfig-paths/register index.ts", + "events": { + "restart": "clear" + } +} diff --git a/tests/server/package.json b/tests/server/package.json new file mode 100644 index 0000000..d530d59 --- /dev/null +++ b/tests/server/package.json @@ -0,0 +1,36 @@ +{ + "name": "server", + "version": "1.0.0", + "license": "MIT", + "scripts": { + "start": "nodemon", + "build": "rimraf ./dist && tsup ./index.ts --dts --format cjs,esm ", + "prod": "npx pm2 start ecosystem.config.js --env production", + "restart": "pm2 restart ecosystem.config.js --env production", + "stop": "npx pm2 stop ecosystem.config.js" + }, + "dependencies": { + "fs-extra": "^10.0.0", + "koa": "^2.13.1", + "koa-body": "^4.2.0", + "koa-bodyparser": "^4.3.0", + "koa-route": "^3.2.0", + "koa-router": "^10.1.1", + "koa-static": "^5.0.0", + "koa-websocket": "^6.0.0", + "koa2-cors": "^2.0.6" + }, + "devDependencies": { + "@types/koa": "^2.13.4", + "@types/koa-bodyparser": "^5.0.2", + "@types/koa-router": "^7.4.4", + "@types/node": "^16.7.1", + "nodemon": "^2.0.12", + "pm2": "^5.1.1", + "rimraf": "^3.0.2", + "ts-node": "^10.2.1", + "tsconfig-paths": "^3.11.0", + "tsup": "^4.14.0", + "typescript": "^4.3.5" + } +} diff --git a/tests/server/routes.ts b/tests/server/routes.ts new file mode 100644 index 0000000..7fe6475 --- /dev/null +++ b/tests/server/routes.ts @@ -0,0 +1,23 @@ +import UserController from './controller/UserController'; +import FileController from './controller/FileController'; + +export default [ + // user + { + path: '/login', + method: 'post', + action: UserController.login, + }, + { + path: '/getUserInfoById', + method: 'get', + action: UserController.getUserInfoById, + }, + + // file + { + path: '/upload', + method: 'post', + action: FileController.upload, + }, +]; diff --git a/tests/server/service/FileService.ts b/tests/server/service/FileService.ts new file mode 100644 index 0000000..dfd378f --- /dev/null +++ b/tests/server/service/FileService.ts @@ -0,0 +1,54 @@ +import path from 'path'; +import fs from 'fs-extra'; + +const uploadUrl = 'http://localhost:3300/static/upload'; +const filePath = path.join(__dirname, '../static/upload/'); + +fs.ensureDir(filePath); +export default class UserService { + async upload(ctx, files, isMultiple) { + let fileReader, fileResource, writeStream; + + const fileFunc = function (file) { + fileReader = fs.createReadStream(file.path); + fileResource = filePath + `/${file.name}`; + console.log(fileResource); + + writeStream = fs.createWriteStream(fileResource); + fileReader.pipe(writeStream); + }; + + const returnFunc = function (flag) { + if (flag) { + let url = ''; + for (let i = 0; i < files.length; i++) { + url += uploadUrl + `/${files[i].name},`; + } + url = url.replace(/,$/gi, ''); + ctx.body = { + url: url, + code: 0, + message: 'upload Success!', + }; + } else { + ctx.body = { + url: uploadUrl + `/${files.name}`, + code: 0, + message: 'upload Success!', + }; + } + }; + console.log(isMultiple, files.length); + + if (isMultiple) { + for (let i = 0; i < files.length; i++) { + const f1 = files[i]; + fileFunc(f1); + } + } else { + fileFunc(files); + } + fs.ensureDir(filePath); + returnFunc(isMultiple); + } +} diff --git a/tests/server/service/UserService.ts b/tests/server/service/UserService.ts new file mode 100644 index 0000000..da61628 --- /dev/null +++ b/tests/server/service/UserService.ts @@ -0,0 +1,25 @@ +import { Result } from '../utils'; + +const fakeUserInfo = { + userId: '1', + username: 'Jeecg', + realname: 'Jeecg Admin', + desc: 'manager', + password: '123456', + token: 'fakeToken1', + roles: [ + { + roleName: 'Super Admin', + value: 'super', + }, + ], +}; +export default class UserService { + async login() { + return Result.success(fakeUserInfo); + } + + async getUserInfoById() { + return Result.success(fakeUserInfo); + } +} diff --git a/tests/server/tsconfig.json b/tests/server/tsconfig.json new file mode 100644 index 0000000..76203ed --- /dev/null +++ b/tests/server/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": false, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es6", + "sourceMap": false, + "esModuleInterop": true, + "outDir": "./dist", + "baseUrl": "./" + }, + "exclude": ["node_modules"] +} diff --git a/tests/server/utils.ts b/tests/server/utils.ts new file mode 100644 index 0000000..7fd0b3f --- /dev/null +++ b/tests/server/utils.ts @@ -0,0 +1,9 @@ +export class Result { + static success(data: any) { + return { + code: 0, + success: true, + result: data, + }; + } +} diff --git a/tests/test.spec.ts b/tests/test.spec.ts new file mode 100644 index 0000000..a8b4454 --- /dev/null +++ b/tests/test.spec.ts @@ -0,0 +1,16 @@ +// import { mount } from '@vue/test-utils'; +// import { Button } from '/@/components/Button'; + +test('if jest is normal.', async () => { + expect('jest').toEqual('jest'); +}); + +// TODO Vue component testing is not supported temporarily +// test('is a Vue instance.', async () => { +// const wrapper = mount(Button, { +// slots: { +// default: 'Button text', +// }, +// }); +// expect(wrapper.html()).toContain('Button text'); +// }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..18b6cdf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,49 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "strictFunctionTypes": false, + "jsx": "preserve", + "baseUrl": ".", + "allowJs": true, + "sourceMap": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "experimentalDecorators": true, + "lib": ["dom", "esnext"], + "types": ["vite/client", "jest"], + "typeRoots": ["./node_modules/@types/", "./types","./node_modules"], + "noImplicitAny": false, + "skipLibCheck": true, + "paths": { + "@rys-fe/vite-plugin-theme/es/client": ["build/vite/plugin/theme-plugin/client/client.ts"], + "@rys-fe/vite-plugin-theme/es/colorUtils": ["build/vite/plugin/theme-plugin/client/colorUtils.ts"], + "@rys-fe/vite-plugin-theme": ["build/vite/plugin/theme-plugin/index.ts"], + "/@/*": ["src/*"], + "/#/*": ["types/*"], + "@/*": ["src/*"], + "#/*": ["types/*"], + "~icons/*": ["node_modules/unplugin-icons/types/*"] + } + }, + "include": [ + "tests/**/*.ts", + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue", + "types/**/*.d.ts", + "types/**/*.ts", + "build/**/*.ts", + "build/**/*.d.ts", + "mock/**/*.ts", + "vite.config.ts" + ], + "exclude": ["node_modules", "tests/server/**/*.ts", "dist", "**/*.js"] +} diff --git a/types/axios.d.ts b/types/axios.d.ts new file mode 100644 index 0000000..c64a764 --- /dev/null +++ b/types/axios.d.ts @@ -0,0 +1,55 @@ +export type ErrorMessageMode = 'none' | 'modal' | 'message' | undefined; +export type SuccessMessageMode = 'none' | 'success' | 'error' | undefined; + +export interface RequestOptions { + // 将请求参数拼接到url + joinParamsToUrl?: boolean; + // 格式化请求参数时间 + formatDate?: boolean; + // 是否处理请求结果 + isTransformResponse?: boolean; + // 是否返回本地响应头,需要获取响应头时使用此属性 + isReturnNativeResponse?: boolean; + // 默认将prefix 添加到url + joinPrefix?: boolean; + // 接口地址,如果保留为空,则使用默认值 + apiUrl?: string; + // 请求拼接路径 + urlPrefix?: string; + // 错误消息提示类型 + errorMessageMode?: ErrorMessageMode; + // 成功消息提示类型 + successMessageMode?: SuccessMessageMode; + // 是否添加时间戳 + joinTime?: boolean; + ignoreCancelToken?: boolean; + //是否在标头中发送令牌 + withToken?: boolean; +} + +export interface Result { + code: number; + type: 'success' | 'error' | 'warning'; + message: string; + result: T; +} + +//文件上传参数 +export interface UploadFileParams { + // 其他参数 + data?: Recordable; + // 文件参数接口字段名 + name?: string; + // 文件 + file: File | Blob; + // 文件名 + filename?: string; + [key: string]: any; +} +//文件返回参数 +export interface UploadFileCallBack { + // 成功回调方法 + success?: any; + // 是否返回响应头,需要获取响应头时使用此属性 + isReturnResponse?: boolean; +} diff --git a/types/config.d.ts b/types/config.d.ts new file mode 100644 index 0000000..21d7f95 --- /dev/null +++ b/types/config.d.ts @@ -0,0 +1,207 @@ +import { MenuTypeEnum, MenuModeEnum, TriggerEnum, MixSidebarTriggerEnum } from '/@/enums/menuEnum'; +import { + ContentEnum, + PermissionModeEnum, + ThemeEnum, + RouterTransitionEnum, + SettingButtonPositionEnum, + SessionTimeoutProcessingEnum, +} from '/@/enums/appEnum'; + +import { CacheTypeEnum } from '/@/enums/cacheEnum'; + +export type LocaleType = 'zh_CN' | 'en' | 'ru' | 'ja' | 'ko'; + +export interface MenuSetting { + bgColor: string; + fixed: boolean; + collapsed: boolean; + canDrag: boolean; + show: boolean; + hidden: boolean; + split: boolean; + menuWidth: number; + mode: MenuModeEnum; + type: MenuTypeEnum; + theme: ThemeEnum; + // 代码逻辑说明: 【QQYUN-8922】左侧导航栏文字颜色调整区分彩色和暗黑 + isThemeBright: boolean; + topMenuAlign: 'start' | 'center' | 'end'; + trigger: TriggerEnum; + accordion: boolean; + closeMixSidebarOnChange: boolean; + collapsedShowTitle: boolean; + mixSideTrigger: MixSidebarTriggerEnum; + mixSideFixed: boolean; +} + +export interface MultiTabsSetting { + cache: boolean; + show: boolean; + showQuick: boolean; + canDrag: boolean; + showRedo: boolean; + showFold: boolean; + theme: string; +} + +export interface HeaderSetting { + bgColor: string; + fixed: boolean; + show: boolean; + theme: ThemeEnum; + // 是否显示全屏按钮 + showFullScreen: boolean; + // 是否显示锁屏按钮 + useLockPage: boolean; + // 是否显示文档连接 + showDoc: boolean; + // 是否显示消息图标 + showNotice: boolean; + // 是否显示搜索按钮 + showSearch: boolean; +} + +export interface LocaleSetting { + // 是否显示国际化切换按钮 + showPicker: boolean; + // Current language + locale: LocaleType; + // default language + fallback: LocaleType; + // available Locales + availableLocales: LocaleType[]; +} + +export interface TransitionSetting { + // Whether to open the page switching animation + enable: boolean; + // Route basic switching animation + basicTransition: RouterTransitionEnum; + // Whether to open page switching loading + openPageLoading: boolean; + // Whether to open the top progress bar + openNProgress: boolean; +} + +export interface ProjectConfig { + // Storage location of permission related information + permissionCacheType: CacheTypeEnum; + // Whether to show the configuration button + showSettingButton: boolean; + // Whether to show the theme switch button + showDarkModeToggle: boolean; + // Configure where the button is displayed + settingButtonPosition: SettingButtonPositionEnum; + // Permission mode + permissionMode: PermissionModeEnum; + // Session timeout processing + sessionTimeoutProcessing: SessionTimeoutProcessingEnum; + // Website gray mode, open for possible mourning dates + grayMode: boolean; + // Whether to turn on the color weak mode + colorWeak: boolean; + // Theme color + themeColor: string; + // Theme Mode + themeMode: string; + + // The main interface is displayed in full screen, the menu is not displayed, and the top + fullContent: boolean; + // content width + contentMode: ContentEnum; + // Whether to display the logo + showLogo: boolean; + // Whether to show the global footer + showFooter: boolean; + // menuType: MenuTypeEnum; + headerSetting: HeaderSetting; + // menuSetting + menuSetting: MenuSetting; + // Multi-tab settings + multiTabsSetting: MultiTabsSetting; + // Animation configuration + transitionSetting: TransitionSetting; + // pageLayout whether to enable keep-alive + openKeepAlive: boolean; + // Lock screen time + lockTime: number; + // Show breadcrumbs + showBreadCrumb: boolean; + // Show breadcrumb icon + showBreadCrumbIcon: boolean; + // Use error-handler-plugin + useErrorHandle: boolean; + // Whether to open back to top + useOpenBackTop: boolean; + // Is it possible to embed iframe pages + canEmbedIFramePage: boolean; + // Whether to delete unclosed messages and notify when switching the interface + closeMessageOnSwitch: boolean; + // Whether to cancel the http request that has been sent but not responded when switching the interface. + removeAllHttpPending: boolean; + aiIconShow: boolean; +} + +export interface GlobConfig { + // Site title + title: string; + // Service interface url + apiUrl: string; + domainUrl: string; + // Upload url (作废) + uploadUrl?: string; + openSso?: string; + openQianKun?: string; + casBaseUrl?: string; + // onlineview url + viewUrl?: string; + // Service interface url prefix + urlPrefix?: string; + // Project abbreviation + shortName: string; + // 短标题 + shortTitle: string; + // 使用新任务弹窗 + useNewTaskModal: boolean; + // 当前是否运行在 electron 平台 + isElectronPlatform: boolean; + + // 【JEECG作为乾坤子应用】是否以乾坤子应用模式启动 + isQiankunMicro: boolean; + // 【JEECG作为乾坤子应用】乾坤子应用入口 + qiankunMicroAppEntry?: string; +} +export interface GlobEnvConfig { + // Site title + VITE_GLOB_APP_TITLE: string; + // Service interface url + VITE_GLOB_API_URL: string; + VITE_USE_MOCK: string; + // Service interface url prefix + VITE_GLOB_API_URL_PREFIX?: string; + // Project abbreviation + VITE_GLOB_APP_SHORT_NAME: string; + //是否开启单点登录 + VITE_GLOB_APP_OPEN_SSO: string; + //是否开启微应用模式 + VITE_GLOB_APP_OPEN_QIANKUN: string; + //单点服务端地址 + VITE_GLOB_APP_CAS_BASE_URL: string; + VITE_GLOB_DOMAIN_URL: string; + // Upload url + VITE_GLOB_UPLOAD_URL?: string; + // view url + VITE_GLOB_ONLINE_VIEW_URL?: string; + // 全局隐藏哪些布局,多个用逗号隔开 + VITE_GLOB_HIDE_LAYOUT_TYPES?: string; + + // 【JEECG作为乾坤子应用】填写后将作为乾坤子应用启动,主应用注册时AppName需保持一致 + VITE_GLOB_QIANKUN_MICRO_APP_NAME?: string; + // 【JEECG作为乾坤子应用】作为乾坤子应用启动时必填,需与qiankun主应用注册子应用时填写的 entry 保持一致 + VITE_GLOB_QIANKUN_MICRO_APP_ENTRY?: string; + //在线文档编辑版本。可选属性:wps, onlyoffice + VITE_GLOB_ONLINE_DOCUMENT_VERSION?: string; + // 当前运行在什么平台 + VITE_GLOB_RUN_PLATFORM?: 'web' | 'electron'; +} diff --git a/types/global.d.ts b/types/global.d.ts new file mode 100644 index 0000000..b0f1ea1 --- /dev/null +++ b/types/global.d.ts @@ -0,0 +1,100 @@ +import type { ComponentRenderProxy, VNode, VNodeChild, ComponentPublicInstance, FunctionalComponent, PropType as VuePropType } from 'vue'; + +declare global { + const __APP_INFO__: { + pkg: { + name: string; + version: string; + dependencies: Recordable; + devDependencies: Recordable; + }; + lastBuildTime: string; + }; + // declare interface Window { + // // Global vue app instance + // __APP__: App; + // } + + // vue + declare type PropType = VuePropType; + declare type VueNode = VNodeChild | JSX.Element | JSX.IntrinsicElements; + + export type Writable = { + -readonly [P in keyof T]: T[P]; + }; + + declare type Nullable = T | null; + declare type NonNullable = T extends null | undefined ? never : T; + declare type Recordable = Record; + declare type ReadonlyRecordable = { + readonly [key: string]: T; + }; + declare type Indexable = { + [key: string]: T; + }; + declare type DeepPartial = { + [P in keyof T]?: DeepPartial; + }; + declare type TimeoutHandle = ReturnType; + declare type IntervalHandle = ReturnType; + + declare interface ChangeEvent extends Event { + target: HTMLInputElement; + } + + declare interface WheelEvent { + path?: EventTarget[]; + } + interface ImportMetaEnv extends ViteEnv { + readonly BASE_URL: string; + readonly MODE: string; + readonly DEV: boolean; + readonly PROD: boolean; + readonly SSR: boolean; + __: unknown; + } + + declare interface ViteEnv { + VITE_PORT: number; + VITE_USE_MOCK: boolean; + VITE_PUBLIC_PATH: string; + VITE_PROXY: [string, string][]; + VITE_GLOB_APP_TITLE: string; + VITE_GLOB_APP_SHORT_NAME: string; + VITE_USE_CDN: boolean; + VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'none'; + VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE: boolean; + // 【JEECG作为乾坤子应用】乾坤子应用名,主应用注册时AppName需保持一致 + VITE_GLOB_QIANKUN_MICRO_APP_NAME?: string; + // 【JEECG作为乾坤子应用】非必填,需与qiankun主应用注册子应用时填写的 entry 保持一致 + VITE_GLOB_QIANKUN_MICRO_APP_ENTRY?: string; + // 当前运行在什么平台 + VITE_GLOB_RUN_PLATFORM?: 'web' | 'electron'; + // iconify图标使用在线还是本地。可选属性:online, local + VITE_GLOB_ICONIFY_USE_TYPE?: 'online' | 'local'; + } + + declare function parseInt(s: string | number, radix?: number): number; + + declare function parseFloat(string: string | number): number; + + namespace JSX { + // tslint:disable no-empty-interface + type Element = VNode; + // tslint:disable no-empty-interface + type ElementClass = ComponentRenderProxy; + interface ElementAttributesProperty { + $props: any; + } + interface IntrinsicElements { + [elem: string]: any; + } + interface IntrinsicAttributes { + [elem: string]: any; + } + } +} + +declare module 'vue' { + export type JSXComponent = { new (): ComponentPublicInstance } | FunctionalComponent; +} diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 0000000..b279c0a --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,27 @@ +declare interface Fn { + (...arg: T[]): R; +} + +declare interface PromiseFn { + (...arg: T[]): Promise; +} + +declare type RefType = T | null; + +declare type LabelValueOptions = { + label: string; + value: any; + [key: string]: string | number | boolean; +}[]; + +declare type EmitType = (event: string, ...args: any[]) => void; + +declare type TargetContext = '_self' | '_blank'; + +declare interface ComponentElRef { + $el: T; +} + +declare type ComponentRef = ComponentElRef | null; + +declare type ElRef = Nullable; diff --git a/types/main.d.ts b/types/main.d.ts new file mode 100644 index 0000000..fb0f998 --- /dev/null +++ b/types/main.d.ts @@ -0,0 +1,10 @@ +// 应用参数 +export type MainAppProps = { + container?: HTMLElement; + // 隐藏侧边栏(菜单) + hideSider?: boolean; + // 隐藏顶部 + hideHeader?: boolean; + // 隐藏 多Tab 切换 + hideMultiTabs?: boolean; +} diff --git a/types/module.d.ts b/types/module.d.ts new file mode 100644 index 0000000..eda5c85 --- /dev/null +++ b/types/module.d.ts @@ -0,0 +1,35 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue'; + const Component: DefineComponent<{}, {}, any>; + export default Component; +} + +declare module 'ant-design-vue/es/locale/*' { + import { Locale } from 'ant-design-vue/types/locale-provider'; + const locale: Locale & ReadonlyRecordable; + export default locale as Locale & ReadonlyRecordable; +} + +declare module 'virtual:*' { + const result: any; + export default result; +} + +declare module 'virtual:pwa-register/vue' { + import type { Ref } from 'vue'; + + export interface RegisterSWOptions { + immediate?: boolean; + onNeedRefresh?: () => void; + onOfflineReady?: () => void; + onRegistered?: (registration: ServiceWorkerRegistration | undefined) => void; + onRegisterError?: (error: any) => void; + } + + export function useRegisterSW(options?: RegisterSWOptions): { + needRefresh: Ref; + offlineReady: Ref; + updateServiceWorker: (reloadPage?: boolean) => Promise; + }; +} + diff --git a/types/store.d.ts b/types/store.d.ts new file mode 100644 index 0000000..7b9349a --- /dev/null +++ b/types/store.d.ts @@ -0,0 +1,59 @@ +import { ErrorTypeEnum } from '/@/enums/exceptionEnum'; +import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; +import { RoleInfo } from '/@/api/sys/model/userModel'; + +// Lock screen information +export interface LockInfo { + // Password required + pwd?: string | undefined; + // Is it locked? + isLock?: boolean; +} + +// Error-log information +export interface ErrorLogInfo { + // Type of error + type: ErrorTypeEnum; + // Error file + file: string; + // Error name + name?: string; + // Error message + message: string; + // Error stack + stack?: string; + // Error detail + detail: string; + // Error url + url: string; + // Error time + time?: string; +} + +export interface UserInfo { + id: string | number; + userId: string | number; + username: string; + realname: string; + avatar: string; + desc?: string; + homePath?: string; + tenantid?: string | number; + roles: RoleInfo[]; + orgCode?: string; +} + +export interface LoginInfo { + multi_depart?: string | number; + userInfo?: object; + departs?: []; + tenantList?: []; + isLogin?: boolean; +} + +export interface BeforeMiniState { + menuCollapsed?: boolean; + menuSplit?: boolean; + menuMode?: MenuModeEnum; + menuType?: MenuTypeEnum; +} diff --git a/types/unplugin-icons.d.ts b/types/unplugin-icons.d.ts new file mode 100644 index 0000000..878a992 --- /dev/null +++ b/types/unplugin-icons.d.ts @@ -0,0 +1,10 @@ +/** + * unplugin-icons 类型声明 + * 为 ~icons 路径提供类型支持 + */ + +declare module '~icons/*' { + import { FunctionalComponent, SVGAttributes } from 'vue'; + const component: FunctionalComponent; + export default component; +} diff --git a/types/utils.d.ts b/types/utils.d.ts new file mode 100644 index 0000000..6500d44 --- /dev/null +++ b/types/utils.d.ts @@ -0,0 +1,5 @@ +import type { ComputedRef, Ref } from 'vue'; + +export type DynamicProps = { + [P in keyof T]: Ref | T[P] | ComputedRef; +}; diff --git a/types/vue-router.d.ts b/types/vue-router.d.ts new file mode 100644 index 0000000..93fd788 --- /dev/null +++ b/types/vue-router.d.ts @@ -0,0 +1,45 @@ +export {}; + +declare module 'vue-router' { + interface RouteMeta extends Record { + orderNo?: number; + // title + title: string; + // dynamic router level. + dynamicLevel?: number; + // dynamic router real route path (For performance). + realPath?: string; + // Whether to ignore permissions + ignoreAuth?: boolean; + // role info + roles?: RoleEnum[]; + // Whether not to cache + ignoreKeepAlive?: boolean; + // Is it fixed on tab + affix?: boolean; + // icon on tab + icon?: string; + frameSrc?: string; + // current page transition + transitionName?: string; + // Whether the route has been dynamically added + hideBreadcrumb?: boolean; + // Hide submenu + hideChildrenInMenu?: boolean; + // Carrying parameters + carryParam?: boolean; + // Used internally to mark single-level menus + single?: boolean; + // Currently active menu + currentActiveMenu?: string; + // Never show in tab + hideTab?: boolean; + // Never show in menu + hideMenu?: boolean; + isLink?: boolean; + // only build for Menu + ignoreRoute?: boolean; + // Hide path for children + hidePathForChildren?: boolean; + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..ff5a849 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,199 @@ +import type { UserConfig, ConfigEnv } from 'vite'; +import pkg from './package.json'; +import dayjs from 'dayjs'; +import { loadEnv } from 'vite'; +import { resolve } from 'path'; +import { generateModifyVars } from './build/generate/generateModifyVars'; +import { createProxy } from './build/vite/proxy'; +import { wrapperEnv } from './build/utils'; +import { createVitePlugins } from './build/vite/plugin'; +import { OUTPUT_DIR } from './build/constant'; + +function pathResolve(dir: string) { + return resolve(process.cwd(), '.', dir); +} + +const { dependencies, devDependencies, name, version } = pkg; +const __APP_INFO__ = { + pkg: { dependencies, devDependencies, name, version }, + lastBuildTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), +}; + +export default async ({ command, mode }: ConfigEnv): Promise => { + const root = process.cwd(); + + const env = loadEnv(mode, root); + + // The boolean type read by loadEnv is a string. This function can be converted to boolean type + const viteEnv = wrapperEnv(env); + + const { VITE_PORT, VITE_PUBLIC_PATH, VITE_PROXY } = viteEnv; + + const isBuild = command === 'build'; + + const serverOptions: Recordable = {} + + // ----- [begin] 【JEECG作为乾坤子应用】 ----- + const {VITE_GLOB_QIANKUN_MICRO_APP_NAME, VITE_GLOB_QIANKUN_MICRO_APP_ENTRY} = viteEnv; + const isQiankunMicro = VITE_GLOB_QIANKUN_MICRO_APP_NAME != null && VITE_GLOB_QIANKUN_MICRO_APP_NAME !== ''; + if (isQiankunMicro && !isBuild) { + serverOptions.cors = true; + serverOptions.origin = VITE_GLOB_QIANKUN_MICRO_APP_ENTRY!.split('/').slice(0, 3).join('/'); + } + // ----- [end] 【JEECG作为乾坤子应用】 ----- + + console.log('[init] Start Port: ', VITE_PORT); + console.debug('[init] Vite Proxy Config: ', VITE_PROXY); + + + return { + base: isQiankunMicro ? VITE_GLOB_QIANKUN_MICRO_APP_ENTRY : VITE_PUBLIC_PATH, + root, + resolve: { + alias: [ + // @logicflow/vue-node-registry 1.1.13 的 npm 包只发布了 src/,但 package.json + // main/module 指向 lib/、es/(不存在)。vite 6 esbuild 宽松能找到 src,rolldown 严格直接报错。 + // 暂时直接把 import 重定向到 src/index.ts。 + { + find: /^@logicflow\/vue-node-registry$/, + replacement: pathResolve('node_modules/@logicflow/vue-node-registry/src/index.ts'), + }, + // 把 @rys-fe/vite-plugin-theme 的客户端运行时重定向到项目内置版本(vite 8 适配) + // 用 RegExp 精确匹配,避免被父级别名误吞;不写后缀让 vite 自动用 resolve.extensions 补全 + { + find: /^@rys-fe\/vite-plugin-theme\/es\/client$/, + replacement: pathResolve('build/vite/plugin/theme-plugin/client/client'), + }, + { + find: /^@rys-fe\/vite-plugin-theme\/es\/colorUtils$/, + replacement: pathResolve('build/vite/plugin/theme-plugin/client/colorUtils'), + }, + { + find: /^@rys-fe\/vite-plugin-theme$/, + replacement: pathResolve('build/vite/plugin/theme-plugin/index'), + }, + { + find: 'vue-i18n', + replacement: 'vue-i18n/dist/vue-i18n.cjs.js', + }, + // /@/xxxx => src/xxxx + { + find: /\/@\//, + replacement: pathResolve('src') + '/', + }, + // /#/xxxx => types/xxxx + { + find: /\/#\//, + replacement: pathResolve('types') + '/', + }, + { + find: /@\//, + replacement: pathResolve('src') + '/', + }, + // /#/xxxx => types/xxxx + { + find: /#\//, + replacement: pathResolve('types') + '/', + }, + ], + }, + server: { + // Listening on all local IPs + host: true, + // @ts-ignore + https: false, + port: VITE_PORT, + // Load proxy configuration from .env + proxy: createProxy(VITE_PROXY), + // 合并 server 配置 + ...serverOptions, + // update-begin--author:liaozhiyang---date:20260306---for:【QQYUN-14801】vite启动的时候,预构建一些入口页面,访问时快一些 + // 启动时预构建 + warmup: { + clientFiles: [ + './src/main.ts', + './src/App.vue', + './src/views/system/loginmini/MiniLogin.vue', + 'src/layouts/default/index.vue' + ], + }, + // update-end--author:liaozhiyang---date:20260306---for:【QQYUN-14801】vite启动的时候,预构建一些入口页面,访问时快一些 + }, + build: { + minify: 'esbuild', + target: 'es2015', + cssTarget: 'chrome80', + outDir: OUTPUT_DIR, + rollupOptions: { + // 关闭除屑优化,防止删除重要代码,导致打包后功能出现异常 + // treeshake: false, + output: { + chunkFileNames: 'js/[name]-[hash].js', // 引入文件名的名称 + entryFileNames: 'js/[name]-[hash].js', // 包的入口文件名称 + // manualChunks配置 (依赖包从大到小排列) + manualChunks: { + // vue vue-router合并打包 + 'vue-vendor': ['vue', 'vue-router'], + 'emoji-mart-vue-fast': ['emoji-mart-vue-fast'], + }, + }, + }, + // 关闭brotliSize显示可以稍微减少打包时间 + reportCompressedSize: false, + // 提高超大静态资源警告大小 + chunkSizeWarningLimit: 2000, + }, + esbuild: { + //清除全局的console.log和debug + drop: isBuild ? ['console', 'debugger'] : [], + }, + define: { + // setting vue-i18-next + // Suppress warning + __INTLIFY_PROD_DEVTOOLS__: false, + __APP_INFO__: JSON.stringify(__APP_INFO__), + }, + css: { + preprocessorOptions: { + less: { + modifyVars: generateModifyVars(), + javascriptEnabled: true, + }, + }, + }, + + // The vite plugin used by the project. The quantity is large, so it is separately extracted and managed + plugins: await createVitePlugins(viteEnv, isBuild, isQiankunMicro), + + optimizeDeps: { + esbuildOptions: { + target: 'es2020', + }, + // @iconify/iconify: The dependency is dynamically and virtually loaded by @purge-icons/generated, so it needs to be specified explicitly + include: [ + // 强制预构建clipboard,解决Vite6对CommonJS模块的严格检查 + 'clipboard', + '@vue/shared', + '@iconify/iconify', + 'ant-design-vue/es/locale/zh_CN', + 'ant-design-vue/es/locale/en_US', + // update-begin--author:scott---date:20260427---for: 集成 @jeecg/aiflow(预编译 lib 在 node_modules)时, + // Vite 默认不扫描 node_modules 里已打包的 mjs,导致 ant-design-vue/es/vc-picker/generate/dayjs.js + // 引入的 dayjs 插件子路径(UMD/CJS)未被预打包,运行时报 "does not provide an export named 'default'"。 + // 显式列出 vc-picker 用到的全部 dayjs 插件,强制 esbuild 预打包成 ESM。 + 'dayjs/plugin/advancedFormat', + 'dayjs/plugin/customParseFormat', + 'dayjs/plugin/weekday', + 'dayjs/plugin/localeData', + 'dayjs/plugin/weekOfYear', + 'dayjs/plugin/weekYear', + 'dayjs/plugin/quarterOfYear', + // update-end--author:scott---date:20260427---for: 集成 @jeecg/aiflow 时 dayjs 插件 default 导出报错 + ], + exclude: [ + //升级vite4后,需要排除online和aiflow依赖 + '@jeecg/aiflow', + ], + }, + }; +}; diff --git a/开发规范-人工版.md b/开发规范-人工版.md new file mode 100644 index 0000000..1d7c24b --- /dev/null +++ b/开发规范-人工版.md @@ -0,0 +1,154 @@ +# ghb-base 开发规范(人工版) + +> 给不用 AI 编码的开发者阅读。用 AI 写代码的看 `.claude/` 目录。 + +## 一、返回格式(不变式) + +**所有接口必须用 `Result` 包装:** + +```java +// ✅ 正确 +return Result.OK(data); +return Result.OK("操作成功", data); +return Result.error("参数错误"); + +// ❌ 禁止 +return data; +return "success"; +return Map.of(...); +``` + +**分页格式:** `{ code, message, data: { records, total, size, current } }` + +**日期格式:** 统一 `yyyy-MM-dd HH:mm:ss` + +**状态值:** 枚举中文值必须从 `contract/common/status-map.md` 取,不许硬编码前端文案同义替换("已拒绝"="拒绝"之类)。 + +## 二、分层规则 + +``` +controller/ — 参数校验 + 调用 service + 返回 Result,不许写业务逻辑 +service/ — 业务逻辑,事务管理 +mapper/ — SQL(MyBatis XML 或 @Select),不许写业务逻辑 +entity/ — 表映射,纯数据对象 +``` + +**禁止:Controller 超过 20 行、Controller 里有 if/else 嵌套、Service 里直接操作 HttpServletRequest** + +## 三、命名规则 + +| 元素 | 规则 | 示例 | +| ------ | ------------------ | ------------------------------------- | +| 类名 | 大驼峰,名词 | `TaskController`,❌ `taskController` | +| 方法名 | 小驼峰,动词开头 | `getById()`,❌ `getbyid()` | +| 变量 | 小驼峰,有意义 | `taskList`,❌ `list` `data` | +| 包名 | 全小写 | `com.ghb.base.modules.business` | +| 表名 | 小写,下划线,单数 | `task_apply`,❌ `TaskApply` | +| 字段名 | 小写,下划线 | `create_time`,❌ `createdAt` | +| 主键 | 统一 `id` | ❌ `task_id` | +| 外键 | `关联表_id` | `user_id`,❌ `uid` | +| 布尔 | `is_` 前缀 | `is_deleted`,❌ `deleted` | + +**长度控制:** 类名/表名超过 3 个单词或 30 字符必须缩写。缩写必须一眼能认出含义: + +| 完整 | 缩写 | +| ---------- | ---- | +| department | dept | +| permission | perm | +| message | msg | +| enterprise | ent | + +## 四、字段类型 + +| 场景 | 类型 | +| ---- | ------------------------------------ | +| 金额 | `DECIMAL(15,2)`,❌ `FLOAT` `DOUBLE` | +| 时间 | `DATETIME`,❌ `VARCHAR` `TIMESTAMP` | +| 状态 | `VARCHAR(20)`,存英文枚举值 | +| 文本 | `VARCHAR(N)`(长度写死,不用 TEXT) | + +**必有字段:** 每张表必须包含 `id` `create_by` `create_time` `update_by` `update_time`。每个字段必须有 `COMMENT`。 + +## 五、索引规则 + +| 场景 | 索引 | +| -------------- | --------------- | +| 外键 | 必须建索引 | +| WHERE 高频字段 | 必须建索引 | +| 唯一约束 | `uk_表名_字段` | +| 普通索引 | `idx_表名_字段` | + +## 六、Git 规则 + +**分支:** `dev`(开发)/ `master`(生产)。新功能从 `dev` 拉,合回 `dev`。 + +**Commit 格式:** + +``` +: <简短描述> + +[AI: 模型名] +``` + +**Type:** feat / fix / refactor / docs / style / test / chore + +**禁止:** `git push --force`、commit 只写 "update"、提交含硬编码密钥 + +## 七、安全规则 + +- 所有接口(除登录)必须验证 JWT Token +- 涉及租户数据的 SQL 必须带 `tenant_id` 条件 +- 密码/密钥/Token 禁止硬编码、禁止 log 输出、禁止提交到 Git +- 文件上传限制大小(≤10MB)和类型(白名单) +- 用户输入必须做 XSS 过滤 +- 日志不输出敏感信息(手机号脱敏 176\*\*\*\*2303) + +## 八、编码规则 + +- 所有源文件:UTF-8 无 BOM +- 禁止 GBK/GB2312 +- `ghb-backend/.editorconfig` 和 `ghb-frontend/.editorconfig` 各一个,不要覆盖 +- **Windows 终端重定向(`>` `>>`)会按 GBK 写入,不要用** + +## 九、测试规则 + +**必须写测试:** + +- 新增 Service 方法 → 单元测试 +- 新增 Controller 接口 → 集成测试 +- 涉及金额计算 → 单元测试(多组边界值) +- 涉及状态流转 → 单元测试(覆盖所有路径) + +**错误码:** 200 成功 / 400 参数错误 / 401 未登录 / 403 无权限 / 500 服务器异常 + +## 十、前端规则 + +**样式铁律:** + +- 颜色禁硬编码(`color: #333` ❌),走主题变量 +- 字号/间距禁硬编码,走全局类或 Less 变量 +- 所有文案必须走 i18n,不能写死中文 + +**命名规则:** + +| 元素 | 规则 | 示例 | +| --------- | ---------- | ---------------------------------- | +| 组件文件 | kebab-case | `task-card.vue`,❌ `TaskCard.vue` | +| 组件 name | PascalCase | `TaskCard` | +| CSS 类名 | kebab-case | `.task-card`,❌ `.taskCard` | +| JS 变量 | 小驼峰 | `taskList`,❌ `task_list` | + +**代码修改痕迹:** 所有新增或修改的代码块必须用 `// update-begin` / `// update-end` 包裹,注明 author/date/原因。 + +## 十一、提交前检查表 + +``` +□ 接口变更 → contract/ 同步更新 +□ 返回格式 → 全部用 Result 包装 +□ 手写 SQL → 有 tenant_id 条件 +□ 新增表 → create_time / update_time / COMMENT 完整 +□ 金额字段 → DECIMAL 类型 +□ 无硬编码魔法数字/裸色值 +□ Commit 格式正确 +□ 测试通过 +```