Skip to content

Commit

Permalink
feat(projects): add tab store, PageTab
Browse files Browse the repository at this point in the history
  • Loading branch information
honghuangdc committed Nov 6, 2023
1 parent b80a086 commit ecd0df2
Show file tree
Hide file tree
Showing 14 changed files with 629 additions and 18 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
"prepare": "simple-git-hooks"
},
"dependencies": {
"@better-scroll/core": "2.5.1",
"@iconify/vue": "4.1.1",
"@sa/color-palette": "workspace:*",
"@sa/hooks": "workspace:*",
"@sa/materials": "workspace:*",
"@sa/request": "workspace:^",
"@sa/request": "workspace:*",
"@sa/utils": "workspace:*",
"@vueuse/core": "10.5.0",
"ant-design-vue": "4.0.6",
Expand Down
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 52 additions & 0 deletions src/components/custom/better-scroll.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue';
import { useElementSize } from '@vueuse/core';
import BScroll from '@better-scroll/core';
import type { Options } from '@better-scroll/core';
defineOptions({ name: 'BetterScroll' });
interface Props {
/**
* BetterScroll options
* @link https://better-scroll.github.io/docs/zh-CN/guide/base-scroll-options.html
*/
options: Options;
}
const props = defineProps<Props>();
const bsWrap = ref<HTMLElement>();
const bsContent = ref<HTMLElement>();
const { width: wrapWidth } = useElementSize(bsWrap);
const { width, height } = useElementSize(bsContent);
const instance = ref<BScroll>();
const isScrollY = computed(() => Boolean(props.options.scrollY));
function initBetterScroll() {
if (!bsWrap.value) return;
instance.value = new BScroll(bsWrap.value, props.options);
}
// refresh BS when scroll element size changed
watch([() => wrapWidth.value, () => width.value, () => height.value], () => {
instance.value?.refresh();
});
onMounted(() => {
initBetterScroll();
});
defineExpose({ instance });
</script>

<template>
<div ref="bsWrap" class="h-full text-left">
<div ref="bsContent" class="inline-block" :class="{ 'h-full': !isScrollY }">
<slot></slot>
</div>
</div>
</template>

<style scoped></style>
3 changes: 2 additions & 1 deletion src/enum/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export enum SetupStoreId {
App = 'app-store',
Theme = 'theme-store',
Auth = 'auth-store',
Route = 'route-store'
Route = 'route-store',
Tab = 'tab-store'
}
2 changes: 1 addition & 1 deletion src/hooks/common/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { router as globalRouter } from '@/router';
/**
* router push
* @description jump to the specified route, it can replace function router.push
* @param inSetup
* @param inSetup whether is in vue script setup
*/
export function useRouterPush(inSetup = true) {
const router = inSetup ? useRouter() : globalRouter;
Expand Down
114 changes: 113 additions & 1 deletion src/layouts/modules/global-tab/index.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,123 @@
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue';
import { useRoute } from 'vue-router';
import { useElementBounding } from '@vueuse/core';
import { PageTab } from '@sa/materials';
import BetterScroll from '@/components/custom/better-scroll.vue';
import { useTabStore } from '@/store/modules/tab';
import { useThemeStore } from '@/store/modules/theme';
defineOptions({
name: 'GlobalTab'
});
const route = useRoute();
const theme = useThemeStore();
const tab = useTabStore();
const bsWrapper = ref<HTMLElement>();
const { width: bsWrapperWidth, left: bsWrapperLeft } = useElementBounding(bsWrapper);
const bsScroll = ref<InstanceType<typeof BetterScroll>>();
const tabRef = ref<HTMLElement>();
const isChromeMode = false;
const TAB_DATA_ID = 'data-tab-id';
type TabNamedNodeMap = NamedNodeMap & {
[TAB_DATA_ID]: Attr;
};
async function scrollToActiveTab() {
await nextTick();
if (!tabRef.value) return;
const { children } = tabRef.value;
for (let i = 0; i < children.length; i += 1) {
const child = children[i];
const { value: tabId } = (child.attributes as TabNamedNodeMap)[TAB_DATA_ID];
if (tabId === tab.activeTabId) {
const { left, width } = child.getBoundingClientRect();
const clientX = left + width / 2;
setTimeout(() => {
scrollByClientX(clientX);
}, 50);
break;
}
}
}
function scrollByClientX(clientX: number) {
const currentX = clientX - bsWrapperLeft.value;
const deltaX = currentX - bsWrapperWidth.value / 2;
if (bsScroll.value?.instance) {
const { maxScrollX, x: leftX, scrollBy } = bsScroll.value.instance;
const rightX = maxScrollX - leftX;
const update = deltaX > 0 ? Math.max(-deltaX, rightX) : Math.min(-deltaX, -leftX);
scrollBy(update, 0, 300);
}
}
function init() {
tab.initTabStore(route);
}
// watch
watch(
() => route.fullPath,
() => {
tab.addTab(route);
}
);
watch(
() => tab.activeTabId,
() => {
scrollToActiveTab();
}
);
// init
init();
</script>

<template>
<DarkModeContainer class="flex-y-center wh-full shadow-tab"></DarkModeContainer>
<DarkModeContainer class="flex-y-center wh-full pl-16px shadow-tab">
<div ref="bsWrapper" class="flex-1-hidden h-full">
<BetterScroll ref="bsScroll" :options="{ scrollX: true, scrollY: false }">
<div ref="tabRef" class="flex h-full pr-18px" :class="[isChromeMode ? 'items-end' : 'items-center gap-12px']">
<PageTab
v-for="item in tab.tabs"
:key="item.id"
mode="button"
:[TAB_DATA_ID]="item.id"
:dark-mode="theme.darkMode"
:active="item.id === tab.activeTabId"
:active-color="theme.themeColor"
:closable="!tab.isTabRetain(item.id)"
@click="tab.switchRouteByTab(item)"
@close="tab.removeTab(item.id)"
>
<template #prefix>
<SvgIcon
:icon="item.icon"
:local-icon="item.localIcon"
class="inline-block align-text-bottom text-16px"
/>
</template>
{{ item.label }}
</PageTab>
</div>
</BetterScroll>
</div>
</DarkModeContainer>
</template>

<style scoped></style>
1 change: 1 addition & 0 deletions src/router/elegant/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export const generatedRoutes: GeneratedRoute[] = [
name: 'manage_user-detail',
path: '/manage/user-detail/:id',
component: 'view.manage_user-detail',
props: true,
meta: {
title: 'manage_user-detail',
i18nKey: 'route.manage_user-detail',
Expand Down
4 changes: 1 addition & 3 deletions src/router/guard/permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ export function createPermissionGuard(router: Router) {
router.beforeEach(async (to, from, next) => {
const pass = await createAuthRouteGuard(to, from, next);

if (!pass) {
return;
}
if (!pass) return;

// 1. route with href
if (to.meta.href) {
Expand Down
4 changes: 4 additions & 0 deletions src/store/modules/route/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import {
} from './shared';
import { useAppStore } from '../app';
import { useAuthStore } from '../auth';
import { useTabStore } from '../tab';

export const useRouteStore = defineStore(SetupStoreId.Route, () => {
const app = useAppStore();
const auth = useAuthStore();
const tab = useTabStore();
const scope = effectScope();
const { bool: isInitAuthRoute, setBool: setIsInitAuthRoute } = useBoolean();

Expand Down Expand Up @@ -101,6 +103,8 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
} else {
await initDynamicAuthRoute();
}

tab.initHomeTab(router);
}

/**
Expand Down
Loading

0 comments on commit ecd0df2

Please sign in to comment.