Help.vue 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. <template>
  2. <div class="help-container">
  3. <div class="help-header">
  4. <div class="header-left">
  5. <el-select
  6. v-model="selectedTabValue"
  7. placeholder="请选择帮助主题"
  8. size="default"
  9. style="width: 250px; margin-right: 20px;"
  10. @change="handleTabSelect"
  11. >
  12. <el-option
  13. v-for="tab in tabOptions"
  14. :key="tab.value"
  15. :label="tab.label"
  16. :value="tab.value"
  17. />
  18. </el-select>
  19. <h1>使用帮助</h1>
  20. </div>
  21. <el-button type="info" plain @click="openSwagger">
  22. <el-icon style="margin-right: 5px"><Link /></el-icon>
  23. API 文档 (Swagger)
  24. </el-button>
  25. </div>
  26. <div class="tabs-container" v-if="tabs.length > 0">
  27. <div class="tabs-wrapper">
  28. <div
  29. v-for="tab in tabs"
  30. :key="tab.id"
  31. class="tab-item"
  32. :class="{ active: activeTabId === tab.id }"
  33. @click="switchTab(tab.id)"
  34. >
  35. <span class="tab-label">{{ tab.label }}</span>
  36. <el-icon
  37. class="tab-close"
  38. @click.stop="closeTab(tab.id)"
  39. v-if="tabs.length > 1"
  40. >
  41. <Close />
  42. </el-icon>
  43. </div>
  44. </div>
  45. </div>
  46. <div class="tab-content">
  47. <component
  48. :is="getComponentByValue(activeTabValue)"
  49. v-if="activeTabValue"
  50. />
  51. </div>
  52. </div>
  53. </template>
  54. <script setup lang="ts">
  55. import { ref, computed, provide } from 'vue'
  56. import { Link, Close } from '@element-plus/icons-vue'
  57. import { useHelpDocs } from '../composables/useHelpDocs'
  58. // Import new components
  59. import IntegrationOverview from './help/IntegrationOverview.vue'
  60. import FastIntegration from './help/FastIntegration.vue'
  61. import CustomLogin from './help/CustomLogin.vue'
  62. import TicketExchange from './help/TicketExchange.vue'
  63. import ApiComparison from './help/ApiComparison.vue'
  64. import AccountSync from './help/AccountSync.vue'
  65. import UserSyncPull from './help/UserSyncPull.vue'
  66. import MessageIntegration from './help/MessageIntegration.vue'
  67. import MinIOFilePermissions from './help/MinIOFilePermissions.vue'
  68. import AccountManagement from './help/AccountManagement.vue'
  69. import OidcIntegration from './help/OidcIntegration.vue'
  70. import ClientApi from './help/ClientApi.vue'
  71. import Tbd from './help/Tbd.vue'
  72. interface Tab {
  73. id: string
  74. value: string
  75. label: string
  76. }
  77. const selectedTabValue = ref('')
  78. const tabs = ref<Tab[]>([])
  79. const activeTabId = ref<string>('')
  80. const { openSwagger } = useHelpDocs()
  81. // Tab选项配置
  82. const tabOptions = [
  83. { label: '平台对接概述', value: 'integration-overview' },
  84. { label: '快速对接 (Redirect)', value: 'fast-integration' },
  85. { label: '自定义登录页面', value: 'custom-login' },
  86. { label: '票据交互', value: 'ticket-exchange' },
  87. { label: '接口对比', value: 'api-comparison' },
  88. { label: '账号同步 (M2M)', value: 'account-sync' },
  89. { label: '全量用户同步', value: 'user-sync-pull' },
  90. { label: '消息中心对接', value: 'message-integration' },
  91. { label: '客户端接口 API', value: 'client-api' },
  92. { label: '文件存储权限控制', value: 'minio-file-permissions' },
  93. { label: '平台账号管理', value: 'account-management' },
  94. { label: 'OIDC 集成指南', value: 'oidc-integration' },
  95. { label: '其他帮助 (待定)', value: 'tbd' }
  96. ]
  97. // 组件映射
  98. const componentMap: Record<string, any> = {
  99. 'integration-overview': IntegrationOverview,
  100. 'fast-integration': FastIntegration,
  101. 'custom-login': CustomLogin,
  102. 'ticket-exchange': TicketExchange,
  103. 'api-comparison': ApiComparison,
  104. 'account-sync': AccountSync,
  105. 'user-sync-pull': UserSyncPull,
  106. 'message-integration': MessageIntegration,
  107. 'client-api': ClientApi,
  108. 'minio-file-permissions': MinIOFilePermissions,
  109. 'account-management': AccountManagement,
  110. 'oidc-integration': OidcIntegration,
  111. 'tbd': Tbd
  112. }
  113. // 当前激活的标签页值
  114. const activeTabValue = computed(() => {
  115. const activeTab = tabs.value.find(tab => tab.id === activeTabId.value)
  116. return activeTab?.value || ''
  117. })
  118. // 根据value获取组件
  119. const getComponentByValue = (value: string) => {
  120. return componentMap[value] || null
  121. }
  122. // 处理下拉框选择
  123. const handleTabSelect = (value: string) => {
  124. // 检查是否已存在该标签页
  125. const existingTab = tabs.value.find(tab => tab.value === value)
  126. if (existingTab) {
  127. // 如果已存在,切换到该标签页
  128. activeTabId.value = existingTab.id
  129. } else {
  130. // 如果不存在,创建新标签页
  131. const tabOption = tabOptions.find(opt => opt.value === value)
  132. if (tabOption) {
  133. const newTab: Tab = {
  134. id: `tab-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
  135. value: value,
  136. label: tabOption.label
  137. }
  138. tabs.value.push(newTab)
  139. activeTabId.value = newTab.id
  140. }
  141. }
  142. // 清空下拉框选择(可选,让用户知道已经添加了)
  143. selectedTabValue.value = ''
  144. }
  145. // 切换标签页
  146. const switchTab = (tabId: string) => {
  147. activeTabId.value = tabId
  148. }
  149. // 关闭标签页
  150. const closeTab = (tabId: string) => {
  151. const index = tabs.value.findIndex(tab => tab.id === tabId)
  152. if (index !== -1) {
  153. tabs.value.splice(index, 1)
  154. // 如果关闭的是当前激活的标签页,切换到其他标签页
  155. if (activeTabId.value === tabId) {
  156. if (tabs.value.length > 0) {
  157. // 优先切换到右侧的标签页,如果没有则切换到左侧
  158. const newIndex = index < tabs.value.length ? index : index - 1
  159. activeTabId.value = tabs.value[newIndex].id
  160. } else {
  161. activeTabId.value = ''
  162. }
  163. }
  164. }
  165. }
  166. // 初始化时添加默认标签页
  167. const initDefaultTab = () => {
  168. const defaultTab: Tab = {
  169. id: `tab-${Date.now()}`,
  170. value: 'integration-overview',
  171. label: '平台对接概述'
  172. }
  173. tabs.value.push(defaultTab)
  174. activeTabId.value = defaultTab.id
  175. }
  176. // 组件挂载时初始化
  177. initDefaultTab()
  178. // 提供给子组件使用,用于切换tab
  179. provide('switchHelpTab', handleTabSelect)
  180. </script>
  181. <style scoped>
  182. .help-container {
  183. padding: 30px;
  184. max-width: 1000px;
  185. margin: 0 auto;
  186. background-color: #fff;
  187. border-radius: 8px;
  188. min-height: 80vh;
  189. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
  190. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  191. }
  192. .help-header {
  193. display: flex;
  194. justify-content: space-between;
  195. align-items: center;
  196. border-bottom: 1px solid #eaecef;
  197. margin-bottom: 20px;
  198. padding-bottom: 15px;
  199. }
  200. .header-left {
  201. display: flex;
  202. align-items: center;
  203. flex: 1;
  204. }
  205. h1 {
  206. font-size: 28px;
  207. color: #1f2f3d;
  208. font-weight: 600;
  209. margin: 0;
  210. padding: 0;
  211. border-bottom: none;
  212. }
  213. .tabs-container {
  214. margin-bottom: 20px;
  215. border-bottom: 1px solid #eaecef;
  216. }
  217. .tabs-wrapper {
  218. display: flex;
  219. gap: 4px;
  220. overflow-x: auto;
  221. padding-bottom: 0;
  222. }
  223. .tabs-wrapper::-webkit-scrollbar {
  224. height: 6px;
  225. }
  226. .tabs-wrapper::-webkit-scrollbar-track {
  227. background: #f1f1f1;
  228. border-radius: 3px;
  229. }
  230. .tabs-wrapper::-webkit-scrollbar-thumb {
  231. background: #c1c1c1;
  232. border-radius: 3px;
  233. }
  234. .tabs-wrapper::-webkit-scrollbar-thumb:hover {
  235. background: #a8a8a8;
  236. }
  237. .tab-item {
  238. display: flex;
  239. align-items: center;
  240. gap: 8px;
  241. padding: 10px 16px;
  242. background-color: #f5f7fa;
  243. border: 1px solid #e4e7ed;
  244. border-bottom: none;
  245. border-radius: 4px 4px 0 0;
  246. cursor: pointer;
  247. transition: all 0.3s;
  248. white-space: nowrap;
  249. position: relative;
  250. min-width: 120px;
  251. max-width: 250px;
  252. }
  253. .tab-item:hover {
  254. background-color: #ecf5ff;
  255. border-color: #b3d8ff;
  256. }
  257. .tab-item.active {
  258. background-color: #fff;
  259. border-color: #409eff;
  260. border-bottom-color: #fff;
  261. color: #409eff;
  262. font-weight: 500;
  263. z-index: 1;
  264. margin-bottom: -1px;
  265. }
  266. .tab-label {
  267. flex: 1;
  268. overflow: hidden;
  269. text-overflow: ellipsis;
  270. font-size: 14px;
  271. }
  272. .tab-close {
  273. font-size: 14px;
  274. color: #909399;
  275. cursor: pointer;
  276. padding: 2px;
  277. border-radius: 2px;
  278. transition: all 0.2s;
  279. flex-shrink: 0;
  280. }
  281. .tab-close:hover {
  282. background-color: #f56c6c;
  283. color: #fff;
  284. }
  285. .tab-item.active .tab-close {
  286. color: #409eff;
  287. }
  288. .tab-item.active .tab-close:hover {
  289. background-color: #f56c6c;
  290. color: #fff;
  291. }
  292. .tab-content {
  293. margin-top: 20px;
  294. }
  295. </style>