@steedos-labs/plugin-workflow 3.0.13 → 3.0.15
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.md +24 -1
- package/main/default/applications/approve_workflow.app.yml +10 -9
- package/main/default/manager/uuflow_manager.js +2 -4
- package/main/default/objects/flows/buttons/design_form_layout.button.js +1 -3
- package/main/default/objects/flows/flows.object.yml +4 -3
- 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/instance_detail.page.amis.json +1 -1
- package/main/default/routes/api_workflow_instance_upgrade.router.js +5 -0
- package/main/default/routes/api_workflow_nav.router.js +160 -136
- package/main/default/routes/flow_form_design.ejs +16 -1
- package/main/default/services/flows.service.js +3 -2
- package/main/default/services/instance.service.js +10 -1
- package/package.json +1 -1
- package/public/workflow/index.css +241 -13
package/README.md
CHANGED
|
@@ -6,4 +6,27 @@
|
|
|
6
6
|
* @Description:
|
|
7
7
|
-->
|
|
8
8
|
## 功能说明
|
|
9
|
-
- 审批王服务,包含 流程触发器
|
|
9
|
+
- 审批王服务,包含 流程触发器
|
|
10
|
+
|
|
11
|
+
## 环境变量配置
|
|
12
|
+
|
|
13
|
+
### STEEDOS_WORKFLOW_ENABLE_CATEGORY_FILTER
|
|
14
|
+
- **说明**: 控制是否在"待处理"、"监控箱"视图以及左侧导航菜单中按流程分类进行筛选和显示
|
|
15
|
+
- **默认行为**: 启用分类筛选(当环境变量未设置或设置为除 'false' 以外的任何值时)
|
|
16
|
+
- **可选值**:
|
|
17
|
+
- 不设置或设置为除 `'false'` 以外的任何值: 启用按分类筛选,根据应用(app)显示对应分类的流程
|
|
18
|
+
- `'false'` (字符串): 禁用按分类筛选,显示所有流程
|
|
19
|
+
- **使用场景**: 国产化优化,简化流程展示逻辑
|
|
20
|
+
- **影响范围**:
|
|
21
|
+
- 待处理(Pending)和监控箱(Monitor Box)视图的数据筛选
|
|
22
|
+
- 左侧导航菜单结构:禁用时仅显示box层级(待办箱、监控箱等),不显示分类和流程子菜单
|
|
23
|
+
|
|
24
|
+
**示例配置**:
|
|
25
|
+
```bash
|
|
26
|
+
# 禁用分类筛选
|
|
27
|
+
STEEDOS_WORKFLOW_ENABLE_CATEGORY_FILTER=false
|
|
28
|
+
|
|
29
|
+
# 启用分类筛选(以下任一方式)
|
|
30
|
+
# STEEDOS_WORKFLOW_ENABLE_CATEGORY_FILTER=true
|
|
31
|
+
# 或者不设置该环境变量(默认启用)
|
|
32
|
+
```
|
|
@@ -8,6 +8,7 @@ mobile: true
|
|
|
8
8
|
sort: 10
|
|
9
9
|
# tabs:
|
|
10
10
|
# - object_instance_tasks
|
|
11
|
+
default_tab: object_instance_tasks
|
|
11
12
|
|
|
12
13
|
showSidebar: true
|
|
13
14
|
enable_nav_schema: true
|
|
@@ -24,12 +25,12 @@ nav_schema: {
|
|
|
24
25
|
{
|
|
25
26
|
"type": "wrapper",
|
|
26
27
|
"size": "none",
|
|
27
|
-
"className": "instances-sidebar-wrapper mt-1",
|
|
28
|
+
"className": "instances-sidebar-wrapper mt-1 bg-white",
|
|
28
29
|
"body": [
|
|
29
30
|
{
|
|
30
31
|
"type": "service",
|
|
31
32
|
"id": "u:instanceNav",
|
|
32
|
-
"className": "bg-
|
|
33
|
+
"className": "bg-white",
|
|
33
34
|
"onEvent": {
|
|
34
35
|
"@data.changed.instances": {
|
|
35
36
|
"actions": [
|
|
@@ -58,8 +59,8 @@ nav_schema: {
|
|
|
58
59
|
{
|
|
59
60
|
"type": "input-tree",
|
|
60
61
|
"name": "tree",
|
|
61
|
-
"treeContainerClassName": "h-full",
|
|
62
|
-
"className": "instance-box-tree h-full w-full p-0",
|
|
62
|
+
"treeContainerClassName": "h-full bg-white",
|
|
63
|
+
"className": "instance-box-tree h-full w-full p-0 bg-white",
|
|
63
64
|
"id": "u:9f3dd961ca12",
|
|
64
65
|
"stacked": true,
|
|
65
66
|
"multiple": false,
|
|
@@ -112,23 +113,23 @@ nav_schema: {
|
|
|
112
113
|
},
|
|
113
114
|
"menuTpl": {
|
|
114
115
|
"type": "wrapper",
|
|
115
|
-
"className": "flex
|
|
116
|
+
"className": "flex items-center py-1.5 px-3 m-0 rounded-md transition-colors duration-150",
|
|
116
117
|
"body": [
|
|
117
118
|
{
|
|
118
119
|
"type": "tpl",
|
|
119
|
-
"className": "flex-1
|
|
120
|
+
"className": "flex-1 leading-6 truncate instance-menu-label",
|
|
120
121
|
"tpl": "${label}",
|
|
121
122
|
"id": "u:9dee51f00db4"
|
|
122
123
|
},
|
|
123
124
|
{
|
|
124
125
|
"type": "tpl",
|
|
125
|
-
"className": "-
|
|
126
|
+
"className": "ml-auto",
|
|
126
127
|
"tpl": "",
|
|
127
128
|
"badge": {
|
|
128
129
|
"className": "h-0",
|
|
129
130
|
"offset": [
|
|
130
|
-
-
|
|
131
|
-
|
|
131
|
+
-5,
|
|
132
|
+
0
|
|
132
133
|
],
|
|
133
134
|
"mode": "text",
|
|
134
135
|
"text": "${tag | toInt}",
|
|
@@ -605,6 +605,7 @@ UUFlowManager.calculateConditionWithAmis = function (values, condition_str) {
|
|
|
605
605
|
* @returns {Boolean} Condition result
|
|
606
606
|
*/
|
|
607
607
|
UUFlowManager.calculateCondition = function (values, condition_str) {
|
|
608
|
+
// console.log('calculateCondition', values, condition_str);
|
|
608
609
|
try {
|
|
609
610
|
const __values = values;
|
|
610
611
|
|
|
@@ -817,10 +818,7 @@ UUFlowManager.initFormulaValues = async function (instance, values) {
|
|
|
817
818
|
__values["applicant"] = {
|
|
818
819
|
roles: await UUFlowManager.getUserRoles(instance.applicant, instance.space),
|
|
819
820
|
name: instance.applicant_name,
|
|
820
|
-
organization:
|
|
821
|
-
fullname: instance.applicant_organization_fullname,
|
|
822
|
-
name: instance.applicant_organization_name
|
|
823
|
-
},
|
|
821
|
+
organization: await UUFlowManager.getOrganization(instance.applicant_organization),
|
|
824
822
|
id: instance.applicant
|
|
825
823
|
};
|
|
826
824
|
|
|
@@ -60,6 +60,9 @@ fields:
|
|
|
60
60
|
readonly: true
|
|
61
61
|
visible_on: "{{global.mode !='read' ? false : true}}"
|
|
62
62
|
name: current_no
|
|
63
|
+
company_id:
|
|
64
|
+
label: Main Division
|
|
65
|
+
visible_on: "{{true}}"
|
|
63
66
|
description:
|
|
64
67
|
label: Description
|
|
65
68
|
type: textarea
|
|
@@ -77,8 +80,6 @@ fields:
|
|
|
77
80
|
type: textarea
|
|
78
81
|
is_wide: true
|
|
79
82
|
name: help_text
|
|
80
|
-
company_id:
|
|
81
|
-
label: Main Division
|
|
82
83
|
created_by:
|
|
83
84
|
label: Created by
|
|
84
85
|
sort_no: 9999
|
|
@@ -906,7 +907,7 @@ permission_set:
|
|
|
906
907
|
allowEdit: false
|
|
907
908
|
allowRead: true
|
|
908
909
|
modifyAllRecords: false
|
|
909
|
-
viewAllRecords:
|
|
910
|
+
viewAllRecords: false
|
|
910
911
|
admin:
|
|
911
912
|
allowCreate: true
|
|
912
913
|
allowDelete: false
|
|
@@ -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
|
}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"schemaApi": {
|
|
27
27
|
"method": "get",
|
|
28
28
|
"url": "/api/health_check?trace=${recordId}",
|
|
29
|
-
"adaptor": "const result = {data: {'type':'wrapper','className':'p-0 h-full','body':[{'type':'steedos-instance-detail','id':'u:40052b3812c1','label':'Instance Detail','instanceId':context.recordId,'boxName':
|
|
29
|
+
"adaptor": "const urlParams = new URLSearchParams(location.search); const sideListViewId = urlParams.get('side_listview_id'); const result = {data: {'type':'wrapper','className':'p-0 h-full','body':[{'type':'steedos-instance-detail','id':'u:40052b3812c1','label':'Instance Detail','instanceId':context.recordId,'boxName':sideListViewId}],'id':'u:829a40757f0a'}};return result;"
|
|
30
30
|
},
|
|
31
31
|
"body": {},
|
|
32
32
|
"className": "h-full",
|
|
@@ -19,6 +19,11 @@ router.post('/api/workflow/v2/instance/upgrade', requireAuthentication, async fu
|
|
|
19
19
|
'flow','form','applicant_name','applicant_organization','applicant_organization_fullname','applicant_organization_name',
|
|
20
20
|
'code', 'flow_version', 'form_version', 'submit_date', 'flow_name', 'code', 'traces', 'state'
|
|
21
21
|
]});
|
|
22
|
+
if(!record){
|
|
23
|
+
return res.status(200).send({
|
|
24
|
+
instance: false
|
|
25
|
+
});
|
|
26
|
+
}
|
|
22
27
|
if(record.state !== 'draft'){
|
|
23
28
|
return res.status(200).send({
|
|
24
29
|
instance: true
|
|
@@ -56,54 +56,67 @@ const getCategoriesInbox = async (userSession, req, currentUrl) => {
|
|
|
56
56
|
fields: ['_id', 'flow', 'category','flow_name','category_name']
|
|
57
57
|
}, userSession)
|
|
58
58
|
|
|
59
|
+
// Check environment variable to control category filtering
|
|
60
|
+
// Default is enabled (when not set or set to any value except 'false')
|
|
61
|
+
// Only the string 'false' will disable the filtering
|
|
62
|
+
const enableCategoryFilter = process.env.STEEDOS_WORKFLOW_ENABLE_CATEGORY_FILTER !== 'false';
|
|
63
|
+
|
|
59
64
|
const output = [];
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
lodash.each(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
|
|
66
|
+
if (!enableCategoryFilter) {
|
|
67
|
+
// When category filtering is disabled, return empty array (no sub-navigation)
|
|
68
|
+
// Only box-level items will be shown
|
|
69
|
+
} else {
|
|
70
|
+
// Original logic with category grouping
|
|
71
|
+
const categoryGroups = lodash.groupBy(data, 'category_name');
|
|
72
|
+
lodash.each(categoryGroups, (v, k)=>{
|
|
73
|
+
let categoryBadge = 0;
|
|
74
|
+
const flowGroups = lodash.groupBy(v, 'flow_name');
|
|
75
|
+
const flows = [];
|
|
76
|
+
const categoryValue = `/app/${appId}/instance_tasks/view/none?side_object=instance_tasks&side_listview_id=inbox&additionalFilters=['category','=',${v[0].category?"'" + v[0].category + "'":v[0].category}]&flowId=&categoryId=${v[0].category}`;
|
|
77
|
+
let categoryIsUnfolded = false;
|
|
78
|
+
lodash.each(flowGroups, (v2, k2)=>{
|
|
79
|
+
const flowValue = `/app/${appId}/instance_tasks/view/none?side_object=instance_tasks&side_listview_id=inbox&additionalFilters=['flow','=','${v2[0].flow}']&flowId=${v2[0].flow}&categoryId=${v[0].category}`;
|
|
80
|
+
let flowIsUnfolded = false;
|
|
81
|
+
if(currentUrl == flowValue){
|
|
82
|
+
flowIsUnfolded = true;
|
|
83
|
+
categoryIsUnfolded = true;
|
|
84
|
+
}
|
|
85
|
+
categoryBadge += v2.length
|
|
86
|
+
flows.push({
|
|
87
|
+
label: k2,
|
|
88
|
+
flow_name: k2,
|
|
89
|
+
tag:v2.length,
|
|
90
|
+
options:{
|
|
91
|
+
level:3,
|
|
92
|
+
value: v2[0].flow,
|
|
93
|
+
name: 'flow',
|
|
94
|
+
to: flowValue,
|
|
95
|
+
},
|
|
96
|
+
value: flowValue,
|
|
97
|
+
unfolded: flowIsUnfolded
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
if(currentUrl == categoryValue){
|
|
72
101
|
categoryIsUnfolded = true;
|
|
73
102
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
tag:
|
|
79
|
-
options:{
|
|
80
|
-
level:
|
|
81
|
-
value:
|
|
82
|
-
name: '
|
|
83
|
-
to:
|
|
103
|
+
output.push({
|
|
104
|
+
label: k == 'null' || k == 'undefined' || !k ? "未分类" : k,
|
|
105
|
+
children: flows,
|
|
106
|
+
category_name: k == 'null' || k == 'undefined' || !k ? "未分类" : k,
|
|
107
|
+
tag: v.length,
|
|
108
|
+
options: {
|
|
109
|
+
level: 2,
|
|
110
|
+
value: v[0].category,
|
|
111
|
+
name: 'category',
|
|
112
|
+
to: categoryValue,
|
|
84
113
|
},
|
|
85
|
-
value:
|
|
86
|
-
unfolded:
|
|
114
|
+
value: categoryValue,
|
|
115
|
+
unfolded: categoryIsUnfolded
|
|
87
116
|
})
|
|
88
117
|
})
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
output.push({
|
|
93
|
-
label: k == 'null' || k == 'undefined' || !k ? "未分类" : k,
|
|
94
|
-
children: flows,
|
|
95
|
-
category_name: k == 'null' || k == 'undefined' || !k ? "未分类" : k,
|
|
96
|
-
tag: v.length,
|
|
97
|
-
options: {
|
|
98
|
-
level: 2,
|
|
99
|
-
value: v[0].category,
|
|
100
|
-
name: 'category',
|
|
101
|
-
to: categoryValue,
|
|
102
|
-
},
|
|
103
|
-
value: categoryValue,
|
|
104
|
-
unfolded: categoryIsUnfolded
|
|
105
|
-
})
|
|
106
|
-
})
|
|
118
|
+
}
|
|
119
|
+
|
|
107
120
|
return {
|
|
108
121
|
schema: output,
|
|
109
122
|
count: data.length,
|
|
@@ -121,119 +134,130 @@ const getCategoriesMonitor = async (userSession, req, currentUrl) => {
|
|
|
121
134
|
let output = [];
|
|
122
135
|
let monitorIsUnfolded = false;
|
|
123
136
|
|
|
137
|
+
// Check environment variable to control category filtering
|
|
138
|
+
// Default is enabled (when not set or set to any value except 'false')
|
|
139
|
+
// Only the string 'false' will disable the filtering
|
|
140
|
+
const enableCategoryFilter = process.env.STEEDOS_WORKFLOW_ENABLE_CATEGORY_FILTER !== 'false';
|
|
141
|
+
|
|
124
142
|
try {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
143
|
+
let flows = [];
|
|
144
|
+
|
|
145
|
+
if (!enableCategoryFilter) {
|
|
146
|
+
// When category filtering is disabled, return empty array (no sub-navigation)
|
|
147
|
+
// Only box-level items will be shown
|
|
148
|
+
// Still need to check permissions for hasFlowsPer (used to control monitor box visibility)
|
|
149
|
+
if (!hasFlowsPer) {
|
|
150
|
+
const flowIds = await WorkflowManager.getMyAdminOrMonitorFlows(userSession.spaceId, userSession.userId);
|
|
151
|
+
hasFlowsPer = flowIds && flowIds.length > 0;
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
// Original logic with category grouping
|
|
155
|
+
const apps = await objectql.getObject('apps').find({filters: ['space', '=', userSession.spaceId], fields: ['_id', 'code']});
|
|
156
|
+
const appsMap = new Map(Object.entries(lodash.keyBy(apps, '_id')));
|
|
157
|
+
const categories = await objectql.getObject('categories').find({filters: ["space", "=", `${userSession.spaceId}`], sort: "sort_no desc"})
|
|
158
|
+
for (const item of categories) {
|
|
159
|
+
if (item.app) {
|
|
160
|
+
item.app__expand = appsMap.get(item.app)
|
|
161
|
+
} else {
|
|
162
|
+
item.app__expand = {}
|
|
163
|
+
}
|
|
137
164
|
}
|
|
138
|
-
}
|
|
139
165
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
currentAppCategories = categories;
|
|
143
|
-
}else{
|
|
144
|
-
currentAppCategories = lodash.filter(categories, (category) => {
|
|
145
|
-
if(category.app__expand?.code == appId) return true;
|
|
146
|
-
else return false;
|
|
147
|
-
})
|
|
148
|
-
if(currentAppCategories.length == 0) {
|
|
149
|
-
//如果没有任何分类绑定该app,则该app显示所有分类(该规则为审批王规则)
|
|
166
|
+
let currentAppCategories = [];
|
|
167
|
+
if (appId == "approve_workflow") {
|
|
150
168
|
currentAppCategories = categories;
|
|
169
|
+
} else {
|
|
170
|
+
currentAppCategories = lodash.filter(categories, (category) => {
|
|
171
|
+
if (category.app__expand?.code == appId) return true;
|
|
172
|
+
else return false;
|
|
173
|
+
})
|
|
174
|
+
if (currentAppCategories.length == 0) {
|
|
175
|
+
//如果没有任何分类绑定该app,则该app显示所有分类(该规则为审批王规则)
|
|
176
|
+
currentAppCategories = categories;
|
|
177
|
+
}
|
|
151
178
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
179
|
+
let categoriesIds = lodash.map(currentAppCategories, '_id');
|
|
180
|
+
|
|
181
|
+
if (!hasFlowsPer) {
|
|
182
|
+
const flowIds = await WorkflowManager.getMyAdminOrMonitorFlows(userSession.spaceId, userSession.userId);
|
|
183
|
+
hasFlowsPer = flowIds && flowIds.length > 0;
|
|
184
|
+
if (hasFlowsPer) {
|
|
185
|
+
flows = await objectql.getObject('flows').find({
|
|
186
|
+
filters: [["_id","in", flowIds],"and",["category","in", categoriesIds], "and", ["state", "=", "enabled"]],
|
|
187
|
+
fields: ['_id', 'name', 'category', 'sort_no'],
|
|
188
|
+
sort:"sort_no desc"
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
161
192
|
flows = await objectql.getObject('flows').find({
|
|
162
|
-
filters:
|
|
193
|
+
filters:[["space", "=", userSession.spaceId],["category","in", categoriesIds],["state", "=", "enabled"]],
|
|
163
194
|
fields: ['_id', 'name', 'category', 'sort_no'],
|
|
164
195
|
sort:"sort_no desc"
|
|
165
|
-
})
|
|
196
|
+
})
|
|
166
197
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
filters:[["space", "=", userSession.spaceId],["category","in", categoriesIds],["state", "=", "enabled"]],
|
|
171
|
-
fields: ['_id', 'name', 'category', 'sort_no'],
|
|
172
|
-
sort:"sort_no desc"
|
|
173
|
-
})
|
|
174
|
-
// console.log(`find flows`, new Date().getTime() - s1)
|
|
175
|
-
}
|
|
176
|
-
if (flows.length > 0) {
|
|
177
|
-
const categoriesMap = new Map(Object.entries(lodash.keyBy(categories, '_id')));
|
|
198
|
+
|
|
199
|
+
if (flows.length > 0) {
|
|
200
|
+
const categoriesMap = new Map(Object.entries(lodash.keyBy(categories, '_id')));
|
|
178
201
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
202
|
+
for (const item of flows) {
|
|
203
|
+
if (item.category) {
|
|
204
|
+
item.category__expand = categoriesMap.get(item.category)
|
|
205
|
+
} else {
|
|
206
|
+
item.category__expand = {}
|
|
207
|
+
}
|
|
184
208
|
}
|
|
185
|
-
}
|
|
186
209
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
210
|
+
const categoryGroups = lodash.groupBy(flows, 'category__expand.name');
|
|
211
|
+
lodash.each(categoryGroups, (v, k) => {
|
|
212
|
+
const flowGroups = lodash.groupBy(v, 'name');
|
|
213
|
+
const flows = [];
|
|
214
|
+
const categoryValue = `/app/${appId}/instances/view/none?side_object=instances&side_listview_id=monitor&additionalFilters=['category','=',${v[0].category__expand?"'" + v[0].category__expand._id + "'":null}]&flowId=&categoryId=${v[0].category__expand && v[0].category__expand._id}`;
|
|
215
|
+
let categoryIsUnfolded = false;
|
|
216
|
+
lodash.each(flowGroups, (v2, k2) => {
|
|
217
|
+
const flowValue = `/app/${appId}/instances/view/none?side_object=instances&side_listview_id=monitor&additionalFilters=['flow','=','${v2[0]._id}']&flowId=${v2[0]._id}&categoryId=${v[0].category__expand && v[0].category__expand._id}`;
|
|
218
|
+
let flowIsUnfolded = false;
|
|
219
|
+
if (currentUrl == flowValue) {
|
|
220
|
+
flowIsUnfolded = true;
|
|
221
|
+
categoryIsUnfolded = true;
|
|
222
|
+
monitorIsUnfolded = true;
|
|
223
|
+
}
|
|
224
|
+
flows.push({
|
|
225
|
+
label: k2,
|
|
226
|
+
flow_name: k2,
|
|
227
|
+
options: {
|
|
228
|
+
level: 3,
|
|
229
|
+
value: v2[0]._id,
|
|
230
|
+
name: 'flow',
|
|
231
|
+
to: flowValue,
|
|
232
|
+
},
|
|
233
|
+
value: flowValue,
|
|
234
|
+
unfolded: flowIsUnfolded
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
if (currentUrl == categoryValue) {
|
|
198
238
|
categoryIsUnfolded = true;
|
|
199
239
|
monitorIsUnfolded = true;
|
|
200
240
|
}
|
|
201
|
-
|
|
202
|
-
label:
|
|
203
|
-
|
|
241
|
+
output.push({
|
|
242
|
+
label: k == 'null' || k == 'undefined' || !k? "未分类" : k,
|
|
243
|
+
children: flows,
|
|
244
|
+
category_name: k == 'null' || k == 'undefined' || !k ? "未分类" : k,
|
|
204
245
|
options: {
|
|
205
|
-
level:
|
|
206
|
-
value:
|
|
207
|
-
name: '
|
|
208
|
-
to:
|
|
246
|
+
level: 2,
|
|
247
|
+
value: v[0].category__expand && v[0].category__expand._id,
|
|
248
|
+
name: 'category',
|
|
249
|
+
to: categoryValue,
|
|
209
250
|
},
|
|
210
|
-
value:
|
|
211
|
-
unfolded:
|
|
251
|
+
value: categoryValue,
|
|
252
|
+
unfolded: categoryIsUnfolded
|
|
212
253
|
})
|
|
213
254
|
})
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
children: flows,
|
|
221
|
-
category_name: k == 'null' || k == 'undefined' || !k ? "未分类" : k,
|
|
222
|
-
options: {
|
|
223
|
-
level: 2,
|
|
224
|
-
value: v[0].category__expand && v[0].category__expand._id,
|
|
225
|
-
name: 'category',
|
|
226
|
-
to: categoryValue,
|
|
227
|
-
},
|
|
228
|
-
value: categoryValue,
|
|
229
|
-
unfolded: categoryIsUnfolded
|
|
230
|
-
})
|
|
231
|
-
})
|
|
232
|
-
output = lodash.sortBy(output, [function (o) {
|
|
233
|
-
return lodash.findIndex(categories, (e) => {
|
|
234
|
-
return e._id == o.options.value;
|
|
235
|
-
});
|
|
236
|
-
}]);
|
|
255
|
+
output = lodash.sortBy(output, [function (o) {
|
|
256
|
+
return lodash.findIndex(categories, (e) => {
|
|
257
|
+
return e._id == o.options.value;
|
|
258
|
+
});
|
|
259
|
+
}]);
|
|
260
|
+
}
|
|
237
261
|
}
|
|
238
262
|
} catch (error) {
|
|
239
263
|
console.log(error)
|
|
@@ -302,7 +302,22 @@
|
|
|
302
302
|
tpl.type = "input-text";
|
|
303
303
|
}
|
|
304
304
|
if(field.formula){
|
|
305
|
-
|
|
305
|
+
if(field.formula.startsWith('{') && (field.formula.endsWith('}') || field.formula.indexOf("}") > 0)){
|
|
306
|
+
// {申请人姓名}.organization.fullname 转换为 ${申请人姓名.organization.fullname}
|
|
307
|
+
// {申请人姓名.organization.fullname} 转换为 ${申请人姓名.organization.fullname}
|
|
308
|
+
if(field.formula.indexOf("}.") > 0){
|
|
309
|
+
// {申请人姓名}.organization.fullname
|
|
310
|
+
let formula = field.formula;
|
|
311
|
+
formula = formula.substring(1, formula.indexOf("}"));
|
|
312
|
+
tpl.value = `\${${formula}${field.formula.substring( field.formula.indexOf("}") + 1 )}}`;
|
|
313
|
+
} else {
|
|
314
|
+
// {申请人姓名.organization.fullname}
|
|
315
|
+
tpl.value = `$${field.formula}`;
|
|
316
|
+
}
|
|
317
|
+
}else{
|
|
318
|
+
// 替换掉所有双引号"
|
|
319
|
+
tpl.value = field.formula.replace(/"/g, '');
|
|
320
|
+
}
|
|
306
321
|
}
|
|
307
322
|
break;
|
|
308
323
|
case "number":
|
|
@@ -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数据
|
|
@@ -283,7 +283,16 @@ module.exports = {
|
|
|
283
283
|
},
|
|
284
284
|
getAppCategoriesIds: {
|
|
285
285
|
async handler(appId) {
|
|
286
|
-
|
|
286
|
+
// Check environment variable to control category filtering
|
|
287
|
+
// Default is enabled (when not set or set to any value except 'false')
|
|
288
|
+
// Only the string 'false' will disable the filtering
|
|
289
|
+
const enableCategoryFilter = process.env.STEEDOS_WORKFLOW_ENABLE_CATEGORY_FILTER !== 'false';
|
|
290
|
+
|
|
291
|
+
if (!enableCategoryFilter) {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (appId == "approve_workflow") {
|
|
287
296
|
return [];
|
|
288
297
|
}
|
|
289
298
|
const categories = await objectql.getObject('categories').directFind({ filters: [['app', '=', appId]] });
|
package/package.json
CHANGED
|
@@ -52,7 +52,27 @@
|
|
|
52
52
|
|
|
53
53
|
.instance-box-tree .antd-TreeControl{
|
|
54
54
|
padding: 0;
|
|
55
|
+
background: #ffffff;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Remove default tree styling to allow custom styling */
|
|
59
|
+
.instance-box-tree .antd-Tree-itemLabel-item{
|
|
60
|
+
padding: 0 !important;
|
|
61
|
+
margin: 0 !important;
|
|
62
|
+
display: flex !important;
|
|
63
|
+
align-items: center !important;
|
|
64
|
+
width: 100%; /* Ensure it spans the width */
|
|
55
65
|
}
|
|
66
|
+
|
|
67
|
+
/* Ensure the label container is also flex centered and full width */
|
|
68
|
+
.instance-box-tree .antd-Tree-itemLabel {
|
|
69
|
+
display: flex !important;
|
|
70
|
+
align-items: center !important;
|
|
71
|
+
width: 100%;
|
|
72
|
+
padding-right: 4px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Tree item wrapper - removed margin to allow full-row background */
|
|
56
76
|
/* .instance-box-tree .antd-TreeControl{
|
|
57
77
|
padding: 0;
|
|
58
78
|
border-bottom: 0px;
|
|
@@ -78,17 +98,42 @@
|
|
|
78
98
|
.instance-box-tree .antd-Tree-itemIcon{
|
|
79
99
|
margin-right: 4px;
|
|
80
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
|
+
|
|
81
114
|
.instance-box-tree .antd-Badge-text{
|
|
82
|
-
background: #
|
|
115
|
+
background: #e5e7eb;
|
|
116
|
+
color: #4b5563;
|
|
117
|
+
font-size: 11px;
|
|
118
|
+
padding: 0px 6px;
|
|
119
|
+
border-radius: 10px;
|
|
120
|
+
font-weight: 500;
|
|
121
|
+
line-height: 18px;
|
|
122
|
+
min-width: 18px;
|
|
123
|
+
display: inline-block;
|
|
124
|
+
text-align: center;
|
|
125
|
+
position: static !important;
|
|
126
|
+
transform: none !important;
|
|
83
127
|
}
|
|
84
128
|
.instance-box-tree .antd-Tree-item:nth-child(1) .antd-Badge-text{
|
|
85
|
-
background: #
|
|
129
|
+
background: #ef4444;
|
|
130
|
+
color: #ffffff;
|
|
86
131
|
}
|
|
87
132
|
/* .instance-box-tree .antd-Tree-itemLabel-item{
|
|
88
133
|
height: 1.6rem;
|
|
89
134
|
} */
|
|
90
135
|
.instance-box-tree .antd-TplField span{
|
|
91
|
-
width: 85%;
|
|
136
|
+
/* width: 85%; */
|
|
92
137
|
display: block;
|
|
93
138
|
overflow: hidden;
|
|
94
139
|
white-space: nowrap;
|
|
@@ -115,12 +160,157 @@
|
|
|
115
160
|
border: none !important;
|
|
116
161
|
}
|
|
117
162
|
|
|
118
|
-
|
|
119
|
-
|
|
163
|
+
/* Base styling for all menu labels - must be specific enough */
|
|
164
|
+
.instance-box-tree .antd-Tree .instance-menu-label {
|
|
165
|
+
font-weight: 400;
|
|
166
|
+
color: #6b7280;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* Tree item spacing for proper layout */
|
|
170
|
+
.instance-box-tree .antd-Tree-item {
|
|
171
|
+
margin: 2px 8px;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* Selected state styling - Using robust pseudo-element strategy for cross-browser support */
|
|
175
|
+
|
|
176
|
+
/* 1. Reset positions to ensure clean background layering */
|
|
177
|
+
.instance-box-tree .antd-Tree-item {
|
|
178
|
+
margin: 2px 8px; /* Maintain standard margin */
|
|
179
|
+
position: relative !important; /* Context for absolute positioning */
|
|
180
|
+
z-index: 0; /* Create stacking context so -1 doesn't go behind the white container */
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* 2. Remove ANY positioning from the label so it doesn't trap the pseudo-element */
|
|
184
|
+
.instance-box-tree .antd-Tree-itemLabel.is-checked {
|
|
185
|
+
background-color: transparent !important;
|
|
186
|
+
position: static !important;
|
|
120
187
|
}
|
|
121
188
|
|
|
122
|
-
|
|
123
|
-
|
|
189
|
+
/* 3. Helper to make label static if checked is on item */
|
|
190
|
+
.instance-box-tree .antd-Tree-item.is-checked > .antd-Tree-itemLabel {
|
|
191
|
+
background-color: transparent !important;
|
|
192
|
+
position: static !important;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* 4. The background pseudo-element */
|
|
196
|
+
.instance-box-tree .antd-Tree-itemLabel.is-checked::before,
|
|
197
|
+
.instance-box-tree .antd-Tree-item.is-checked > .antd-Tree-itemLabel::before {
|
|
198
|
+
content: "";
|
|
199
|
+
position: absolute;
|
|
200
|
+
left: 0;
|
|
201
|
+
right: 0;
|
|
202
|
+
top: 0; /* Cover full height (margin handles the spacing) */
|
|
203
|
+
bottom: 0;
|
|
204
|
+
background-color: var(--color-brand-50, #dbeafe);
|
|
205
|
+
border-radius: 6px;
|
|
206
|
+
z-index: -1; /* Behind text */
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.instance-box-tree .antd-Tree-itemLabel.is-checked .instance-menu-label,
|
|
210
|
+
.instance-box-tree .antd-Tree-item.is-checked .instance-menu-label {
|
|
211
|
+
color: var(--Menu-light-fontColor-onHover, #1e40af) !important;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.instance-box-tree .antd-Tree-itemLabel.is-checked .antd-Tree-itemIcon,
|
|
215
|
+
.instance-box-tree .antd-Tree-item.is-checked .antd-Tree-itemIcon {
|
|
216
|
+
color: var(--Menu-light-fontColor-onHover, #1e40af) !important;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* Hover state - entire row with light gray background */
|
|
220
|
+
.instance-box-tree .antd-Tree-item:hover {
|
|
221
|
+
background-color: #f3f4f6;
|
|
222
|
+
border-radius: 6px;
|
|
223
|
+
margin: 2px 8px;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.instance-box-tree .antd-Tree-item.is-checked:hover {
|
|
227
|
+
background-color: var(--color-brand-50, #dbeafe) !important;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* Group title styling - larger and bolder */
|
|
231
|
+
.instance-box-tree .antd-Tree .antd-Tree-item[aria-level="1"] .instance-menu-label {
|
|
232
|
+
font-size: 13px !important;
|
|
233
|
+
font-weight: 600;
|
|
234
|
+
color: #374151;
|
|
235
|
+
}
|
|
236
|
+
--isL
|
|
237
|
+
/* Child item (non-leaf) styling - normal size and weight */
|
|
238
|
+
.instance-box-tree .antd-Tree .antd-Tree-item[aria-level="2"]:not(.is-leaf) .instance-menu-label {
|
|
239
|
+
font-size: 13px !important;
|
|
240
|
+
font-weight: 400;
|
|
241
|
+
color: #6b7280;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Leaf node styling - darker and larger for prominence */
|
|
245
|
+
.instance-box-tree .antd-Tree .antd-Tree-item--isLeaf .instance-menu-label {
|
|
246
|
+
font-size: 14px !important;
|
|
247
|
+
font-weight: 600 !important;
|
|
248
|
+
color: rgb(71 85 105 / var(--tw-text-opacity, 1)) !important;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* Override leaf node color when selected */
|
|
252
|
+
.instance-box-tree .antd-Tree .antd-Tree-item--isLeaf .antd-Tree-itemLabel.is-checked .instance-menu-label {
|
|
253
|
+
color: var(--Menu-light-fontColor-onHover, #1e40af) !important;
|
|
254
|
+
font-weight: 600 !important;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Fix alignment and spacing for the custom menu template wrapper */
|
|
258
|
+
.instance-box-tree .antd-Tree-itemText > div {
|
|
259
|
+
padding-top: 0px !important; /* Reduced from 4px to fix "text too low" */
|
|
260
|
+
padding-bottom: 0px !important;
|
|
261
|
+
padding-right: 0px !important; /* Reduced to move content closer to right edge */
|
|
262
|
+
line-height: 24px; /* Explicit line height to ensure centering */
|
|
263
|
+
display: flex;
|
|
264
|
+
align-items: center;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* Ensure the text container takes available width */
|
|
268
|
+
.instance-box-tree .antd-Tree-itemText {
|
|
269
|
+
flex: 1;
|
|
270
|
+
display: flex;
|
|
271
|
+
align-items: center;
|
|
272
|
+
position: relative; /* Ensure z-index works if needed */
|
|
273
|
+
z-index: 1;
|
|
274
|
+
min-width: 0; /* Enable truncation */
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* Icon styling */
|
|
278
|
+
.instance-box-tree .antd-Tree-itemIcon{
|
|
279
|
+
color: #6b7280;
|
|
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;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* Arrow styling */
|
|
306
|
+
.instance-box-tree .antd-Tree-itemArrow{
|
|
307
|
+
color: #9ca3af;
|
|
308
|
+
transition: transform 0.2s ease;
|
|
309
|
+
margin-right: 4px;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.instance-box-tree .antd-Tree-item.is-expanded > .antd-Tree-itemArrow{
|
|
313
|
+
transform: rotate(90deg);
|
|
124
314
|
}
|
|
125
315
|
|
|
126
316
|
|
|
@@ -360,7 +550,7 @@ tbody .color-priority-muted *{
|
|
|
360
550
|
}
|
|
361
551
|
|
|
362
552
|
.steedos-instance-detail-wrapper{
|
|
363
|
-
height: calc(100vh -
|
|
553
|
+
height: calc(100vh - 101px);
|
|
364
554
|
}
|
|
365
555
|
|
|
366
556
|
.steedos-amis-instance-view-body .antd-Panel-title{
|
|
@@ -372,10 +562,27 @@ tbody .color-priority-muted *{
|
|
|
372
562
|
z-index: 900;
|
|
373
563
|
}
|
|
374
564
|
|
|
375
|
-
/*
|
|
376
|
-
|
|
377
|
-
height
|
|
378
|
-
}
|
|
565
|
+
/* Sidebar wrapper - no scrolling as parent handles it */
|
|
566
|
+
.instances-sidebar-wrapper{
|
|
567
|
+
/* Removed overflow and height - parent layer handles scrolling */
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.instances-sidebar-wrapper::-webkit-scrollbar {
|
|
571
|
+
width: 6px;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.instances-sidebar-wrapper::-webkit-scrollbar-track {
|
|
575
|
+
background: transparent;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.instances-sidebar-wrapper::-webkit-scrollbar-thumb {
|
|
579
|
+
background: #d1d5db;
|
|
580
|
+
border-radius: 3px;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.instances-sidebar-wrapper::-webkit-scrollbar-thumb:hover {
|
|
584
|
+
background: #9ca3af;
|
|
585
|
+
}
|
|
379
586
|
|
|
380
587
|
.steedos-instance-related-view-wrapper{
|
|
381
588
|
.antd-Page-header{
|
|
@@ -383,6 +590,12 @@ tbody .color-priority-muted *{
|
|
|
383
590
|
}
|
|
384
591
|
}
|
|
385
592
|
|
|
593
|
+
.instance-form pre{
|
|
594
|
+
white-space: pre-wrap; /* 保留空白符,允许换行 */
|
|
595
|
+
word-wrap: break-word; /* 长单词或URL换行 */
|
|
596
|
+
overflow-wrap: break-word; /* 更现代的属性 */
|
|
597
|
+
}
|
|
598
|
+
|
|
386
599
|
|
|
387
600
|
/* 公共打印隐藏样式 */
|
|
388
601
|
@media print {
|
|
@@ -408,4 +621,19 @@ tbody .color-priority-muted *{
|
|
|
408
621
|
.font-normal{
|
|
409
622
|
border: none !important;
|
|
410
623
|
}
|
|
411
|
-
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/* .steedos-instance-detail-wrapper{
|
|
627
|
+
height: auto !important;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.steedos-amis-instance-view-body{
|
|
631
|
+
height: auto !important;
|
|
632
|
+
}
|
|
633
|
+
.creator-content-wrapper{
|
|
634
|
+
overflow: unset !important;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
body{
|
|
638
|
+
overflow: auto !important;
|
|
639
|
+
} */
|