tabs优化

This commit is contained in:
Gary Fu
2023-12-30 14:07:49 +08:00
parent aeb759c5c0
commit d74c876d70
12 changed files with 210 additions and 39 deletions

View File

@@ -1,7 +1,9 @@
/* eslint-env node */ /* eslint-env node */
export default { module.exports = {
root: true, root: true,
'extends': [ extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'plugin:vue/vue3-recommended', 'plugin:vue/vue3-recommended',
'eslint:recommended', 'eslint:recommended',
'@vue/eslint-config-standard' '@vue/eslint-config-standard'
@@ -10,6 +12,6 @@ export default {
ecmaVersion: 'latest' ecmaVersion: 'latest'
}, },
rules: { rules: {
'vue/multi-word-component-names': "off" 'vue/multi-word-component-names': 'off'
} }
} }

View File

@@ -0,0 +1,78 @@
<script setup>
import { computed } from 'vue'
/**
* @typedef {Object} CommonFormOption
* @property {'input'|'input-number'|'cascader'|'radio'
* |'radio-group'|'checkbox'|'checkbox-group'|'date-picker'
* |'time-picker'|'switch'|'select'|'option'|'slider'|'transfer'|'upload'} type 类型
* @property {any} value
* @property {any} config
* @property {string} prop
* @property {string} label
* @property {string} placeholder
* @property {{clearable:boolean,disabled:boolean}} attrs
* @property {[CommonFormOption]} children 子节点
* @property {Array<RuleItem>} rules 子节点
*/
/**
* @type {CommonFormOption}
*/
const props = defineProps({
type: {
type: String,
default: 'input'
},
value: {
type: Object,
default: null
},
prop: {
type: String,
default: ''
},
label: {
type: String,
default: ''
},
children: {
type: Array,
default: () => []
},
rules: { type: Array, default: () => [] },
placeholder: {
type: String,
default: ''
},
attrs: {
type: Object,
default: null
}
})
const inputType = computed(() => {
return `el-${props.type}`
})
</script>
<template>
<component
:is="inputType"
:prop="prop"
v-bind="attrs"
:placeholder="placeholder"
>
<template v-if="children&&children.length">
<common-form-control
v-for="(childItem, index) in children"
:key="index"
:type="childItem.type"
/>
</template>
</component>
</template>
<style scoped>
</style>

View File

@@ -1,9 +0,0 @@
<script setup>
</script>
<template />
<style scoped>
</style>

View File

