feat: add page header (#1250)

* feat: add page-header component

* update site: page-header

* ts definition update: page-header

* get page-header props with getComponentFromProp func

* optimize page-header

* doc: add page-header actions.md responsive.md

* breadcrumb itemRender add pure function support
pull/1790/head
drafish 2019-12-19 19:03:41 +08:00 committed by tangjinzhou
parent db7f0efce5
commit 4fdf0f0cb1
35 changed files with 1831 additions and 4 deletions

View File

@ -0,0 +1,81 @@
/**
* Wrap of sub component which need use as Button capacity (like Icon component).
* This helps accessibility reader to tread as a interactive button to operation.
*/
import KeyCode from './KeyCode';
import PropTypes from './vue-types';
const inlineStyle = {
border: 0,
background: 'transparent',
padding: 0,
lineHeight: 'inherit',
display: 'inline-block',
};
const TransButton = {
props: {
noStyle: PropTypes.bool,
},
methods: {
onKeyDown(event) {
const { keyCode } = event;
if (keyCode === KeyCode.ENTER) {
event.preventDefault();
}
},
onKeyUp(event) {
const { keyCode } = event;
if (keyCode === KeyCode.ENTER) {
this.$emit('click', event);
}
},
setRef(btn) {
this.div = btn;
},
focus() {
if (this.div) {
this.div.focus();
}
},
blur() {
if (this.div) {
this.div.blur();
}
},
},
render() {
const { noStyle } = this.$props;
return (
<div
role="button"
tabIndex={0}
{...{
directives: [
{
name: 'ant-ref',
value: this.setRef,
},
],
on: {
...this.$listeners,
keydown: this.onKeyDown,
keyup: this.onKeyUp,
},
}}
style={{ ...(!noStyle ? inlineStyle : null) }}
>
{this.$slots.default}
</div>
);
},
};
export default TransButton;

View File

@ -65,7 +65,7 @@ export default {
}
return (
<BreadcrumbItem separator={separator} key={route.breadcrumbName || path}>
{itemRender({ route, params, routes, paths })}
{itemRender({ route, params, routes, paths, h })}
</BreadcrumbItem>
);
});

View File

@ -2,7 +2,7 @@
| Property | Description | Type | Optional | Default |
| --- | --- | --- | --- | --- |
| itemRender | Custom item renderer, slot="itemRender" and slot-scope="{route, params, routes, paths}" | ({route, params, routes, paths}) => vNode | | - |
| itemRender | Custom item renderer, slot="itemRender" and slot-scope="{route, params, routes, paths}" | ({route, params, routes, paths, h}) => vNode | | - |
| params | Routing parameters | object | | - |
| routes | The routing stack information of router | object\[] | | - |
| separator | Custom separator | string\|slot | | `/` |

View File

@ -2,7 +2,7 @@
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- |
| itemRender | 自定义链接函数,和 vue-router 配置使用, 也可使用 slot="itemRender" 和 slot-scope="props" | ({route, params, routes, paths}) => vNode | | - |
| itemRender | 自定义链接函数,和 vue-router 配置使用, 也可使用 slot="itemRender" 和 slot-scope="props" | ({route, params, routes, paths, h}) => vNode | | - |
| params | 路由的参数 | object | | - |
| routes | router 的路由栈信息 | object\[] | | - |
| separator | 分隔符自定义 | string\|slot | | '/' |

View File

