From f2d76bc72daf0f8e3f719539e399daf9062cf80f Mon Sep 17 00:00:00 2001 From: wangdan-fit2cloud Date: Mon, 31 Aug 2026 19:38:45 +0800 Subject: [PATCH] feat: applicaiton detail --- ui/src/assets/iconfont.js | 2 +- ui/src/components/COMPONENT_README.md | 9 +- .../components/business/folder-tree/index.vue | 15 ++- .../business/workspace-dropdown/index.vue | 2 +- ui/src/layout/LayoutHeader.vue | 2 +- ui/src/layout/ResourceDetailLayout.vue | 70 ++++++++++ ui/src/router/ROUTE_README.md | 48 ++++--- ui/src/router/admin/index.ts | 4 +- .../admin/system/modules/application.ts | 13 +- ui/src/router/admin/types.ts | 2 + ui/src/router/admin/utils.ts | 60 ++++++--- .../admin/workspace/modules/application.ts | 39 ++++-- ui/src/styles/app.scss | 8 ++ ui/src/views/VIEW_README.md | 46 +++++-- .../WorkspaceApplicationDetail.vue | 72 ++++++++++ ui/src/views/application-detail/context.ts | 26 ++++ .../overview/OverviewView.vue | 3 + .../setting/SimpleSettingView.vue | 3 + ui/src/views/application/ApplicationView.vue | 45 ++++++- .../application-card/ApplicationCard.vue | 10 +- .../SettingApplicationAction.vue | 13 +- .../AdvancedCreateDialog.vue | 125 ++++++++++++++++++ .../CreateApplicationDropdown.vue} | 46 +++++-- .../create-application/SimpleCreateDialog.vue | 82 ++++++++++++ .../tool/components/CreateToolDropdown.vue | 4 +- 25 files changed, 647 insertions(+), 102 deletions(-) create mode 100644 ui/src/layout/ResourceDetailLayout.vue create mode 100644 ui/src/views/application-detail/WorkspaceApplicationDetail.vue create mode 100644 ui/src/views/application-detail/context.ts create mode 100644 ui/src/views/application-detail/overview/OverviewView.vue create mode 100644 ui/src/views/application-detail/setting/SimpleSettingView.vue create mode 100644 ui/src/views/application/create-application/AdvancedCreateDialog.vue rename ui/src/views/application/{components/ApplicationCreateDropdown.vue => create-application/CreateApplicationDropdown.vue} (59%) create mode 100644 ui/src/views/application/create-application/SimpleCreateDialog.vue diff --git a/ui/src/assets/iconfont.js b/ui/src/assets/iconfont.js index 816a752493a..b18dcddc372 100644 --- a/ui/src/assets/iconfont.js +++ b/ui/src/assets/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_5200172='',(l=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var o,v,t,i,c,d=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!l.__iconfont__svg__cssinject__){l.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}o=function(){var a,h=document.createElement("div");h.innerHTML=l._iconfont_svg_string_5200172,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?d(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),o()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(t=o,i=l.document,c=!1,m(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,e())})}function e(){c||(c=!0,t())}function m(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(m,50)}e()}})(window); \ No newline at end of file +window._iconfont_svg_string_5200172='',(h=>{var a=(l=(l=document.getElementsByTagName("script"))[l.length-1]).getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var o,v,t,i,c,d=function(a,l){l.parentNode.insertBefore(a,l)};if(a&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}o=function(){var a,l=document.createElement("div");l.innerHTML=h._iconfont_svg_string_5200172,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?d(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),o()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(t=o,i=h.document,c=!1,m(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,e())})}function e(){c||(c=!0,t())}function m(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(m,50)}e()}})(window); \ No newline at end of file diff --git a/ui/src/components/COMPONENT_README.md b/ui/src/components/COMPONENT_README.md index 98ece809cfe..eb80c597cd4 100644 --- a/ui/src/components/COMPONENT_README.md +++ b/ui/src/components/COMPONENT_README.md @@ -941,9 +941,10 @@ const formValue = ref>({}) ### FolderTree Workspace 的文件夹虚拟树业务组件。组件根据当前资源上下文和 `source` 调用统一文件夹接口,负责 -文件夹树查询、搜索、排序、创建、编辑、移动和删除。通过 `v-model` 控制当前文件夹 ID,选择 -文件夹时触发 `select`。`showAll`、`showShared` 分别控制全部和共享入口,`rootLabel` 可覆盖根入口 -文案,`disabledFolderIds` 用于只选场景中禁用指定节点。 +文件夹树查询、搜索、排序、创建、编辑、移动和删除。通过 `v-model` 控制当前文件夹 ID,首次加载 +完成后通过 `loaded` 返回该 ID 对应的完整文件夹,选择文件夹时触发 `select`。传入的 ID 不存在时, +优先回退到“全部”入口,否则回退到首个可用文件夹。`showAll`、`showShared` 分别控制全部和共享入口, +`rootLabel` 可覆盖根入口文案,`disabledFolderIds` 用于只选场景中禁用指定节点。 ```vue - + ``` 页面顶部需要触发根目录创建时,通过页面语义处理方法调用组件暴露的 `openCreate()`;外部数据变化 diff --git a/ui/src/components/business/folder-tree/index.vue b/ui/src/components/business/folder-tree/index.vue index 6b8aa0bed84..87f93748f78 100644 --- a/ui/src/components/business/folder-tree/index.vue +++ b/ui/src/components/business/folder-tree/index.vue @@ -29,7 +29,7 @@ const props = withDefaults( const currentNodeKey = defineModel({ default: FOLDER_ENTRY_ID.ALL }) -const emit = defineEmits<{ select: [folder: FolderItem] }>() +const emit = defineEmits<{ loaded: [folder?: FolderItem]; select: [folder: FolderItem] }>() const folderEntries = computed(() => FOLDER_ENTRIES[props.source]) const rootFolderEntry = computed(() => ({ ...folderEntries.value.all, name: props.rootLabel || folderEntries.value.all.name })) @@ -250,6 +250,13 @@ function findFolderById(folders: FolderItem[], folderId: string): FolderItem | u } } +function resolveCurrentFolder() { + if (currentNodeKey.value === FOLDER_ENTRY_ID.ALL && props.showAll) return rootFolderEntry.value + if (currentNodeKey.value === FOLDER_ENTRY_ID.SHARED && props.showShared) return folderEntries.value.shared + + return findFolderById(folderTreeData.value, currentNodeKey.value) ?? (props.showAll ? rootFolderEntry.value : sortTreeData.value[0]) +} + function getFolderDeleteContext(folder: FolderItem) { const deletedFolder = findFolderById(folderTreeData.value, folder.id) ?? folder const positionCache = readCustomPositions() @@ -314,7 +321,11 @@ onMounted(() => { if (Object.values(FOLDER_SORT).includes(savedSort as FolderSort)) { currentSort.value = savedSort as FolderSort } - loadFolders() + void loadFolders().then(() => { + const currentFolder = resolveCurrentFolder() + if (currentFolder) currentNodeKey.value = currentFolder.id + emit('loaded', currentFolder) + }) }) defineExpose({ refresh: loadFolders, openCreate: handleOpenCreateFolder }) diff --git a/ui/src/components/business/workspace-dropdown/index.vue b/ui/src/components/business/workspace-dropdown/index.vue index 17a5c62304d..83d6ae5cb97 100644 --- a/ui/src/components/business/workspace-dropdown/index.vue +++ b/ui/src/components/business/workspace-dropdown/index.vue @@ -12,7 +12,7 @@ const emit = defineEmits<{ select: [option: WorkspaceItem] }>()