mirror of
				https://github.com/tiny-craft/tiny-rdm.git
				synced 2025-10-31 10:36:22 +08:00 
			
		
		
		
	feat: adjusted the content pane to accommodate more information (add sub content tabs).
This commit is contained in:
		| @@ -1,16 +1,19 @@ | ||||
| <script setup> | ||||
| import { computed, onMounted, onUnmounted, reactive, watch } from 'vue' | ||||
| import { types } from '@/consts/support_redis_type.js' | ||||
| import ContentValueHash from '@/components/content_value/ContentValueHash.vue' | ||||
| import ContentValueList from '@/components/content_value/ContentValueList.vue' | ||||
| import ContentValueString from '@/components/content_value/ContentValueString.vue' | ||||
| import ContentValueSet from '@/components/content_value/ContentValueSet.vue' | ||||
| import ContentValueZset from '@/components/content_value/ContentValueZSet.vue' | ||||
| import { get, isEmpty, keyBy, map, size, toUpper } from 'lodash' | ||||
| import useTabStore from 'stores/tab.js' | ||||
| import useConnectionStore from 'stores/connections.js' | ||||
| import ContentServerStatus from '@/components/content_value/ContentServerStatus.vue' | ||||
| import ContentValueStream from '@/components/content_value/ContentValueStream.vue' | ||||
| import Status from '@/components/icons/Status.vue' | ||||
| import { useThemeVars } from 'naive-ui' | ||||
| import { BrowserTabType } from '@/consts/browser_tab_type.js' | ||||
| import Terminal from '@/components/icons/Terminal.vue' | ||||
| import Log from '@/components/icons/Log.vue' | ||||
| import Detail from '@/components/icons/Detail.vue' | ||||
| import ContentValueWrapper from '@/components/content_value/ContentValueWrapper.vue' | ||||
| import ContentCli from '@/components/content_value/ContentCli.vue' | ||||
|  | ||||
| const themeVars = useThemeVars() | ||||
|  | ||||
| /** | ||||
|  * @typedef {Object} ServerStatusItem | ||||
| @@ -112,15 +115,6 @@ onUnmounted(() => { | ||||
|     clearInterval(intervalId) | ||||
| }) | ||||
|  | ||||
| const valueComponents = { | ||||
|     [types.STRING]: ContentValueString, | ||||
|     [types.HASH]: ContentValueHash, | ||||
|     [types.LIST]: ContentValueList, | ||||
|     [types.SET]: ContentValueSet, | ||||
|     [types.ZSET]: ContentValueZset, | ||||
|     [types.STREAM]: ContentValueStream, | ||||
| } | ||||
|  | ||||
| const connectionStore = useConnectionStore() | ||||
| const tabStore = useTabStore() | ||||
| const tab = computed(() => | ||||
| @@ -162,6 +156,7 @@ const tabContent = computed(() => { | ||||
|     } | ||||
|     return { | ||||
|         name: tab.name, | ||||
|         subTab: tab.subTab, | ||||
|         type: toUpper(tab.type), | ||||
|         db: tab.db, | ||||
|         keyPath: tab.key, | ||||
| @@ -177,7 +172,7 @@ const showServerStatus = computed(() => { | ||||
|     return tabContent.value == null || isEmpty(tabContent.value.keyPath) | ||||
| }) | ||||
|  | ||||
| const showNonexists = computed(() => { | ||||
| const isBlankValue = computed(() => { | ||||
|     return tabContent.value.value == null | ||||
| }) | ||||
|  | ||||
| @@ -192,12 +187,44 @@ const onReloadKey = async () => { | ||||
|     } | ||||
|     await connectionStore.loadKeyValue(tab.name, tab.db, tab.key, tab.viewAs) | ||||
| } | ||||
|  | ||||
| const selectedSubTab = computed(() => { | ||||
|     const { subTab = 'status' } = tabStore.currentTab || {} | ||||
|     return subTab | ||||
| }) | ||||
|  | ||||
| const onSwitchSubTab = (name) => { | ||||
|     tabStore.switchSubTab(name) | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
|     <div class="content-container flex-box-v"> | ||||
|         <div v-if="showServerStatus" class="content-container flex-item-expand flex-box-v"> | ||||
|             <!-- select nothing or select server node, display server status --> | ||||
|         <n-tabs | ||||
|             :tabs-padding="5" | ||||
|             :theme-overrides="{ | ||||
|                 tabGapSmallLine: '10px', | ||||
|                 tabGapMediumLine: '10px', | ||||
|                 tabGapLargeLine: '10px', | ||||
|             }" | ||||
|             :value="selectedSubTab" | ||||
|             class="content-sub-tab" | ||||
|             default-value="status" | ||||
|             pane-class="content-sub-tab-pane" | ||||
|             placement="top" | ||||
|             tab-style="padding-left: 10px; padding-right: 10px;" | ||||
|             type="line" | ||||
|             @update:value="onSwitchSubTab"> | ||||
|             <!-- server status pane --> | ||||
|             <n-tab-pane :name="BrowserTabType.Status.toString()"> | ||||
|                 <template #tab> | ||||
|                     <n-space :size="5" :wrap-item="false" align="center" inline justify="center"> | ||||
|                         <n-icon size="16"> | ||||
|                             <status :inverse="selectedSubTab === BrowserTabType.Status.toString()" stroke-width="4" /> | ||||
|                         </n-icon> | ||||
|                         <span>{{ $t('interface.sub_tab.status') }}</span> | ||||
|                     </n-space> | ||||
|                 </template> | ||||
|                 <content-server-status | ||||
|                     v-model:auto-refresh="currentServer.autoRefresh" | ||||
|                     :auto-loading="currentServer.autoLoading" | ||||
| @@ -205,17 +232,23 @@ const onReloadKey = async () => { | ||||
|                     :loading="currentServer.loading" | ||||
|                     :server="currentServer.name" | ||||
|                     @refresh="refreshInfo(currentServer.name, true)" /> | ||||
|         </div> | ||||
|         <div v-else-if="showNonexists" class="content-container flex-item-expand flex-box-v"> | ||||
|             <n-empty :description="$t('interface.nonexist_tab_content')" class="empty-content"> | ||||
|                 <template #extra> | ||||
|                     <n-button :focusable="false" @click="onReloadKey">{{ $t('interface.reload') }}</n-button> | ||||
|             </n-tab-pane> | ||||
|  | ||||
|             <!-- key detail pane --> | ||||
|             <n-tab-pane :name="BrowserTabType.KeyDetail.toString()"> | ||||
|                 <template #tab> | ||||
|                     <n-space :size="5" :wrap-item="false" align="center" inline justify="center"> | ||||
|                         <n-icon size="16"> | ||||
|                             <detail | ||||
|                                 :inverse="selectedSubTab === BrowserTabType.KeyDetail.toString()" | ||||
|                                 fill-color="none" /> | ||||
|                         </n-icon> | ||||
|                         <span>{{ $t('interface.sub_tab.key_detail') }}</span> | ||||
|                     </n-space> | ||||
|                 </template> | ||||
|             </n-empty> | ||||
|         </div> | ||||
|         <component | ||||
|             :is="valueComponents[tabContent.type]" | ||||
|             v-else | ||||
|                 <content-value-wrapper | ||||
|                     :blank="isBlankValue" | ||||
|                     :type="tabContent.type" | ||||
|                     :db="tabContent.db" | ||||
|                     :key-code="tabContent.keyCode" | ||||
|                     :key-path="tabContent.keyPath" | ||||
| @@ -223,7 +256,35 @@ const onReloadKey = async () => { | ||||
|                     :size="tabContent.size" | ||||
|                     :ttl="tabContent.ttl" | ||||
|                     :value="tabContent.value" | ||||
|             :view-as="tabContent.viewAs" /> | ||||
|                     :view-as="tabContent.viewAs" | ||||
|                     @reload="onReloadKey" /> | ||||
|             </n-tab-pane> | ||||
|  | ||||
|             <!-- cli pane --> | ||||
|             <n-tab-pane :name="BrowserTabType.Cli.toString()"> | ||||
|                 <template #tab> | ||||
|                     <n-space :size="5" :wrap-item="false" align="center" inline justify="center"> | ||||
|                         <n-icon size="16"> | ||||
|                             <terminal :inverse="selectedSubTab === BrowserTabType.Cli.toString()" /> | ||||
|                         </n-icon> | ||||
|                         <span>{{ $t('interface.sub_tab.cli') }}</span> | ||||
|                     </n-space> | ||||
|                 </template> | ||||
|                 <content-cli /> | ||||
|             </n-tab-pane> | ||||
|  | ||||
|             <!-- slow log pane --> | ||||
|             <n-tab-pane :name="BrowserTabType.SlowLog.toString()"> | ||||
|                 <template #tab> | ||||
|                     <n-space :size="5" :wrap-item="false" align="center" inline justify="center"> | ||||
|                         <n-icon size="16"> | ||||
|                             <log :inverse="selectedSubTab === BrowserTabType.SlowLog.toString()" /> | ||||
|                         </n-icon> | ||||
|                         <span>{{ $t('interface.sub_tab.slow_log') }}</span> | ||||
|                     </n-space> | ||||
|                 </template> | ||||
|             </n-tab-pane> | ||||
|         </n-tabs> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| @@ -231,27 +292,24 @@ const onReloadKey = async () => { | ||||
| @import '@/styles/content'; | ||||
|  | ||||
| .content-container { | ||||
|     padding: 5px; | ||||
|     //padding: 5px 5px 0; | ||||
|     //padding-top: 0; | ||||
|     box-sizing: border-box; | ||||
|     background-color: v-bind('themeVars.tabColor'); | ||||
| } | ||||
| </style> | ||||
|  | ||||
| <style lang="scss"> | ||||
| .content-sub-tab { | ||||
|     margin-bottom: 5px; | ||||
|     background-color: v-bind('themeVars.bodyColor'); | ||||
|     height: 100%; | ||||
| } | ||||
|  | ||||
| //.tab-item { | ||||
| //    gap: 5px; | ||||
| //    padding: 0 5px 0 10px; | ||||
| //    align-items: center; | ||||
| //    max-width: 150px; | ||||
| // | ||||
| //    transition: all var(--transition-duration-fast) var(--transition-function-ease-in-out-bezier); | ||||
| // | ||||
| //    &-label { | ||||
| //        font-size: 15px; | ||||
| //        text-align: center; | ||||
| //    } | ||||
| // | ||||
| //    &-close { | ||||
| //        &:hover { | ||||
| //            background-color: rgb(176, 177, 182, 0.4); | ||||
| //        } | ||||
| //    } | ||||
| //} | ||||
| .content-sub-tab-pane { | ||||
|     padding: 0 !important; | ||||
|     height: 100%; | ||||
|     background-color: v-bind('themeVars.tabColor'); | ||||
|     overflow: hidden; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -83,7 +83,7 @@ const tab = computed(() => | ||||
|             @dblclick.stop="() => {}"> | ||||
|             <n-space :size="5" :wrap-item="false" align="center" inline justify="center"> | ||||
|                 <n-icon size="18"> | ||||
|                     <server stroke-width="4" :inverse="tabStore.activatedIndex === i" /> | ||||
|                     <server stroke-width="4" /> | ||||
|                 </n-icon> | ||||
|                 <n-ellipsis style="max-width: 150px">{{ t.label }}</n-ellipsis> | ||||
|             </n-space> | ||||
|   | ||||
							
								
								
									
										7
									
								
								frontend/src/components/content_value/ContentCli.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								frontend/src/components/content_value/ContentCli.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <script setup></script> | ||||
|  | ||||
| <template> | ||||
|     <n-empty description="coming soon" class="empty-content"></n-empty> | ||||
| </template> | ||||
|  | ||||
| <style scoped lang="scss"></style> | ||||
| @@ -75,7 +75,7 @@ const infoFilter = ref('') | ||||
| <template> | ||||
|     <n-scrollbar ref="scrollRef"> | ||||
|         <n-back-top :listen-to="scrollRef" /> | ||||
|         <n-space vertical> | ||||
|         <n-space vertical :wrap-item="false" :size="5" style="padding: 5px"> | ||||
|             <n-card> | ||||
|                 <template #header> | ||||
|                     <n-space :wrap-item="false" align="center" inline size="small"> | ||||
|   | ||||
| @@ -0,0 +1,72 @@ | ||||
| <script setup> | ||||
| import { types } from '@/consts/value_view_type.js' | ||||
| import { types as redisTypes } from '@/consts/support_redis_type.js' | ||||
| import ContentValueString from '@/components/content_value/ContentValueString.vue' | ||||
| import ContentValueHash from '@/components/content_value/ContentValueHash.vue' | ||||
| import ContentValueList from '@/components/content_value/ContentValueList.vue' | ||||
| import ContentValueSet from '@/components/content_value/ContentValueSet.vue' | ||||
| import ContentValueZset from '@/components/content_value/ContentValueZSet.vue' | ||||
| import ContentValueStream from '@/components/content_value/ContentValueStream.vue' | ||||
| import { useThemeVars } from 'naive-ui' | ||||
|  | ||||
| const themeVars = useThemeVars() | ||||
|  | ||||
| const props = defineProps({ | ||||
|     blank: Boolean, | ||||
|     type: String, | ||||
|     name: String, | ||||
|     db: Number, | ||||
|     keyPath: String, | ||||
|     keyCode: { | ||||
|         type: Array, | ||||
|         default: null, | ||||
|     }, | ||||
|     ttl: { | ||||
|         type: Number, | ||||
|         default: -1, | ||||
|     }, | ||||
|     value: [String, Object], | ||||
|     size: Number, | ||||
|     viewAs: { | ||||
|         type: String, | ||||
|         default: types.PLAIN_TEXT, | ||||
|     }, | ||||
| }) | ||||
|  | ||||
| const emit = defineEmits(['reload']) | ||||
|  | ||||
| const valueComponents = { | ||||
|     [redisTypes.STRING]: ContentValueString, | ||||
|     [redisTypes.HASH]: ContentValueHash, | ||||
|     [redisTypes.LIST]: ContentValueList, | ||||
|     [redisTypes.SET]: ContentValueSet, | ||||
|     [redisTypes.ZSET]: ContentValueZset, | ||||
|     [redisTypes.STREAM]: ContentValueStream, | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
|     <n-empty v-if="props.blank" :description="$t('interface.nonexist_tab_content')" class="empty-content"> | ||||
|         <template #extra> | ||||
|             <n-button :focusable="false" @click="emit('reload')">{{ $t('interface.reload') }}</n-button> | ||||
|         </template> | ||||
|     </n-empty> | ||||
|     <component | ||||
|         class="content-value-wrapper" | ||||
|         :is="valueComponents[props.type]" | ||||
|         v-else | ||||
|         :db="props.db" | ||||
|         :key-code="props.keyCode" | ||||
|         :key-path="props.keyPath" | ||||
|         :name="props.name" | ||||
|         :size="props.size" | ||||
|         :ttl="props.ttl" | ||||
|         :value="props.value" | ||||
|         :view-as="props.viewAs" /> | ||||
| </template> | ||||
|  | ||||
| <style scoped lang="scss"> | ||||
| .content-value-wrapper { | ||||
|     background-color: v-bind('themeVars.bodyColor'); | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										109
									
								
								frontend/src/components/icons/Detail.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								frontend/src/components/icons/Detail.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | ||||
| <script setup> | ||||
| const emit = defineEmits(['update:modelValue']) | ||||
|  | ||||
| const props = defineProps({ | ||||
|     inverse: { | ||||
|         type: Boolean, | ||||
|         default: false, | ||||
|     }, | ||||
|     strokeWidth: { | ||||
|         type: [Number, String], | ||||
|         default: 3, | ||||
|     }, | ||||
| }) | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
|     <svg v-if="props.inverse" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
|         <rect | ||||
|             x="6" | ||||
|             y="6" | ||||
|             width="36" | ||||
|             height="36" | ||||
|             rx="3" | ||||
|             fill="currentColor" | ||||
|             stroke="currentColor" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linejoin="round" /> | ||||
|         <rect | ||||
|             x="13" | ||||
|             y="13" | ||||
|             width="8" | ||||
|             height="8" | ||||
|             fill="#FFF" | ||||
|             stroke="#FFF" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             d="M27 13L35 13" | ||||
|             stroke="#FFF" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             d="M27 20L35 20" | ||||
|             stroke="#FFF" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             d="M13 28L35 28" | ||||
|             stroke="#FFF" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             d="M13 35H35" | ||||
|             stroke="#FFF" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|     </svg> | ||||
|     <svg v-else viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
|         <rect | ||||
|             x="6" | ||||
|             y="6" | ||||
|             width="36" | ||||
|             height="36" | ||||
|             rx="3" | ||||
|             fill="none" | ||||
|             stroke="currentColor" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linejoin="round" /> | ||||
|         <rect | ||||
|             x="13" | ||||
|             y="13" | ||||
|             width="8" | ||||
|             height="8" | ||||
|             fill="none" | ||||
|             stroke="currentColor" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             d="M27 13L35 13" | ||||
|             stroke="currentColor" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             d="M27 20L35 20" | ||||
|             stroke="currentColor" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             d="M13 28L35 28" | ||||
|             stroke="currentColor" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             d="M13 35H35" | ||||
|             stroke="currentColor" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|     </svg> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss" scoped></style> | ||||
							
								
								
									
										67
									
								
								frontend/src/components/icons/Terminal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								frontend/src/components/icons/Terminal.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| <script setup> | ||||
| const emit = defineEmits(['update:modelValue']) | ||||
|  | ||||
| const props = defineProps({ | ||||
|     inverse: { | ||||
|         type: Boolean, | ||||
|         default: false, | ||||
|     }, | ||||
|     strokeWidth: { | ||||
|         type: [Number, String], | ||||
|         default: 3, | ||||
|     }, | ||||
| }) | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
|     <svg v-if="props.inverse" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
|         <rect | ||||
|             x="4" | ||||
|             y="8" | ||||
|             width="40" | ||||
|             height="32" | ||||
|             rx="2" | ||||
|             fill="currentColor" | ||||
|             stroke="currentColor" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             d="M12 18L19 24L12 30" | ||||
|             stroke="#FFF" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             d="M23 32H36" | ||||
|             stroke="#FFF" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|     </svg> | ||||
|     <svg v-else viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
|         <rect | ||||
|             x="4" | ||||
|             y="8" | ||||
|             width="40" | ||||
|             height="32" | ||||
|             rx="2" | ||||
|             fill="none" | ||||
|             stroke="currentColor" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             d="M12 18L19 24L12 30" | ||||
|             stroke="currentColor" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|         <path | ||||
|             d="M23 32H36" | ||||
|             stroke="currentColor" | ||||
|             :stroke-width="props.strokeWidth" | ||||
|             stroke-linecap="round" | ||||
|             stroke-linejoin="round" /> | ||||
|     </svg> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss" scoped></style> | ||||
							
								
								
									
										10
									
								
								frontend/src/consts/browser_tab_type.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								frontend/src/consts/browser_tab_type.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| /** | ||||
|  * all types of Browser sub tabs | ||||
|  * @enum {string} | ||||
|  */ | ||||
| export const BrowserTabType = { | ||||
|     Status: 'status', | ||||
|     KeyDetail: 'key_detail', | ||||
|     Cli: 'cli', | ||||
|     SlowLog: 'slow_log', | ||||
| } | ||||
| @@ -87,13 +87,19 @@ | ||||
|     "load_more": "Load More Keys", | ||||
|     "load_all": "Load All Left Keys", | ||||
|     "more_action": "More Action", | ||||
|     "nonexist_tab_content": "Selected key does not exist. Please retry", | ||||
|     "nonexist_tab_content": "Selected key does not exist or no key is selected. Please retry", | ||||
|     "empty_server_content": "Select and open a connection from the left", | ||||
|     "empty_server_list": "No redis server", | ||||
|     "action": "Action", | ||||
|     "type": "Type", | ||||
|     "score": "Score", | ||||
|     "total": "Length: {size}" | ||||
|     "total": "Length: {size}", | ||||
|     "sub_tab": { | ||||
|       "status": "Status", | ||||
|       "key_detail": "Key Detail", | ||||
|       "cli": "Command Line", | ||||
|       "slow_log": "Slow Log" | ||||
|     } | ||||
|   }, | ||||
|   "ribbon": { | ||||
|     "server": "Server", | ||||
|   | ||||
| @@ -87,13 +87,19 @@ | ||||
|     "load_more": "加载更多键", | ||||
|     "load_all": "加载剩余所有键", | ||||
|     "more_action": "更多操作", | ||||
|     "nonexist_tab_content": "所选键不存在,请尝试刷新重试", | ||||
|     "nonexist_tab_content": "所选键不存在或未选中任何键,请尝试刷新重试", | ||||
|     "empty_server_content": "可以从左边选择并打开连接", | ||||
|     "empty_server_list": "还没添加Redis服务器", | ||||
|     "action": "操作", | ||||
|     "type": "类型", | ||||
|     "score": "分值", | ||||
|     "total": "总数:{size}" | ||||
|     "total": "总数:{size}", | ||||
|     "sub_tab": { | ||||
|       "status": "状态", | ||||
|       "key_detail": "键详情", | ||||
|       "cli": "命令行", | ||||
|       "slow_log": "慢日志" | ||||
|     } | ||||
|   }, | ||||
|   "ribbon": { | ||||
|     "server": "服务器", | ||||
|   | ||||
| @@ -52,7 +52,7 @@ import useTabStore from './tab.js' | ||||
| import { types } from '@/consts/support_redis_type.js' | ||||
| import { decodeRedisKey, nativeRedisKey } from '@/utils/key_convert.js' | ||||
| import { KeyViewType } from '@/consts/key_view_type.js' | ||||
| import { nextTick } from 'vue' | ||||
| import { BrowserTabType } from '@/consts/browser_tab_type.js' | ||||
|  | ||||
| const useConnectionStore = defineStore('connections', { | ||||
|     /** | ||||
| @@ -668,6 +668,7 @@ const useConnectionStore = defineStore('connections', { | ||||
|                         const k = decodeRedisKey(key) | ||||
|                         const binaryKey = k !== key | ||||
|                         tab.upsertTab({ | ||||
|                             subTab: BrowserTabType.KeyDetail, | ||||
|                             server, | ||||
|                             db, | ||||
|                             type, | ||||
| @@ -690,6 +691,7 @@ const useConnectionStore = defineStore('connections', { | ||||
|                 } | ||||
|  | ||||
|                 tab.upsertTab({ | ||||
|                     subTab: BrowserTabType.Status, | ||||
|                     server, | ||||
|                     db, | ||||
|                     type: 'none', | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { find, findIndex, get, size } from 'lodash' | ||||
| import { find, findIndex, get, isEmpty, set, size } from 'lodash' | ||||
| import { defineStore } from 'pinia' | ||||
|  | ||||
| const useTabStore = defineStore('tab', { | ||||
| @@ -6,6 +6,7 @@ const useTabStore = defineStore('tab', { | ||||
|      * @typedef {Object} TabItem | ||||
|      * @property {string} name connection name | ||||
|      * @property {boolean} blank is blank tab | ||||
|      * @property {string} subTab secondary tab value | ||||
|      * @property {string} [title] tab title | ||||
|      * @property {string} [icon] tab icon | ||||
|      * @property {string[]} selectedKeys | ||||
| @@ -64,12 +65,16 @@ const useTabStore = defineStore('tab', { | ||||
|          * | ||||
|          * @param idx | ||||
|          * @param {boolean} [switchNav] | ||||
|          * @param {string} [subTab] | ||||
|          * @private | ||||
|          */ | ||||
|         _setActivatedIndex(idx, switchNav) { | ||||
|         _setActivatedIndex(idx, switchNav, subTab) { | ||||
|             this.activatedIndex = idx | ||||
|             if (switchNav === true) { | ||||
|                 this.nav = idx >= 0 ? 'browser' : 'server' | ||||
|                 if (!isEmpty(subTab)) { | ||||
|                     set(this.tabList, [idx, 'subTab'], subTab) | ||||
|                 } | ||||
|             } else { | ||||
|                 if (idx < 0) { | ||||
|                     this.nav = 'server' | ||||
| @@ -79,6 +84,7 @@ const useTabStore = defineStore('tab', { | ||||
|  | ||||
|         /** | ||||
|          * update or insert a new tab if not exists with the same name | ||||
|          * @param {string} subTab | ||||
|          * @param {string} server | ||||
|          * @param {number} [db] | ||||
|          * @param {number} [type] | ||||
| @@ -89,11 +95,13 @@ const useTabStore = defineStore('tab', { | ||||
|          * @param {*} [value] | ||||
|          * @param {string} [viewAs] | ||||
|          */ | ||||
|         upsertTab({ server, db, type, ttl, key, keyCode, size, value, viewAs }) { | ||||
|         upsertTab({ subTab, server, db, type, ttl, key, keyCode, size, value, viewAs }) { | ||||
|             let tabIndex = findIndex(this.tabList, { name: server }) | ||||
|             if (tabIndex === -1) { | ||||
|                 this.tabList.push({ | ||||
|                     name: server, | ||||
|                     title: server, | ||||
|                     subTab, | ||||
|                     server, | ||||
|                     db, | ||||
|                     type, | ||||
| @@ -105,9 +113,10 @@ const useTabStore = defineStore('tab', { | ||||
|                     viewAs, | ||||
|                 }) | ||||
|                 tabIndex = this.tabList.length - 1 | ||||
|             } | ||||
|             } else { | ||||
|                 const tab = this.tabList[tabIndex] | ||||
|                 tab.blank = false | ||||
|                 tab.subTab = subTab | ||||
|                 // tab.title = db !== undefined ? `${server}/db${db}` : `${server}` | ||||
|                 tab.title = server | ||||
|                 tab.server = server | ||||
| @@ -119,7 +128,8 @@ const useTabStore = defineStore('tab', { | ||||
|                 tab.size = size | ||||
|                 tab.value = value | ||||
|                 tab.viewAs = viewAs | ||||
|             this._setActivatedIndex(tabIndex, true) | ||||
|             } | ||||
|             this._setActivatedIndex(tabIndex, true, subTab) | ||||
|             // this.activatedTab = tab.name | ||||
|         }, | ||||
|  | ||||
| @@ -162,6 +172,14 @@ const useTabStore = defineStore('tab', { | ||||
|             // this.activatedIndex = tabIndex | ||||
|         }, | ||||
|  | ||||
|         switchSubTab(name) { | ||||
|             const tab = this.currentTab | ||||
|             if (tab == null) { | ||||
|                 return | ||||
|             } | ||||
|             tab.subTab = name | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * | ||||
|          * @param {number} tabIndex | ||||
|   | ||||
| @@ -1,8 +1,6 @@ | ||||
| .content-container { | ||||
|   height: 100%; | ||||
|   overflow: hidden; | ||||
|   padding-top: 2px; | ||||
|   padding-bottom: 5px; | ||||
|   box-sizing: border-box; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -75,7 +75,7 @@ body { | ||||
| } | ||||
|  | ||||
| .content-wrapper { | ||||
|   //height: 100%; | ||||
|   height: 100%; | ||||
|   flex-grow: 1; | ||||
|   overflow: hidden; | ||||
|   gap: 5px; | ||||
| @@ -95,6 +95,7 @@ body { | ||||
|   .value-wrapper { | ||||
|     //border-top: v-bind('themeVars.borderColor') 1px solid; | ||||
|     user-select: text; | ||||
|     height: 100%; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 tiny-craft
					tiny-craft