@ -140,6 +140,7 @@ import { default as Empty } from './empty';
import { default as Result } from './result';
import { default as Descriptions } from './descriptions';
import { default as PageHeader } from './page-header';
const components = [
Base,
@ -201,6 +202,7 @@ const components = [
Empty,
Result,
Descriptions,
PageHeader,
];
const install = function(Vue) {
@ -287,6 +289,7 @@ export {
Empty,
Result,
Descriptions,
PageHeader,
};
export default {

View File

@ -47,4 +47,7 @@ export default {
Icon: {
icon: 'icon',
},
PageHeader: {
back: 'Back',
},
};

View File

@ -39,4 +39,7 @@ export default {
Empty: {
description: 'No hay datos',
},
PageHeader: {
back: 'volver',
},
};

View File

@ -39,4 +39,7 @@ export default {
Empty: {
description: 'Нет данных',
},
PageHeader: {
back: 'назад',
},
};

View File

@ -47,4 +47,7 @@ export default {
Icon: {
icon: '图标',
},
PageHeader: {
back: '返回',
},
};

View File

@ -39,4 +39,7 @@ export default {
Empty: {
description: '無此資料',
},
PageHeader: {
back: '返回',
},
};

View File

@ -0,0 +1,896 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/page-header/demo/actions.md correctly 1`] = `
<div>
<div
class="ant-page-header"
>
<div
class="ant-page-header-heading"
>
<div
class="ant-page-header-back"
>
<div
aria-label="Back"
class="ant-page-header-back-button"
role="button"
style="border: 0px; background: transparent; padding: 0px; line-height: inherit; display: inline-block;"
tabindex="0"
>
<i
aria-label="icon: arrow-left"
class="anticon anticon-arrow-left"
>
<svg
aria-hidden="true"
class=""
data-icon="arrow-left"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M872 474H286.9l350.2-304c5.6-4.9 2.2-14-5.2-14h-88.5c-3.9 0-7.6 1.4-10.5 3.9L155 487.8a31.96 31.96 0 0 0 0 48.3L535.1 866c1.5 1.3 3.3 2 5.2 2h91.5c7.4 0 10.8-9.2 5.2-14L286.9 550H872c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
/>
</svg>
</i>
</div>
</div>
<span
class="ant-page-header-heading-title"
>
Title
</span>
<span
class="ant-page-header-heading-sub-title"
>
This is a subtitle
</span>
<span
class="ant-page-header-heading-extra"
>
<button
class="ant-btn"
type="button"
>
<span>
Operation
</span>
</button>
<button
class="ant-btn"
type="button"
>
<span>
Operation
</span>
</button>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Primary
</span>
</button>
</span>
</div>
<div
class="ant-page-header-content"
>
<div
class="ant-descriptions ant-descriptions-small"
>
<div
class="ant-descriptions-view"
>
<table>
<tbody>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="1"
>
<span
class="ant-descriptions-item-label ant-descriptions-item-colon"
>
Created
</span>
<span
class="ant-descriptions-item-content"
>
Lili Qu
</span>
</td>
<td
class="ant-descriptions-item"
colspan="1"
>
<span
class="ant-descriptions-item-label ant-descriptions-item-colon"
>
Association
</span>
<span
class="ant-descriptions-item-content"
>
<a>
421421
</a>
</span>
</td>
<td
class="ant-descriptions-item"
colspan="1"
>
<span
class="ant-descriptions-item-label ant-descriptions-item-colon"
>
Creation Time
</span>
<span
class="ant-descriptions-item-content"
>
2017-01-10
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="1"
>
<span
class="ant-descriptions-item-label ant-descriptions-item-colon"
>
Effective Time
</span>
<span
class="ant-descriptions-item-content"
>
2017-10-10
</span>
</td>
<td
class="ant-descriptions-item"
colspan="2"
>
<span
class="ant-descriptions-item-label ant-descriptions-item-colon"
>
Remarks
</span>
<span
class="ant-descriptions-item-content"
>
Gonghu Road, Xihu District, Hangzhou, Zhejiang, China
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<br />
<div
class="ant-page-header"
>
<div
class="ant-page-header-heading"
>
<div
class="ant-page-header-back"
>
<div
aria-label="Back"
class="ant-page-header-back-button"
role="button"
style="border: 0px; background: transparent; padding: 0px; line-height: inherit; display: inline-block;"
tabindex="0"
>
<i
aria-label="icon: arrow-left"
class="anticon anticon-arrow-left"
>
<svg
aria-hidden="true"
class=""
data-icon="arrow-left"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M872 474H286.9l350.2-304c5.6-4.9 2.2-14-5.2-14h-88.5c-3.9 0-7.6 1.4-10.5 3.9L155 487.8a31.96 31.96 0 0 0 0 48.3L535.1 866c1.5 1.3 3.3 2 5.2 2h91.5c7.4 0 10.8-9.2 5.2-14L286.9 550H872c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
/>
</svg>
</i>
</div>
</div>
<span
class="ant-page-header-heading-title"
>
Title
</span>
<span
class="ant-page-header-heading-sub-title"
>
This is a subtitle
</span>
<span
class="ant-page-header-heading-tags"
>
<div
class="ant-tag ant-tag-blue"
>
Running
</div>
</span>
<span
class="ant-page-header-heading-extra"
>
<button
class="ant-btn"
type="button"
>
<span>
Operation
</span>
</button>
<button
class="ant-btn"
type="button"
>
<span>
Operation
</span>
</button>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Primary
</span>
</button>
</span>
</div>
<div
class="ant-page-header-content"
>
<div
class="ant-row-flex"
>
<div
class="ant-statistic"
>
<div
class="ant-statistic-title"
>
Status
</div>
<div
class="ant-statistic-content"
>
<span
class="ant-statistic-content-value"
>
Pending
</span>
</div>
</div>
<div
class="ant-statistic"
style="margin: 0px 32px;"
>
<div
class="ant-statistic-title"
>
Price
</div>
<div
class="ant-statistic-content"
>
<span
class="ant-statistic-content-prefix"
>
$
</span>
<span
class="ant-statistic-content-value"
>
<span
class="ant-statistic-content-value-int"
>
568
</span>
<span
class="ant-statistic-content-value-decimal"
>
.08
</span>
</span>
</div>
</div>
<div
class="ant-statistic"
>
<div
class="ant-statistic-title"
>
Balance
</div>
<div
class="ant-statistic-content"
>
<span
class="ant-statistic-content-prefix"
>
$
</span>
<span
class="ant-statistic-content-value"
>
<span
class="ant-statistic-content-value-int"
>
3,345
</span>
<span
class="ant-statistic-content-value-decimal"
>
.08
</span>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders ./components/page-header/demo/basic.md correctly 1`] = `
<div
class="ant-page-header"
>
<div
class="ant-page-header-heading"
>
<div
class="ant-page-header-back"
>
<div
aria-label="Back"
class="ant-page-header-back-button"
role="button"
style="border: 0px; background: transparent; padding: 0px; line-height: inherit; display: inline-block;"
tabindex="0"
>
<i
aria-label="icon: arrow-left"
class="anticon anticon-arrow-left"
>
<svg
aria-hidden="true"
class=""
data-icon="arrow-left"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M872 474H286.9l350.2-304c5.6-4.9 2.2-14-5.2-14h-88.5c-3.9 0-7.6 1.4-10.5 3.9L155 487.8a31.96 31.96 0 0 0 0 48.3L535.1 866c1.5 1.3 3.3 2 5.2 2h91.5c7.4 0 10.8-9.2 5.2-14L286.9 550H872c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
/>
</svg>
</i>
</div>
</div>
<span
class="ant-page-header-heading-title"
>
Title
</span>
<span
class="ant-page-header-heading-sub-title"
>
This is a subtitle
</span>
</div>
</div>
`;
exports[`renders ./components/page-header/demo/breadcrumb.md correctly 1`] = `
<div
class="ant-page-header has-breadcrumb"
>
<div
class="ant-breadcrumb"
>
<span>
<span
class="ant-breadcrumb-link"
>
<a
href="#/index"
>
First-level Menu
</a>
</span>
<span
class="ant-breadcrumb-separator"
>
/
</span>
</span>
<span>
<span
class="ant-breadcrumb-link"
>
<a
href="#/index/first"
>
Second-level Menu
</a>
</span>
<span
class="ant-breadcrumb-separator"
>
/
</span>
</span>
<span>
<span
class="ant-breadcrumb-link"
>
<span>
Third-level Menu
</span>
</span>
<span
class="ant-breadcrumb-separator"
>
/
</span>
</span>
</div>
<div
class="ant-page-header-heading"
>
<span
class="ant-page-header-heading-title"
>
Title
</span>
<span
class="ant-page-header-heading-sub-title"
>
This is a subtitle
</span>
</div>
</div>
`;
exports[`renders ./components/page-header/demo/responsive.md correctly 1`] = `
<div>
<div
class="ant-page-header has-footer"
>
<div
class="ant-page-header-heading"
>
<div
class="ant-page-header-back"
>
<div
aria-label="Back"
class="ant-page-header-back-button"
role="button"
style="border: 0px; background: transparent; padding: 0px; line-height: inherit; display: inline-block;"
tabindex="0"
>
<i
aria-label="icon: arrow-left"
class="anticon anticon-arrow-left"
>
<svg
aria-hidden="true"
class=""
data-icon="arrow-left"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M872 474H286.9l350.2-304c5.6-4.9 2.2-14-5.2-14h-88.5c-3.9 0-7.6 1.4-10.5 3.9L155 487.8a31.96 31.96 0 0 0 0 48.3L535.1 866c1.5 1.3 3.3 2 5.2 2h91.5c7.4 0 10.8-9.2 5.2-14L286.9 550H872c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
/>
</svg>
</i>
</div>
</div>
<span
class="ant-page-header-heading-title"
>
Title
</span>
<span
class="ant-page-header-heading-sub-title"
>
This is a subtitle
</span>
<span
class="ant-page-header-heading-extra"
>
<button
class="ant-btn"
type="button"
>
<span>
Operation
</span>
</button>
<button
class="ant-btn"
type="button"
>
<span>
Operation
</span>
</button>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Primary
</span>
</button>
</span>
</div>
<div
class="ant-page-header-content"
>
<div
class="content"
>
<div
class="main"
>
<div
class="ant-descriptions ant-descriptions-small"
>
<div
class="ant-descriptions-view"
>
<table>
<tbody>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="1"
>
<span
class="ant-descriptions-item-label ant-descriptions-item-colon"
>
Created
</span>
<span
class="ant-descriptions-item-content"
>
Lili Qu
</span>
</td>
<td
class="ant-descriptions-item"
colspan="1"
>
<span
class="ant-descriptions-item-label ant-descriptions-item-colon"
>
Association
</span>
<span
class="ant-descriptions-item-content"
>
<a>
421421
</a>
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="1"
>
<span
class="ant-descriptions-item-label ant-descriptions-item-colon"
>
Creation Time
</span>
<span
class="ant-descriptions-item-content"
>
2017-01-10
</span>
</td>
<td
class="ant-descriptions-item"
colspan="1"
>
<span
class="ant-descriptions-item-label ant-descriptions-item-colon"
>
Effective Time
</span>
<span
class="ant-descriptions-item-content"
>
2017-10-10
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="2"
>
<span
class="ant-descriptions-item-label ant-descriptions-item-colon"
>
Remarks
</span>
<span
class="ant-descriptions-item-content"
>
Gonghu Road, Xihu District, Hangzhou, Zhejiang, China
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div
class="extra"
>
<div
style="display: flex; justify-content: flex-end;"
>
<div
class="ant-statistic"
style="margin-right: 32px;"
>
<div
class="ant-statistic-title"
>
Status
</div>
<div
class="ant-statistic-content"
>
<span
class="ant-statistic-content-value"
>
Pending
</span>
</div>
</div>
<div
class="ant-statistic"
>
<div
class="ant-statistic-title"
>
Price
</div>
<div
class="ant-statistic-content"
>
<span
class="ant-statistic-content-prefix"
>
$
</span>
<span
class="ant-statistic-content-value"
>
<span
class="ant-statistic-content-value-int"
>
568
</span>
<span
class="ant-statistic-content-value-decimal"
>
.08
</span>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-page-header-footer"
>
<div
class="ant-tabs ant-tabs-top ant-tabs-line"
>
<div
class="ant-tabs-bar ant-tabs-top-bar"
role="tablist"
tabindex="0"
>
<div
class="ant-tabs-nav-container"
>
<span
class="ant-tabs-tab-prev ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-prev-icon"
>
<i
aria-label="icon: left"
class="ant-tabs-tab-prev-icon-target anticon anticon-left"
>
<svg
aria-hidden="true"
class=""
data-icon="left"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 0 0 0 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</i>
</span>
</span>
<span
class="ant-tabs-tab-next ant-tabs-tab-btn-disabled"
unselectable="unselectable"
>
<span
class="ant-tabs-tab-next-icon"
>
<i
aria-label="icon: right"
class="ant-tabs-tab-next-icon-target anticon anticon-right"
>
<svg
aria-hidden="true"
class=""
data-icon="right"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z"
/>
</svg>
</i>
</span>
</span>
<div
class="ant-tabs-nav-wrap"
>
<div
class="ant-tabs-nav-scroll"
>
<div
class="ant-tabs-nav ant-tabs-nav-animated"
>
<div>
<div
aria-disabled="false"
aria-selected="true"
class="ant-tabs-tab-active ant-tabs-tab"
role="tab"
>
Details
</div>
<div
aria-disabled="false"
aria-selected="false"
class=" ant-tabs-tab"
role="tab"
>
Rule
</div>
</div>
<div
class="ant-tabs-ink-bar ant-tabs-ink-bar-animated"
style="display: block; transform: translate3d(0px,0,0); webkit-transform: translate3d(0px,0,0); width: 0px;"
/>
</div>
</div>
</div>
</div>
</div>
<div
role="presentation"
style="width: 0px; height: 0px; overflow: hidden; position: absolute;"
tabindex="0"
/>
<div
class="ant-tabs-content ant-tabs-content-animated ant-tabs-top-content"
style="margin-left: 0%;"
>
<div
aria-hidden="false"
class="ant-tabs-tabpane ant-tabs-tabpane-active"
role="tabpanel"
>
<div
role="presentation"
style="width: 0px; height: 0px; overflow: hidden; position: absolute;"
tabindex="0"
/>
<div
role="presentation"
style="width: 0px; height: 0px; overflow: hidden; position: absolute;"
tabindex="0"
/>
</div>
<div
aria-hidden="true"
class="ant-tabs-tabpane ant-tabs-tabpane-inactive"
role="tabpanel"
/>
</div>
<div
role="presentation"
style="width: 0px; height: 0px; overflow: hidden; position: absolute;"
tabindex="0"
/>
</div>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PageHeader pageHeader should not render blank dom 1`] = `
<div
class="ant-page-header"
/>
`;
exports[`PageHeader pageHeader should support class 1`] = `
<div
class="ant-page-header not-works"
>
<div
class="ant-page-header-heading"
>
<span
class="ant-page-header-heading-title"
>
Page Title
</span>
</div>
</div>
`;

View File

@ -0,0 +1,3 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('page-header', { getDomFromElement: true });

View File

@ -0,0 +1,133 @@
import { createLocalVue, mount } from '@vue/test-utils';
import PageHeader from '..';
import ref from 'vue-ref';
const localVue = createLocalVue();
localVue.use(ref, { name: 'ant-ref' });
describe('PageHeader', () => {
it('pageHeader should not contain back it back', () => {
const routes = [
{
path: 'index',
breadcrumbName: 'First-level Menu',
},
{
path: 'first',
breadcrumbName: 'Second-level Menu',
},
{
path: 'second',
breadcrumbName: 'Third-level Menu',
},
];
const wrapper = mount({
render() {
return <PageHeader title="Page Title" breadcrumb={{ props: { routes } }} />;
},
});
expect(wrapper.findAll('.ant-page-header-back')).toHaveLength(0);
});
it('pageHeader should have breadcrumb', () => {
const routes = [
{
path: 'index',
breadcrumbName: 'First-level Menu',
},
];
const wrapper = mount({
render() {
return <PageHeader title="Page Title" breadcrumb={{ props: { routes } }} />;
},
});
expect(wrapper.findAll('.ant-breadcrumb')).toHaveLength(1);
expect(wrapper.findAll('.ant-page-header-back')).toHaveLength(0);
});
it('pageHeader should no contain back', () => {
const wrapper = mount({
render() {
return <PageHeader title="Page Title" backIcon={false} />;
},
});
expect(wrapper.findAll('.ant-page-header-back')).toHaveLength(0);
});
it('pageHeader should contain back it back', () => {
const callback = jest.fn(() => true);
const wrapper = mount(
{
render() {
return <PageHeader title="Page Title" onBack={callback} />;
},
},
{ localVue },
);
expect(wrapper.findAll('.ant-page-header-back')).toHaveLength(1);
});
it('pageHeader onBack transfer', () => {
const callback = jest.fn(() => true);
const wrapper = mount(
{
render() {
return <PageHeader title="Page Title" onBack={callback} />;
},
},
{ localVue },
);
wrapper.find('div.ant-page-header-back-button').trigger('click');
expect(callback).toHaveBeenCalled();
});
it('pageHeader should support class', () => {
const wrapper = mount({
render() {
return <PageHeader title="Page Title" class="not-works" backIcon={false} />;
},
});
expect(wrapper.element).toMatchSnapshot();
});
it('pageHeader should not render blank dom', () => {
const wrapper = mount({
render() {
return <PageHeader title={false} />;
},
});
expect(wrapper.element).toMatchSnapshot();
});
it('breadcrumbs and back icon can coexist', () => {
const routes = [
{
path: 'index',
breadcrumbName: 'First-level Menu',
},
{
path: 'first',
breadcrumbName: 'Second-level Menu',
},
{
path: 'second',
breadcrumbName: 'Third-level Menu',
},
];
const wrapper = mount({
render() {
return <PageHeader title="Title" breadcrumb={{ props: { routes } }} />;
},
});
expect(wrapper.findAll('.ant-breadcrumb')).toHaveLength(1);
const wrapperBack = mount(
{
render() {
return <PageHeader title="Title" breadcrumb={{ props: { routes } }} onBack={() => {}} />;
},
},
{ localVue },
);
expect(wrapperBack.findAll('.ant-breadcrumb')).toHaveLength(1);
});
});

View File

@ -0,0 +1,74 @@
<cn>
#### 多种形态的 PageHeader
使用操作区,并自定义子节点,适合使用在需要展示一些复杂的信息,帮助用户快速了解这个页面的信息和操作。
</cn>
<us>
#### Various forms of PageHeader
Use the operating area and customize the sub-nodes, suitable for use in the need to display some complex information to help users quickly understand the information and operations of this page.
</us>
```tpl
<template>
<div>
<a-page-header
@back="() => $router.go(-1)"
title="Title"
subTitle="This is a subtitle"
>
<template slot="extra">
<a-button key="3">Operation</a-button>
<a-button key="2">Operation</a-button>
<a-button key="1" type="primary">
Primary
</a-button>
</template>
<a-descriptions size="small" :column="3">
<a-descriptions-item label="Created">Lili Qu</a-descriptions-item>
<a-descriptions-item label="Association">
<a>421421</a>
</a-descriptions-item>
<a-descriptions-item label="Creation Time">2017-01-10</a-descriptions-item>
<a-descriptions-item label="Effective Time">2017-10-10</a-descriptions-item>
<a-descriptions-item label="Remarks">
Gonghu Road, Xihu District, Hangzhou, Zhejiang, China
</a-descriptions-item>
</a-descriptions>
</a-page-header>
<br />
<a-page-header
@back="() => $router.go(-1)"
title="Title"
subTitle="This is a subtitle"
>
<template slot="tags">
<a-tag color="blue">Running</a-tag>
</template>
<template slot="extra">
<a-button key="3">Operation</a-button>
<a-button key="2">Operation</a-button>
<a-button key="1" type="primary">
Primary
</a-button>
</template>
<a-row type="flex">
<a-statistic title="Status" value="Pending" />
<a-statistic
title="Price"
prefix="$"
:value="568.08"
:style="{
margin: '0 32px',
}"
/>
<a-statistic title="Balance" prefix="$" :value="3345.08" />
</a-row>
</a-page-header>
</div>
</template>
<style>
tr:last-child td {
padding-bottom: 0;
}
</style>
```

View File

@ -0,0 +1,20 @@
<cn>
#### 标准样式
标准页头,适合使用在需要简单描述的场景。
</cn>
<us>
#### Basic Page Header
Standard header, suitable for use in scenarios that require a brief description.
</us>
```tpl
<template>
<a-page-header @back="() => null" title="Title" subTitle="This is a subtitle" />
</template>
<style>
.code-box-demo .ant-page-header {
border: 1px solid rgb(235, 237, 240);
}
</style>
```

View File

@ -0,0 +1,37 @@
<cn>
#### 带面包屑页头
带面包屑页头,适合层级比较深的页面,让用户可以快速导航。
</cn>
<us>
#### Use with breadcrumbs
With breadcrumbs, it is suitable for deeper pages, allowing users to navigate quickly.
</us>
```tpl
<template>
<a-page-header title="Title" :breadcrumb="{ props: { routes } }" subTitle="This is a subtitle" />
</template>
<script>
export default {
data() {
return {
routes: [
{
path: 'index',
breadcrumbName: 'First-level Menu',
},
{
path: 'first',
breadcrumbName: 'Second-level Menu',
},
{
path: 'second',
breadcrumbName: 'Third-level Menu',
},
],
};
},
};
</script>
```

View File

@ -0,0 +1,57 @@
<script>
import Basic from './basic';
import Breadcrumb from './breadcrumb';
import Actions from './actions';
import Responsive from './responsive';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
import '../style';
const md = {
cn: `# PageHeader 页头
页头位于页容器中页容器顶部起到了内容概览和引导页级操作的作用包括由面包屑标题页面内容简介页面级操作等页面级导航组成
## 何时使用
当需要使用户快速理解当前页是什么以及方便用户使用页面功能时使用通常也可被用作页面间导航
## 代码演示
`,
us: `# PageHeader
The header can be used to declare the page topic, display important information about the page that the user is interested in, and carry the action items related to the current page (including page-level operations, inter-page navigation, etc.)
## When To Use
It can also be used as inter-page navigation when it is needed to make the user quickly understand what the current page is and to facilitate the user to use the page function.
## Examples
`,
};
export default {
category: 'Components',
subtitle: '页头',
type: 'Navigation',
title: 'PageHeader',
cols: 1,
render() {
return (
<div>
<md cn={md.cn} us={md.us} />
<Basic />
<Breadcrumb />
<Actions />
<Responsive />
<api>
<CN slot="cn" />
<US />
</api>
</div>
);
},
};
</script>

View File

@ -0,0 +1,88 @@
<cn>
#### 响应式
在不同大小的屏幕下,应该有不同的表现
</cn>
<us>
#### responsive
Under different screen sizes, there should be different performance
</us>
```tpl
<template>
<div>
<a-page-header @back="() => $router.go(-1)" title="Title" subTitle="This is a subtitle">
<template slot="extra">
<a-button key="3">Operation</a-button>
<a-button key="2">Operation</a-button>
<a-button key="1" type="primary">
Primary
</a-button>
</template>
<template slot="footer">
<a-tabs defaultActiveKey="1">
<a-tab-pane tab="Details" key="1" />
<a-tab-pane tab="Rule" key="2" />
</a-tabs>
</template>
<div class="content">
<div class="main">
<a-descriptions size="small" :column="2">
<a-descriptions-item label="Created">Lili Qu</a-descriptions-item>
<a-descriptions-item label="Association">
<a>421421</a>
</a-descriptions-item>
<a-descriptions-item label="Creation Time">2017-01-10</a-descriptions-item>
<a-descriptions-item label="Effective Time">2017-10-10</a-descriptions-item>
<a-descriptions-item label="Remarks">
Gonghu Road, Xihu District, Hangzhou, Zhejiang, China
</a-descriptions-item>
</a-descriptions>
</div>
<div class="extra">
<div
:style="{
display: 'flex',
width: 'max-content',
justifyContent: 'flex-end',
}"
>
<a-statistic
title="Status"
value="Pending"
:style="{
marginRight: '32px',
}"
/>
<a-statistic title="Price" prefix="$" :value="568.08" />
</div>
</div>
</div>
</a-page-header>
</div>
</template>
<style>
tr:last-child td {
padding-bottom: 0;
}
#components-page-header-demo-responsive .content {
display: flex;
}
@media (max-width: 576px) {
#components-page-header-demo-responsive .content {
display: block;
}
#components-page-header-demo-responsive .main {
width: 100%;
margin-bottom: 12px;
}
#components-page-header-demo-responsive .extra {
width: 100%;
margin-left: 0;
text-align: left;
}
}
</style>
```

View File

@ -0,0 +1,18 @@
## API
| Param | Description | Type | Default value |
| --- | --- | --- | --- |
| title | custom title text | string\|slot | - |
| subTitle | custom subTitle text | string\|slot | - |
| avatar | Avatar next to the title bar | [avatar props](/components/avatar/) | - |
| backIcon | custom back icon, if false the back icon will not be displayed | string\|slot | `<Icon type="arrow-left" />` |
| tags | Tag list next to title | [Tag](/components/tag/)[] \| [Tag](/components/tag/) | - |
| extra | Operating area, at the end of the line of the title line | string\|slot | - |
| breadcrumb | breadcrumb config | [breadcrumb](/components/breadcrumb/) | - |
| footer | PageHeader's footer, generally used to render TabBar | string\|slot | - |
### Events
| Events Name | Description | Arguments |
| ------------- | -------------------------------------- | ----------------- |
| back | back icon click event | function(e) |

View File

@ -0,0 +1,128 @@
import PropTypes from '../_util/vue-types';
import { getComponentFromProp } from '../_util/props-util';
import { ConfigConsumerProps } from '../config-provider';
import Icon from '../icon';
import Breadcrumb from '../breadcrumb';
import Avatar from '../avatar';
import TransButton from '../_util/transButton';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import Base from '../base';
export const PageHeaderProps = {
backIcon: PropTypes.any,
prefixCls: PropTypes.string,
title: PropTypes.any,
subTitle: PropTypes.any,
breadcrumb: PropTypes.object,
tags: PropTypes.any,
footer: PropTypes.any,
extra: PropTypes.any,
avatar: PropTypes.object,
};
const renderBack = (instance, prefixCls, backIcon, onBack) => {
const h = instance.$createElement;
if (!backIcon || !onBack) {
return null;
}
return (
<LocaleReceiver componentName="PageHeader">
{({ back }) => (
<div class={`${prefixCls}-back`}>
<TransButton
onClick={e => {
instance.$emit('back', e);
}}
class={`${prefixCls}-back-button`}
aria-label={back}
>
{backIcon}
</TransButton>
</div>
)}
</LocaleReceiver>
);
};
const renderBreadcrumb = (h, breadcrumb) => {
return <Breadcrumb {...breadcrumb } />;
};
const renderTitle = (h, prefixCls, instance) => {
const {
avatar,
} = instance;
const title = getComponentFromProp(instance, 'title');
const subTitle = getComponentFromProp(instance, 'subTitle');
const tags = getComponentFromProp(instance, 'tags');
const extra = getComponentFromProp(instance, 'extra');
const backIcon = getComponentFromProp(instance, 'backIcon') || <Icon type="arrow-left" />;
const onBack = instance.$listeners.back;
const headingPrefixCls = `${prefixCls}-heading`;
if (title || subTitle || tags || extra) {
const backIconDom = renderBack(instance, prefixCls, backIcon, onBack);
return (
<div class={headingPrefixCls}>
{backIconDom}
{avatar && <Avatar {...avatar } />}
{title && <span class={`${headingPrefixCls}-title`}>{title}</span>}
{subTitle && <span class={`${headingPrefixCls}-sub-title`}>{subTitle}</span>}
{tags && <span class={`${headingPrefixCls}-tags`}>{tags}</span>}
{extra && <span class={`${headingPrefixCls}-extra`}>{extra}</span>}
</div>
);
}
return null;
};
const renderFooter = (h, prefixCls, footer) => {
if (footer) {
return <div class={`${prefixCls}-footer`}>{footer}</div>;
}
return null;
};
const renderChildren = (h, prefixCls, children) => {
return <div class={`${prefixCls}-content`}>{children}</div>;
};
const PageHeader = {
name: 'APageHeader',
props: PageHeaderProps,
inject: {
configProvider: { default: () => ConfigConsumerProps },
},
render(h) {
const { getPrefixCls } = this.configProvider;
const {
prefixCls: customizePrefixCls,
breadcrumb,
} = this.$props;
const footer = getComponentFromProp(this, 'footer');
const children = this.$slots.default;
const prefixCls = getPrefixCls('page-header', customizePrefixCls);
const breadcrumbDom = breadcrumb && breadcrumb.props && breadcrumb.props.routes ? renderBreadcrumb(h, breadcrumb) : null;
const className = [prefixCls, {
'has-breadcrumb': breadcrumbDom,
'has-footer': footer,
}];
return (
<div class={className}>
{breadcrumbDom}
{renderTitle(h, prefixCls, this)}
{children && renderChildren(h, prefixCls, children)}
{renderFooter(h, prefixCls, footer)}
</div>
);
},
};
/* istanbul ignore next */
PageHeader.install = function(Vue) {
Vue.use(Base);
Vue.component(PageHeader.name, PageHeader);
};
export default PageHeader;

View File

@ -0,0 +1,18 @@
## API
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| title | 自定义标题文字 | string\|slot | - |
| subTitle | 自定义的二级标题文字 | string\|slot | - |
| avatar | 标题栏旁的头像 | [avatar props](/components/avatar-cn/) | - |
| backIcon | 自定义 back icon ,如果为 false 不渲染 back icon | string\|slot | `<Icon type="arrow-left" />` |
| tags | title 旁的 tag 列表 | [Tag](/components/tag-cn/)[] \| [Tag](/components/tag-cn/) | - |
| extra | 操作区,位于 title 行的行尾 | string\|slot | - |
| breadcrumb | 面包屑的配置 | [breadcrumb](/components/breadcrumb-cn/) | - |
| footer | PageHeader 的页脚,一般用于渲染 TabBar | string\|slot | - |
### 事件
| 事件名称 | 说明 | 回调参数 |
| ------------- | -------------------------------------- | ----------------- |
| back | 返回按钮的点击事件 | function(e) |

View File

@ -0,0 +1,5 @@
import './index.less';
// style dependencies
import '../../breadcrumb/style';
import '../../avatar/style';

View File

@ -0,0 +1,115 @@
@import '../../style/themes/default';
@import '../../style/mixins/index';
@pageheader-prefix-cls: ~'@{ant-prefix}-page-header';
.@{pageheader-prefix-cls} {
.reset-component;
position: relative;
padding: @page-header-padding;
&.has-breadcrumb {
padding-top: @page-header-padding-breadcrumb;
}
&.has-footer {
padding-bottom: @page-header-padding-vertical;
}
&-back {
float: left;
margin: 6px 0;
margin-right: 16px;
font-size: 20px;
line-height: 1;
&-button {
.operation-unit();
color: @page-header-back-color;
cursor: pointer;
}
}
.@{ant-prefix}-divider-vertical {
height: 14px;
margin: 0 12px;
vertical-align: middle;
}
.@{ant-prefix}-breadcrumb + &-heading {
margin-top: 8px;
}
&-heading {
width: 100%;
overflow: hidden;
&-title {
display: block;
float: left;
margin-bottom: 0;
padding-right: 12px;
color: @heading-color;
font-weight: 600;
font-size: @heading-3-size;
line-height: 32px;
}
.@{ant-prefix}-avatar {
float: left;
margin-right: 12px;
}
&-sub-title {
float: left;
margin: 5px 0;
margin-right: 12px;
color: @text-color-secondary;
font-size: 14px;
line-height: 22px;
}
&-tags {
float: left;
margin: 4px 0;
}
&-extra {
float: right;
> * {
margin-left: 8px;
}
> *:first-child {
margin-left: 0;
}
}
}
&-content {
padding-top: 16px;
overflow: hidden;
}
&-footer {
margin-top: 16px;
.@{ant-prefix}-tabs-bar {
margin-bottom: 1px;
border-bottom: 0;
.@{ant-prefix}-tabs-nav .@{ant-prefix}-tabs-tab {
padding: 8px;
font-size: 16px;
}
}
}
@media (max-width: @screen-sm) {
&-heading {
&-extra {
display: block;
float: unset;
width: 100%;
padding-top: 12px;
overflow: hidden;
}
}
}
}

View File

@ -57,3 +57,4 @@ import './empty/style';
import './statistic/style';
import './result/style';
import './descriptions/style';
import './page-header/style';

View File

@ -6,3 +6,4 @@
@import 'iconfont';
@import 'motion';
@import 'reset';
@import 'operation-unit';

View File

@ -0,0 +1,18 @@
@import '../../style/themes/default';
.operation-unit() {
color: @link-color;
text-decoration: none;
outline: none;
cursor: pointer;
transition: color 0.3s;
&:focus,
&:hover {
color: @link-hover-color;
}
&:active {
color: @link-active-color;
}
}

View File

@ -54,6 +54,10 @@
@font-size-base: 14px;
@font-size-lg: @font-size-base + 2px;
@font-size-sm: 12px;
@heading-1-size: ceil(@font-size-base * 2.71);
@heading-2-size: ceil(@font-size-base * 2.14);
@heading-3-size: ceil(@font-size-base * 1.71);
@heading-4-size: ceil(@font-size-base * 1.42);
@line-height-base: 1.5;
@border-radius-base: 4px;
@border-radius-sm: 2px;
@ -492,6 +496,13 @@
@pagination-font-family: Arial;
@pagination-font-weight-active: 500;
// PageHeader
// ---
@page-header-padding: 24px;
@page-header-padding-vertical: 16px;
@page-header-padding-breadcrumb: 12px;
@page-header-back-color: #000;
// Breadcrumb
// ---
@breadcrumb-base-color: @text-color-secondary;

View File

@ -63,6 +63,7 @@ import {
Base,
Result,
Descriptions,
PageHeader,
} from 'ant-design-vue';
Vue.prototype.$message = message;
@ -134,6 +135,7 @@ Vue.use(ConfigProvider);
Vue.use(Empty);
Vue.use(Result);
Vue.use(Descriptions);
Vue.use(PageHeader);
/* v1.1.2 registration methods */
// Vue.component(Affix.name, Affix) // a-affix

View File

@ -93,6 +93,13 @@ export default {
title: 'Pagination',
cols: 1,
},
pageHeader: {
category: 'Components',
subtitle: '页头',
type: 'Navigation',
title: 'PageHeader',
cols: 1,
},
popconfirm: {
category: 'Components',
subtitle: '气泡确认框',

View File

@ -470,5 +470,11 @@ export default [
{
path: 'descriptions-cn',
component: () => import('../components/descriptions/demo/index.vue'),
path: 'page-header',
component: () => import('../components/page-header/demo/index.vue'),
},
{
path: 'page-header-cn',
component: () => import('../components/page-header/demo/index.vue'),
},
];

View File

@ -65,6 +65,7 @@ Array [
"Empty",
"Result",
"Descriptions",
"PageHeader",
"default",
]
`;

