新增前端vue
This commit is contained in:
9
web-vue/packages/core/components/Page/index.ts
Normal file
9
web-vue/packages/core/components/Page/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { withInstall } from '@jeesite/core/utils';
|
||||
|
||||
import pageFooter from './src/PageFooter.vue';
|
||||
import pageWrapper from './src/PageWrapper.vue';
|
||||
|
||||
export const PageFooter = withInstall(pageFooter);
|
||||
export const PageWrapper = withInstall(pageWrapper);
|
||||
|
||||
export const PageWrapperFixedHeightKey = 'PageWrapperFixedHeight';
|
||||
52
web-vue/packages/core/components/Page/src/PageFooter.vue
Normal file
52
web-vue/packages/core/components/Page/src/PageFooter.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div :class="prefixCls" :style="{ width: getCalcContentWidth }">
|
||||
<div :class="`${prefixCls}__left`">
|
||||
<slot name="left"></slot>
|
||||
</div>
|
||||
<slot></slot>
|
||||
<div :class="`${prefixCls}__right`">
|
||||
<slot name="right"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useMenuSetting } from '@jeesite/core/hooks/setting/useMenuSetting';
|
||||
import { useDesign } from '@jeesite/core/hooks/web/useDesign';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PageFooter',
|
||||
inheritAttrs: false,
|
||||
setup() {
|
||||
const { prefixCls } = useDesign('page-footer');
|
||||
const { getCalcContentWidth } = useMenuSetting();
|
||||
return { prefixCls, getCalcContentWidth };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'jeesite-page-footer';
|
||||
|
||||
.@{prefix-cls} {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: @page-footer-z-index;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding: 0 24px;
|
||||
line-height: 35.5px;
|
||||
background-color: @component-background;
|
||||
border-top: 1px solid @header-light-bottom-border-color;
|
||||
//box-shadow:
|
||||
// 0 -6px 16px -8px rgb(0 0 0 / 8%),
|
||||
// 0 -9px 28px 0 rgb(0 0 0 / 5%),
|
||||
// 0 -12px 48px 16px rgb(0 0 0 / 3%);
|
||||
transition: width 0.2s;
|
||||
|
||||
&__left {
|
||||
flex: 1 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
477
web-vue/packages/core/components/Page/src/PageWrapper.vue
Normal file
477
web-vue/packages/core/components/Page/src/PageWrapper.vue
Normal file
@@ -0,0 +1,477 @@
|
||||
<!--
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
* @author Vben、ThinkGem
|
||||
-->
|
||||
<template>
|
||||
<div :class="getClass" ref="wrapperRef">
|
||||
<PageHeader ref="headerRef" v-if="getShowHeader" v-bind="omit($attrs, 'class')" :ghost="ghost" :title="title">
|
||||
<template #title v-if="$slots.headerTitle">
|
||||
<slot name="headerTitle"></slot>
|
||||
</template>
|
||||
<template #subTitle v-if="$slots.headerSubTitle">
|
||||
<slot name="headerSubTitle"></slot>
|
||||
</template>
|
||||
<template #default>
|
||||
<template v-if="content">
|
||||
{{ content }}
|
||||
</template>
|
||||
<slot name="headerContent" v-else></slot>
|
||||
</template>
|
||||
<template #[item]="data" v-for="item in getHeaderSlots">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</PageHeader>
|
||||
<div :class="getContentClass" :style="getContentStyle" ref="contentRef">
|
||||
<Layout v-if="sidebar || sidebarRight" :style="getSidebarContentStyle">
|
||||
<Layout.Sider
|
||||
v-if="sidebar"
|
||||
class="sidebar"
|
||||
v-model:collapsed="collapsed"
|
||||
:collapsedWidth="0"
|
||||
:collapsible="true"
|
||||
:trigger="null"
|
||||
:width="getSidebarWidth"
|
||||
breakpoint="md"
|
||||
@breakpoint="onBreakpoint"
|
||||
>
|
||||
<div class="sidebar-content" :style="getSidebarContentStyle">
|
||||
<slot name="sidebar"></slot>
|
||||
</div>
|
||||
<template v-if="!sidebarResizer">
|
||||
<div class="sidebar-close" v-if="!collapsed" @click="collapsed = !collapsed">
|
||||
<Icon icon="i-fa:angle-left" />
|
||||
</div>
|
||||
<div class="sidebar-open" v-else @click="collapsed = !collapsed">
|
||||
<Icon icon="i-fa:angle-left" style="transform: rotate(180deg)" />
|
||||
</div>
|
||||
</template>
|
||||
</Layout.Sider>
|
||||
<template v-if="sidebar && sidebarResizer">
|
||||
<Resizer position="left" v-model:collapsed="collapsed" @move="onSidebarMove" />
|
||||
</template>
|
||||
<template v-if="sidebar && !sidebarResizer">
|
||||
<div v-if="!collapsed" style="margin-right: 12px"></div>
|
||||
</template>
|
||||
<Layout.Content>
|
||||
<slot></slot>
|
||||
</Layout.Content>
|
||||
<template v-if="sidebarRight && sidebarResizerRight">
|
||||
<Resizer position="right" v-model:collapsed="collapsedRight" @move="onSidebarMoveRight" />
|
||||
</template>
|
||||
<template v-if="sidebarRight && !sidebarResizerRight">
|
||||
<div v-if="!collapsedRight" style="margin-left: 12px"></div>
|
||||
</template>
|
||||
<Layout.Sider
|
||||
v-if="sidebarRight"
|
||||
:reverseArrow="true"
|
||||
class="sidebar right"
|
||||
v-model:collapsed="collapsedRight"
|
||||
:collapsedWidth="0"
|
||||
:collapsible="true"
|
||||
:trigger="null"
|
||||
:width="getSidebarWidthRight"
|
||||
breakpoint="md"
|
||||
@breakpoint="onBreakpointRight"
|
||||
>
|
||||
<template v-if="!sidebarResizerRight">
|
||||
<div class="sidebar-close right" v-if="!collapsedRight" @click="collapsedRight = !collapsedRight">
|
||||
<Icon icon="i-fa:angle-right" />
|
||||
</div>
|
||||
<div class="sidebar-open right" v-else @click="collapsedRight = !collapsedRight">
|
||||
<Icon icon="i-fa:angle-right" style="transform: rotate(180deg)" />
|
||||
</div>
|
||||
</template>
|
||||
<div class="sidebar-content" :style="getSidebarContentStyle">
|
||||
<slot name="sidebarRight"></slot>
|
||||
</div>
|
||||
</Layout.Sider>
|
||||
</Layout>
|
||||
<slot v-else></slot>
|
||||
</div>
|
||||
<PageFooter v-if="getShowFooter" ref="footerRef">
|
||||
<template #left>
|
||||
<slot name="leftFooter"></slot>
|
||||
</template>
|
||||
<template #right>
|
||||
<slot name="rightFooter"></slot>
|
||||
</template>
|
||||
</PageFooter>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="PageWrapper">
|
||||
import {
|
||||
computed,
|
||||
CSSProperties,
|
||||
PropType,
|
||||
provide,
|
||||
onBeforeMount,
|
||||
onUpdated,
|
||||
useSlots,
|
||||
useAttrs,
|
||||
watch,
|
||||
ref,
|
||||
unref,
|
||||
} from 'vue';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import PageFooter from './PageFooter.vue';
|
||||
|
||||
import { useDesign } from '@jeesite/core/hooks/web/useDesign';
|
||||
import { useEmitter } from '@jeesite/core/store/modules/user';
|
||||
import { propTypes } from '@jeesite/core/utils/propTypes';
|
||||
import { omit } from 'lodash-es';
|
||||
import { Layout, PageHeader } from 'ant-design-vue';
|
||||
import { useContentHeight } from '@jeesite/core/hooks/web/useContentHeight';
|
||||
import { PageWrapperFixedHeightKey } from '..';
|
||||
import { Icon } from '@jeesite/core/components/Icon';
|
||||
import { Resizer } from '@jeesite/core/components/Resizer';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
title: propTypes.string,
|
||||
dense: propTypes.bool,
|
||||
ghost: propTypes.bool,
|
||||
content: propTypes.string,
|
||||
contentStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
},
|
||||
contentBackground: propTypes.bool.def(true),
|
||||
contentFullHeight: propTypes.bool,
|
||||
contentClass: propTypes.string,
|
||||
fixedHeight: propTypes.bool.def(true),
|
||||
upwardSpace: propTypes.oneOfType([propTypes.number, propTypes.string]).def(0),
|
||||
|
||||
sidebarWidth: propTypes.number.def(230),
|
||||
sidebarResizer: propTypes.bool.def(true),
|
||||
sidebarMinWidth: propTypes.number.def(0),
|
||||
|
||||
sidebarWidthRight: propTypes.number.def(230),
|
||||
sidebarResizerRight: propTypes.bool.def(true),
|
||||
sidebarMinWidthRight: propTypes.number.def(0),
|
||||
});
|
||||
|
||||
const slots = useSlots();
|
||||
const attrs = useAttrs();
|
||||
const emitter = useEmitter();
|
||||
const wrapperRef = ref(null);
|
||||
const headerRef = ref(null);
|
||||
const contentRef = ref(null);
|
||||
const footerRef = ref(null);
|
||||
const { prefixCls } = useDesign('page-wrapper');
|
||||
|
||||
const collapsed = ref<boolean>(false);
|
||||
const sidebar = !!slots.sidebar;
|
||||
const offsetXMoved = ref(0);
|
||||
|
||||
const collapsedRight = ref<boolean>(false);
|
||||
const sidebarRight = !!slots.sidebarRight;
|
||||
const offsetXMovedRight = ref(0);
|
||||
|
||||
provide(
|
||||
PageWrapperFixedHeightKey,
|
||||
computed(() => props.fixedHeight),
|
||||
);
|
||||
|
||||
const getIsContentFullHeight = computed(() => {
|
||||
return props.contentFullHeight || sidebar;
|
||||
});
|
||||
|
||||
const getUpwardSpace = computed(() => props.upwardSpace);
|
||||
const { redoHeight, setCompensation, contentHeight } = useContentHeight(
|
||||
getIsContentFullHeight,
|
||||
wrapperRef,
|
||||
[headerRef, footerRef],
|
||||
[contentRef],
|
||||
getUpwardSpace,
|
||||
);
|
||||
setCompensation({ useLayoutFooter: true, elements: [footerRef] });
|
||||
|
||||
const getClass = computed(() => {
|
||||
return [
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}--dense`]: props.dense || sidebar,
|
||||
},
|
||||
attrs.class ?? {},
|
||||
];
|
||||
});
|
||||
|
||||
const getHeaderSlots = computed(() => {
|
||||
return Object.keys(omit(slots, 'default', 'leftFooter', 'rightFooter', 'headerContent'));
|
||||
});
|
||||
|
||||
const getShowHeader = computed(
|
||||
() =>
|
||||
props.title !== 'false' && (props.content || slots.headerContent || props.title || getHeaderSlots.value.length),
|
||||
);
|
||||
|
||||
const getShowFooter = computed(() => slots?.leftFooter || slots?.rightFooter);
|
||||
|
||||
const getContentStyle = computed((): CSSProperties => {
|
||||
const { contentFullHeight, contentStyle, fixedHeight } = props;
|
||||
const height = `${(unref(contentHeight) || 800) - (!sidebar ? -12 : 0)}px`;
|
||||
|
||||
if (sidebar) {
|
||||
return {
|
||||
...contentStyle,
|
||||
minHeight: height,
|
||||
};
|
||||
} else if (contentFullHeight) {
|
||||
return {
|
||||
...contentStyle,
|
||||
minHeight: height,
|
||||
...(fixedHeight || sidebar ? { height } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
return { ...contentStyle };
|
||||
});
|
||||
|
||||
const getSidebarContentHeight = ref(0);
|
||||
const getSidebarContentStyle = computed((): CSSProperties => {
|
||||
if (getSidebarContentHeight.value <= 0) return {};
|
||||
return {
|
||||
height: `${getSidebarContentHeight.value}px`,
|
||||
minHeight: `${getSidebarContentHeight.value}px`,
|
||||
};
|
||||
});
|
||||
|
||||
// 自适应侧边栏高度 by think gem
|
||||
function calcSidebarContentHeight() {
|
||||
if (props.contentFullHeight && contentHeight.value) {
|
||||
const height = contentHeight.value - 14;
|
||||
getSidebarContentHeight.value = height < 300 ? 300 : height;
|
||||
return;
|
||||
}
|
||||
let height = 0;
|
||||
const el = unref(contentRef) as any;
|
||||
if (!el || el.clientHeight <= 0) return;
|
||||
const table = el.querySelector('.jeesite-basic-table');
|
||||
if (table) {
|
||||
height = table.clientHeight;
|
||||
}
|
||||
if (height <= 0) {
|
||||
const content = el.querySelector('.ant-layout-content');
|
||||
if (content && content.children && content.children.length > 0) {
|
||||
height = content.children[0].clientHeight;
|
||||
}
|
||||
}
|
||||
const mainContentHeight = contentHeight.value || 0;
|
||||
if (height < mainContentHeight) {
|
||||
height = mainContentHeight - 12;
|
||||
}
|
||||
// console.log('calcSidebarContentHeight', height);
|
||||
getSidebarContentHeight.value = height;
|
||||
}
|
||||
|
||||
if (sidebar) {
|
||||
onBeforeMount(() => {
|
||||
emitter.on('on-page-wrapper-resize', () => {
|
||||
setTimeout(calcSidebarContentHeight, 500);
|
||||
});
|
||||
});
|
||||
onUpdated(useDebounceFn(calcSidebarContentHeight, 300));
|
||||
}
|
||||
|
||||
const getContentClass = computed(() => {
|
||||
const { contentBackground, contentClass } = props;
|
||||
return [
|
||||
`${prefixCls}-content`,
|
||||
contentClass,
|
||||
{
|
||||
[`${prefixCls}-content-bg`]: contentBackground && !sidebar,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [getShowFooter.value],
|
||||
() => {
|
||||
redoHeight();
|
||||
},
|
||||
{
|
||||
flush: 'post',
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
const getSidebarWidth = computed(() => {
|
||||
const width = props.sidebarWidth + offsetXMoved.value - 12;
|
||||
return width < props.sidebarMinWidth ? props.sidebarMinWidth : width;
|
||||
});
|
||||
|
||||
function onBreakpoint(broken: boolean) {
|
||||
if (broken) collapsed.value = true;
|
||||
}
|
||||
|
||||
function onSidebarMove(_event, offsetX: number) {
|
||||
offsetXMoved.value = offsetXMoved.value - offsetX;
|
||||
}
|
||||
|
||||
const getSidebarWidthRight = computed(() => {
|
||||
const width = props.sidebarWidthRight + offsetXMovedRight.value - 12;
|
||||
return width < props.sidebarMinWidthRight ? props.sidebarMinWidthRight : width;
|
||||
});
|
||||
|
||||
function onBreakpointRight(broken: boolean) {
|
||||
if (broken) collapsedRight.value = true;
|
||||
}
|
||||
|
||||
function onSidebarMoveRight(_event, offsetX: number) {
|
||||
offsetXMovedRight.value = offsetXMovedRight.value + offsetX;
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'jeesite-page-wrapper';
|
||||
|
||||
.@{prefix-cls} {
|
||||
position: relative;
|
||||
|
||||
.@{prefix-cls}-content {
|
||||
// margin: 16px;
|
||||
padding: 12px;
|
||||
margin-bottom: 13px;
|
||||
border-radius: 5px;
|
||||
color: @text-color-base;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ant-page-header {
|
||||
// margin: 16px;
|
||||
margin-bottom: 12px;
|
||||
padding: 4px 16px;
|
||||
border-radius: 5px;
|
||||
|
||||
.ant-page-header-heading-title {
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.ant-page-header-content {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
padding: 0 0 8px;
|
||||
}
|
||||
|
||||
.anticon {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&:empty {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-content-bg {
|
||||
background-color: @component-background;
|
||||
}
|
||||
|
||||
&--dense {
|
||||
.@{prefix-cls}-content {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.ant-page-header {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-layout {
|
||||
background-color: @content-bg;
|
||||
|
||||
.sidebar {
|
||||
background-color: @content-bg;
|
||||
transition: none;
|
||||
//min-height: 400px;
|
||||
|
||||
&-content {
|
||||
// margin: 15px 0 0 15px;
|
||||
// margin-right: 15px;
|
||||
//height: calc(100% - 14px);
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
|
||||
.jeesite-basic-tree-header {
|
||||
padding: 10px 6px;
|
||||
min-height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
&-open,
|
||||
&-close {
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
top: 54px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
text-align: center;
|
||||
transition: transform 0.3s;
|
||||
color: rgb(0 0 0 / 50%);
|
||||
border-radius: 40px;
|
||||
box-shadow:
|
||||
0 2px 8px -2px rgb(0 0 0 / 5%),
|
||||
0 1px 4px -1px rgb(25 15 15 / 7%),
|
||||
0 0 1px 0 rgb(0 0 0 / 8%);
|
||||
|
||||
&:hover {
|
||||
color: rgb(0 0 0 / 80%);
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&-open {
|
||||
left: -12px;
|
||||
|
||||
&.right {
|
||||
right: -12px;
|
||||
}
|
||||
}
|
||||
|
||||
&-close {
|
||||
right: -12px;
|
||||
|
||||
&.right {
|
||||
left: -12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='dark'] {
|
||||
.@{prefix-cls} {
|
||||
.ant-layout {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
&-open,
|
||||
&-close {
|
||||
border: 1px solid #555;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&-open {
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
&-close {
|
||||
border-right-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user