浏览代码

帮助页面浏览方式修改

liuq 1 月之前
父节点
当前提交
30868062d9
共有 1 个文件被更改,包括 243 次插入37 次删除
  1. 243 37
      frontend/src/views/Help.vue

+ 243 - 37
frontend/src/views/Help.vue

@@ -1,49 +1,62 @@
 <template>
   <div class="help-container">
     <div class="help-header">
-      <h1>使用帮助</h1>
+      <div class="header-left">
+        <el-select 
+          v-model="selectedTabValue" 
+          placeholder="请选择帮助主题"
+          size="default"
+          style="width: 250px; margin-right: 20px;"
+          @change="handleTabSelect"
+        >
+          <el-option
+            v-for="tab in tabOptions"
+            :key="tab.value"
+            :label="tab.label"
+            :value="tab.value"
+          />
+        </el-select>
+        <h1>使用帮助</h1>
+      </div>
       <el-button type="info" plain @click="openSwagger">
         <el-icon style="margin-right: 5px"><Link /></el-icon>
         API 文档 (Swagger)
       </el-button>
     </div>
     
-    <div class="tab-selector">
-      <el-select 
-        v-model="activeTab" 
-        placeholder="请选择帮助主题"
-        size="large"
-        style="width: 100%; max-width: 500px;"
-      >
-        <el-option
-          v-for="tab in tabOptions"
-          :key="tab.value"
-          :label="tab.label"
-          :value="tab.value"
-        />
-      </el-select>
+    <div class="tabs-container" v-if="tabs.length > 0">
+      <div class="tabs-wrapper">
+        <div 
+          v-for="(tab, index) in tabs" 
+          :key="tab.id"
+          class="tab-item"
+          :class="{ active: activeTabId === tab.id }"
+          @click="switchTab(tab.id)"
+        >
+          <span class="tab-label">{{ tab.label }}</span>
+          <el-icon 
+            class="tab-close" 
+            @click.stop="closeTab(tab.id)"
+            v-if="tabs.length > 1"
+          >
+            <Close />
+          </el-icon>
+        </div>
+      </div>
     </div>
 
     <div class="tab-content">
-      <IntegrationOverview v-if="activeTab === 'integration-overview'" />
-      <FastIntegration v-if="activeTab === 'fast-integration'" />
-      <CustomLogin v-if="activeTab === 'custom-login'" />
-      <TicketExchange v-if="activeTab === 'ticket-exchange'" />
-      <ApiComparison v-if="activeTab === 'api-comparison'" />
-      <AccountSync v-if="activeTab === 'account-sync'" />
-      <UserSyncPull v-if="activeTab === 'user-sync-pull'" />
-      <MessageIntegration v-if="activeTab === 'message-integration'" />
-      <MinIOFilePermissions v-if="activeTab === 'minio-file-permissions'" />
-      <AccountManagement v-if="activeTab === 'account-management'" />
-      <OidcIntegration v-if="activeTab === 'oidc-integration'" />
-      <Tbd v-if="activeTab === 'tbd'" />
+      <component 
+        :is="getComponentByValue(activeTabValue)" 
+        v-if="activeTabValue"
+      />
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
-import { Link } from '@element-plus/icons-vue'
+import { ref, computed } from 'vue'
+import { Link, Close } from '@element-plus/icons-vue'
 import { useHelpDocs } from '../composables/useHelpDocs'
 
 // Import new components
@@ -60,7 +73,16 @@ import AccountManagement from './help/AccountManagement.vue'
 import OidcIntegration from './help/OidcIntegration.vue'
 import Tbd from './help/Tbd.vue'
 
-const activeTab = ref('integration-overview')
+interface Tab {
+  id: string
+  value: string
+  label: string
+}
+
+const selectedTabValue = ref('')
+const tabs = ref<Tab[]>([])
+const activeTabId = ref<string>('')
+
 const { openSwagger } = useHelpDocs()
 
 // Tab选项配置
@@ -78,6 +100,97 @@ const tabOptions = [
   { label: 'OIDC 集成指南', value: 'oidc-integration' },
   { label: '其他帮助 (待定)', value: 'tbd' }
 ]
