@steedos-labs/plugin-workflow 3.0.14 → 3.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README_TEMPLATE.md +222 -0
- package/convert-templates.js +51 -0
- package/main/default/objects/flows/flows.object.yml +16 -0
- package/main/default/objects/instance_tasks/buttons/instance_new.button.yml +9 -9
- package/main/default/objects/instances/buttons/instance_delete_many.button.yml +1 -1
- package/main/default/pages/flow_selector.page.amis.json +1 -1
- package/main/default/pages/page_instance_print.page.amis.json +33 -0
- package/main/default/services/flows.service.js +3 -2
- package/package.json +4 -2
- package/public/workflow/index.css +107 -2
- package/run.js +388 -0
- package/src/rests/index.js +1 -0
- package/src/rests/migrateTemplates.js +154 -0
- package/src/util/templateConverter.js +335 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Workflow Template Export/Import
|
|
2
|
+
|
|
3
|
+
## 概述 Overview
|
|
4
|
+
|
|
5
|
+
这是一个独立的脚本工具,用于导出和导入工作流模板(表单模板和打印模板),支持从 Blaze 格式转换为 Liquid 格式。
|
|
6
|
+
|
|
7
|
+
This is a standalone script tool for exporting and importing workflow templates (form templates and print templates), supporting conversion from Blaze to Liquid format.
|
|
8
|
+
|
|
9
|
+
## 安装 Installation
|
|
10
|
+
|
|
11
|
+
在运行脚本前,需要先安装依赖:
|
|
12
|
+
Before running the script, install dependencies:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cd steedos-packages/plugin-workflow
|
|
16
|
+
npm install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
脚本使用原生 MongoDB 驱动连接数据库,连接信息从环境变量 `MONGO_URL` 读取。
|
|
20
|
+
The script uses native MongoDB driver to connect to the database. Connection info is read from `MONGO_URL` environment variable.
|
|
21
|
+
|
|
22
|
+
## 快速开始 Quick Start
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd steedos-packages/plugin-workflow
|
|
26
|
+
|
|
27
|
+
# 导出所有模板 Export all templates
|
|
28
|
+
node run.js export
|
|
29
|
+
|
|
30
|
+
# 导出特定流程 Export specific flow
|
|
31
|
+
node run.js export <flowId>
|
|
32
|
+
|
|
33
|
+
# 转换 .blaze 文件为 .liquid (手动或使用 Copilot)
|
|
34
|
+
# Convert .blaze files to .liquid (manually or with Copilot)
|
|
35
|
+
|
|
36
|
+
# 导入转换后的模板 Import converted templates
|
|
37
|
+
node run.js import <flowId>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 目录结构 Directory Structure
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
flows-template/
|
|
44
|
+
├── {flowId}.instance_template.blaze # 导出的表单模板 (Blaze 格式)
|
|
45
|
+
├── {flowId}.print_template.blaze # 导出的打印模板 (Blaze 格式)
|
|
46
|
+
├── {flowId}.fields.json # 字段定义
|
|
47
|
+
├── {flowId}.instance_template.liquid # 转换后的表单模板 (Liquid 格式,用于导入)
|
|
48
|
+
└── {flowId}.print_template.liquid # 转换后的打印模板 (Liquid 格式,用于导入)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 命令使用 Commands
|
|
52
|
+
|
|
53
|
+
### 导出模板 Export Templates
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# 导出所有包含模板的流程
|
|
57
|
+
# Export all flows with templates
|
|
58
|
+
node run.js export
|
|
59
|
+
|
|
60
|
+
# 导出特定流程
|
|
61
|
+
# Export specific flow
|
|
62
|
+
node run.js export <flowId>
|
|
63
|
+
|
|
64
|
+
# 导出到自定义目录
|
|
65
|
+
# Export to custom directory
|
|
66
|
+
node run.js export <flowId> /path/to/output
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**示例 Example:**
|
|
70
|
+
```bash
|
|
71
|
+
node run.js export abc123
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**输出 Output:**
|
|
75
|
+
```
|
|
76
|
+
Found 1 flow(s) to export.
|
|
77
|
+
|
|
78
|
+
Exporting flow: Leave Request (abc123)
|
|
79
|
+
✓ abc123.instance_template.blaze
|
|
80
|
+
✓ abc123.print_template.blaze
|
|
81
|
+
✓ abc123.fields.json
|
|
82
|
+
Exported 3 file(s)
|
|
83
|
+
|
|
84
|
+
✅ Export completed. Files saved to: /path/to/flows-template
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 导入模板 Import Templates
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# 导入流程的所有模板
|
|
91
|
+
# Import both templates for a flow
|
|
92
|
+
node run.js import <flowId>
|
|
93
|
+
|
|
94
|
+
# 仅导入表单模板
|
|
95
|
+
# Import only instance_template
|
|
96
|
+
node run.js import <flowId> instance_template
|
|
97
|
+
|
|
98
|
+
# 仅导入打印模板
|
|
99
|
+
# Import only print_template
|
|
100
|
+
node run.js import <flowId> print_template
|
|
101
|
+
|
|
102
|
+
# 从自定义目录导入
|
|
103
|
+
# Import from custom directory
|
|
104
|
+
node run.js import <flowId> instance_template /path/to/input
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**示例 Example:**
|
|
108
|
+
```bash
|
|
109
|
+
node run.js import abc123
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**输出 Output:**
|
|
113
|
+
```
|
|
114
|
+
Importing templates for flow: Leave Request (abc123)
|
|
115
|
+
ℹ Backing up old instance_template to instance_template_backup
|
|
116
|
+
✓ Imported instance_template from abc123.instance_template.liquid
|
|
117
|
+
ℹ Backing up old print_template to print_template_backup
|
|
118
|
+
✓ Imported print_template from abc123.print_template.liquid
|
|
119
|
+
|
|
120
|
+
✅ Import completed. Imported 2 template(s).
|
|
121
|
+
- instance_template: updated (old value backed up)
|
|
122
|
+
- print_template: updated (old value backed up)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## 工作流程 Workflow
|
|
126
|
+
|
|
127
|
+
1. **导出 Export**: 运行导出命令,将模板保存为 `.blaze` 文件
|
|
128
|
+
2. **转换 Convert**: 使用 Copilot 或其他工具将 `.blaze` 文件转换为 `.liquid` 格式
|
|
129
|
+
3. **导入 Import**: 运行导入命令,将转换后的 `.liquid` 模板加载回数据库
|
|
130
|
+
4. **备份 Backup**: 导入时自动备份旧模板到 `{模板名}_backup` 字段
|
|
131
|
+
|
|
132
|
+
## 备份字段 Backup Fields
|
|
133
|
+
|
|
134
|
+
导入模板时,旧值会自动备份到:
|
|
135
|
+
When importing templates, old values are backed up to:
|
|
136
|
+
|
|
137
|
+
- `instance_template_backup`: 表单模板备份 (Form template backup)
|
|
138
|
+
- `print_template_backup`: 打印模板备份 (Print template backup)
|
|
139
|
+
|
|
140
|
+
这些备份字段在界面中隐藏,但可以通过程序访问。
|
|
141
|
+
These backup fields are hidden in the UI but can be accessed programmatically.
|
|
142
|
+
|
|
143
|
+
## 字段定义 Field Definitions
|
|
144
|
+
|
|
145
|
+
`.fields.json` 文件包含表单的字段定义(来自 `current.fields`),为模板转换工具(如 Copilot)提供字段结构和类型的上下文信息。
|
|
146
|
+
|
|
147
|
+
The `.fields.json` file contains field definitions from the form's `current.fields`, providing context about field structure and types for conversion tools like Copilot.
|
|
148
|
+
|
|
149
|
+
**示例 Example:**
|
|
150
|
+
```json
|
|
151
|
+
[
|
|
152
|
+
{
|
|
153
|
+
"_id": "field1",
|
|
154
|
+
"code": "userName",
|
|
155
|
+
"name": "User Name",
|
|
156
|
+
"type": "text",
|
|
157
|
+
"is_required": true
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"_id": "field2",
|
|
161
|
+
"code": "requestDate",
|
|
162
|
+
"name": "Request Date",
|
|
163
|
+
"type": "date",
|
|
164
|
+
"is_required": true
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 数据库配置 Database Configuration
|
|
170
|
+
|
|
171
|
+
脚本使用环境变量配置的 MongoDB 连接(通过 `.env` 文件中的 `MONGO_URL`)。
|
|
172
|
+
|
|
173
|
+
The script uses the MongoDB connection configured via environment variables (through `MONGO_URL` in `.env` file).
|
|
174
|
+
|
|
175
|
+
## 错误处理 Error Handling
|
|
176
|
+
|
|
177
|
+
脚本提供清晰的错误信息:
|
|
178
|
+
The script provides clear error messages:
|
|
179
|
+
|
|
180
|
+
- `Flow not found: abc123` - 验证流程 ID 是否存在 (Verify the flow ID exists)
|
|
181
|
+
- `No template files found for flow abc123` - 确保 `.liquid` 文件存在 (Ensure `.liquid` files exist)
|
|
182
|
+
- `Permission denied reading file` - 检查文件权限 (Check file permissions)
|
|
183
|
+
- `No flows found with templates` - 没有包含模板的流程 (No flows have templates)
|
|
184
|
+
|
|
185
|
+
## 使用提示 Tips
|
|
186
|
+
|
|
187
|
+
1. 导入模板前务必备份数据库 (Always backup your database before importing)
|
|
188
|
+
2. 旧模板值会自动备份到 `{模板名}_backup` 字段 (Old values are automatically backed up)
|
|
189
|
+
3. 先在开发环境测试导入 (Test imports in development environment first)
|
|
190
|
+
4. 使用字段定义了解模板结构 (Use field definitions to understand template structure)
|
|
191
|
+
5. 导入前仔细检查转换后的模板 (Review converted templates carefully before importing)
|
|
192
|
+
6. `flows-template` 目录自动被 git 忽略 (The directory is automatically ignored by git)
|
|
193
|
+
|
|
194
|
+
## 完整示例 Complete Example
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# 1. 导出模板 Export templates
|
|
198
|
+
cd steedos-packages/plugin-workflow
|
|
199
|
+
node run.js export abc123
|
|
200
|
+
|
|
201
|
+
# 2. 查看导出的文件 View exported files
|
|
202
|
+
ls -la flows-template/
|
|
203
|
+
# abc123.instance_template.blaze
|
|
204
|
+
# abc123.print_template.blaze
|
|
205
|
+
# abc123.fields.json
|
|
206
|
+
|
|
207
|
+
# 3. 转换模板 Convert templates
|
|
208
|
+
# 使用 Copilot 或手动将 .blaze 文件转换为 .liquid
|
|
209
|
+
# Use Copilot or manually convert .blaze files to .liquid
|
|
210
|
+
#
|
|
211
|
+
# 示例转换 Example conversion:
|
|
212
|
+
# {{#if userName}} → {% if userName %}
|
|
213
|
+
# {{userName}} → {{ userName }}
|
|
214
|
+
# {{/if}} → {% endif %}
|
|
215
|
+
|
|
216
|
+
# 4. 导入转换后的模板 Import converted templates
|
|
217
|
+
node run.js import abc123
|
|
218
|
+
|
|
219
|
+
# 5. 验证 Verify
|
|
220
|
+
# 检查数据库中的 flows 记录,确认模板已更新且备份已创建
|
|
221
|
+
# Check flows record in database to confirm templates are updated and backups are created
|
|
222
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const converter = require('./src/util/templateConverter');
|
|
4
|
+
|
|
5
|
+
const convertDir = path.join(__dirname, 'flows-template/export');
|
|
6
|
+
|
|
7
|
+
if (!fs.existsSync(convertDir)) {
|
|
8
|
+
console.error(`Directory not found: ${convertDir}`);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
console.log('==========================================');
|
|
13
|
+
console.log('开始转换模板 (Blaze -> Liquid + AMIS)');
|
|
14
|
+
console.log('==========================================');
|
|
15
|
+
|
|
16
|
+
const blazeFiles = fs.readdirSync(convertDir).filter(f => f.endsWith('.blaze'));
|
|
17
|
+
const fieldsCache = {}; // Cache fields.json content
|
|
18
|
+
|
|
19
|
+
blazeFiles.forEach(file => {
|
|
20
|
+
const content = fs.readFileSync(path.join(convertDir, file), 'utf8');
|
|
21
|
+
const flowId = file.split('.')[0];
|
|
22
|
+
const isPrint = file.includes('print_template');
|
|
23
|
+
|
|
24
|
+
// Load fields def
|
|
25
|
+
let fields = fieldsCache[flowId];
|
|
26
|
+
if (!fields) {
|
|
27
|
+
const fieldsPath = path.join(convertDir, `${flowId}.fields.json`);
|
|
28
|
+
if (fs.existsSync(fieldsPath)) {
|
|
29
|
+
try {
|
|
30
|
+
// Read fields as array
|
|
31
|
+
fields = JSON.parse(fs.readFileSync(fieldsPath, 'utf8'));
|
|
32
|
+
fieldsCache[flowId] = fields;
|
|
33
|
+
} catch (e) {
|
|
34
|
+
console.error(`Error parsing fields for ${flowId}:`, e.message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const newContent = converter.convertTemplate(content, fields, isPrint);
|
|
41
|
+
const newFile = file.replace('.blaze', '.liquid');
|
|
42
|
+
fs.writeFileSync(path.join(convertDir, newFile), newContent);
|
|
43
|
+
console.log(`✓ ${file} -> ${newFile}`);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error(`✗ Failed to convert ${file}:`, error.message);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
console.log('==========================================');
|
|
50
|
+
console.log('转换完成');
|
|
51
|
+
console.log('==========================================');
|
|
@@ -456,12 +456,28 @@ fields:
|
|
|
456
456
|
language: html
|
|
457
457
|
is_wide: true
|
|
458
458
|
group: template
|
|
459
|
+
instance_template_backup:
|
|
460
|
+
label: Form Template Backup
|
|
461
|
+
type: code
|
|
462
|
+
language: html
|
|
463
|
+
is_wide: true
|
|
464
|
+
group: template
|
|
465
|
+
hidden: true
|
|
466
|
+
omit: true
|
|
459
467
|
print_template:
|
|
460
468
|
label: Print Template
|
|
461
469
|
type: code
|
|
462
470
|
language: html
|
|
463
471
|
is_wide: true
|
|
464
472
|
group: template
|
|
473
|
+
print_template_backup:
|
|
474
|
+
label: Print Template Backup
|
|
475
|
+
type: code
|
|
476
|
+
language: html
|
|
477
|
+
is_wide: true
|
|
478
|
+
group: template
|
|
479
|
+
hidden: true
|
|
480
|
+
omit: true
|
|
465
481
|
name_formula:
|
|
466
482
|
label: Formula of Title
|
|
467
483
|
type: text
|
|
@@ -19,15 +19,15 @@ amis_schema: |-
|
|
|
19
19
|
"title": "${'CustomLabels.instance_action_new_dialog_title' | t}",
|
|
20
20
|
"body": [
|
|
21
21
|
{
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
"type": "service",
|
|
23
|
+
"dsType": "api",
|
|
24
|
+
"schemaApi": {
|
|
25
|
+
"url": "/api/v6/functions/pages/schema?pageId=flow_selector",
|
|
26
|
+
"method": "get"
|
|
27
|
+
},
|
|
28
|
+
"initFetchSchema": true,
|
|
29
29
|
"onEvent": {
|
|
30
|
-
"
|
|
30
|
+
"flows.selected": {
|
|
31
31
|
"weight": 0,
|
|
32
32
|
"actions": [
|
|
33
33
|
{
|
|
@@ -71,7 +71,7 @@ amis_schema: |-
|
|
|
71
71
|
"closeOnEsc": true,
|
|
72
72
|
"closeOnOutside": false,
|
|
73
73
|
"showCloseButton": true,
|
|
74
|
-
"size": "
|
|
74
|
+
"size": "xl",
|
|
75
75
|
"actions": false
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "liquid",
|
|
3
|
-
"template": "<style>\n /* 动效 */\n @keyframes fadeUpSpring {\n 0% {\n opacity: 0;\n transform: translateY(10px);\n }\n\n 100% {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n @keyframes starPop {\n 0% {\n transform: scale(1);\n }\n\n 40% {\n transform: scale(1.35) rotate(15deg);\n }\n\n 100% {\n transform: scale(1) rotate(0);\n }\n }\n\n .no-scrollbar::-webkit-scrollbar {\n display: none;\n }\n\n .no-scrollbar {\n -ms-overflow-style: none;\n scrollbar-width: none;\n }\n\n .line-clamp-2 {\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n }\n</style>\n\n<div class=\"flex h-full w-full flex-row items-stretch overflow-hidden bg-white font-sans text-gray-900 antialiased\">\n\n <div class=\"flex h-full w-[260px] shrink-0 flex-col border-r border-gray-200/80 bg-[#F2F2F7] z-20\">\n\n <div class=\"shrink-0 px-3 pt-4 pb-2\">\n <div class=\"px-2 mb-3 text-2xl font-bold tracking-tight text-black\">流程</div>\n <div class=\"relative group\">\n <div class=\"pointer-events-none absolute inset-y-0 left-0 flex items-center pl-2.5 text-gray-500\">\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n </div>\n <input type=\"text\" id=\"searchInput\" placeholder=\"搜索\"\n class=\"w-full rounded-[10px] border-none bg-[#767680]/10 py-1.5 pl-9 pr-3 text-[14px] text-gray-900 placeholder:text-gray-500 outline-none transition-all duration-200 focus:bg-white focus:shadow-sm focus:ring-2 focus:ring-blue-500/20\">\n </div>\n </div>\n\n <div class=\"flex-1 overflow-y-auto px-2 pb-4 no-scrollbar space-y-0.5\" id=\"sidebarList\">\n </div>\n </div>\n\n <div class=\"relative flex-1 h-full overflow-y-auto scroll-smooth bg-white z-10\" id=\"mainContent\">\n <div id=\"contentContainer\" class=\"flex h-full w-full flex-col items-center justify-center\">\n <div class=\"inline-flex items-center gap-2 text-gray-400 text-sm animate-pulse\">\n <svg class=\"animate-spin h-4 w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\"></path>\n </svg>\n <span>正在加载资源...</span>\n </div>\n </div>\n </div>\n</div>\n\n<script>\n // --- Service Layer ---\n const WorkflowService = {\n apiBase: \"\", \n \n getHeaders: function() { return { 'Content-Type': 'application/json' }; },\n \n getCategories: async function() {\n try {\n const url = `\\${this.apiBase}/api/v6/data/categories?skip=0&top=100&sort=sort_no&fields=name`;\n const res = await fetch(url, { headers: this.getHeaders() });\n const json = await res.json();\n return json.data || [];\n } catch (e) { return []; }\n },\n \n getFlows: async function() {\n try {\n const filters = JSON.stringify([[\"perms.users_can_add\",\"=\",data.context.userId], \"or\", [\"perms.orgs_can_add\",\"in\",data.context.user.organizations_parents]]);\n const url = `\\${this.apiBase}/api/v6/data/flows?skip=0&top=5000&sort=sort_no&fields=name%2Ccategory&filters=\\${encodeURIComponent(filters)}`;\n const res = await fetch(url, { headers: this.getHeaders() });\n const json = await res.json();\n return json.data || [];\n } catch (e) { return []; }\n },\n \n getData: async function() {\n const [categories, flows] = await Promise.all([this.getCategories(), this.getFlows()]);\n const categoryMap = {};\n categories.forEach(c => categoryMap[c._id] = c.name);\n const mappedFlows = flows.map(f => ({\n id: f._id, name: f.name, categoryId: f.category,\n categoryName: categoryMap[f.category] || \"其他流程\" \n }));\n return { categories: categories, flows: mappedFlows };\n },\n \n getFavorites: function() {\n const saved = localStorage.getItem('steedos_fav_ids');\n return saved ? JSON.parse(saved) : [];\n },\n \n toggleFavorite: function(flowId, isFav) {\n let favs = this.getFavorites();\n if (isFav) { if (!favs.includes(flowId)) favs.push(flowId); } \n else { favs = favs.filter(id => id !== flowId); }\n localStorage.setItem('steedos_fav_ids', JSON.stringify(favs));\n return favs;\n }\n };\n\n // --- UI Controller ---\n const AppState = { allFlows: [], categories: [], favorites: [] };\n const sidebarEl = document.getElementById('sidebarList');\n const contentEl = document.getElementById('contentContainer');\n const searchInput = document.getElementById('searchInput');\n\n async function init() {\n try {\n const data = await WorkflowService.getData();\n AppState.allFlows = data.flows;\n AppState.categories = data.categories;\n AppState.favorites = WorkflowService.getFavorites();\n renderUI();\n } catch (e) {\n contentEl.innerHTML = `<div class=\"text-gray-400 text-sm\">加载失败,请检查网络</div>`;\n }\n }\n\n function renderUI(filterText = \"\") {\n sidebarEl.innerHTML = \"\";\n contentEl.innerHTML = \"\";\n contentEl.className = \"block w-full h-full pt-4 px-8 pb-10\";\n\n const isSearching = filterText.length > 0;\n let groups = [];\n\n const favFlows = AppState.allFlows.filter(f => \n AppState.favorites.includes(f.id) && \n (isSearching ? f.name.includes(filterText) : true)\n );\n if (favFlows.length > 0) {\n groups.push({ id: 'fav', name: \"我的收藏\", items: favFlows, isFav: true });\n }\n\n AppState.categories.forEach(cat => {\n const items = AppState.allFlows.filter(f => \n f.categoryId === cat._id &&\n (isSearching ? f.name.includes(filterText) : true)\n );\n if (items.length > 0) {\n groups.push({ id: cat._id, name: cat.name, items: items, isFav: false });\n }\n });\n\n const otherItems = AppState.allFlows.filter(f => \n !AppState.categories.find(c => c._id === f.categoryId) &&\n (isSearching ? f.name.includes(filterText) : true)\n );\n if (otherItems.length > 0) {\n groups.push({ id: 'other', name: \"其他流程\", items: otherItems, isFav: false });\n }\n\n if (groups.length === 0) {\n contentEl.className = \"flex h-full w-full flex-col items-center justify-center\";\n contentEl.innerHTML = `<div class=\"animate-[fadeUpSpring_0.5s_ease-out] text-center\"><div class=\"text-gray-200 text-7xl mb-4\">∅</div><div class=\"text-gray-400 text-sm\">未找到匹配流程</div></div>`;\n return;\n }\n\n groups.forEach((group, index) => {\n const groupId = `group-\\${group.id}`;\n \n // Sidebar Item\n const navItem = document.createElement('div');\n let navBase = \"group flex cursor-pointer items-center justify-between rounded-md px-3 py-2 text-[14px] transition-all duration-200 ease-out select-none\";\n let activeClass = \"bg-[#007AFF] text-white shadow-sm font-medium\";\n let inactiveClass = \"text-gray-700 hover:bg-black/5 active:bg-black/10\";\n \n navItem.className = `\\${navBase} \\${index === 0 ? activeClass : inactiveClass}`;\n const badgeClass = index === 0 ? \"text-white/80\" : \"text-gray-400 group-hover:text-gray-500\";\n \n navItem.innerHTML = `\n <span class=\"truncate\">\\${group.isFav ? '★ ' : ''}\\${group.name}</span>\n <span class=\"\\${badgeClass} text-[12px] font-medium transition-colors\">\\${group.items.length}</span>\n `;\n navItem.onclick = () => {\n Array.from(sidebarEl.children).forEach(el => {\n el.className = `\\${navBase} \\${inactiveClass}`;\n el.querySelector('span:last-child').className = \"text-gray-400 group-hover:text-gray-500 text-[12px] font-medium transition-colors\";\n });\n navItem.className = `\\${navBase} \\${activeClass}`;\n navItem.querySelector('span:last-child').className = \"text-white/80 text-[12px] font-medium transition-colors\";\n document.getElementById(groupId)?.scrollIntoView({ behavior: 'smooth', block: 'start' });\n };\n sidebarEl.appendChild(navItem);\n\n // Content Header\n const section = document.createElement('div');\n section.id = groupId;\n section.className = \"mb-10\";\n const headerColor = group.isFav ? 'text-amber-500' : 'text-gray-900';\n section.innerHTML = `<div class=\"sticky top-0 z-20 mb-4 bg-white/95 py-3 text-xl font-bold tracking-tight backdrop-blur-xl text-left border-b border-gray-100 \\${headerColor}\">\\${group.name}</div>`;\n\n const grid = document.createElement('div');\n grid.className = 'grid grid-cols-[repeat(auto-fill,minmax(260px,1fr))] gap-4';\n\n group.items.forEach((flow, i) => {\n const isFav = AppState.favorites.includes(flow.id);\n const colorMap = [\n 'bg-blue-50 text-blue-600',\n 'bg-orange-50 text-orange-600',\n 'bg-emerald-50 text-emerald-600',\n 'bg-indigo-50 text-indigo-600'\n ];\n const colorClass = colorMap[(flow.name.length + i) % 4];\n const firstChar = flow.name.replace(/【.*?】/g, '').charAt(0) || flow.name.charAt(0);\n\n const card = document.createElement('div');\n card.className = 'group relative flex h-auto min-h-[72px] cursor-pointer items-center rounded-2xl border border-gray-100 bg-white p-3 text-left shadow-[0_2px_8px_rgba(0,0,0,0.04)] ring-1 ring-black/[0.02] transition-all duration-300 ease-out animate-[fadeUpSpring_0.6s_cubic-bezier(0.16,1,0.3,1)_forwards] hover:-translate-y-1 hover:border-gray-200 hover:shadow-[0_12px_24px_rgba(0,0,0,0.08)] active:scale-[0.98] active:bg-gray-50';\n card.style.animationDelay = `\\${Math.min(i * 0.04, 0.6)}s`;\n card.style.opacity = '0'; \n \n const iconClass = isFav \n ? 'text-yellow-400 fill-current' \n : 'text-gray-300 group-hover/btn:text-gray-400 fill-none stroke-current stroke-[1.5]';\n const btnBgClass = isFav\n ? 'opacity-100 hover:scale-110'\n : 'opacity-0 group-hover:opacity-100 hover:bg-gray-100 hover:scale-110';\n\n // 修改点: 添加 top-1/2 -translate-y-1/2 实现绝对垂直居中\n card.innerHTML = `\n <div class=\"star-btn group/btn absolute right-2 top-1/2 -translate-y-1/2 z-20 flex h-8 w-8 items-center justify-center rounded-full transition-all duration-200 \\${btnBgClass}\" title=\"\\${isFav ? '取消收藏' : '加入收藏'}\">\n <svg class=\"h-5 w-5 transition-colors duration-300 \\${iconClass}\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z\" />\n </svg>\n </div>\n \n <div class=\"mr-4 flex h-11 w-11 shrink-0 items-center justify-center rounded-xl text-[16px] font-bold \\${colorClass}\">\\${firstChar}</div>\n <div class=\"flex-1 pr-8 text-[15px] font-medium text-gray-900 line-clamp-2 leading-relaxed tracking-tight\" title=\"\\${flow.name}\">\\${flow.name}</div>\n `;\n\n card.onclick = () => {\n setTimeout(() => {\n console.log(\"选中的流程ID:\", flow.id);\n //alert(`准备发起: \\${data.context.user.name}`);\n data._scoped.doAction([\n {\n \"actionType\": \"broadcast\",\n \"args\": {\n \"eventName\": \"flows.selected\"\n },\n \"data\": {\n \"value\": flow.id\n }\n }\n ])\n }, 50);\n };\n\n const starBtn = card.querySelector('.star-btn');\n const starIcon = starBtn.querySelector('svg');\n \n starBtn.onclick = (e) => {\n e.stopPropagation();\n const newFavState = !starBtn.classList.contains('active-fav');\n \n if (newFavState) {\n starBtn.classList.add('active-fav', 'opacity-100');\n starBtn.classList.add('animate-[starPop_0.4s_ease-out]');\n starIcon.setAttribute('class', 'h-5 w-5 transition-colors duration-300 text-yellow-400 fill-current');\n } else {\n starBtn.classList.remove('active-fav', 'opacity-100');\n starBtn.classList.remove('animate-[starPop_0.4s_ease-out]');\n starIcon.setAttribute('class', 'h-5 w-5 transition-colors duration-300 text-gray-300 group-hover/btn:text-gray-400 fill-none stroke-current stroke-[1.5]');\n }\n\n AppState.favorites = WorkflowService.toggleFavorite(flow.id, newFavState);\n setTimeout(() => renderUI(searchInput.value), 300);\n };\n \n if (isFav) starBtn.classList.add('active-fav');\n\n grid.appendChild(card);\n });\n\n section.appendChild(grid);\n contentEl.appendChild(section);\n });\n }\n\n searchInput.addEventListener('input', (e) => renderUI(e.target.value.trim()));\n init();\n</script>",
|
|
3
|
+
"template": "<style>\n @keyframes fadeUpSpring {\n 0% { opacity: 0; transform: translateY(10px); }\n 100% { opacity: 1; transform: translateY(0); }\n }\n \n /* Make scrollbars standardized and visible */\n ::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n }\n ::-webkit-scrollbar-track {\n background: transparent;\n }\n ::-webkit-scrollbar-thumb {\n background-color: rgba(0, 0, 0, 0.25); /* Darker for visibility on gray bg */\n border-radius: 4px;\n border: 2px solid transparent; /* Creates padding effect */\n background-clip: content-box;\n }\n ::-webkit-scrollbar-thumb:hover {\n background-color: rgba(0, 0, 0, 0.4);\n }\n\n /* \n Fix outer modal scrollbar - SAFER VERSION \n Only apply these aggressive overrides (no padding, hidden overflow)\n to the specific modal that contains our component (identified by #steedosFlowSelectorSidebarList).\n This prevents breaking other stacked modals like 'Confirm Dialogs'.\n */\n .antd-Modal-body:has(#steedosFlowSelectorSidebarList) {\n overflow: hidden !important;\n padding: 0 !important; /* Optional: maximize space */\n display: flex;\n flex-direction: column;\n }\n\n /* Ensure the AMIS container fills height if needed */\n .antd-Service, .liquid-amis-container {\n height: 100%;\n }\n</style>\n\n<!-- Main Container: Fixed Height 70vh. -->\n<div class=\"flex h-[70vh] max-h-[800px] w-full overflow-hidden font-sans text-gray-900 bg-white\" style=\"min-height: 0;\">\n\n <!-- Left Sidebar -->\n <!-- flex-col, h-full, overflow-hidden -->\n <div class=\"flex flex-col w-[260px] h-full border-r border-gray-200 bg-[#F2F2F7] shrink-0 overflow-hidden\">\n <!-- Header -->\n <div class=\"shrink-0 pt-4 pb-2 px-3\">\n <div class=\"px-2 mb-3 text-2xl font-bold tracking-tight text-black\">流程</div>\n <div class=\"relative group\">\n <div class=\"pointer-events-none absolute inset-y-0 left-0 flex items-center pl-2.5 text-gray-500\">\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n </div>\n <input type=\"text\" id=\"searchInput\" placeholder=\"搜索\" class=\"w-full rounded-[10px] border-none bg-[#767680]/10 py-1.5 pl-9 pr-3 text-[14px] text-gray-900 placeholder:text-gray-500 outline-none transition-all duration-200 focus:bg-white focus:shadow-sm focus:ring-2 focus:ring-blue-500/20\">\n </div>\n </div>\n \n <!-- List Container -->\n <!-- min-h-0 is CRITICAL for flex child scrolling -->\n <div class=\"flex-1 min-h-0 overflow-y-auto px-2 pb-4 space-y-0.5 scroll-smooth\" id=\"steedosFlowSelectorSidebarList\">\n </div>\n </div>\n\n <!-- Right Content -->\n <!-- flex-1 fills remaining width -->\n <div class=\"flex-1 h-full relative bg-white overflow-hidden\">\n <!-- Absolute inset-0 locks the scroll container size -->\n <div id=\"mainContentScroll\" class=\"absolute inset-0 overflow-y-auto scroll-smooth p-6\">\n <div id=\"contentContainer\" class=\"w-full h-auto min-h-full\">\n <div class=\"flex h-full w-full flex-col items-center justify-center pt-20\">\n <div class=\"inline-flex items-center gap-2 text-gray-400 text-sm animate-pulse\">\n <svg class=\"animate-spin h-4 w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\"></path>\n </svg>\n <span>正在加载资源...</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n\n<script>\n const WorkflowService = {\n apiBase: \"\", \n getHeaders: function() { return { 'Content-Type': 'application/json' }; },\n getData: async function() {\n try {\n const appId = (typeof data !== 'undefined' && data.context && data.context.app_id) ? data.context.app_id : \"\";\n const url = this.apiBase + \"/service/api/flows/getList?action=new&appId=\" + encodeURIComponent(appId);\n const res = await fetch(url, { headers: this.getHeaders() });\n const treeData = await res.json();\n const categories = [];\n const parsedFlows = [];\n if (Array.isArray(treeData)) {\n treeData.forEach(cat => {\n categories.push({ _id: cat._id, name: cat.name });\n if (Array.isArray(cat.flows)) {\n cat.flows.forEach(f => {\n parsedFlows.push({\n id: f._id, name: f.name, categoryId: cat._id, categoryName: cat.name || \"其他流程\" \n });\n });\n }\n });\n }\n return { categories: categories, flows: parsedFlows };\n } catch (e) { \n console.error(\"WorkflowService Error:\", e);\n return { categories: [], flows: [] }; \n }\n },\n getFavorites: function() {\n const saved = localStorage.getItem('steedos_fav_ids');\n return saved ? JSON.parse(saved) : [];\n },\n toggleFavorite: function(flowId, isFav) {\n let favs = this.getFavorites();\n if (isFav) { if (!favs.includes(flowId)) favs.push(flowId); } \n else { favs = favs.filter(id => id !== flowId); }\n localStorage.setItem('steedos_fav_ids', JSON.stringify(favs));\n return favs;\n }\n };\n\n const AppState = { allFlows: [], categories: [], favorites: [] };\n const sidebarEl = document.getElementById('steedosFlowSelectorSidebarList');\n const contentEl = document.getElementById('contentContainer');\n const searchInput = document.getElementById('searchInput');\n\n async function init() {\n try {\n const data = await WorkflowService.getData();\n AppState.allFlows = data.flows;\n AppState.categories = data.categories;\n AppState.favorites = WorkflowService.getFavorites();\n renderUI();\n } catch (e) {\n contentEl.innerHTML = `<div class=\"text-gray-400 text-sm\">加载失败,请检查网络</div>`;\n }\n }\n\n function renderUI(filterText = \"\") {\n sidebarEl.innerHTML = \"\";\n contentEl.innerHTML = \"\";\n\n const isSearching = filterText.length > 0;\n let groups = [];\n\n const favFlows = AppState.allFlows.filter(f => \n AppState.favorites.includes(f.id) && \n (isSearching ? f.name.includes(filterText) : true)\n );\n if (favFlows.length > 0) {\n groups.push({ id: 'fav', name: \"我的收藏\", items: favFlows, isFav: true });\n }\n\n AppState.categories.forEach(cat => {\n const items = AppState.allFlows.filter(f => \n f.categoryId === cat._id &&\n (isSearching ? f.name.includes(filterText) : true)\n );\n if (items.length > 0) {\n groups.push({ id: cat._id, name: cat.name, items: items, isFav: false });\n }\n });\n\n const otherItems = AppState.allFlows.filter(f => \n !AppState.categories.find(c => c._id === f.categoryId) &&\n (isSearching ? f.name.includes(filterText) : true)\n );\n if (otherItems.length > 0) {\n groups.push({ id: 'other', name: \"其他流程\", items: otherItems, isFav: false });\n }\n\n if (groups.length === 0) {\n contentEl.innerHTML = `<div class=\"animate-[fadeUpSpring_0.5s_ease-out] text-center pt-20\"><div class=\"text-gray-200 text-7xl mb-4\">∅</div><div class=\"text-gray-400 text-sm\">未找到匹配流程</div></div>`;\n return;\n }\n\n groups.forEach((group, index) => {\n const groupId = `group-\\${group.id}`;\n const navItem = document.createElement('div');\n let navBase = \"group flex cursor-pointer items-center justify-between rounded-md px-3 py-2 text-[14px] transition-all duration-200 ease-out select-none\";\n let activeClass = \"bg-[#007AFF] text-white shadow-sm font-medium\";\n let inactiveClass = \"text-gray-700 hover:bg-black/5 active:bg-black/10\";\n \n navItem.className = `\\${navBase} \\${index === 0 ? activeClass : inactiveClass}`;\n const badgeClass = index === 0 ? \"text-white/80\" : \"text-gray-400 group-hover:text-gray-500\";\n \n navItem.innerHTML = `<span class=\"truncate\">\\${group.isFav ? '★ ' : ''}\\${group.name}</span><span class=\"\\${badgeClass} text-[12px] font-medium transition-colors\">\\${group.items.length}</span>`;\n \n navItem.onclick = () => {\n Array.from(sidebarEl.children).forEach(el => {\n el.className = `\\${navBase} \\${inactiveClass}`;\n el.querySelector('span:last-child').className = \"text-gray-400 group-hover:text-gray-500 text-[12px] font-medium transition-colors\";\n });\n navItem.className = `\\${navBase} \\${activeClass}`;\n navItem.querySelector('span:last-child').className = \"text-white/80 text-[12px] font-medium transition-colors\";\n \n const target = document.getElementById(groupId);\n const container = document.getElementById('mainContentScroll');\n if(target && container) {\n const targetTop = target.getBoundingClientRect().top; \n const containerTop = container.getBoundingClientRect().top; \n container.scrollTo({ top: container.scrollTop + targetTop - containerTop - 16, behavior: 'smooth' });\n }\n };\n sidebarEl.appendChild(navItem);\n\n const section = document.createElement('div');\n section.id = groupId;\n section.className = \"mb-10\";\n const headerColor = group.isFav ? 'text-amber-500' : 'text-gray-900';\n section.innerHTML = `<div class=\"sticky top-0 z-20 mb-4 bg-white/95 pb-2 text-xl font-bold tracking-tight backdrop-blur-xl text-left border-b border-gray-100 \\${headerColor}\">\\${group.name}</div>`;\n\n const grid = document.createElement('div');\n grid.className = 'grid grid-cols-[repeat(auto-fill,minmax(260px,1fr))] gap-4';\n\n group.items.forEach((flow, i) => {\n const isFav = AppState.favorites.includes(flow.id);\n const colorMap = ['bg-blue-50 text-blue-600', 'bg-orange-50 text-orange-600', 'bg-emerald-50 text-emerald-600', 'bg-indigo-50 text-indigo-600'];\n const colorClass = colorMap[(flow.name.length + i) % 4];\n const firstChar = flow.name.replace(/【.*?】/g, '').charAt(0) || flow.name.charAt(0);\n const card = document.createElement('div');\n card.className = 'group relative flex h-auto min-h-[72px] cursor-pointer items-center rounded-2xl border border-gray-100 bg-white p-3 text-left shadow-[0_2px_8px_rgba(0,0,0,0.04)] ring-1 ring-black/[0.02] transition-all duration-300 ease-out animate-[fadeUpSpring_0.6s_cubic-bezier(0.16,1,0.3,1)_forwards] hover:-translate-y-1 hover:border-gray-200 hover:shadow-[0_12px_24px_rgba(0,0,0,0.08)] active:scale-[0.98] active:bg-gray-50';\n card.style.animationDelay = `\\${Math.min(i * 0.04, 0.6)}s`;\n card.style.opacity = '0';\n const iconClass = isFav ? 'text-yellow-400 fill-current' : 'text-gray-300 group-hover/btn:text-gray-400 fill-none stroke-current stroke-[1.5]';\n const btnBgClass = isFav ? 'opacity-100 hover:scale-110' : 'opacity-0 group-hover:opacity-100 hover:bg-gray-100 hover:scale-110';\n\n card.innerHTML = `\n <div class=\"star-btn group/btn absolute right-2 top-1/2 -translate-y-1/2 z-20 flex h-8 w-8 items-center justify-center rounded-full transition-all duration-200 \\${btnBgClass}\" title=\"\\${isFav ? '取消收藏' : '加入收藏'}\">\n <svg class=\"h-5 w-5 transition-colors duration-300 \\${iconClass}\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z\" />\n </svg>\n </div>\n <div class=\"mr-4 flex h-11 w-11 shrink-0 items-center justify-center rounded-xl text-[16px] font-bold \\${colorClass}\">\\${firstChar}</div>\n <div class=\"flex-1 pr-8 text-[15px] font-medium text-gray-900 line-clamp-2 leading-relaxed tracking-tight\" title=\"\\${flow.name}\">\\${flow.name}</div>\n `;\n card.onclick = () => {\n setTimeout(() => {\n data._scoped.doAction([\n { \"actionType\": \"broadcast\", \"args\": { \"eventName\": \"flows.selected\" }, \"data\": { \"value\": flow.id } }\n ])\n }, 50);\n };\n const starBtn = card.querySelector('.star-btn');\n const starIcon = starBtn.querySelector('svg');\n starBtn.onclick = (e) => {\n e.stopPropagation();\n const newFavState = !starBtn.classList.contains('active-fav');\n if (newFavState) {\n starBtn.classList.add('active-fav', 'opacity-100');\n starBtn.classList.add('animate-[starPop_0.4s_ease-out]');\n starIcon.setAttribute('class', 'h-5 w-5 transition-colors duration-300 text-yellow-400 fill-current');\n } else {\n starBtn.classList.remove('active-fav', 'opacity-100');\n starBtn.classList.remove('animate-[starPop_0.4s_ease-out]');\n starIcon.setAttribute('class', 'h-5 w-5 transition-colors duration-300 text-gray-300 group-hover/btn:text-gray-400 fill-none stroke-current stroke-[1.5]');\n }\n AppState.favorites = WorkflowService.toggleFavorite(flow.id, newFavState);\n setTimeout(() => renderUI(searchInput.value), 300);\n };\n if (isFav) starBtn.classList.add('active-fav');\n grid.appendChild(card);\n });\n section.appendChild(grid);\n contentEl.appendChild(section);\n });\n }\n\n searchInput.addEventListener('input', (e) => renderUI(e.target.value.trim()));\n init();\n</script>\n",
|
|
4
4
|
"className": "h-full"
|
|
5
5
|
}
|
|
@@ -352,6 +352,39 @@
|
|
|
352
352
|
},
|
|
353
353
|
".antd-Table-content-colDragLine": {
|
|
354
354
|
"display": "none !important"
|
|
355
|
+
},
|
|
356
|
+
"@media print": {
|
|
357
|
+
"html, body, #root, #main, .creator-content-wrapper, .builder-component, .builder-content, .builder-blocks, .builder-block, .amis-scope, .amis-routes-wrapper": {
|
|
358
|
+
"width": "100% !important",
|
|
359
|
+
"height": "auto !important",
|
|
360
|
+
"overflow": "visible !important",
|
|
361
|
+
"display": "block !important",
|
|
362
|
+
"position": "static !important"
|
|
363
|
+
},
|
|
364
|
+
".resize-sensor, .no-print, .antd-Page-toolbar, .steedos-global-header-root": {
|
|
365
|
+
"display": "none !important"
|
|
366
|
+
},
|
|
367
|
+
".steedos-instance-related-view-wrapper, .steedos-instance-related-view-wrapper .antd-Page-content, .steedos-instance-related-view-wrapper .antd-Page-main, .steedos-instance-related-view-wrapper .antd-Page-body, .steedos-instance-related-view-wrapper .steedos-instance-detail-wrapper, .steedos-instance-related-view-wrapper .antd-Service, .steedos-instance-related-view-wrapper .antd-Wrapper, .steedos-instance-related-view-wrapper .steedos-amis-instance-view, .steedos-instance-related-view-wrapper .steedos-amis-instance-view-body, .steedos-instance-related-view-wrapper .steedos-amis-instance-view-content, .steedos-instance-related-view-wrapper .liquid-amis-container": {
|
|
368
|
+
"display": "block !important",
|
|
369
|
+
"height": "auto !important",
|
|
370
|
+
"width": "100% !important",
|
|
371
|
+
"overflow": "visible !important",
|
|
372
|
+
"flex": "none !important",
|
|
373
|
+
"position": "static !important"
|
|
374
|
+
},
|
|
375
|
+
".steedos-instance-related-view-wrapper .instance-form": {
|
|
376
|
+
"margin": "0 auto"
|
|
377
|
+
},
|
|
378
|
+
".steedos-instance-related-view-wrapper .antd-Table-contentWrap": {
|
|
379
|
+
"overflow": "visible !important",
|
|
380
|
+
"height": "auto !important"
|
|
381
|
+
},
|
|
382
|
+
".steedos-instance-related-view-wrapper .instance-form-view td": {
|
|
383
|
+
"border-width": "1px !important"
|
|
384
|
+
},
|
|
385
|
+
".steedos-instance-related-view-wrapper .instance-approve-history .antd-Table-table tr td": {
|
|
386
|
+
"border-bottom": "1px solid #e8e8e8 !important"
|
|
387
|
+
}
|
|
355
388
|
}
|
|
356
389
|
},
|
|
357
390
|
"wrapperCustomStyle": {}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* @Author: baozhoutao@steedos.com
|
|
3
3
|
* @Date: 2023-01-14 11:31:56
|
|
4
|
-
* @LastEditors:
|
|
5
|
-
* @LastEditTime:
|
|
4
|
+
* @LastEditors: 殷亮辉 yinlianghui@hotoa.com
|
|
5
|
+
* @LastEditTime: 2026-01-15 13:28:24
|
|
6
6
|
* @Description:
|
|
7
7
|
*/
|
|
8
8
|
const objectql = require("@steedos/objectql");
|
|
@@ -31,6 +31,7 @@ module.exports = {
|
|
|
31
31
|
},
|
|
32
32
|
actions: {
|
|
33
33
|
flows__getList: {
|
|
34
|
+
rest: "GET /getList",
|
|
34
35
|
graphql: {
|
|
35
36
|
query: `
|
|
36
37
|
#按权限获取flows数据
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steedos-labs/plugin-workflow",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.16",
|
|
4
4
|
"main": "package.service.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build:watch": "tsc --watch",
|
|
8
|
-
"release": "npm publish --registry https://registry.npmjs.org && npx cnpm sync @steedos-labs/plugin-workflow"
|
|
8
|
+
"release": "npm publish --registry https://registry.npmjs.org && npx cnpm sync @steedos-labs/plugin-workflow",
|
|
9
|
+
"export-templates": "node run.js export",
|
|
10
|
+
"convert-templates": "node convert-templates.js"
|
|
9
11
|
},
|
|
10
12
|
"dependencies": {
|
|
11
13
|
"graphql-parse-resolve-info": "^4.12.3",
|
|
@@ -98,6 +98,19 @@
|
|
|
98
98
|
.instance-box-tree .antd-Tree-itemIcon{
|
|
99
99
|
margin-right: 4px;
|
|
100
100
|
} */
|
|
101
|
+
/* Fix Badge layout: force it to flow naturally in flex layout to display full content for large numbers like 9999 */
|
|
102
|
+
/* Also ensures it stays aligned to the right of the text label without overlapping */
|
|
103
|
+
.instance-box-tree .antd-Badge {
|
|
104
|
+
height: auto !important;
|
|
105
|
+
width: auto !important;
|
|
106
|
+
position: static !important;
|
|
107
|
+
transform: none !important;
|
|
108
|
+
flex-shrink: 0;
|
|
109
|
+
margin-left: 4px;
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
}
|
|
113
|
+
|
|
101
114
|
.instance-box-tree .antd-Badge-text{
|
|
102
115
|
background: #e5e7eb;
|
|
103
116
|
color: #4b5563;
|
|
@@ -109,6 +122,8 @@
|
|
|
109
122
|
min-width: 18px;
|
|
110
123
|
display: inline-block;
|
|
111
124
|
text-align: center;
|
|
125
|
+
position: static !important;
|
|
126
|
+
transform: none !important;
|
|
112
127
|
}
|
|
113
128
|
.instance-box-tree .antd-Tree-item:nth-child(1) .antd-Badge-text{
|
|
114
129
|
background: #ef4444;
|
|
@@ -118,7 +133,7 @@
|
|
|
118
133
|
height: 1.6rem;
|
|
119
134
|
} */
|
|
120
135
|
.instance-box-tree .antd-TplField span{
|
|
121
|
-
width: 85%;
|
|
136
|
+
/* width: 85%; */
|
|
122
137
|
display: block;
|
|
123
138
|
overflow: hidden;
|
|
124
139
|
white-space: nowrap;
|
|
@@ -243,6 +258,7 @@
|
|
|
243
258
|
.instance-box-tree .antd-Tree-itemText > div {
|
|
244
259
|
padding-top: 0px !important; /* Reduced from 4px to fix "text too low" */
|
|
245
260
|
padding-bottom: 0px !important;
|
|
261
|
+
padding-right: 0px !important; /* Reduced to move content closer to right edge */
|
|
246
262
|
line-height: 24px; /* Explicit line height to ensure centering */
|
|
247
263
|
display: flex;
|
|
248
264
|
align-items: center;
|
|
@@ -261,7 +277,29 @@
|
|
|
261
277
|
/* Icon styling */
|
|
262
278
|
.instance-box-tree .antd-Tree-itemIcon{
|
|
263
279
|
color: #6b7280;
|
|
264
|
-
margin-right:
|
|
280
|
+
margin-right: 0px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* Hide icons for leaf nodes at level >= 3 (final menu items within category groups) */
|
|
284
|
+
.instance-box-tree .antd-Tree .antd-Tree-item--isLeaf[style*="calc(2 *"] .antd-Tree-itemIcon,
|
|
285
|
+
.instance-box-tree .antd-Tree .antd-Tree-item--isLeaf[style*="calc(3 *"] .antd-Tree-itemIcon,
|
|
286
|
+
.instance-box-tree .antd-Tree .antd-Tree-item--isLeaf[style*="calc(4 *"] .antd-Tree-itemIcon,
|
|
287
|
+
.instance-box-tree .antd-Tree .antd-Tree-item--isLeaf[style*="calc(5 *"] .antd-Tree-itemIcon {
|
|
288
|
+
display: none;
|
|
289
|
+
margin-right: 0px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* Reduce left padding for leaf nodes to compensate for hidden icon */
|
|
293
|
+
.instance-box-tree .antd-Tree .antd-Tree-item--isLeaf .antd-Tree-itemLabel{
|
|
294
|
+
padding-left: 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* Hide arrow placeholder for leaf nodes at level >= 3 to align text left without shifting container */
|
|
298
|
+
.instance-box-tree .antd-Tree .antd-Tree-item--isLeaf[style*="calc(2 *"] .antd-Tree-itemArrowPlaceholder,
|
|
299
|
+
.instance-box-tree .antd-Tree .antd-Tree-item--isLeaf[style*="calc(3 *"] .antd-Tree-itemArrowPlaceholder,
|
|
300
|
+
.instance-box-tree .antd-Tree .antd-Tree-item--isLeaf[style*="calc(4 *"] .antd-Tree-itemArrowPlaceholder,
|
|
301
|
+
.instance-box-tree .antd-Tree .antd-Tree-item--isLeaf[style*="calc(5 *"] .antd-Tree-itemArrowPlaceholder {
|
|
302
|
+
width: 22px;
|
|
265
303
|
}
|
|
266
304
|
|
|
267
305
|
/* Arrow styling */
|
|
@@ -559,6 +597,73 @@ tbody .color-priority-muted *{
|
|
|
559
597
|
}
|
|
560
598
|
|
|
561
599
|
|
|
600
|
+
.instance-form,.instance-template .td-title{
|
|
601
|
+
text-align: center;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.instance-form .td-childfield{
|
|
605
|
+
text-align: left;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.instance-template .table-page-body .antd-Form-label{
|
|
609
|
+
display: none !important;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.instance-form .form-table {
|
|
613
|
+
border-collapse: collapse;
|
|
614
|
+
border: 2px solid black;
|
|
615
|
+
}
|
|
616
|
+
.instance-form .form-table td {
|
|
617
|
+
border: 1px solid black;
|
|
618
|
+
padding: 4px 6px;
|
|
619
|
+
border-collapse: collapse;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.instance-form .td-childfield{
|
|
623
|
+
padding: 0 !important;
|
|
624
|
+
border: none !important;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
.instance-form .steedos-input-table .antd-Table-table colgroup col:nth-child(2) {
|
|
629
|
+
width: 0px !important;
|
|
630
|
+
min-width: 0px !important;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.instance-form .steedos-input-table .antd-Table-table thead tr th:first-child {
|
|
634
|
+
width: 60px;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.instance-form .steedos-input-table .antd-Table-table thead .antd-Table-operationCell {
|
|
638
|
+
width: 0px !important;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.instance-form .steedos-input-table .antd-Table-table .steedos-input-table-column-operation {
|
|
642
|
+
border: none !important;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.instance-form .steedos-input-table .antd-Table-table .steedos-input-table-column-operation .antd-OperationField {
|
|
646
|
+
margin-left: -55px;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.instance-form td .loading {
|
|
650
|
+
display: none !important;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
.instance-form .antd-Table-headToolbar{
|
|
655
|
+
display: none !important;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.table-page-title{
|
|
659
|
+
border: none !important;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
.instance-name .page-title{
|
|
663
|
+
text-align: center;
|
|
664
|
+
border: none !important;
|
|
665
|
+
}
|
|
666
|
+
|
|
562
667
|
/* 公共打印隐藏样式 */
|
|
563
668
|
@media print {
|
|
564
669
|
.no-print {
|