|
|
@@ -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>
|