mirror of
https://github.com/Tencent/tdesign-vue-next-starter.git
synced 2024-11-10 11:44:40 +08:00
Merge pull request #98 from Tencent/feat/multiple-tabs-support
feat: 支持多标签tab页的功能
This commit is contained in:
commit
f2d9752190
4
.env
4
.env
|
@ -1,3 +1,3 @@
|
|||
VITE_SOME_KEY=123
|
||||
# 打包路径
|
||||
VITE_BASE_URL = /
|
||||
# 打包路径 根据项目不同按需配置
|
||||
VITE_BASE_URL = ./
|
21
.eslintrc
21
.eslintrc
|
@ -15,10 +15,7 @@
|
|||
"defineProps": "readonly",
|
||||
"defineEmits": "readonly"
|
||||
},
|
||||
"plugins": [
|
||||
"vue",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"plugins": ["vue", "@typescript-eslint"],
|
||||
"parserOptions": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"sourceType": "module",
|
||||
|
@ -28,27 +25,22 @@
|
|||
}
|
||||
},
|
||||
"settings": {
|
||||
"import/extensions": [
|
||||
".js",
|
||||
".jsx",
|
||||
".ts",
|
||||
".tsx"
|
||||
]
|
||||
"import/extensions": [".js", ".jsx", ".ts", ".tsx"]
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"no-continue": "off",
|
||||
"no-restricted-syntax": "off",
|
||||
"no-plusplus": "off",
|
||||
"no-param-reassign": "off",
|
||||
"no-shadow": "off",
|
||||
"no-param-reassign": "off",
|
||||
"no-shadow": "off",
|
||||
"guard-for-in": "off",
|
||||
|
||||
"import/extensions": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"import/no-extraneous-dependencies": "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/explicit-module-boundary-types": "off",
|
||||
"vue/first-attribute-linebreak": 0
|
||||
|
@ -61,8 +53,7 @@
|
|||
"vue/require-default-prop": 0,
|
||||
"vue/multi-word-component-names": 0,
|
||||
"vue/no-reserved-props": 0,
|
||||
"vue/no-v-html": 0,
|
||||
|
||||
"vue/no-v-html": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -8,6 +8,7 @@ export default {
|
|||
isFooterAside: false,
|
||||
isSidebarFixed: true,
|
||||
isHeaderFixed: true,
|
||||
isUseTabsRouter: false,
|
||||
showHeader: true,
|
||||
backgroundTheme: 'blueGrey',
|
||||
brandTheme: 'default',
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { RouteRecordName } from 'vue-router';
|
||||
import STYLE_CONFIG from '@/config/style';
|
||||
|
||||
export interface ResDataType {
|
||||
|
@ -37,3 +38,17 @@ export interface NotificationItem {
|
|||
date: 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>;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,30 @@
|
|||
<template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<router-view v-if="!isRefreshing" v-slot="{ Component }">
|
||||
<transition name="fade" mode="out-in">
|
||||
<component :is="Component" />
|
||||
<keep-alive :include="aliveViews">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ComputedRef } 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);
|
||||
}) as ComputedRef<string[]>;
|
||||
|
||||
const isRefreshing = computed(() => {
|
||||
const tabsRouterStore = useTabsRouterStore();
|
||||
const { refreshing } = tabsRouterStore;
|
||||
return refreshing;
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import '@/style/variables';
|
||||
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import { defineComponent, computed } from 'vue';
|
||||
import { defineComponent, computed, nextTick, onMounted, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { usePermissionStore, useSettingStore } from '@/store';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { usePermissionStore, useSettingStore, useTabsRouterStore } from '@/store';
|
||||
|
||||
import TDesignHeader from './components/Header.vue';
|
||||
import TDesignBreadcrumb from './components/Breadcrumb.vue';
|
||||
import TDesignFooter from './components/Footer.vue';
|
||||
import TDesignSideNav from './components/SideNav';
|
||||
import TDesignContent from './components/Content.vue';
|
||||
import LayoutHeader from './components/Header.vue';
|
||||
import LayoutBreadcrumb from './components/Breadcrumb.vue';
|
||||
import LayoutFooter from './components/Footer.vue';
|
||||
import LayoutSideNav from './components/SideNav';
|
||||
import LayoutContent from './components/Content.vue';
|
||||
import Setting from './setting.vue';
|
||||
|
||||
import { prefix } from '@/config/global';
|
||||
import TdesignSetting from './setting.vue';
|
||||
|
||||
import '@/style/layout.less';
|
||||
|
||||
const name = `${prefix}-base-layout`;
|
||||
|
@ -19,8 +20,10 @@ export default defineComponent({
|
|||
name,
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const permissionStore = usePermissionStore();
|
||||
const settingStore = useSettingStore();
|
||||
const tabsRouterStore = useTabsRouterStore();
|
||||
const { routers: menuRouters } = storeToRefs(permissionStore);
|
||||
const setting = storeToRefs(settingStore);
|
||||
|
||||
|
@ -57,10 +60,59 @@ export default defineComponent({
|
|||
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 = () => {
|
||||
return (
|
||||
settingStore.showSidebar && (
|
||||
<TDesignSideNav
|
||||
<LayoutSideNav
|
||||
showLogo={settingStore.showSidebarLogo}
|
||||
layout={settingStore.layout}
|
||||
isFixed={settingStore.isSidebarFixed}
|
||||
|
@ -75,7 +127,7 @@ export default defineComponent({
|
|||
const renderHeader = () => {
|
||||
return (
|
||||
settingStore.showHeader && (
|
||||
<TDesignHeader
|
||||
<LayoutHeader
|
||||
showLogo={settingStore.showHeaderLogo}
|
||||
theme={settingStore.displayMode}
|
||||
layout={settingStore.layout}
|
||||
|
@ -90,18 +142,72 @@ export default defineComponent({
|
|||
const renderFooter = () => {
|
||||
return (
|
||||
<t-footer class={`${prefix}-footer-layout`}>
|
||||
<TDesignFooter />
|
||||
<LayoutFooter />
|
||||
</t-footer>
|
||||
);
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
const { showBreadcrumb, showFooter } = settingStore;
|
||||
const { showBreadcrumb, showFooter, isUseTabsRouter } = settingStore;
|
||||
const { tabRouters } = tabsRouterStore;
|
||||
return (
|
||||
<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`}>
|
||||
{showBreadcrumb && <TDesignBreadcrumb />}
|
||||
<TDesignContent />
|
||||
{showBreadcrumb && <LayoutBreadcrumb />}
|
||||
<LayoutContent />
|
||||
</t-content>
|
||||
{showFooter && renderFooter()}
|
||||
</t-layout>
|
||||
|
@ -134,7 +240,7 @@ export default defineComponent({
|
|||
<t-layout class={this.mainLayoutCls}>{[sidebar, content]}</t-layout>
|
||||
</t-layout>
|
||||
)}
|
||||
<TdesignSetting />
|
||||
<Setting />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -82,6 +82,9 @@
|
|||
<t-form-item label="显示 Footer" name="showFooter">
|
||||
<t-switch v-model="formData.showFooter" />
|
||||
</t-form-item>
|
||||
<t-form-item label="使用 多标签Tab页" name="isUseTabsRouter">
|
||||
<t-switch v-model="formData.isUseTabsRouter"></t-switch>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<div class="setting-info">
|
||||
<p>请复制后手动修改配置文件: /src/config/style.ts</p>
|
||||
|
|
|
@ -202,6 +202,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DashboardBase',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
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 }];
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import './index.less';
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@import '@/style/variables.less';
|
||||
|
||||
|
|
|
@ -57,6 +57,13 @@
|
|||
</card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DashboardDetail',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, onUnmounted, watch, computed } from 'vue';
|
||||
|
||||
|
@ -149,6 +156,7 @@ const onMaterialChange = (value: string[]) => {
|
|||
lineChart.setOption(getFolderLineDataSet({ dateTime: value, ...chartColors.value }));
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url('./index.less');
|
||||
</style>
|
||||
|
|
|
@ -109,6 +109,13 @@
|
|||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DetailAdvanced',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { prefix } from '@/config/global';
|
||||
|
@ -175,6 +182,7 @@ const onConfirm = () => {
|
|||
visible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url('./index.less');
|
||||
</style>
|
||||
|
|
|
@ -26,6 +26,13 @@
|
|||
</card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DetailBase',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Card from '@/components/card/index.vue';
|
||||
|
||||
|
@ -108,6 +115,7 @@ const BASE_INFO_DATA = [
|
|||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url('./index.less');
|
||||
</style>
|
||||
|
|
|
@ -68,6 +68,13 @@
|
|||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DetailDeploy',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref, watch, computed } from 'vue';
|
||||
|
||||
|
@ -195,6 +202,7 @@ const deleteClickOp = (e) => {
|
|||
data.value.splice(e.rowIndex, 1);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url('../base/index.less');
|
||||
</style>
|
||||
|
|
|
@ -49,6 +49,13 @@
|
|||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DetailSecondary',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
@ -114,6 +121,7 @@ const deleteMsg = () => {
|
|||
store.setMsgData(changeMsg);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url('./index.less');
|
||||
</style>
|
||||
|
|
|
@ -156,6 +156,13 @@
|
|||
</div>
|
||||
</t-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'FormBase',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { MessagePlugin } from 'tdesign-vue-next';
|
||||
|
@ -190,6 +197,7 @@ const formatResponse = (res) => {
|
|||
return { ...res, error: '上传失败,请重试', url: res.url };
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url('./index.less');
|
||||
</style>
|
||||
|
|
|
@ -139,6 +139,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'FormStep',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
@ -186,6 +193,7 @@ const complete = () => {
|
|||
router.replace({ path: '/detail/advanced' });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url('./index.less');
|
||||
</style>
|
||||
|
|
|
@ -66,6 +66,13 @@
|
|||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ListBase',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
@ -171,6 +178,7 @@ const handleClickDelete = (row: { rowIndex: any }) => {
|
|||
confirmVisible.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '@/style/variables';
|
||||
|
||||
|
|
|
@ -60,6 +60,13 @@
|
|||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ListCard',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { SearchIcon } from 'tdesign-icons-vue-next';
|
||||
|
@ -141,6 +148,7 @@ const handleManageProduct = (product) => {
|
|||
formData.value = { ...product, status: product?.isSetup ? '1' : '0' };
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '@/style/variables.less';
|
||||
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
<template>
|
||||
<common-table />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ListFilter',
|
||||
};
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import CommonTable from '../components/CommonTable.vue';
|
||||
</script>
|
||||
|
|
|
@ -15,6 +15,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ListTree',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { SearchIcon } from 'tdesign-icons-vue-next';
|
||||
|
@ -34,6 +41,7 @@ const onInput = () => {
|
|||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '@/style/variables.less';
|
||||
.table-tree-container {
|
||||
|
|
|
@ -22,6 +22,11 @@
|
|||
<footer class="copyright">Copyright @ 2021-2022 Tencent. All Rights Reserved</footer>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'LoginIndex',
|
||||
};
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
|
@ -35,6 +40,7 @@ const switchType = (val: string) => {
|
|||
type.value = val;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import url('./index.less');
|
||||
</style>
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
<t-button @click="() => $router.push('/')">返回首页</t-button>
|
||||
</result>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Result403',
|
||||
};
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import Result from '@/components/result/index.vue';
|
||||
</script>
|
||||
|
|
|
@ -4,6 +4,12 @@
|
|||
</result>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Result404',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Result from '@/components/result/index.vue';
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
<t-button @click="() => $router.push('/')">返回首页</t-button>
|
||||
</result>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Result500',
|
||||
};
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import Result from '@/components/result/index.vue';
|
||||
</script>
|
||||
|
|
|
@ -18,11 +18,16 @@
|
|||
</div>
|
||||
</result>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ResultBrowserIncompatible',
|
||||
};
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import Result from '@/components/result/index.vue';
|
||||
import Thumbnail from '@/components/thumbnail/index.vue';
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '@/style/variables.less';
|
||||
|
||||
|
|
|
@ -9,7 +9,11 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ResultFail',
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import '@/style/variables.less';
|
||||
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
</result>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ResultNetworkError',
|
||||
};
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import Result from '@/components/result/index.vue';
|
||||
</script>
|
||||
|
|
|
@ -9,7 +9,11 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ResultSuccess',
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import '@/style/variables.less';
|
||||
|
||||
|
|
|
@ -89,6 +89,11 @@
|
|||
</t-col>
|
||||
</t-row>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'UserIndex',
|
||||
};
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, onUnmounted, watch, computed } from 'vue';
|
||||
import * as echarts from 'echarts/core';
|
||||
|
@ -172,6 +177,7 @@ watch(
|
|||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import url('./index.less');
|
||||
</style>
|
||||
|
|
|
@ -11,13 +11,13 @@ export default [
|
|||
children: [
|
||||
{
|
||||
path: 'base',
|
||||
name: 'dashboardBase',
|
||||
name: 'DashboardBase',
|
||||
component: () => import('@/pages/dashboard/base/index.vue'),
|
||||
meta: { title: '概览仪表盘' },
|
||||
},
|
||||
{
|
||||
path: 'detail',
|
||||
name: 'dashboardDetail',
|
||||
name: 'DashboardDetail',
|
||||
component: () => import('@/pages/dashboard/detail/index.vue'),
|
||||
meta: { title: '统计报表' },
|
||||
},
|
||||
|
|
|
@ -13,25 +13,25 @@ export default [
|
|||
children: [
|
||||
{
|
||||
path: 'base',
|
||||
name: 'listBase',
|
||||
name: 'ListBase',
|
||||
component: () => import('@/pages/list/base/index.vue'),
|
||||
meta: { title: '基础列表页' },
|
||||
},
|
||||
{
|
||||
path: 'card',
|
||||
name: 'listCard',
|
||||
name: 'ListCard',
|
||||
component: () => import('@/pages/list/card/index.vue'),
|
||||
meta: { title: '卡片列表页' },
|
||||
},
|
||||
{
|
||||
path: 'filter',
|
||||
name: 'listFilter',
|
||||
name: 'ListFilter',
|
||||
component: () => import('@/pages/list/filter/index.vue'),
|
||||
meta: { title: '筛选列表页' },
|
||||
},
|
||||
{
|
||||
path: 'tree',
|
||||
name: 'listTree',
|
||||
name: 'ListTree',
|
||||
component: () => import('@/pages/list/tree/index.vue'),
|
||||
meta: { title: '树状筛选列表页' },
|
||||
},
|
||||
|
@ -46,13 +46,13 @@ export default [
|
|||
children: [
|
||||
{
|
||||
path: 'base',
|
||||
name: 'formBase',
|
||||
name: 'FormBase',
|
||||
component: () => import('@/pages/form/base/index.vue'),
|
||||
meta: { title: '基础表单页' },
|
||||
},
|
||||
{
|
||||
path: 'step',
|
||||
name: 'formStep',
|
||||
name: 'FormStep',
|
||||
component: () => import('@/pages/form/step/index.vue'),
|
||||
meta: { title: '分步表单页' },
|
||||
},
|
||||
|
@ -67,25 +67,25 @@ export default [
|
|||
children: [
|
||||
{
|
||||
path: 'base',
|
||||
name: 'detailBase',
|
||||
name: 'DetailBase',
|
||||
component: () => import('@/pages/detail/base/index.vue'),
|
||||
meta: { title: '基础详情页' },
|
||||
},
|
||||
{
|
||||
path: 'advanced',
|
||||
name: 'detailAdvanced',
|
||||
name: 'DetailAdvanced',
|
||||
component: () => import('@/pages/detail/advanced/index.vue'),
|
||||
meta: { title: '多卡片详情页' },
|
||||
},
|
||||
{
|
||||
path: 'deploy',
|
||||
name: 'detailDeploy',
|
||||
name: 'DetailDeploy',
|
||||
component: () => import('@/pages/detail/deploy/index.vue'),
|
||||
meta: { title: '数据详情页' },
|
||||
},
|
||||
{
|
||||
path: 'secondary',
|
||||
name: 'detailSecondary',
|
||||
name: 'DetailSecondary',
|
||||
component: () => import('@/pages/detail/secondary/index.vue'),
|
||||
meta: { title: '二级详情页' },
|
||||
},
|
||||
|
@ -100,43 +100,43 @@ export default [
|
|||
children: [
|
||||
{
|
||||
path: 'success',
|
||||
name: 'resultSuccess',
|
||||
name: 'ResultSuccess',
|
||||
component: () => import('@/pages/result/success/index.vue'),
|
||||
meta: { title: '成功页' },
|
||||
},
|
||||
{
|
||||
path: 'fail',
|
||||
name: 'resultFail',
|
||||
name: 'ResultFail',
|
||||
component: () => import('@/pages/result/fail/index.vue'),
|
||||
meta: { title: '失败页' },
|
||||
},
|
||||
{
|
||||
path: 'network-error',
|
||||
name: 'warningNetworkError',
|
||||
name: 'ResultNetworkError',
|
||||
component: () => import('@/pages/result/network-error/index.vue'),
|
||||
meta: { title: '网络异常' },
|
||||
},
|
||||
{
|
||||
path: '403',
|
||||
name: 'warning403',
|
||||
name: 'Result403',
|
||||
component: () => import('@/pages/result/403/index.vue'),
|
||||
meta: { title: '无权限' },
|
||||
},
|
||||
{
|
||||
path: '404',
|
||||
name: 'warning404',
|
||||
name: 'Result404',
|
||||
component: () => import('@/pages/result/404/index.vue'),
|
||||
meta: { title: '访问页面不存在页' },
|
||||
},
|
||||
{
|
||||
path: '500',
|
||||
name: 'warning500',
|
||||
name: 'Result500',
|
||||
component: () => import('@/pages/result/500/index.vue'),
|
||||
meta: { title: '服务器出错页' },
|
||||
},
|
||||
{
|
||||
path: 'browser-incompatible',
|
||||
name: 'warningBrowserIncompatible',
|
||||
name: 'ResultBrowserIncompatible',
|
||||
component: () => import('@/pages/result/browser-incompatible/index.vue'),
|
||||
meta: { title: '浏览器不兼容页' },
|
||||
},
|
||||
|
|
|
@ -11,7 +11,7 @@ export default [
|
|||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: 'userIndex',
|
||||
name: 'UserIndex',
|
||||
component: () => import('@/pages/user/index.vue'),
|
||||
meta: { title: '个人中心' },
|
||||
},
|
||||
|
|
|
@ -8,5 +8,6 @@ export * from './modules/notification';
|
|||
export * from './modules/permission';
|
||||
export * from './modules/user';
|
||||
export * from './modules/setting';
|
||||
export * from './modules/tabs-router';
|
||||
|
||||
export default store;
|
||||
|
|
51
src/store/modules/tabs-router.ts
Normal file
51
src/store/modules/tabs-router.ts
Normal 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);
|
||||
}
|
|
@ -56,13 +56,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
&-content-layout {
|
||||
padding: @spacer-3;
|
||||
}
|
||||
|
||||
&-layout{
|
||||
height: calc(100vh - 64px);
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
&-content-layout {
|
||||
padding: @spacer-3;
|
||||
&-tabs-nav {
|
||||
max-width: 100%;
|
||||
position: fixed;
|
||||
overflow: visible;
|
||||
z-index: 999;
|
||||
}
|
||||
&-tabs-nav + .@{prefix}-content-layout {
|
||||
padding-top: 48px + @spacer-3;
|
||||
}
|
||||
}
|
||||
|
||||
&-footer-layout {
|
||||
|
@ -151,6 +160,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.route-tabs-dropdown {
|
||||
.t-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
|
|
Loading…
Reference in New Issue
Block a user