View File

@ -20,7 +20,11 @@ export default function demoTest(component, options = {}) {
const demo = require(`../.${file}`).default || require(`../.${file}`); // eslint-disable-line global-require, import/no-dynamic-require
const wrapper = mount(demo, { sync: false });
Vue.nextTick(() => {
expect(wrapper.html()).toMatchSnapshot();
// should get dom from element
// snap files copy from antd does not need to change
// or just change a little
const dom = options.getDomFromElement ? wrapper.element : wrapper.html();
expect(dom).toMatchSnapshot();
MockDate.reset();
wrapper.destroy();
done();

View File

@ -63,6 +63,7 @@ import { Tooltip } from './tootip/tooltip';
import { Upload } from './upload';
import { Result } from './result';
import { Descriptions } from './descriptions/descriptions'
import { PageHeader } from './page-header';
/**
* Install all ant-design-vue components into Vue.
@ -133,4 +134,5 @@ export {
Skeleton,
Result,
Descriptions,
PageHeader,
};

60
types/page-header.d.ts vendored Normal file
View File

@ -0,0 +1,60 @@
// Project: https://github.com/vueComponent/ant-design-vue
// Definitions by: drafish <https://github.com/drafish>
// Definitions: https://github.com/vueComponent/ant-design-vue/types
import { AntdComponent } from './component';
export declare class PageHeader extends AntdComponent {
/**
* Custom backIcon
* @default <Icon type="arrow-left" />
* @type any (string | slot)
*/
backIcon: any;
/**
* Custom prefixCls
* @type string
*/
prefixCls: string;
/**
* Custom title
* @type any (string | slot)
*/
title: any;
/**
* Custom subTitle
* @type any (string | slot)
*/
subTitle: any;
breadcrumb: object;
/**
* Custom tags
* @type any (string | slot)
*/
tags: any;
/**
* Custom footer
* @type any (string | slot)
*/
footer: any;
/**
* Custom extra
* @type any (string | slot)
*/
extra: any;
avatar: object;
/**
* Specify a callback that will be called when a user clicks backIcon.
*/
back(): void;
}