feat: support tabs page

This commit is contained in:
Uyarn 2022-03-24 01:37:51 +08:00
parent dda2f6e144
commit 8440b7cabe
34 changed files with 406 additions and 65 deletions

4
.env
View File

@ -1,3 +1,3 @@
VITE_SOME_KEY=123 VITE_SOME_KEY=123
# 打包路径 # 打包路径 根据项目不同按需配置
VITE_BASE_URL = / VITE_BASE_URL = ./

View File

@ -15,10 +15,7 @@
"defineProps": "readonly", "defineProps": "readonly",
"defineEmits": "readonly" "defineEmits": "readonly"
}, },
"plugins": [ "plugins": ["vue", "@typescript-eslint"],
"vue",
"@typescript-eslint"
],
"parserOptions": { "parserOptions": {
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"sourceType": "module", "sourceType": "module",
@ -28,27 +25,22 @@
} }
}, },
"settings": { "settings": {
"import/extensions": [ "import/extensions": [".js", ".jsx", ".ts", ".tsx"]
".js",
".jsx",
".ts",
".tsx"
]
}, },
"rules": { "rules": {
"no-console": "off", "no-console": "off",
"no-continue": "off", "no-continue": "off",
"no-restricted-syntax": "off", "no-restricted-syntax": "off",
"no-plusplus": "off", "no-plusplus": "off",
"no-param-reassign": "off", "no-param-reassign": "off",
"no-shadow": "off", "no-shadow": "off",
"guard-for-in": "off", "guard-for-in": "off",
"import/extensions": "off", "import/extensions": "off",
"import/no-unresolved": "off", "import/no-unresolved": "off",
"import/no-extraneous-dependencies": "off", "import/no-extraneous-dependencies": "off",
"import/prefer-default-export": "off", "import/prefer-default-export": "off",
"import/first": "off", // https://github.com/vuejs/vue-eslint-parser/issues/58
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/explicit-module-boundary-types": "off",
"vue/first-attribute-linebreak": 0 "vue/first-attribute-linebreak": 0
@ -61,9 +53,8 @@
"vue/require-default-prop": 0, "vue/require-default-prop": 0,
"vue/multi-word-component-names": 0, "vue/multi-word-component-names": 0,
"vue/no-reserved-props": 0, "vue/no-reserved-props": 0,
"vue/no-v-html": 0, "vue/no-v-html": 0
} }
} }
] ]
} }

View File

