Commit 6bec1bcd authored by Sendya's avatar Sendya

feat: add fixedHeader

parent 12d82f90
......@@ -44,6 +44,6 @@
"vue-svg-icon-loader": "^2.1.1",
"vue-template-compiler": "^2.6.10",
"webpack-bundle-analyzer": "^3.6.0",
"webpack-theme-color-replacer": "^1.3.2"
"webpack-theme-color-replacer": "^1.3.11"
}
}
......@@ -60,6 +60,7 @@ const menuHeaderRender = (h, logo, title) => {
const rightContentRender = (h, props) => {
const cls = {
'ant-pro-global-header-index-right': true,
'ant-pro-global-header-topmenu': props.isTop,
[`ant-pro-global-header-index-${props.theme}`]: true
}
return (
......@@ -95,13 +96,15 @@ export default {
// 媒体查询
query: {},
// 布局类型
layout: 'sidemenu', // 'sidemenu', 'topmenu'
layout: 'topmenu', // 'sidemenu', 'topmenu'
// 定宽: true / 流式: false
contentWidth: true,
fixedHeader: false,
fixSiderbar: false,
// 主题 'dark' | 'light'
theme: 'dark',
// 主色调
primaryColor: 'daybreak',
primaryColor: '#1890ff',
// 是否手机模式
isMobile: false
}
......@@ -109,7 +112,6 @@ export default {
render (h) {
const {
collapsed,
contentWidth,
autoHideHeader,
layout,
theme,
......@@ -138,6 +140,25 @@ export default {
const handleColorChange = (color) => {
this.primaryColor = color
}
const handleLayoutChange = (value) => {
this.layout = value
if (value === 'sidemenu') {
this.contentWidth = false
}
}
const handleLayoutSttingChange = ({ type, value }) => {
console.log('type', type, 'value', value)
if (type === 'contentWidth') {
this.contentWidth = value === 'Fixed'
}
if (type === 'fixedHeader') {
this.fixedHeader = value
}
if (type === 'fixSiderbar') {
this.fixSiderbar = value
}
}
const cdProps = {
props: {
menus,
......@@ -147,28 +168,36 @@ export default {
handleMediaQuery,
handleCollapse,
layout,
contentWidth,
contentWidth: this.contentWidth,
fixedHeader: this.fixedHeader,
fixSiderbar: this.fixSiderbar,
theme,
isMobile: this.isMobile,
// custom render
rightContentRender,
footerRender,
i18nRender,
menuHeaderRender
menuHeaderRender,
// logo: LogoSvg,
// title: defaultSettings.title
title: 'Ant Design Pro'
}
}
return (
<ProLayout {...cdProps}>
<SettingDrawer
settings={{}}
navTheme={theme}
layout={layout}
primaryColor={primaryColor}
contentWidth={this.contentWidth}
fixedHeader={this.fixedHeader}
fixSiderbar={this.fixSiderbar}
onThemeChange={handleThemeChange}
onColorChange={handleColorChange}
onLayoutChange={handleLayoutChange}
onLayoutSettingChange={handleLayoutSttingChange}
/>
<router-view />
</ProLayout>
......
......@@ -11,6 +11,10 @@
}
}
}
&.ant-pro-global-header-topmenu {
margin-right: 0;
}
.account {
.antd-pro-global-header-index-avatar {
margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0;
......
......@@ -51,5 +51,14 @@ export default {
'app.setting.pagestyle.dark': 'Dark style',
'app.setting.pagestyle.realdark': 'RealDark style',
'app.setting.themecolor': 'Theme Color',
'app.setting.navigationmode': 'Navigation Mode'
'app.setting.navigationmode': 'Navigation Mode',
'app.setting.content-width': 'Content Width',
'app.setting.fixedheader': 'Fixed Header',
'app.setting.fixedsidebar': 'Fixed Sidebar',
'app.setting.sidemenu': 'Side Menu Layout',
'app.setting.topmenu': 'Top Menu Layout',
'app.setting.content-width.fixed': 'Fixed',
'app.setting.content-width.fluid': 'Fluid',
'app.setting.othersettings': 'Other Settings',
'app.setting.weakmode': 'Weak Mode'
}
......@@ -67,6 +67,7 @@ const headerRender = (h, props) => {
if (props.headerRender === false) {
return null
}
console.log('headerRender', props)
return <HeaderView { ...{ props } } />
}
......@@ -80,12 +81,15 @@ const BasicLayout = {
const { props, children } = content
const {
layout,
// contentWidth,
// theme,
isMobile,
collapsed,
mediaQuery,
handleMediaQuery,
handleCollapse,
contentWidth,
siderWidth,
fixSiderbar,
i18nRender = defaultI18nRender
} = props
......@@ -93,30 +97,40 @@ const BasicLayout = {
const rightContentRender = getComponentFromProp(content, 'rightContentRender')
const collapsedButtonRender = getComponentFromProp(content, 'collapsedButtonRender')
const menuHeaderRender = getComponentFromProp(content, 'menuHeaderRender')
const isTopMenu = layout === 'topmenu'
const cdProps = {
...props,
hasSiderMenu: !isTopMenu,
footerRender,
menuHeaderRender,
rightContentRender,
collapsedButtonRender
}
console.log('cdProps', cdProps)
return (
<ConfigProvider i18nRender={i18nRender}>
<ConfigProvider i18nRender={i18nRender} contentWidth={contentWidth}>
<ContainerQuery query={MediaQueryEnum} onChange={handleMediaQuery}>
<Layout class={{ 'basicLayout': true, ...mediaQuery }}>
<Layout class={{
'ant-pro-basicLayout': true,
'ant-pro-topmenu': isTopMenu,
...mediaQuery
}}>
<SiderMenuWrapper
{ ...{ props: cdProps } }
collapsed={collapsed}
onCollapse={handleCollapse}
/>
<Layout class={[layout]} style={{ paddingLeft: '0', minHeight: '100vh' }}>
<Layout class={[layout]} style={{
paddingLeft: fixSiderbar ? `${siderWidth}px` : '0',
minHeight: '100vh'
}}>
{headerRender(h, {
...cdProps,
mode: 'horizontal',
})}
<WrapContent class="ant-pro-basicLayout-content">
<WrapContent class="ant-pro-basicLayout-content" contentWidth={contentWidth}>
{children}
</WrapContent>
<Layout.Footer>
......
......@@ -58,10 +58,8 @@
max-height: 100%;
}
}
}
.basicLayout {
// append hook styles
.ant-layout-sider-children {
height: 100%;
......
/* eslint-disable */
import './Header.less'
import { Layout } from 'ant-design-vue'
import BaseMenu from './components/RouteMenu/BaseMenu'
import { defaultRenderLogoAntTitle, SiderMenuProps } from './components/SiderMenu/SiderMenu'
import GlobalHeader, { GlobalHeaderProps } from './components/GlobalHeader'
import { VueFragment } from './components'
import { isFun } from './utils/util'
const { Header } = Layout
......@@ -24,6 +24,10 @@ export const HeaderViewProps = {
type: [Function, Object],
required: false
},
hasSiderMenu: {
type: Boolean,
default: false
},
autoHideHeader: {
type: Boolean,
required: true
......@@ -40,9 +44,6 @@ export const HeaderViewProps = {
type: null,
required: false
},
siderWidth: {
type: Number
},
visible: {
type: Boolean,
default: true
......@@ -55,7 +56,7 @@ const renderContent = (h, props) => {
const contentWidth = props.contentWidth
const baseCls = 'ant-pro-top-nav-header'
const { logo, title, theme, isMobile, headerRender, rightContentRender } = props
const rightContentProps = { theme }
const rightContentProps = { theme, isTop, isMobile }
let defaultDom = <GlobalHeader {...{ props: props }} />
if (isTop && !isMobile) {
defaultDom = (
......@@ -63,7 +64,7 @@ const renderContent = (h, props) => {
<div class={[`${baseCls}-main`, contentWidth ? 'wide' : '']}>
<div class={`${baseCls}-left`}>
<div class={`${baseCls}-logo`} key="logo" id="logo">
{defaultRenderLogoAntTitle(h, logo, title, null)}
{defaultRenderLogoAntTitle(h, { logo, title, menuHeaderRender: null })}
</div>
</div>
<div class={`${baseCls}-menu`} style={{ maxWidth: `${maxWidth}px`, flex: 1 }}>
......@@ -86,18 +87,43 @@ const HeaderView = {
render (h) {
const {
visible,
siderWidth: width,
isMobile,
layout,
collapsed,
siderWidth,
fixedHeader,
autoHideHeader,
hasSiderMenu,
} = this.$props
const props = this.$props
const isTop = layout === 'topmenu'
const needSettingWidth = fixedHeader && hasSiderMenu && !isTop && !isMobile
const className = {
'ant-pro-fixed-header': fixedHeader,
'ant-pro-top-menu': isTop,
}
// 没有 <></> 暂时代替写法
return (
visible ? (
<Header
style={{ padding: 0, width, zIndex: 2 }}
class={autoHideHeader ? 'ant-pro-fixed-header' : ''}
>
{renderContent(h, props)}
</Header>
<VueFragment>
{ fixedHeader && <Header />}
<Header
style={{
padding: 0,
width: needSettingWidth
? `calc(100% - ${collapsed ? 80 : siderWidth}px)`
: '100%',
zIndex: 9,
right: fixedHeader ? 0 : undefined
}}
class={className}
>
{renderContent(h, props)}
</Header>
</VueFragment>
) : null
)
}
......
@import "~ant-design-vue/es/style/themes/default";
@top-nav-header-prefix-cls: ~'@{ant-prefix}-pro-top-nav-header';
@pro-layout-fixed-header-prefix-cls: ~'@{ant-prefix}-pro-fixed-header';
.@{top-nav-header-prefix-cls} {
position: relative;
......@@ -72,6 +73,12 @@
}
}
.@{pro-layout-fixed-header-prefix-cls} {
z-index: 9;
width: 100%;
transition: width 0.2s;
}
.drop-down {
&.menu {
.anticon {
......
import PropTypes from 'ant-design-vue/es/_util/vue-types'
import { ConfigProvider, Layout } from 'ant-design-vue'
import GridContent from './components/GridContent'
const { Content } = Layout
const WrapContentProps = {
isChildrenLayout: PropTypes.bool,
location: PropTypes.any,
contentHeight: PropTypes.number
contentHeight: PropTypes.number,
contentWidth: PropTypes.bool
}
const WrapContent = {
......@@ -14,7 +16,8 @@ const WrapContent = {
props: WrapContentProps,
render (h) {
const {
isChildrenLayout
isChildrenLayout,
contentWidth
} = this.$props
return (
<Content>
......@@ -27,7 +30,7 @@ const WrapContent = {
}}
>
<div class="ant-pro-basicLayout-children-content-wrap">
{this.$slots.default}
<GridContent contentWidth={contentWidth}>{this.$slots.default}</GridContent>
</div>
</ConfigProvider>
</Content>
......
......@@ -4,11 +4,13 @@ const ConfigProvider = {
name: 'ProConfigProvider',
props: {
i18nRender: PropTypes.any,
contentWidth: PropTypes.bool,
},
provide () {
const _self = this
return {
locale: _self.$props.i18nRender
locale: _self.$props.i18nRender,
contentWidth: _self.$props.contentWidth
}
},
render () {
......
export default {
name: 'VueFragment',
functional: true,
render (h, ctx) {
return ctx.children.length > 1 ? h('div',{}, ctx.children) : ctx.children
}
}
......@@ -18,6 +18,10 @@ export const GlobalHeaderProps = {
type: Boolean,
default: () => false
},
fixedHeader: {
type: Boolean,
default: false
},
logo: {
type: null,
default: () => null
......
......@@ -130,7 +130,7 @@ const defaultPageHeaderRender = (h, props, pageMeta, i18nRender) => {
const PageHeaderWrapper = {
name: 'PageHeaderWrapper',
props: PageHeaderWrapperProps,
inject: ['locale'],
inject: ['locale', 'contentWidth'],
render (h) {
const children = this.$slots.default
const content = getComponentFromProp(this, 'content')
......@@ -139,6 +139,7 @@ const PageHeaderWrapper = {
const pageMeta = useContext(this.$props.route || this.$route)
const i18n = this.$props.i18nRender || this.locale || defaultI18nRender
const contentWidth = this.$props.contentWidth || this.contentWidth || false
// 当未设置 back props 或未监听 @back,不显示 back
const onBack = this.$props.back
const back = onBack && (() => {
......@@ -181,7 +182,7 @@ const PageHeaderWrapper = {
<GridContent>{defaultPageHeaderRender(h, props, pageMeta, i18n)}</GridContent>
</div>
{ children ? (
<GridContent>
<GridContent contentWidth={contentWidth}>
<div class={`${prefixedClassName}-children-content`}>
{children}
</div>
......
import { Tooltip, Icon } from 'ant-design-vue'
import { defaultI18nRender } from './index'
const BlockCheckboxProps = {
value: {
type: String,
default: null
},
// Item: { key, url, title }
list: {
type: Array,
default: () => []
default: null
}
}
......@@ -16,18 +18,39 @@ const baseClassName = 'ant-pro-setting-drawer-block-checbox'
const BlockCheckbox = {
props: BlockCheckboxProps,
inject: ['locale'],
render (h) {
const { value, list } = this
const i18n = this.$props.i18nRender || this.locale || defaultI18nRender
const items = list || [
{
key: 'sidemenu',
url:
'https://gw.alipayobjects.com/zos/antfincdn/XwFOFbLkSM/LCkqqYNmvBEbokSDscrm.svg',
title: i18n('app.setting.sidemenu'),
},
{
key: 'topmenu',
url:
'https://gw.alipayobjects.com/zos/antfincdn/URETY8%24STp/KDNDBbriJhLwuqMoxcAr.svg',
title: i18n('app.setting.topmenu'),
},
]
const handleChange = (key) => {
this.$emit('change', key)
}
const disableStyle = {
cursor: 'not-allowed'
}
return (
<div class={baseClassName} key={value}>
{list.map(item => (
{items.map(item => (
<Tooltip title={item.title} key={item.key}>
<div class={`${baseClassName}-item`} onClick={() => handleChange(item.key)}>
<div class={`${baseClassName}-item`} style={ item.disable && disableStyle } onClick={() => !item.disable && handleChange(item.key)}>
<img src={item.url} alt={item.key} />
<div
class={`${baseClassName}-selectIcon`}
......
import PropTypes from 'ant-design-vue/es/_util/vue-types'
import { List, Tooltip, Select, Switch } from 'ant-design-vue'
import { defaultI18nRender } from './index'
export const renderLayoutSettingItem = (h, item) => {
const action = {...item.action}
return (
<Tooltip title={item.disabled ? item.disabledReason : ''} placement="left">
<List.Item actions={[action]}>
<span style={{ opacity: item.disabled ? 0.5 : 1 }}>{item.title}</span>
</List.Item>
</Tooltip>
)
}
export const LayoutSettingProps = {
contentWidth: PropTypes.bool,
fixedHeader: PropTypes.bool,
fixSiderbar: PropTypes.bool,
layout: PropTypes.oneOf(['sidemenu', 'topmenu'])
}
export default {
props: LayoutSettingProps,
inject: ['locale'],
render (h) {
const i18n = this.$props.i18nRender || this.locale || defaultI18nRender
const { contentWidth, fixedHeader, layout, fixSiderbar } = this
const handleChange = (type, value) => {
this.$emit('change', { type, value })
}
return (
<List
split={false}
dataSource={[
{
title: i18n('app.setting.content-width'),
action: (
<Select
value={contentWidth && 'Fixed' || 'Fluid'}
size="small"
onSelect={(value) => handleChange('contentWidth', value)}
style={{ width: '80px' }}
>
{layout === 'sidemenu' ? null : (
<Select.Option value="Fixed">
{i18n('app.setting.content-width.fixed')}
</Select.Option>
)}
<Select.Option value="Fluid">
{i18n('app.setting.content-width.fluid')}
</Select.Option>
</Select>
),
},
{
title: i18n('app.setting.fixedheader'),
action: (
<Switch
size="small"
checked={!!fixedHeader}
onChange={(checked) => handleChange('fixedHeader', checked)}
/>
),
},
{
title: i18n('app.setting.fixedsidebar'),
disabled: layout === 'topmenu',
disabledReason: i18n('app.setting.fixedsidebar.hint'),
action: (
<Switch
size="small"
checked={!!fixSiderbar}
onChange={(checked) => handleChange('fixSiderbar', checked)}
/>
),
},
]}
renderItem={(item, index) => renderLayoutSettingItem(h, item)}
/>
)
}
}
......@@ -9,16 +9,16 @@ const baseClassName = 'theme-color'
export const TagProps = {
color: PropTypes.string,
check: PropTypes.bool,
handleClick: PropTypes.func,
check: PropTypes.bool
}
const Tag = {
props: TagProps,
render (h) {
const { color, check, handleClick } = this
functional: true,
render (h, content) {
const { props: { color, check }, data, ...rest } = content
return (
<div onClick={handleClick} style={{ backgroundColor: color }} ref="colorRef">
<div {...data} style={{ backgroundColor: color }}>
{ check ? <Icon type="check" /> : null }
</div>
)
......@@ -34,15 +34,10 @@ export const ThemeColorProps = {
const ThemeColor = {
props: ThemeColorProps,
inject: ['locale'],
render (h) {
const { title, value, colors } = this
const { title, value, colors = [] } = this
const i18n = this.$props.i18nRender || this.locale || defaultI18nRender
const colorList = colors || []
if (colorList.length < 1) {
return null
}
const handleChange = (key) => {
this.$emit('change', key)
}
......@@ -51,20 +46,19 @@ const ThemeColor = {
<div class={baseClassName} ref={'ref'}>
<h3 class={`${baseClassName}-title`}>{title}</h3>
<div class={`${baseClassName}-content`}>
{colorList.map(item => {
{colors.map(item => {
const themeKey = genThemeToString(item.key)
const check = value === item.key || genThemeToString(value) === item.key
return (
<Tooltip
key={item.color}
title={
themeKey ? i18n(`app.setting.themecolor.${themeKey}`) : item.key
}
title={themeKey ? i18n(`app.setting.themecolor.${themeKey}`) : item.key}
>
<Tag
class={`${baseClassName}-block`}
color={item.color}
check={value === item.key || genThemeToString(value) === item.key}
handleClick={() => handleChange(item.key)}
check={check}
onClick={() => handleChange(item.key)}
/>
</Tooltip>
)
......
import './index.less'
import PropTypes from 'ant-design-vue/es/_util/vue-types'
import { Divider, Drawer, Icon } from 'ant-design-vue'
import { Divider, Drawer, List, Switch, Icon } from 'ant-design-vue'
import BlockCheckbox from './BlockCheckbox'
import ThemeColor from './ThemeColor'
import LayoutSetting, { renderLayoutSettingItem } from './LayoutChange'
import { updateTheme } from '../../utils/dynamicTheme'
const baseClassName = 'ant-pro-setting-drawer'
......@@ -50,7 +51,7 @@ const getThemeList = (i18nRender) => {
const darkColorList = [
{
key: 'daybreak',
key: '#1890ff',
color: '#1890ff',
theme: 'dark',
}
......@@ -58,7 +59,7 @@ const getThemeList = (i18nRender) => {
const lightColorList = [
{
key: 'daybreak',
key: '#1890ff',
color: '#1890ff',
theme: 'dark',
}
......@@ -66,6 +67,8 @@ const getThemeList = (i18nRender) => {
if (list.find((item) => item.theme === 'dark')) {
themeList.push({
// disable click
disable: true,
key: 'realDark',
url: 'https://gw.alipayobjects.com/zos/antfincdn/hmKaLQvmY2/LCkqqYNmvBEbokSDscrm.svg',
title: i18nRender('app.setting.pagestyle.realdark'),
......@@ -98,7 +101,6 @@ const getThemeList = (i18nRender) => {
}
const changeSetting = (key, value, hideMessageLoading) => {
console.log('handleColorChange', key, value)
if (key === 'navTheme') {
// 更新主题
}
......@@ -119,6 +121,9 @@ export const SettingDrawerProps = {
primaryColor: PropTypes.string,
layout: PropTypes.oneOf(['sidemenu', 'topmenu']),
colorWeak: PropTypes.bool,
contentWidth: PropTypes.bool,
fixedHeader: PropTypes.bool,
fixSiderbar: PropTypes.bool,
}
const SettingDrawer = {
......@@ -137,6 +142,9 @@ const SettingDrawer = {
navTheme = 'dark',
primaryColor = 'daybreak',
layout = 'sidemenu',
fixedHeader = false,
fixSiderbar = false,
contentWidth = false,
colorWeak
} = this
const i18n = this.$props.i18nRender || this.locale || defaultI18nRender
......@@ -151,6 +159,10 @@ const SettingDrawer = {
this.$emit('themeChange', key)
}
const handleLayoutSettingChange = (val) => {
this.$emit('layoutSettingChange', val)
}
return (
<Drawer
visible={this.show}
......@@ -202,6 +214,34 @@ const SettingDrawer = {
}} />
</Body>
<LayoutSetting
contentWidth={contentWidth}
fixedHeader={fixedHeader}
fixSiderbar={fixSiderbar}
layout={layout}
onChange={handleLayoutSettingChange}
/>
<Divider />
<Body title={i18n('app.setting.othersettings')}>
<List
split={false}
renderItem={(item) => renderLayoutSettingItem(h, item)}
dataSource={[
{
title: i18n('app.setting.weakmode'),
action: (
<Switch
size="small"
checked={!!colorWeak}
onChange={(checked) => changeSetting('colorWeak', checked)}
/>
),
},
]}
/>
</Body>
</div>
</Drawer>
)
......
......@@ -54,6 +54,10 @@ export const SiderMenuProps = {
type: String,
default: 'inline'
},
fixSiderbar: {
type: Boolean,
default: false
},
logo: {
type: null,
default: ''
......@@ -125,7 +129,7 @@ const SiderMenu = {
i18nRender,
menuHeaderRender
} = this
console.log('fixSiderbar', fixSiderbar)
const siderCls = ['ant-pro-sider-menu-sider']
if (fixSiderbar) siderCls.push('fix-sider-bar')
if (theme === 'light') siderCls.push('light')
......
......@@ -2,6 +2,7 @@ import RouteMenu from './RouteMenu'
import SiderMenuWrapper, { SiderMenu, SiderMenuProps } from './SiderMenu'
import PageHeaderWrapper from './PageHeaderWrapper'
import GlobalFooter from './GlobalFooter'
import VueFragment from './Fragment'
export {
RouteMenu,
......@@ -9,5 +10,6 @@ export {
SiderMenuProps,
SiderMenuWrapper,
PageHeaderWrapper,
GlobalFooter
GlobalFooter,
VueFragment
}
......@@ -14,7 +14,7 @@ export const themeColor = {
return lightens.concat(colorPalettes).concat(rgb)
},
changeColor (newColor) {
let options = {
const options = {
newColors: this.getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors`
changeUrl (cssUrl) {
return `/${cssUrl}` // while router is not `hash` mode, it needs absolute path
......@@ -26,9 +26,7 @@ export const themeColor = {
export const updateTheme = newPrimaryColor => {
const hideMessage = message.loading('正在切换主题', 0)
themeColor.changeColor(newPrimaryColor).finally(t => {
setTimeout(() => {
hideMessage()
}, 1500)
themeColor.changeColor(newPrimaryColor).then(r => {
hideMessage()
})
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment