@steedos-labs/plugin-workflow 3.0.14 → 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/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/services/flows.service.js +3 -2
- package/package.json +1 -1
- package/public/workflow/index.css +40 -2
|
@@ -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
|
}
|
|
@@ -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
|
@@ -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 */
|