@ -8,6 +8,7 @@ export default {
isFooterAside: false, isFooterAside: false,
isSidebarFixed: true, isSidebarFixed: true,
isHeaderFixed: true, isHeaderFixed: true,
isUseTabsRouter: false,
showHeader: true, showHeader: true,
backgroundTheme: 'blueGrey', backgroundTheme: 'blueGrey',
brandTheme: 'default', brandTheme: 'default',

View File

@ -1,3 +1,4 @@
import { RouteRecordName } from 'vue-router';
import STYLE_CONFIG from '@/config/style'; import STYLE_CONFIG from '@/config/style';
export interface ResDataType { export interface ResDataType {
@ -37,3 +38,17 @@ export interface NotificationItem {
date: string; date: string;
quality: string; quality: string;
} }
export interface TRouterInfo {
path: string;
routeIdx?: number;
title?: string;
name?: RouteRecordName;
isAlive?: boolean;
isHome?: boolean;
}
export interface TTabRouterType {
isRefreshing: boolean;
tabRouterList: Array<TRouterInfo>;
}

View File

@ -1,10 +1,30 @@
<template> <template>
<router-view v-slot="{ Component }"> <router-view v-if="!isRefreshing" v-slot="{ Component }">
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<component :is="Component" /> <keep-alive :include="aliveViews">
<component :is="Component" />
</keep-alive>
</transition> </transition>
</router-view> </router-view>
</template> </template>
<script setup lang="ts">
import { computed } from 'vue';
import { useTabsRouterStore } from '@/store';
const aliveViews = computed(() => {
const tabsRouterStore = useTabsRouterStore();
const { tabRouters } = tabsRouterStore;
return tabRouters.filter((route) => route.isAlive).map((route) => route.name);
});
const isRefreshing = computed(() => {
const tabsRouterStore = useTabsRouterStore();
const { refreshing } = tabsRouterStore;
return refreshing;
});
</script>
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/variables'; @import '@/style/variables';

View File

@ -1,16 +1,17 @@
import { defineComponent, computed } from 'vue'; import { defineComponent, computed, nextTick, onMounted, watch } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useRoute } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { usePermissionStore, useSettingStore } from '@/store'; import { usePermissionStore, useSettingStore, useTabsRouterStore } from '@/store';
import TDesignHeader from './components/Header.vue'; import LayoutHeader from './components/Header.vue';
import TDesignBreadcrumb from './components/Breadcrumb.vue'; import LayoutBreadcrumb from './components/Breadcrumb.vue';
import TDesignFooter from './components/Footer.vue'; import LayoutFooter from './components/Footer.vue';
import TDesignSideNav from './components/SideNav'; import LayoutSideNav from './components/SideNav';
import TDesignContent from './components/Content.vue'; import LayoutContent from './components/Content.vue';
import Setting from './setting.vue';
import { prefix } from '@/config/global'; import { prefix } from '@/config/global';
import TdesignSetting from './setting.vue';
import '@/style/layout.less'; import '@/style/layout.less';
const name = `${prefix}-base-layout`; const name = `${prefix}-base-layout`;
@ -19,8 +20,10 @@ export default defineComponent({
name, name,
setup() { setup() {
const route = useRoute(); const route = useRoute();
const router = useRouter();
const permissionStore = usePermissionStore(); const permissionStore = usePermissionStore();
const settingStore = useSettingStore(); const settingStore = useSettingStore();
const tabsRouterStore = useTabsRouterStore();
const { routers: menuRouters } = storeToRefs(permissionStore); const { routers: menuRouters } = storeToRefs(permissionStore);
const setting = storeToRefs(settingStore); const setting = storeToRefs(settingStore);
@ -57,10 +60,59 @@ export default defineComponent({
return newMenuRouters; return newMenuRouters;
}); });
const appendNewRoute = () => {
const {
path,
meta: { title },
name,
} = route;
tabsRouterStore.appendTabRouterList({ path, title: title as string, name, isAlive: true });
};
onMounted(() => {
appendNewRoute();
});
watch(
() => route.path,
() => {
appendNewRoute();
},
);
const handleRemove = ({ value: path, index }) => {
const { tabRouters } = tabsRouterStore;
const nextRouter = tabRouters[index + 1] || tabRouters[index - 1];
tabsRouterStore.subtractCurrentTabRouter({ path, routeIdx: index });
if (path === route.path) {
router.push(nextRouter.path);
}
};
const handleChangeCurrentTab = (path: string) => {
router.push(path);
};
const handleRefresh = (currentPath: string, routeIdx: number) => {
tabsRouterStore.toggleTabRouterAlive(routeIdx);
nextTick(() => {
tabsRouterStore.toggleTabRouterAlive(routeIdx);
router.replace({ path: currentPath });
});
};
const handleCloseAhead = (path: string, routeIdx: number) => {
tabsRouterStore.subtractTabRouterAhead({ path, routeIdx });
};
const handleCloseBehind = (path: string, routeIdx: number) => {
tabsRouterStore.subtractTabRouterBehind({ path, routeIdx });
};
const handleCloseOther = (path: string, routeIdx: number) => {
tabsRouterStore.subtractTabRouterOther({ path, routeIdx });
};
const renderSidebar = () => { const renderSidebar = () => {
return ( return (
settingStore.showSidebar && ( settingStore.showSidebar && (
<TDesignSideNav <LayoutSideNav
showLogo={settingStore.showSidebarLogo} showLogo={settingStore.showSidebarLogo}
layout={settingStore.layout} layout={settingStore.layout}
isFixed={settingStore.isSidebarFixed} isFixed={settingStore.isSidebarFixed}
@ -75,7 +127,7 @@ export default defineComponent({
const renderHeader = () => { const renderHeader = () => {
return ( return (
settingStore.showHeader && ( settingStore.showHeader && (
<TDesignHeader <LayoutHeader
showLogo={settingStore.showHeaderLogo} showLogo={settingStore.showHeaderLogo}
theme={settingStore.displayMode} theme={settingStore.displayMode}
layout={settingStore.layout} layout={settingStore.layout}
@ -90,18 +142,72 @@ export default defineComponent({
const renderFooter = () => { const renderFooter = () => {
return ( return (
<t-footer class={`${prefix}-footer-layout`}> <t-footer class={`${prefix}-footer-layout`}>
<TDesignFooter /> <LayoutFooter />
</t-footer> </t-footer>
); );
}; };
const renderContent = () => { const renderContent = () => {
const { showBreadcrumb, showFooter } = settingStore; const { showBreadcrumb, showFooter, isUseTabsRouter } = settingStore;
const { tabRouters } = tabsRouterStore;
return ( return (
<t-layout class={[`${prefix}-layout`]}> <t-layout class={[`${prefix}-layout`]}>
{isUseTabsRouter && (
<t-tabs
theme="card"
class={`${prefix}-layout-tabs-nav`}
value={route.path}
onChange={handleChangeCurrentTab}
style={{ maxWidth: '100%', position: 'fixed', overflow: 'visible' }}
onRemove={handleRemove}
>
{tabRouters.map((router: any, idx: number) => (
<t-tab-panel
value={router.path}
key={`${router.path}_${idx}`}
label={
<t-dropdown
trigger="context-menu"
minColumnWidth={128}
popupProps={{ overlayClassName: 'router-tabs-dropdown' }}
v-slots={{
dropdown: () => (
<t-dropdown-menu>
<t-dropdown-item onClick={() => handleRefresh(router.path, idx)}>
<t-icon name="refresh" />
</t-dropdown-item>
{idx > 0 && (
<t-dropdown-item onClick={() => handleCloseAhead(router.path, idx)}>
<t-icon name="arrow-left" />
</t-dropdown-item>
)}
{idx < tabRouters.length - 1 && (
<t-dropdown-item onClick={() => handleCloseBehind(router.path, idx)}>
<t-icon name="arrow-right" />
</t-dropdown-item>
)}
<t-dropdown-item onClick={() => handleCloseOther(router.path, idx)}>
<t-icon name="close-circle" />
</t-dropdown-item>
</t-dropdown-menu>
),
}}
>
{!router.isHome ? router.title : <t-icon name="home" />}
</t-dropdown>
}
removable={!router.isHome}
/>
))}
</t-tabs>
)}
<t-content class={`${prefix}-content-layout`}> <t-content class={`${prefix}-content-layout`}>
{showBreadcrumb && <TDesignBreadcrumb />} {showBreadcrumb && <LayoutBreadcrumb />}
<TDesignContent /> <LayoutContent />
</t-content> </t-content>
{showFooter && renderFooter()} {showFooter && renderFooter()}
</t-layout> </t-layout>
@ -134,7 +240,7 @@ export default defineComponent({
<t-layout class={this.mainLayoutCls}>{[sidebar, content]}</t-layout> <t-layout class={this.mainLayoutCls}>{[sidebar, content]}</t-layout>
</t-layout> </t-layout>
)} )}
<TdesignSetting /> <Setting />
</div> </div>
); );
}, },

View File

@ -82,6 +82,9 @@
<t-form-item label="显示 Footer" name="showFooter"> <t-form-item label="显示 Footer" name="showFooter">
<t-switch v-model="formData.showFooter" /> <t-switch v-model="formData.showFooter" />
</t-form-item> </t-form-item>
<t-form-item label="使用 多标签Tab页" name="isUseTabsRouter">
<t-switch v-model="formData.isUseTabsRouter"></t-switch>
</t-form-item>
</t-form> </t-form>
<div class="setting-info"> <div class="setting-info">
<p>请复制后手动修改配置文件: /src/config/style.ts</p> <p>请复制后手动修改配置文件: /src/config/style.ts</p>

View File

@ -202,6 +202,13 @@
</div> </div>
</div> </div>
</template> </template>
<script lang="ts">
export default {
name: 'DashboardBase',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, watch, ref, onUnmounted, nextTick, computed } from 'vue'; import { onMounted, watch, ref, onUnmounted, nextTick, computed } from 'vue';
@ -386,9 +393,11 @@ const getRankClass = (index: number) => {
return ['dashboard-rank', { 'dashboard-rank__top': index < 3 }]; return ['dashboard-rank', { 'dashboard-rank__top': index < 3 }];
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import './index.less'; @import './index.less';
</style> </style>
<style lang="less"> <style lang="less">
@import '@/style/variables.less'; @import '@/style/variables.less';

View File

@ -57,6 +57,13 @@
</card> </card>
</div> </div>
</template> </template>
<script lang="ts">
export default {
name: 'DashboardDetail',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, onMounted, onUnmounted, watch, computed } from 'vue'; import { nextTick, onMounted, onUnmounted, watch, computed } from 'vue';
@ -149,6 +156,7 @@ const onMaterialChange = (value: string[]) => {
lineChart.setOption(getFolderLineDataSet({ dateTime: value, ...chartColors.value })); lineChart.setOption(getFolderLineDataSet({ dateTime: value, ...chartColors.value }));
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./index.less'); @import url('./index.less');
</style> </style>

View File

@ -109,6 +109,13 @@
</t-dialog> </t-dialog>
</div> </div>
</template> </template>
<script lang="ts">
export default {
name: 'DetailAdvanced',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { prefix } from '@/config/global'; import { prefix } from '@/config/global';
@ -175,6 +182,7 @@ const onConfirm = () => {
visible.value = false; visible.value = false;
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./index.less'); @import url('./index.less');
</style> </style>

View File

@ -26,6 +26,13 @@
</card> </card>
</div> </div>
</template> </template>
<script lang="ts">
export default {
name: 'DetailBase',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import Card from '@/components/card/index.vue'; import Card from '@/components/card/index.vue';
@ -108,6 +115,7 @@ const BASE_INFO_DATA = [
}, },
]; ];
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./index.less'); @import url('./index.less');
</style> </style>

View File

@ -68,6 +68,13 @@
</t-dialog> </t-dialog>
</div> </div>
</template> </template>
<script lang="ts">
export default {
name: 'DetailDeploy',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, onUnmounted, ref, watch, computed } from 'vue'; import { onMounted, onUnmounted, ref, watch, computed } from 'vue';
@ -195,6 +202,7 @@ const deleteClickOp = (e) => {
data.value.splice(e.rowIndex, 1); data.value.splice(e.rowIndex, 1);
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('../base/index.less'); @import url('../base/index.less');
</style> </style>

View File

@ -49,6 +49,13 @@
/> />
</div> </div>
</template> </template>
<script lang="ts">
export default {
name: 'DetailSecondary',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
@ -114,6 +121,7 @@ const deleteMsg = () => {
store.setMsgData(changeMsg); store.setMsgData(changeMsg);
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./index.less'); @import url('./index.less');
</style> </style>

View File

@ -156,6 +156,13 @@
</div> </div>
</t-form> </t-form>
</template> </template>
<script lang="ts">
export default {
name: 'FormBase',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next'; import { MessagePlugin } from 'tdesign-vue-next';
@ -190,6 +197,7 @@ const formatResponse = (res) => {
return { ...res, error: '上传失败,请重试', url: res.url }; return { ...res, error: '上传失败,请重试', url: res.url };
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./index.less'); @import url('./index.less');
</style> </style>

View File

@ -139,6 +139,13 @@
</div> </div>
</div> </div>
</template> </template>
<script lang="ts">
export default {
name: 'FormStep',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -186,6 +193,7 @@ const complete = () => {
router.replace({ path: '/detail/advanced' }); router.replace({ path: '/detail/advanced' });
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./index.less'); @import url('./index.less');
</style> </style>

View File

@ -66,6 +66,13 @@
/> />
</div> </div>
</template> </template>
<script lang="ts">
export default {
name: 'ListBase',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -171,6 +178,7 @@ const handleClickDelete = (row: { rowIndex: any }) => {
confirmVisible.value = true; confirmVisible.value = true;
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/variables'; @import '@/style/variables';

View File

@ -60,6 +60,13 @@
/> />
</div> </div>
</template> </template>
<script lang="ts">
export default {
name: 'ListCard',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { SearchIcon } from 'tdesign-icons-vue-next'; import { SearchIcon } from 'tdesign-icons-vue-next';
@ -141,6 +148,7 @@ const handleManageProduct = (product) => {
formData.value = { ...product, status: product?.isSetup ? '1' : '0' }; formData.value = { ...product, status: product?.isSetup ? '1' : '0' };
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/variables.less'; @import '@/style/variables.less';

View File

@ -1,6 +1,11 @@
<template> <template>
<common-table /> <common-table />
</template> </template>
<script lang="ts">
export default {
name: 'ListFilter',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import CommonTable from '../components/CommonTable.vue'; import CommonTable from '../components/CommonTable.vue';
</script> </script>

View File

@ -15,6 +15,13 @@
</div> </div>
</div> </div>
</template> </template>
<script lang="ts">
export default {
name: 'ListTree',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { SearchIcon } from 'tdesign-icons-vue-next'; import { SearchIcon } from 'tdesign-icons-vue-next';
@ -34,6 +41,7 @@ const onInput = () => {
}; };
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/variables.less'; @import '@/style/variables.less';
.table-tree-container { .table-tree-container {

View File

@ -22,6 +22,11 @@
<footer class="copyright">Copyright @ 2021-2022 Tencent. All Rights Reserved</footer> <footer class="copyright">Copyright @ 2021-2022 Tencent. All Rights Reserved</footer>
</div> </div>
</template> </template>
<script lang="ts">
export default {
name: 'LoginIndex',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
@ -35,6 +40,7 @@ const switchType = (val: string) => {
type.value = val; type.value = val;
}; };
</script> </script>
<style lang="less"> <style lang="less">
@import url('./index.less'); @import url('./index.less');
</style> </style>

View File

@ -3,7 +3,11 @@
<t-button @click="() => $router.push('/')">返回首页</t-button> <t-button @click="() => $router.push('/')">返回首页</t-button>
</result> </result>
</template> </template>
<script lang="ts">
export default {
name: 'Result403',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import Result from '@/components/result/index.vue'; import Result from '@/components/result/index.vue';
</script> </script>

View File

@ -4,6 +4,12 @@
</result> </result>
</template> </template>
<script lang="ts">
export default {
name: 'Result404',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import Result from '@/components/result/index.vue'; import Result from '@/components/result/index.vue';
</script> </script>

View File

@ -3,7 +3,11 @@
<t-button @click="() => $router.push('/')">返回首页</t-button> <t-button @click="() => $router.push('/')">返回首页</t-button>
</result> </result>
</template> </template>
<script lang="ts">
export default {
name: 'Result500',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import Result from '@/components/result/index.vue'; import Result from '@/components/result/index.vue';
</script> </script>

View File

@ -18,11 +18,16 @@
</div> </div>
</result> </result>
</template> </template>
<script lang="ts">
export default {
name: 'ResultBrowserIncompatible',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import Result from '@/components/result/index.vue'; import Result from '@/components/result/index.vue';
import Thumbnail from '@/components/thumbnail/index.vue'; import Thumbnail from '@/components/thumbnail/index.vue';
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/variables.less'; @import '@/style/variables.less';

View File

@ -9,7 +9,11 @@
</div> </div>
</div> </div>
</template> </template>
<script lang="ts">
export default {
name: 'ResultFail',
};
</script>
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/variables.less'; @import '@/style/variables.less';

View File

@ -7,6 +7,11 @@
</result> </result>
</template> </template>
<script lang="ts">
export default {
name: 'ResultNetworkError',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import Result from '@/components/result/index.vue'; import Result from '@/components/result/index.vue';
</script> </script>

View File

@ -9,7 +9,11 @@
</div> </div>
</div> </div>
</template> </template>
<script lang="ts">
export default {
name: 'ResultSuccess',
};
</script>
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/variables.less'; @import '@/style/variables.less';

View File

@ -89,6 +89,11 @@
</t-col> </t-col>
</t-row> </t-row>
</template> </template>
<script lang="ts">
export default {
name: 'UserIndex',
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, onMounted, onUnmounted, watch, computed } from 'vue'; import { nextTick, onMounted, onUnmounted, watch, computed } from 'vue';
import * as echarts from 'echarts/core'; import * as echarts from 'echarts/core';
@ -172,6 +177,7 @@ watch(
}, },
); );
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./index.less'); @import url('./index.less');
</style> </style>

View File

@ -11,13 +11,13 @@ export default [
children: [ children: [
{ {
path: 'base', path: 'base',
name: 'dashboardBase', name: 'DashboardBase',
component: () => import('@/pages/dashboard/base/index.vue'), component: () => import('@/pages/dashboard/base/index.vue'),
meta: { title: '概览仪表盘' }, meta: { title: '概览仪表盘' },
}, },
{ {
path: 'detail', path: 'detail',
name: 'dashboardDetail', name: 'DashboardDetail',
component: () => import('@/pages/dashboard/detail/index.vue'), component: () => import('@/pages/dashboard/detail/index.vue'),
meta: { title: '统计报表' }, meta: { title: '统计报表' },
}, },

View File

@ -13,25 +13,25 @@ export default [
children: [ children: [
{ {
path: 'base', path: 'base',
name: 'listBase', name: 'ListBase',
component: () => import('@/pages/list/base/index.vue'), component: () => import('@/pages/list/base/index.vue'),
meta: { title: '基础列表页' }, meta: { title: '基础列表页' },
}, },
{ {
path: 'card', path: 'card',
name: 'listCard', name: 'ListCard',
component: () => import('@/pages/list/card/index.vue'), component: () => import('@/pages/list/card/index.vue'),
meta: { title: '卡片列表页' }, meta: { title: '卡片列表页' },
}, },
{ {
path: 'filter', path: 'filter',
name: 'listFilter', name: 'ListFilter',
component: () => import('@/pages/list/filter/index.vue'), component: () => import('@/pages/list/filter/index.vue'),
meta: { title: '筛选列表页' }, meta: { title: '筛选列表页' },
}, },
{ {
path: 'tree', path: 'tree',
name: 'listTree', name: 'ListTree',
component: () => import('@/pages/list/tree/index.vue'), component: () => import('@/pages/list/tree/index.vue'),
meta: { title: '树状筛选列表页' }, meta: { title: '树状筛选列表页' },
}, },
@ -46,13 +46,13 @@ export default [
children: [ children: [
{ {
path: 'base', path: 'base',
name: 'formBase', name: 'FormBase',
component: () => import('@/pages/form/base/index.vue'), component: () => import('@/pages/form/base/index.vue'),
meta: { title: '基础表单页' }, meta: { title: '基础表单页' },
}, },
{ {
path: 'step', path: 'step',
name: 'formStep', name: 'FormStep',
component: () => import('@/pages/form/step/index.vue'), component: () => import('@/pages/form/step/index.vue'),
meta: { title: '分步表单页' }, meta: { title: '分步表单页' },
}, },
@ -67,25 +67,25 @@ export default [
children: [ children: [
{ {
path: 'base', path: 'base',
name: 'detailBase', name: 'DetailBase',
component: () => import('@/pages/detail/base/index.vue'), component: () => import('@/pages/detail/base/index.vue'),
meta: { title: '基础详情页' }, meta: { title: '基础详情页' },
}, },
{ {
path: 'advanced', path: 'advanced',
name: 'detailAdvanced', name: 'DetailAdvanced',
component: () => import('@/pages/detail/advanced/index.vue'), component: () => import('@/pages/detail/advanced/index.vue'),
meta: { title: '多卡片详情页' }, meta: { title: '多卡片详情页' },
}, },
{ {
path: 'deploy', path: 'deploy',
name: 'detailDeploy', name: 'DetailDeploy',
component: () => import('@/pages/detail/deploy/index.vue'), component: () => import('@/pages/detail/deploy/index.vue'),
meta: { title: '数据详情页' }, meta: { title: '数据详情页' },
}, },
{ {
path: 'secondary', path: 'secondary',
name: 'detailSecondary', name: 'DetailSecondary',
component: () => import('@/pages/detail/secondary/index.vue'), component: () => import('@/pages/detail/secondary/index.vue'),
meta: { title: '二级详情页' }, meta: { title: '二级详情页' },
}, },
@ -100,43 +100,43 @@ export default [
children: [ children: [
{ {
path: 'success', path: 'success',
name: 'resultSuccess', name: 'ResultSuccess',
component: () => import('@/pages/result/success/index.vue'), component: () => import('@/pages/result/success/index.vue'),
meta: { title: '成功页' }, meta: { title: '成功页' },
}, },
{ {
path: 'fail', path: 'fail',
name: 'resultFail', name: 'ResultFail',
component: () => import('@/pages/result/fail/index.vue'), component: () => import('@/pages/result/fail/index.vue'),
meta: { title: '失败页' }, meta: { title: '失败页' },
}, },
{ {
path: 'network-error', path: 'network-error',
name: 'warningNetworkError', name: 'ResultNetworkError',
component: () => import('@/pages/result/network-error/index.vue'), component: () => import('@/pages/result/network-error/index.vue'),
meta: { title: '网络异常' }, meta: { title: '网络异常' },
}, },
{ {
path: '403', path: '403',
name: 'warning403', name: 'Result403',
component: () => import('@/pages/result/403/index.vue'), component: () => import('@/pages/result/403/index.vue'),
meta: { title: '无权限' }, meta: { title: '无权限' },
}, },
{ {
path: '404', path: '404',
name: 'warning404', name: 'Result404',
component: () => import('@/pages/result/404/index.vue'), component: () => import('@/pages/result/404/index.vue'),
meta: { title: '访问页面不存在页' }, meta: { title: '访问页面不存在页' },
}, },
{ {
path: '500', path: '500',
name: 'warning500', name: 'Result500',
component: () => import('@/pages/result/500/index.vue'), component: () => import('@/pages/result/500/index.vue'),
meta: { title: '服务器出错页' }, meta: { title: '服务器出错页' },
}, },
{ {
path: 'browser-incompatible', path: 'browser-incompatible',
name: 'warningBrowserIncompatible', name: 'ResultBrowserIncompatible',
component: () => import('@/pages/result/browser-incompatible/index.vue'), component: () => import('@/pages/result/browser-incompatible/index.vue'),
meta: { title: '浏览器不兼容页' }, meta: { title: '浏览器不兼容页' },
}, },

View File

@ -11,7 +11,7 @@ export default [
children: [ children: [
{ {
path: 'index', path: 'index',
name: 'userIndex', name: 'UserIndex',
component: () => import('@/pages/user/index.vue'), component: () => import('@/pages/user/index.vue'),
meta: { title: '个人中心' }, meta: { title: '个人中心' },
}, },

View File

@ -8,5 +8,6 @@ export * from './modules/notification';
export * from './modules/permission'; export * from './modules/permission';
export * from './modules/user'; export * from './modules/user';
export * from './modules/setting'; export * from './modules/setting';
export * from './modules/tabs-router';
export default store; export default store;

View File

@ -0,0 +1,51 @@
import { defineStore } from 'pinia';
import { TRouterInfo, TTabRouterType } from '@/interface';
import { store } from '@/store';
const state = {
tabRouterList: [{ path: '/dashboard/base', routeIdx: 0, title: '仪表盘', name: 'DashboardBase', isHome: true }],
isRefreshing: false,
};
export const useTabsRouterStore = defineStore('tabsRouter', {
state: () => state,
getters: {
tabRouters: (state: TTabRouterType) => state.tabRouterList,
refreshing: (state: TTabRouterType) => state.isRefreshing,
},
actions: {
toggleTabRouterAlive(routeIdx: number) {
this.isRefreshing = !this.isRefreshing;
this.tabRouters[routeIdx].isAlive = !this.tabRouterList[routeIdx].isAlive;
},
appendTabRouterList(newRoute: TRouterInfo) {
if (!this.tabRouterList.find((route: TRouterInfo) => route.path === newRoute.path)) {
// eslint-disable-next-line no-param-reassign
this.tabRouterList = this.tabRouterList.concat(newRoute);
}
},
subtractCurrentTabRouter(newRoute: TRouterInfo) {
const { routeIdx } = newRoute;
this.tabRouterList = this.tabRouterList.slice(0, routeIdx).concat(this.tabRouterList.slice(routeIdx + 1));
},
subtractTabRouterBehind(newRoute: TRouterInfo) {
const { routeIdx } = newRoute;
this.tabRouterList = this.tabRouterList.slice(0, routeIdx + 1);
},
subtractTabRouterAhead(newRoute: TRouterInfo) {
const { routeIdx } = newRoute;
this.tabRouterList = this.tabRouterList.slice(routeIdx);
},
subtractTabRouterOther(newRoute: TRouterInfo) {
const { routeIdx } = newRoute;
this.tabRouterList = [this.tabRouterList?.[routeIdx]];
},
removeTabRouterList() {
this.tabRouterList = [];
},
},
});
export function getTabsRouterStore() {
return useTabsRouterStore(store);
}

View File

@ -56,13 +56,22 @@
} }
} }
&-content-layout {
padding: @spacer-3;
}
&-layout{ &-layout{
height: calc(100vh - 64px); height: calc(100vh - 64px);
overflow-y: scroll; overflow-y: scroll;
} &-tabs-nav {
max-width: 100%;
&-content-layout { position: fixed;
padding: @spacer-3; overflow: visible;
z-index: 999;
}
&-tabs-nav + .@{prefix}-content-layout {
padding-top: 48px + @spacer-3;
}
} }
&-footer-layout { &-footer-layout {
@ -151,6 +160,12 @@
} }
} }
.route-tabs-dropdown {
.t-icon {
margin-right: 8px;
}
}
.logo-container { .logo-container {
cursor: pointer; cursor: pointer;
display: inline-flex; display: inline-flex;