+
+// 组件映射
+const componentMap: Record<string, any> = {
+  'integration-overview': IntegrationOverview,
+  'fast-integration': FastIntegration,
+  'custom-login': CustomLogin,
+  'ticket-exchange': TicketExchange,
+  'api-comparison': ApiComparison,
+  'account-sync': AccountSync,
+  'user-sync-pull': UserSyncPull,
+  'message-integration': MessageIntegration,
+  'minio-file-permissions': MinIOFilePermissions,
+  'account-management': AccountManagement,
+  'oidc-integration': OidcIntegration,
+  'tbd': Tbd
+}
+
+// 当前激活的标签页值
+const activeTabValue = computed(() => {
+  const activeTab = tabs.value.find(tab => tab.id === activeTabId.value)
+  return activeTab?.value || ''
+})
+
+// 根据value获取组件
+const getComponentByValue = (value: string) => {
+  return componentMap[value] || null
+}
+
+// 处理下拉框选择
+const handleTabSelect = (value: string) => {
+  // 检查是否已存在该标签页
+  const existingTab = tabs.value.find(tab => tab.value === value)
+  
+  if (existingTab) {
+    // 如果已存在,切换到该标签页
+    activeTabId.value = existingTab.id
+  } else {
+    // 如果不存在,创建新标签页
+    const tabOption = tabOptions.find(opt => opt.value === value)
+    if (tabOption) {
+      const newTab: Tab = {
+        id: `tab-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
+        value: value,
+        label: tabOption.label
+      }
+      tabs.value.push(newTab)
+      activeTabId.value = newTab.id
+    }
+  }
+  
+  // 清空下拉框选择(可选,让用户知道已经添加了)
+  selectedTabValue.value = ''
+}
+
+// 切换标签页
+const switchTab = (tabId: string) => {
+  activeTabId.value = tabId
+}
+
+// 关闭标签页
+const closeTab = (tabId: string) => {
+  const index = tabs.value.findIndex(tab => tab.id === tabId)
+  if (index !== -1) {
+    tabs.value.splice(index, 1)
+    
+    // 如果关闭的是当前激活的标签页,切换到其他标签页
+    if (activeTabId.value === tabId) {
+      if (tabs.value.length > 0) {
+        // 优先切换到右侧的标签页,如果没有则切换到左侧
+        const newIndex = index < tabs.value.length ? index : index - 1
+        activeTabId.value = tabs.value[newIndex].id
+      } else {
+        activeTabId.value = ''
+      }
+    }
+  }
+}
+
+// 初始化时添加默认标签页
+const initDefaultTab = () => {
+  const defaultTab: Tab = {
+    id: `tab-${Date.now()}`,
+    value: 'integration-overview',
+    label: '平台对接概述'
+  }
+  tabs.value.push(defaultTab)
+  activeTabId.value = defaultTab.id
+}
+
+// 组件挂载时初始化
+initDefaultTab()
 </script>
 
 <style scoped>
@@ -101,14 +214,10 @@ const tabOptions = [
   padding-bottom: 15px;
 }
 
-.tab-selector {
-  margin-bottom: 30px;
+.header-left {
   display: flex;
-  justify-content: flex-start;
-}
-
-.tab-content {
-  margin-top: 20px;
+  align-items: center;
+  flex: 1;
 }
 
 h1 {
@@ -119,4 +228,101 @@ h1 {
   padding: 0;
   border-bottom: none;
 }
+
+.tabs-container {
+  margin-bottom: 20px;
+  border-bottom: 1px solid #eaecef;
+}
+
+.tabs-wrapper {
+  display: flex;
+  gap: 4px;
+  overflow-x: auto;
+  padding-bottom: 0;
+}
+
+.tabs-wrapper::-webkit-scrollbar {
+  height: 6px;
+}
+
+.tabs-wrapper::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 3px;
+}
+
+.tabs-wrapper::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 3px;
+}
+
+.tabs-wrapper::-webkit-scrollbar-thumb:hover {
+  background: #a8a8a8;
+}
+
+.tab-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 10px 16px;
+  background-color: #f5f7fa;
+  border: 1px solid #e4e7ed;
+  border-bottom: none;
+  border-radius: 4px 4px 0 0;
+  cursor: pointer;
+  transition: all 0.3s;
+  white-space: nowrap;
+  position: relative;
+  min-width: 120px;
+  max-width: 250px;
+}
+
+.tab-item:hover {
+  background-color: #ecf5ff;
+  border-color: #b3d8ff;
+}
+
+.tab-item.active {
+  background-color: #fff;
+  border-color: #409eff;
+  border-bottom-color: #fff;
+  color: #409eff;
+  font-weight: 500;
+  z-index: 1;
+  margin-bottom: -1px;
+}
+
+.tab-label {
+  flex: 1;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  font-size: 14px;
+}
+
+.tab-close {
+  font-size: 14px;
+  color: #909399;
+  cursor: pointer;
+  padding: 2px;
+  border-radius: 2px;
+  transition: all 0.2s;
+  flex-shrink: 0;
+}
+
+.tab-close:hover {
+  background-color: #f56c6c;
+  color: #fff;
+}
+
+.tab-item.active .tab-close {
+  color: #409eff;
+}
+
+.tab-item.active .tab-close:hover {
+  background-color: #f56c6c;
+  color: #fff;
+}
+
+.tab-content {
+  margin-top: 20px;
+}
 </style>