@@ -2,7 +2,26 @@
import { computed } from 'vue' import { computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
/**
* @typedef {Object} CommonMenuItem 菜单对象
* @property {boolean} isDropdown 是否是下拉Dropdown样式
* @property {boolean} isSplit 是否是分割元素
* @property {string} menuCls 自定义样式
* @property {string} index 路由地址
* @property {Object} route 路由
* @property {string} icon 图标
* @property {number} iconSize 图标大小
* @property {string} label 菜单显示名称
* @property {string} labelKey 菜单显示名称的Key国际化需要
* @method iconIf 图标计算函数
* @method click 点击事件
* @property {[CommonMenuItem]} children 子菜单
*/
/**
* @type {Object}
* @property {CommonMenuItem} menuItem 菜单对象
* @property index 序号
*/
const props = defineProps({ const props = defineProps({
menuItem: { menuItem: {
type: Object, type: Object,
@@ -81,7 +100,7 @@ const dropdownClick = menuItem => {
:class="menuCls" :class="menuCls"
@click="menuItem.click&&menuItem.click()" @click="menuItem.click&&menuItem.click()"
> >
<el-dropdown> <el-dropdown class="common-dropdown">
<span class="el-dropdown-link"> <span class="el-dropdown-link">
<common-icon <common-icon
:size="menuItem.iconSize" :size="menuItem.iconSize"
@@ -133,5 +152,10 @@ const dropdownClick = menuItem => {
</template> </template>
<style scoped> <style scoped>
.common-dropdown {
height: 100%;
}
.common-dropdown .el-icon {
margin-top: 20px;
}
</style> </style>

View File

@@ -31,12 +31,22 @@ const selectHistoryTab = path => {
} }
const removeHistoryTab = path => { const removeHistoryTab = path => {
const lastTab = tabsViewStore.removeHistoryTab(path) const lastTab = tabsViewStore.removeHistoryTab({ path })
if (lastTab) { if (lastTab) {
selectHistoryTab(lastTab) selectHistoryTab(lastTab)
} }
} }
const refreshHistoryTab = tab => {
const time = new Date().getTime()
router.push(`${tab.path}?${time}`)
}
const removeOtherHistoryTabs = tab => {
tabsViewStore.removeOtherHistoryTabs(tab)
selectHistoryTab(tab.path)
}
</script> </script>
<template> <template>
@@ -52,6 +62,9 @@ const removeHistoryTab = path => {
<tabs-view-item <tabs-view-item
v-for="item in tabsViewStore.historyTabs" v-for="item in tabsViewStore.historyTabs"
:key="item.path" :key="item.path"
:refresh-history-tab="refreshHistoryTab"
:remove-history-tab="removeHistoryTab"
:remove-other-history-tabs="removeOtherHistoryTabs"
:tab-item="item" :tab-item="item"
/> />
</el-tabs> </el-tabs>

View File

@@ -6,10 +6,16 @@ import { useTabsViewStore } from '@/stores/TabsViewStore'
const tabsViewStore = useTabsViewStore() const tabsViewStore = useTabsViewStore()
const props = defineProps({ const props = defineProps({
/**
* @type RouteRecordRaw
*/
tabItem: { tabItem: {
type: Object, type: Object,
required: true required: true
} },
removeHistoryTab: Function,
removeOtherHistoryTabs: Function,
refreshHistoryTab: Function
}) })
const menuName = computed(() => { const menuName = computed(() => {
@@ -26,6 +32,7 @@ const menuInfo = computed(() => {
:name="tabItem.path" :name="tabItem.path"
> >
<template #label> <template #label>
<el-dropdown trigger="contextmenu">
<span class="custom-tabs-label"> <span class="custom-tabs-label">
<common-icon <common-icon
v-if="tabsViewStore.isShowTabIcon && menuInfo && menuInfo.icon" v-if="tabsViewStore.isShowTabIcon && menuInfo && menuInfo.icon"
@@ -33,6 +40,25 @@ const menuInfo = computed(() => {
/> />
<span>{{ menuName }}</span> <span>{{ menuName }}</span>
</span> </span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
@click="refreshHistoryTab(tabItem)"
>
<common-icon icon="refresh" />
{{ $t('common.label.refresh') }}
</el-dropdown-item>
<el-dropdown-item @click="removeHistoryTab(tabItem.path)">
<common-icon icon="close" />
{{ $t('common.label.close') }}
</el-dropdown-item>
<el-dropdown-item @click="removeOtherHistoryTabs(tabItem)">
<common-icon icon="close" />
{{ $t('common.label.closeOther') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template> </template>
</el-tab-pane> </el-tab-pane>
</template> </template>

View File

@@ -1,5 +1,5 @@
import CommonIcon from '@/components/common-icon/index.vue' import CommonIcon from '@/components/common-icon/index.vue'
import CommonInput from '@/components/common-form-input/index.vue' import CommonFormControl from '@/components/common-form-control/index.vue'
import CommonMenu from '@/components/common-menu/index.vue' import CommonMenu from '@/components/common-menu/index.vue'
import CommonMenuItem from '@/components/common-menu-item/index.vue' import CommonMenuItem from '@/components/common-menu-item/index.vue'
import CommonTabsView from '@/components/common-tabs-view/index.vue' import CommonTabsView from '@/components/common-tabs-view/index.vue'
@@ -13,7 +13,7 @@ export default {
*/ */
install (Vue) { install (Vue) {
Vue.component('CommonIcon', CommonIcon) Vue.component('CommonIcon', CommonIcon)
Vue.component('CommonInput', CommonInput) Vue.component('CommonFormControl', CommonFormControl)
Vue.component('CommonMenu', CommonMenu) Vue.component('CommonMenu', CommonMenu)
Vue.component('CommonMenuItem', CommonMenuItem) Vue.component('CommonMenuItem', CommonMenuItem)
Vue.component('CommonTabsView', CommonTabsView) Vue.component('CommonTabsView', CommonTabsView)

View File

@@ -6,6 +6,8 @@ common.label.login = '登录'
common.label.index = '首页' common.label.index = '首页'
common.label.settings = '设置' common.label.settings = '设置'
common.label.close = '关闭' common.label.close = '关闭'
common.label.refresh = '刷新'
common.label.closeOther = '关闭其他'
common.label.langCn = '中文' common.label.langCn = '中文'
common.label.langEn = 'English' common.label.langEn = 'English'
common.label.language = '语言' common.label.language = '语言'

View File

@@ -6,6 +6,8 @@ common.label.login = 'Login'
common.label.index = 'Home' common.label.index = 'Home'
common.label.settings = 'Settings' common.label.settings = 'Settings'
common.label.close = 'Close' common.label.close = 'Close'
common.label.refresh = 'Refresh'
common.label.closeOther = 'Close Others'
common.label.langCn = '中文' common.label.langCn = '中文'
common.label.langEn = 'English' common.label.langEn = 'English'
common.label.language = 'Language' common.label.language = 'Language'

View File

@@ -1,21 +1,35 @@
import { ref } from 'vue' import { ref } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
/**
* @typedef {Object} TabsViewStore
* @property {boolean} isTabMode 是否开启tab模式
* @property {boolean} isCachedTabMode 是否开启tab缓存
* @property {boolean} isShowTabIcon 是否显示tab的图标
* @property {[import('vue-router').RouteRecordRaw]} historyTabs 历史tab列表
* @property {[string]} cachedTabs 缓存的tab列表
* @method removeHistoryTab
*/
/**
* @return {TabsViewStore}
*/
export const useTabsViewStore = defineStore('tabsView', () => { export const useTabsViewStore = defineStore('tabsView', () => {
const isTabMode = ref(true) const isTabMode = ref(true)
const isCachedTabMode = ref(true) const isCachedTabMode = ref(true)
const isShowTabIcon = ref(true) const isShowTabIcon = ref(true)
/**
* @type {{value: [import('vue-router').RouteRecordRaw]}}
*/
const historyTabs = ref([]) const historyTabs = ref([])
/**
* @type {{value: [string]}}
*/
const cachedTabs = ref([]) const cachedTabs = ref([])
const clearHistoryTabs = () => { const clearHistoryTabs = () => {
if (historyTabs.value.length) { if (historyTabs.value.length) {
const tab = historyTabs.value[0] const tab = historyTabs.value[0]
historyTabs.value = [tab] removeOtherHistoryTabs(tab)
cachedTabs.value = []
if (isCachedTabMode.value && tab.name) {
cachedTabs.value = [tab.name]
}
} }
} }
@@ -26,21 +40,28 @@ export const useTabsViewStore = defineStore('tabsView', () => {
} }
} }
const addHistoryTab = tab => { const addHistoryTab = (tab, insertIdx) => {
// 添加tab // 添加tab
if (isTabMode.value) { if (isTabMode.value) {
const idx = historyTabs.value.findIndex(v => v.path === tab.path) const idx = historyTabs.value.findIndex(v => v.path === tab.path)
if (idx < 0) { if (idx < 0) {
if (insertIdx !== undefined) {
historyTabs.value.splice(insertIdx, 0, tab)
} else {
historyTabs.value.push(Object.assign({}, tab)) // 可能是Proxy需要解析出来 historyTabs.value.push(Object.assign({}, tab)) // 可能是Proxy需要解析出来
}
if (isCachedTabMode.value && tab.name) { if (isCachedTabMode.value && tab.name) {
console.info('=======================add tab', tab.name)
if (!cachedTabs.value.includes(tab.name)) {
cachedTabs.value.push(tab.name) cachedTabs.value.push(tab.name)
} }
} }
} }
} }
const removeHistoryTab = path => { }
const removeHistoryTab = tab => {
if (historyTabs.value.length > 1) { if (historyTabs.value.length > 1) {
const idx = historyTabs.value.findIndex(v => v.path === path) const idx = historyTabs.value.findIndex(v => v.path === tab.path)
if (idx > -1) { if (idx > -1) {
removeCachedTab(historyTabs.value[idx]) removeCachedTab(historyTabs.value[idx])
// 删除tab // 删除tab
@@ -59,6 +80,14 @@ export const useTabsViewStore = defineStore('tabsView', () => {
} }
} }
const removeOtherHistoryTabs = tab => {
historyTabs.value = [tab]
cachedTabs.value = []
if (isCachedTabMode.value && tab.name) {
cachedTabs.value = [tab.name]
}
}
return { return {
isTabMode, isTabMode,
isCachedTabMode, isCachedTabMode,
@@ -81,6 +110,7 @@ export const useTabsViewStore = defineStore('tabsView', () => {
isShowTabIcon.value = !isShowTabIcon.value isShowTabIcon.value = !isShowTabIcon.value
}, },
removeHistoryTab, removeHistoryTab,
removeOtherHistoryTabs,
clearHistoryTabs, clearHistoryTabs,
findHistoryTab, findHistoryTab,
addHistoryTab addHistoryTab

View File

@@ -35,7 +35,10 @@ const showLeftMenu = computed(() => {
name="slide-fade" name="slide-fade"
mode="out-in" mode="out-in"
> >
<KeepAlive :include="tabsViewStore.cachedTabs"> <KeepAlive
:include="tabsViewStore.cachedTabs"
:max="10"
>
<component <component
:is="Component" :is="Component"
:key="route.fullPath" :key="route.fullPath"

View File

@@ -5,7 +5,7 @@ const globalConfigStore = useGlobalConfigStore()
</script> </script>
<template> <template>
<el-drawer v-model="globalConfigStore.isShowSettings" direction="rtl"> <el-drawer v-model="globalConfigStore.isShowSettings" direction="rtl" :size="350">
<template #header> <template #header>
<strong>{{ $t('common.label.settings') }}</strong> <strong>{{ $t('common.label.settings') }}</strong>
</template> </template>