Commit 2cd72319 authored by Sendya's avatar Sendya

refactor: pro-layout v5 begin (vue3 support)

parent b2c22607
{
"env": {
"test": {
"presets": ["@babel/preset-env"],
"plugins": [
"@babel/plugin-transform-runtime",
"transform-vue-jsx",
"transform-object-assign",
"transform-class-properties"
]
}
"env": {
"test": {
"presets": [["@babel/preset-env", { "targets": { "node": "current" } }]],
"plugins": [
"@vue/babel-plugin-jsx",
"@babel/plugin-transform-member-expression-literals",
"@babel/plugin-transform-object-assign",
"@babel/plugin-transform-property-literals",
"@babel/plugin-transform-spread",
"@babel/plugin-transform-template-literals",
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-proposal-class-properties"
]
}
}
}
......@@ -2,3 +2,4 @@ node_modules/
**/*.spec.*
**/style/
*.html
src/icons
{
"root": true,
"env": {
"browser": true,
"node": true,
"jasmine": true,
"jest": true,
"es6": true
},
"parserOptions": {
"parser": "babel-eslint"
},
"extends": ["plugin:vue/strongly-recommended", "prettier"],
"rules": {
"no-var": "error",
"camelcase": "off",
"no-extra-boolean-cast": "off",
"semi": [2, "never", {"beforeStatementContinuationChars": "never"}],
"vue/require-prop-types": "off",
"vue/require-default-prop": "off",
"vue/no-reserved-keys": "off",
"vue/prop-name-casing": "off",
"vue/max-attributes-per-line": [
2,
{
"singleline": 20,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}
]
}
}
module.exports = {
root: true,
env: {
node: true,
},
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/typescript/recommended',
'@vue/prettier',
'@vue/prettier/@typescript-eslint',
],
parserOptions: {
ecmaVersion: 2020,
},
rules: {
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-inferrable-types': 0,
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
},
};
......@@ -29,4 +29,12 @@ build/
.vscode
# publish
*.tgz
\ No newline at end of file
*.tgz
/icons
/*.js
/*.d.ts
!.jest.js
!Icon.d.ts
!.fatherrc.js
!.eslintrc.js
\ No newline at end of file
const libDir = process.env.LIB_DIR;
const transformIgnorePatterns = [
'/dist/', 'node_modules\/[^/]+?\/(?!(es|node_modules)\/)', // Ignore modules without es dir
'/dist/',
'node_modules/[^/]+?/(?!(es|node_modules)/)', // Ignore modules without es dir
];
module.exports = {
testURL: 'http://localhost/',
moduleFileExtensions: [
"js",
"jsx",
"json",
"vue",
],
moduleFileExtensions: ['js', 'ts', 'tsx', 'json', 'vue'],
modulePathIgnorePatterns: ['/_site/'],
testPathIgnorePatterns: [
'/node_modules/', 'node'
],
testPathIgnorePatterns: ['/node_modules/', 'node'],
transform: {
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest",
"^.+\\.(js|jsx)$": "<rootDir>/node_modules/babel-jest"
'.*\\.(vue)$': '<rootDir>/node_modules/vue-jest',
'^.+\\.(ts|tsx)$': '<rootDir>/node_modules/babel-jest',
},
testRegex: libDir === 'dist'
? 'demo\\.test\\.js$'
: '.*\\.test\\.js$',
testRegex: libDir === 'dist' ? 'demo\\.test\\.ts$' : '.*\\.test\\.tsx$',
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/$1",
'^@/(.*)$': '<rootDir>/$1',
},
snapshotSerializers: ["<rootDir>/node_modules/jest-serializer-vue"],
snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
collectCoverage: process.env.COVERAGE === 'true',
collectCoverageFrom: [
"src/**/*.{js,jsx,vue}",
],
transformIgnorePatterns
collectCoverageFrom: ['src/**/*.{ts,tsx,vue}'],
transformIgnorePatterns,
verbose: true,
};
tests/
test/
examples/
src/
scripts/
.eslintignore
.eslintrc.js
.fatherrc.js
.gitignore
jest.config.js
.jest.js
tsconfig.json
tslint.json
English | [简体中文](./README.zh-CN.md)
<h1 align="center">
Ant Design Pro Layout
</h1>
<h1 align="center">Ant Design Pro Layout</h1>
<div align="center">
## Usage
[![NPM version](https://img.shields.io/npm/v/@ant-design/icons-vue.svg?style=flat)](https://npmjs.org/package/@ant-design-vue/pro-layout)
[![NPM downloads](http://img.shields.io/npm/dm/@ant-design/icons-vue.svg?style=flat)](https://npmjs.org/package/@ant-design-vue/pro-layout)
</div>
## Install
```bash
npm i @ant-design-vue/pro-layout --save
// or
# yarn
yarn add @ant-design-vue/pro-layout
# npm
npm i @ant-design-vue/pro-layout -S
```
```jsx
import ProLayout from '@ant-design-vue/pro-layout'
## Basic Usage
// by jsx
export default {
name: 'BasicLayout',
render () {
return (
<ProLayout>
<router-view />
</ProLayout>
)
}
}
First, you should add the icons that you need into the library.
```js
import { createApp } from 'vue'
import ProLayout, { PageContainer } from '@ant-design-vue/pro-layout';
const app = createApp();
app.use(ProLayout)
.use(PageContainer)
.mount('#app')
```
After that, you can use pro-layout in your Vue components as simply as this:
```vue
<template>
<pro-layout
:menus="menus"
:collapsed="collapsed"
:theme="theme"
:layout="layout"
:contentWidth="contentWidth"
:auto-hide-header="autoHideHeader"
:mediaQuery="query"
:isMobile="isMobile"
:handleMediaQuery="handleMediaQuery"
:handleCollapse="handleCollapse"
>
<template v-slot:menuHeaderRender>
<div>
<img src="../assets/logo.svg" />
<h1>Pro Layout</h1>
</div>
</template>
<template v-slot:rightContentRender>
<div :class="['ant-pro-global-header-index-right', layout === 'topmenu' && `ant-pro-global-header-index-${theme}`]">
rightContentRender
</div>
</template>
<template v-slot:footerRender>
<div>footerRender</div>
</template>
<setting-drawer navTheme="dark" />
<router-view />
</pro-layout>
<pro-layout>
<router-view />
</pro-layout>
</template>
```
or `TSX`
```jsx
import ProLayout from '@ant-design-vue/pro-layout'
<script>
// by template
import ProLayout, { SettingDrawer } from '@ant-design-vue/pro-layout'
import { asyncRouterMap } from '../config/router.config'
export default {
name: 'BasicLayout',
data () {
return {
menus: [],
collapsed: false,
autoHideHeader: false,
query: {},
layout: 'sidemenu',
contentWidth: 'Fluid',
theme: 'dark',
isMobile: false
}
},
created () {
this.menus = asyncRouterMap.find(item => item.path === '/').children
},
methods: {
handleMediaQuery (query) {
this.query = query
if (this.isMobile && !query['screen-xs']) {
this.isMobile = false
return
}
if (!this.isMobile && query['screen-xs']) {
this.isMobile = true
this.collapsed = false
}
},
handleCollapse (collapsed) {
this.collapsed = collapsed
export default defineComponent({
setup () {
return (): JSX.Element => (
<ProLayout>
<RouterView />
</ProLayout>
)
}
},
components: {
SettingDrawer
}
}
</script>
})
```
## Build project
## API
### ProLayout
| Property | Description | Type | Default Value |
| --- | --- | --- | --- |
| title | layout in the upper left corner title | VNode \| String | `'Ant Design Pro'` |
| logo | layout top left logo url | VNode \| render | - |
| loading`*` | layout loading status | boolean | - |
| layout | layout menu mode, sidemenu: right navigation, topmenu: top navigation | 'sidemenu' \| 'topmenu' | `'sidemenu'` |
| contentWidth | content mode of layout, Fluid: adaptive, Fixed: fixed width 1200px | 'Fixed' \| 'Fluid' | `Fluid` |
| theme | Navigation menu theme | 'light' \| 'dark' | `'dark'` |
| menus | Vue-router `routes` prop | Object | `[{}]` |
| collapsed | control menu's collapse and expansion | boolean | true |
| isMobile | is mobile | boolean | false |
| handleCollapse | folding collapse event of menu | (collapsed: boolean) => void | - |
| menuHeaderRender | render logo and title | v-slot \| VNode \| (logo,title)=>VNode \| false | - |
| headerRender | custom header render method | (props: BasicLayoutProps) => VNode | - |
| rightContentRender | header right content render method | (props: HeaderViewProps) => VNode | - |
| collapsedButtonRender | custom collapsed button method | (collapsed: boolean) => VNode | - |
| footerRender | custom footer render method | (props: BasicLayoutProps) => VNode | - |
| breadcrumbRender | custom breadcrumb render method | ({ route, params, routes, paths, h }) => VNode[] | - |
| i18nRender | i18n | Function (key: string) => string \| `false` | `false` |
| handleMediaQuery | media matchs callback | (querys: []) => void | - |
| mediaQuery | media matchs | Array | - |
### PageHeaderWrapper
| Property | Description | Type | Default Value |
| --- | --- | --- | --- |
| content | Content area | VNode \| v-slot | - |
| extra | Extra content area, on the right side of content | VNode \| v-slot | - |
| extraContent | Extra content area, on the right side of content | VNode \| v-slot | - |
| tabList | Tabs title list | `Array<{key: string, tab: sting}>` | - |
| tab-change | Switch panel callback | (key) => void | - |
| tab-active-key | The currently highlighted tab item | string | - |
### SettingDrawer
#### {settings}
| Property | Description | Type | Default Value |
| ---- | ---- | ---- | ---- |
| theme | Theme | `dark` `light` `realDark` | `light` |
| layout | Sider Layout | `sidemenu` `topmenu` | `sidemenu` |
| primaryColor | Primary color (*development only) | `#1890ff` | |
| contentWidth | content mode of layout, Fluid: adaptive, Fixed: fixed width 1200px | 'Fixed' \| 'Fluid' | `Fluid` |
```bash
npm run generate # Generate files to ./src
npm run compile # Build library
npm run test # Runing Test
```
[English](./README.md) | 简体中文
<h1 align="center">Ant Design Pro Layout</h1>
## 使用
```bash
npm i @ant-design-vue/pro-layout --save
// 或者
yarn add @ant-design-vue/pro-layout
```
```jsx
import ProLayout from '@ant-design-vue/pro-layout'
export default {
name: 'BasicLayout',
render () {
return (
<ProLayout>
<router-view />
</ProLayout>
)
}
}
```
```vue
<template>
<pro-layout
:menus="menus"
:collapsed="collapsed"
:theme="theme"
:layout="layout"
:contentWidth="contentWidth"
:auto-hide-header="autoHideHeader"
:mediaQuery="query"
:isMobile="isMobile"
:handleMediaQuery="handleMediaQuery"
:handleCollapse="handleCollapse"
>
<template v-slot:menuHeaderRender>
<div>
<img src="../assets/logo.svg" />
<h1>Pro Layout</h1>
</div>
</template>
<template v-slot:rightContentRender>
<div :class="['ant-pro-global-header-index-right', layout === 'topmenu' && `ant-pro-global-header-index-${theme}`]">
rightContentRender
</div>
</template>
<template v-slot:footerRender>
<div>footerRender</div>
</template>
<setting-drawer navTheme="dark" />
<router-view />
</pro-layout>
</template>
<script>
import ProLayout, { SettingDrawer } from '@ant-design-vue/pro-layout'
import { asyncRouterMap } from '../config/router.config'
export default {
name: 'BasicLayout',
data () {
return {
menus: [],
collapsed: false,
autoHideHeader: false,
query: {},
layout: 'sidemenu',
contentWidth: 'Fluid',
theme: 'dark',
isMobile: false
}
},
created () {
this.menus = asyncRouterMap.find(item => item.path === '/').children
},
methods: {
handleMediaQuery (query) {
this.query = query
if (this.isMobile && !query['screen-xs']) {
this.isMobile = false
return
}
if (!this.isMobile && query['screen-xs']) {
this.isMobile = true
this.collapsed = false
}
},
handleCollapse (collapsed) {
this.collapsed = collapsed
}
},
components: {
SettingDrawer
}
}
</script>
```
## API
### ProLayout
| Property | Description | Type | Default Value |
| --- | --- | --- | --- |
| title | layout 的 左上角 的 title | VNode \| String | `'Ant Design Pro'` |
| logo | layout 的 左上角 logo 的 url | VNode \| render | - |
| loading`*` | layout 的加载态 | boolean | - |
| menuHeaderRender | 渲染 logo 和 title | v-slot \| VNode \| (logo,title)=>VNode \| false | - |
| layout | layout 的菜单模式, sidemenu: 右侧导航, topmenu: 顶部导航 | 'sidemenu' \| 'topmenu' | `'sidemenu'` |
| contentWidth | layout 的内容模式,Fluid:自适应,Fixed:定宽 1200px | 'Fixed' \| 'Fluid' | `Fluid` |
| theme | 导航的主题 | 'light' \| 'dark' | `'dark'` |
| menus | Vue-router `routes` 属性 | Object | `[{}]` |
| collapsed | 控制菜单的收起和展开 | boolean | true |
| isMobile | 是否为手机模式 | boolean | false |
| handleCollapse | 菜单的折叠收起事件 | (collapsed: boolean) => void | - |
| headerRender | 自定义头的 render 方法 | (props: BasicLayoutProps) => VNode | - |
| rightContentRender | 自定义头右部的 render 方法 | (props: HeaderViewProps) => VNode | - |
| collapsedButtonRender | 自定义 侧栏收缩按钮 的方法 | (collapsed: boolean) => VNode | - |
| footerRender | 自定义 底部区域内容 | (props: BasicLayoutProps) => VNode | - |
| breadcrumbRender | 自定义面包屑渲染方法 | ({ route, params, routes, paths, h }) => VNode[] | - |
| i18nRender | 本地化渲染函数 (this.$t) | Function (key: string) => string \| `false` | `false` |
| handleMediaQuery | 媒体查询回调 | (querys: []) => void | - |
| mediaQuery | ProLayout 当前的媒体查询 | Array | - |
### PageHeaderWrapper
| Property | Description | Type | Default Value |
| --- | --- | --- | --- |
| content | 内容区 | VNode \| v-slot | - |
| extra | 扩展区域 | VNode \| v-slot | - |
| extraContent | 扩展内容区 | VNode \| v-slot | - |
| tabList | Tabs 导航 | `Array<{key: string, tab: sting}>` | - |
| tab-change | Tab 改变事件 | (key) => void | - |
| tab-active-key | 当前 Tab 选中项 | string | - |
### SettingDrawer
#### {settings}
| Property | Description | Type | Default Value |
| ---- | ---- | ---- | ---- |
| theme | 主题 | `dark` `light` `realDark` | `light` |
| layout | 布局模式 | `sidemenu` `topmenu` | `sidemenu` |
| primaryColor | 主色调 (*仅开发环境生效) | `#1890ff` | |
> 1%
last 2 versions
not ie <= 10
NODE_ENV=production
IS_ANALYZ=true
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/strongly-recommended',
'@vue/standard'
],
rules: {
// 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
// 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-console': 'off',
'no-debugger': 'off',
'generator-star-spacing': 'off',
'no-mixed-operators': 0,
'vue/max-attributes-per-line': [
2,
{
'singleline': 5,
'multiline': {
'max': 1,
'allowFirstLine': false
}
}
],
'vue/attribute-hyphenation': 0,
'vue/html-self-closing': 0,
'vue/component-name-in-template-casing': 0,
'vue/html-closing-bracket-spacing': 0,
'vue/singleline-html-element-content-newline': 0,
'vue/no-unused-components': 0,
'vue/multiline-html-element-content-newline': 0,
'vue/no-use-v-if-with-v-for': 0,
'vue/html-closing-bracket-newline': 0,
'vue/no-parsing-error': 0,
'no-tabs': 0,
'quotes': [
2,
'single',
{
'avoidEscape': true,
'allowTemplateLiterals': true
}
],
'semi': [
2,
'never',
{
'beforeStatementContinuationChars': 'never'
}
],
'no-delete-var': 2,
'prefer-const': [
2,
{
'ignoreReadBeforeAssign': false
}
],
'template-curly-spacing': 'off',
'indent': 'off'
},
parserOptions: {
parser: 'babel-eslint'
},
overrides: [
{
files: [
'**/__tests__/*.{j,t}s?(x)',
'**/tests/unit/**/*.spec.{j,t}s?(x)'
],
env: {
jest: true
}
}
]
}
const plugins = []
// lazy load ant-design-vue
// if your use import on Demand, Use this code
plugins.push(['import', {
'libraryName': 'ant-design-vue',
'libraryDirectory': 'es',
'style': true // `style: true` 会加载 less 文件
}])
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
[
'@babel/preset-env',
{
'useBuiltIns': 'entry',
'corejs': 3
}
]
],
plugins
}
import { createApp, onMounted, reactive, watch, watchEffect } from 'vue';
import { menus } from './menus';
import { MenuTheme } from '../src/typings';
import { Card, Space, Button, Switch } from 'ant-design-vue';
import BaseMenu, { useMenuState, MenuMode } from '../src/SiderMenu/BaseMenu';
import * as Icon from '@ant-design/icons-vue';
import 'ant-design-vue/dist/antd.less';
const BaseMenuDemo = {
setup() {
const state = reactive({
theme: 'dark' as MenuTheme,
mode: 'inline' as MenuMode,
themeChecked: true,
modeChecked: true,
})
const { state: menuState } = useMenuState({
collapsed: false,
openKeys: ['/dashboard'] as string[],
selectedKeys: ['/dashboard/monitor'] as string[],
})
onMounted(() => {
watch(() => state.themeChecked, (val) => {
state.theme = val ? 'dark' : 'light'
})
watch(() => state.modeChecked, (val) => {
state.mode = val ? 'inline' : 'horizontal'
})
})
return () => (
<div class="components">
<h2># BaseMenu</h2>
<Card style={{ marginBottom: '24px', background: 'rgb(244,244,244)' }}>
<Space size="middle">
<Button
disabled={state.mode !== 'inline'}
type="primary"
onClick={() => {
menuState.collapsed = !menuState.collapsed
}}
>
{menuState.collapsed ? '展开' : '收起'}
</Button>
<Switch checkedChildren="dark" unCheckedChildren="light" v-model={[state.themeChecked, 'checked']} />
<Switch checkedChildren="inline" unCheckedChildren="horizontal" v-model={[state.modeChecked, 'checked']} />
</Space>
<div style={{ margin: '12px 0' }}>
<p>SelectedKeys: { JSON.stringify(menuState.selectedKeys) }</p>
<p>OpenKeys: { JSON.stringify(menuState.openKeys) }</p>
<p>Collapsed: { JSON.stringify(menuState.collapsed) }</p>
<p>MenuMode: { JSON.stringify(state.mode) }</p>
<p>MenuTheme: { JSON.stringify(state.theme) }</p>
</div>
</Card>
<div class="demo" style="background: rgb(244,244,244);">
<div class="container" style="width: 256px;">
<BaseMenu
menus={menus}
theme={state.theme}
mode={state.mode}
collapsed={menuState.collapsed}
openKeys={menuState.openKeys}
selectedKeys={menuState.selectedKeys}
{...{
'onUpdate:openKeys': $event => {
menuState.openKeys = $event
},
'onUpdate:selectedKeys': $event => {
menuState.selectedKeys = $event
}
}}
/>
</div>
</div>
</div>
);
},
};
const app = createApp(BaseMenuDemo);
const filterIcons = ['default', 'createFromIconfontCN', 'getTwoToneColor', 'setTwoToneColor']
Object.keys(Icon)
.filter(k => !filterIcons.includes(k))
.forEach(k => {
app.component(Icon[k].displayName, Icon[k])
})
app.mount('#__vue-content>div');
// DO NOT CHANGE `storageOptions` CONFIG. UNLESS YOU KNOW WHAT YOU ARE DOING!
export const storageOptions = {
namespace: 'pro__', // key prefix
name: 'ls', // name variable Vue.[ls] or this.[$ls],
storage: 'local' // storage name session, local, memory
}
export default {
navTheme: 'dark',
primaryColor: '#1890FF',
layout: 'sidemenu',
contentWidth: 'Fluid',
fixedHeader: false,
autoHideHeader: false,
fixSiderbar: false,
colorWeak: false,
menu: {
locale: true
},
title: 'Ant Design Pro',
pwa: false,
iconfontUrl: ''
}
const ThemeColorReplacer = require('webpack-theme-color-replacer')
const generate = require('@ant-design/colors/lib/generate').default
const getAntdSerials = (color) => {
// 淡化(即less的tint)
const lightens = new Array(9).fill().map((t, i) => {
return ThemeColorReplacer.varyColor.lighten(color, i / 10)
})
const colorPalettes = generate(color)
const rgb = ThemeColorReplacer.varyColor.toNum3(color.replace('#', '')).join(',')
return lightens.concat(colorPalettes).concat(rgb)
}
const themePluginOption = {
fileName: 'css/theme-colors-[contenthash:8].css',
matchColors: getAntdSerials('#1890ff'), // 主色系列
// 改变样式选择器,解决样式覆盖问题
changeSelector (selector) {
switch (selector) {
case '.ant-calendar-today .ant-calendar-date':
return ':not(.ant-calendar-selected-date):not(.ant-calendar-selected-day)' + selector
case '.ant-btn:focus,.ant-btn:hover':
return '.ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger):not(.ant-btn-link),.ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger):not(.ant-btn-link)'
case '.ant-btn.active,.ant-btn:active':
return '.ant-btn.active:not(.ant-btn-primary):not(.ant-btn-danger):not(.ant-btn-link),.ant-btn:active:not(.ant-btn-primary):not(.ant-btn-danger):not(.ant-btn-link)'
case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
return ':not(.ant-steps-item-process)' + selector
case '.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item-open,.ant-menu-horizontal>.ant-menu-item-selected,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu-active,.ant-menu-horizontal>.ant-menu-submenu-open,.ant-menu-horizontal>.ant-menu-submenu-selected,.ant-menu-horizontal>.ant-menu-submenu:hover':
case '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal > .ant-menu-submenu-selected,.ant-menu-horizontal > .ant-menu-submenu:hover':
return '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu:hover'
case '.ant-menu-horizontal > .ant-menu-item-selected > a':
case '.ant-menu-horizontal>.ant-menu-item-selected>a':
return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item-selected > a'
case '.ant-menu-horizontal > .ant-menu-item > a:hover':
case '.ant-menu-horizontal>.ant-menu-item>a:hover':
return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item > a:hover'
default :
return selector
}
}
}
const dynamicThemePlugin = () => new ThemeColorReplacer(themePluginOption)
module.exports = dynamicThemePlugin
export default {
theme: [
{
key: 'dark',
fileName: 'dark.css',
theme: 'dark'
},
{
key: '#F5222D',
fileName: '#F5222D.css',
modifyVars: {
'@primary-color': '#F5222D'
}
},
{
key: '#FA541C',
fileName: '#FA541C.css',
modifyVars: {
'@primary-color': '#FA541C'
}
},
{
key: '#FAAD14',
fileName: '#FAAD14.css',
modifyVars: {
'@primary-color': '#FAAD14'
}
},
{
key: '#13C2C2',
fileName: '#13C2C2.css',
modifyVars: {
'@primary-color': '#13C2C2'
}
},
{
key: '#52C41A',
fileName: '#52C41A.css',
modifyVars: {
'@primary-color': '#52C41A'
}
},
{
key: '#2F54EB',
fileName: '#2F54EB.css',
modifyVars: {
'@primary-color': '#2F54EB'
}
},
{
key: '#722ED1',
fileName: '#722ED1.css',
modifyVars: {
'@primary-color': '#722ED1'
}
},
{
key: '#F5222D',
theme: 'dark',
fileName: 'dark-#F5222D.css',
modifyVars: {
'@primary-color': '#F5222D'
}
},
{
key: '#FA541C',
theme: 'dark',
fileName: 'dark-#FA541C.css',
modifyVars: {
'@primary-color': '#FA541C'
}
},
{
key: '#FAAD14',
theme: 'dark',
fileName: 'dark-#FAAD14.css',
modifyVars: {
'@primary-color': '#FAAD14'
}
},
{
key: '#13C2C2',
theme: 'dark',
fileName: 'dark-#13C2C2.css',
modifyVars: {
'@primary-color': '#13C2C2'
}
},
{
key: '#52C41A',
theme: 'dark',
fileName: 'dark-#52C41A.css',
modifyVars: {
'@primary-color': '#52C41A'
}
},
{
key: '#2F54EB',
theme: 'dark',
fileName: 'dark-#2F54EB.css',
modifyVars: {
'@primary-color': '#2F54EB'
}
},
{
key: '#722ED1',
theme: 'dark',
fileName: 'dark-#722ED1.css',
modifyVars: {
'@primary-color': '#722ED1'
}
}
]
}
import { createApp, defineComponent, reactive } from 'vue';
import { Card } from 'ant-design-vue';
import { ContentWidth } from '../src/typings';
import GlobalFooter from '../src/GlobalFooter';
import 'ant-design-vue/dist/antd.less';
const GlobalFooterDemo = defineComponent({
setup () {
const state = reactive({
contentWidth: 'Fixed' as ContentWidth
})
return () => (
<div class="components" style={{ background: 'rgb(240, 240, 240)', paddingBottom: '20px' }}>
<Card style={{ marginBottom: '24px', background: 'rgb(244,244,244)' }}>
<h2># BasicLayout</h2>
</Card>
<GlobalFooter
links={[
{
key: '1',
title: 'Pro Layout',
href: 'https://www.github.com/vueComponent/pro-layout',
blankTarget: true,
},
{
key: '2',
title: 'Github',
href: 'https://www.github.com/vueComponent/ant-design-vue-pro',
blankTarget: true,
},
{
key: '3',
title: '@Sendya',
href: 'https://www.github.com/sendya/',
blankTarget: true,
}
]}
copyright={(<a href="https://github.com/vueComponent" target="_blank">vueComponent</a>)}
/>
</div>
);
}
});
createApp(GlobalFooterDemo).mount('#__vue-content>div');
import { createApp, defineComponent, reactive } from 'vue';
import { Card, Space, Button } from 'ant-design-vue';
import { ContentWidth } from '../src/typings';
import GridContent from '../src/GridContent';
import 'ant-design-vue/dist/antd.less';
const GridContentDemo = defineComponent({
setup () {
const state = reactive({
contentWidth: 'Fixed' as ContentWidth
})
return () => (
<div class="components" style={{ background: 'rgb(240, 240, 240)', paddingBottom: '20px' }}>
<Card style={{ marginBottom: '24px', background: 'rgb(244,244,244)' }}>
<Space size="middle">
ContentWidth:
<Button
type="primary"
onClick={() => {
state.contentWidth = state.contentWidth === 'Fixed' ? 'Fluid' : 'Fixed';
}}
>
{state.contentWidth}
</Button>
</Space>
</Card>
<GridContent contentWidth={state.contentWidth} style={{ background: 'rgb(220, 220, 220)', padding: '22px' }}>
Content
<br />
...
<br />
...
<br />
...
<br />
...
<br />
...<br />
</GridContent>
</div>
);
}
});
createApp(GridContentDemo).mount('#__vue-content>div');
.simple-wrap {
position: relative;
margin-bottom: 1rem;
line-height: 1;
&:after {
width: 100%;
height: 1px;
content: '';
position: absolute;
bottom: 0.125em;
left: 0;
background-color: hotpink;
z-index: -100;
}
&:before {
width: 100%;
height: 1px;
content: '';
position: absolute;
top: 0.125em;
left: 0;
background-color: hotpink;
z-index: -100;
}
}
.two-tone-wrap,
.all-icons-wrap {
.container {
display: flex;
flex-flow: row wrap;
width: 80vw;
margin: auto;
}
.card {
height: 90px;
margin: 12px 0 16px;
width: 16.6666%;
text-align: center;
}
.name-description {
display: block;
text-align: center;
transform: scale(0.83);
font-family: 'Lucida Console', Consolas;
white-space: nowrap;
}
.text {
margin: 0 0.5rem;
}
}
.ant-design-doc-set-wrap {
.container {
margin: 40px 0;
list-style: none;
overflow: hidden;
}
.container-item {
float: left;
width: 16.66%;
text-align: center;
list-style: none;
height: 100px;
transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out;
position: relative;
margin: 3px 0;
border-radius: 4px;
overflow: hidden;
padding: 10px 0 0;
}
.title {
text-align: center;
}
}
\ No newline at end of file
import { createApp, reactive } from 'vue';
import { Button, message } from 'ant-design-vue';
import BasicLayout from '../src/BasicLayout';
import 'ant-design-vue/dist/antd.less';
const SimpleDemo = {
setup() {
const state = reactive({
collapsed: false,
});
return () => (
<div className="components">
<h2># BasicLayout</h2>
<BasicLayout
collapsed={state.collapsed}
onCollapsed={collapsed => {
state.collapsed = collapsed;
}}
>
<Button
onClick={() => {
message.info('clicked.');
}}
>
Click Me!!
</Button>
</BasicLayout>
</div>
);
},
};
const app = createApp(SimpleDemo);
app.use(BasicLayout).mount('#__vue-content>div');
module.exports = {
preset: '@vue/cli-plugin-unit-jest'
}
import { RouteProps } from '../src/typings';
export const menus: RouteProps[] = [
{
path: '/dashboard',
name: 'Dashboard',
meta: { icon: 'dashboard-outlined', title: 'Dashboard' },
children: [
{
path: '/dashboard/analysis',
name: 'Analysis',
meta: { icon: 'SmileOutlined', title: 'Analysis' },
},
{
path: '/dashboard/monitor',
name: 'Monitor',
meta: { icon: 'SmileOutlined', title: 'Monitor' },
},
{
path: '/dashboard/workplace',
name: 'Workplace',
meta: { icon: 'SmileOutlined', title: 'Workplace' },
},
],
},
{
path: '/form',
name: 'form',
meta: { title: 'Form', icon: 'SmileOutlined' },
children: [
{
path: '/form/basic-form',
name: 'basic-form',
meta: { title: 'Basic Form' },
},
{
path: '/form/step-form',
name: 'step-form',
meta: { title: 'Step Form' },
},
{
path: '/form/advanced-form',
name: 'advance-form',
meta: { title: 'Advanced Form' },
},
],
},
]
{
"name": "ant-design-pro-example",
"version": "0.1.1",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"lint": "vue-cli-service lint",
"analyz": "vue-cli-service build --mode analyz"
},
"main": "./src/index.js",
"author": "vueComponent",
"repository": {
"type": "git",
"url": "https://github.com/vueComponent/pro-layout"
},
"license": "MIT",
"dependencies": {
"@ant-design/icons": "^4.0.0-alpha.11",
"ant-design-vue": "^1.5.4",
"core-js": "^3.1.2",
"vue-i18n": "^8.15.0",
"vue-ls": "^3.2.1",
"vue-router": "^3.0.6",
"vue-svg-component-runtime": "^1.0.1",
"vuex": "^3.0.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.0.0",
"@vue/cli-plugin-eslint": "^4.0.0",
"@vue/cli-plugin-router": "^4.0.0",
"@vue/cli-plugin-unit-jest": "^4.0.0",
"@vue/cli-plugin-vuex": "^4.0.0",
"@vue/cli-service": "^4.0.0",
"@vue/eslint-config-standard": "^4.0.0",
"@vue/test-utils": "1.0.0-beta.29",
"babel-eslint": "^10.0.1",
"babel-plugin-import": "^1.12.2",
"eslint": "^5.16.0",
"eslint-plugin-html": "^6.0.0",
"eslint-plugin-vue": "^5.0.0",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"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.11"
}
}
module.exports = {
plugins: {
autoprefixer: {}
}
}
import { computed, createApp, defineComponent, inject, reactive, toRefs } from 'vue';
import { Card, Space, Button } from 'ant-design-vue';
import { ContentWidth } from '../src/typings';
import { warning } from '../src/utils';
import GridContent from '../src/GridContent';
import ProProvider, { injectProConfigKey, defaultProProviderProps } from '../src/ProProvider';
import 'ant-design-vue/dist/antd.less';
const trans = {
'render.test.i18n.hello': 'Hello My Friends'
}
const i18nRender = (t: string): string => {
warning(false, `i18n.key ${t}, value: ${trans[t]}`, )
return trans[t];
}
const ProProviderDemo = defineComponent({
setup () {
const state = reactive({
contentWidth: 'Fixed' as ContentWidth,
})
return () => (
<>
<ProProvider i18n={i18nRender} contentWidth={state.contentWidth}>
<h2># BasicLayout</h2>
<div class="components" style={{ background: 'rgb(240, 240, 240)', paddingBottom: '20px' }}>
<Card style={{ marginBottom: '24px', background: 'rgb(244,244,244)' }}>
<Space size="middle">
ContentWidth:
<Button
type="primary"
onClick={() => {
state.contentWidth = state.contentWidth === 'Fixed' ? 'Fluid' : 'Fixed';
}}
>
{state.contentWidth}
</Button>
</Space>
<div class="env">
state.contentWidth: { JSON.stringify(state.contentWidth) }
</div>
</Card>
<TestChildComponent style={{ background: 'rgb(220, 220, 220)', padding: '22px' }}/>
</div>
</ProProvider>
</>
)
}
})
const TestChildComponent = defineComponent({
setup () {
const config = inject(injectProConfigKey, defaultProProviderProps)
return () => {
const { i18n, contentWidth } = config
return (
<div class="test-child-component">
<p>TestChildComponent:</p>
<div>
i18n: {i18n.toString()}
</div>
<div>
contentWidth: {contentWidth}
</div>
<p>{i18n('render.test.i18n.hello')}</p>
</div>
)
}
}
});
const app = createApp(ProProviderDemo);
app.mount('#__vue-content>div');
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Pro Layout</title>
</head>
<body>
<noscript>
<strong>We're sorry but <b>Ant Design Pro</b> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<template>
<div id="app">
<router-view />
</div>
</template>
<style lang="less" scoped>
.nav-container {
width: 400px;
padding-top: 100px;
margin: 0 auto;
ul {
li {
display: inline-block;
margin: 0 10px;
}
}
a {
user-select: none;
text-decoration: none;
&.router-link-active {
color: #FF0000;
}
}
}
</style>
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.6 (67491) - http://www.bohemiancoding.com/sketch -->
<title>Vue</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="69.644116%" y1="0%" x2="69.644116%" y2="100%" id="linearGradient-1">
<stop stop-color="#29CDFF" offset="0%"></stop>
<stop stop-color="#148EFF" offset="37.8600687%"></stop>
<stop stop-color="#0A60FF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-19.8191553%" y1="-36.7931464%" x2="138.57919%" y2="157.637507%" id="linearGradient-2">
<stop stop-color="#29CDFF" offset="0%"></stop>
<stop stop-color="#0F78FF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="68.1279872%" y1="-35.6905737%" x2="30.4400914%" y2="114.942679%" id="linearGradient-3">
<stop stop-color="#FA8E7D" offset="0%"></stop>
<stop stop-color="#F74A5C" offset="51.2635191%"></stop>
<stop stop-color="#F51D2C" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Vue" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group" transform="translate(19.000000, 9.000000)">
<path d="M89.96,90.48 C78.58,93.48 68.33,83.36 67.62,82.48 L46.6604487,62.2292258 C45.5023849,61.1103236 44.8426845,59.5728835 44.8296987,57.9626396 L44.5035564,17.5209948 C44.4948861,16.4458744 44.0537714,15.4195095 43.2796864,14.6733517 L29.6459999,1.53153737 C28.055475,-0.00160504005 25.5232423,0.0449126588 23.9900999,1.63543756 C23.2715121,2.38092066 22.87,3.37600834 22.87,4.41143746 L22.87,64.3864751 C22.87,67.0807891 23.9572233,69.6611067 25.885409,71.5429748 L63.6004615,108.352061 C65.9466323,110.641873 69.6963584,110.624605 72.0213403,108.313281" id="Path-Copy" fill="url(#linearGradient-1)" fill-rule="nonzero" transform="translate(56.415000, 54.831157) scale(-1, 1) translate(-56.415000, -54.831157) "></path>
<path d="M68,90.1163122 C56.62,93.1163122 45.46,83.36 44.75,82.48 L23.7904487,62.2292258 C22.6323849,61.1103236 21.9726845,59.5728835 21.9596987,57.9626396 L21.6335564,17.5209948 C21.6248861,16.4458744 21.1837714,15.4195095 20.4096864,14.6733517 L6.7759999,1.53153737 C5.185475,-0.00160504005 2.65324232,0.0449126588 1.12009991,1.63543756 C0.401512125,2.38092066 3.90211878e-13,3.37600834 3.90798505e-13,4.41143746 L3.94351218e-13,64.3864751 C3.94681177e-13,67.0807891 1.08722326,69.6611067 3.01540903,71.5429748 L40.7807092,108.401101 C43.1069304,110.671444 46.8180151,110.676525 49.1504445,108.412561" id="Path" fill="url(#linearGradient-2)" fill-rule="nonzero"></path>
<path d="M43.2983488,19.0991931 L27.5566079,3.88246244 C26.7624281,3.11476967 26.7409561,1.84862177 27.5086488,1.05444194 C27.8854826,0.664606611 28.4044438,0.444472651 28.9466386,0.444472651 L60.3925021,0.444472651 C61.4970716,0.444472651 62.3925021,1.33990315 62.3925021,2.44447265 C62.3925021,2.9858375 62.1730396,3.50407742 61.7842512,3.88079942 L46.0801285,19.0975301 C45.3051579,19.8484488 44.0742167,19.8491847 43.2983488,19.0991931 Z" id="Path" fill="url(#linearGradient-3)"></path>
</g>
</g>
</svg>
\ No newline at end of file
import { Icon, Menu, Dropdown } from 'ant-design-vue'
import './index.less'
import i18nMixin from '@/store/i18n-mixin'
const locales = ['zh-CN', 'zh-TW', 'en-US', 'pt-BR']
const languageLabels = {
'zh-CN': '简体中文',
'zh-TW': '繁体中文',
'en-US': 'English',
'pt-BR': 'Português'
}
// eslint-disable-next-line
const languageIcons = {
'zh-CN': '🇨🇳',
'zh-TW': '🇭🇰',
'en-US': '🇺🇸',
'pt-BR': '🇧🇷'
}
const SelectLang = {
name: 'SelectLang',
mixins: [i18nMixin],
render () {
const changeLang = ({ key }) => {
this.setLang(key)
}
const langMenu = (
<Menu class={['menu', 'ant-pro-select-lang']} selectedKeys={[this.currentLang]} onClick={changeLang}>
{locales.map(locale => (
<Menu.Item key={locale}>
<span role="img" aria-label={languageLabels[locale]}>
{languageIcons[locale]}
</span>{' '}
{languageLabels[locale]}
</Menu.Item>
))}
</Menu>
)
return (
<Dropdown overlay={langMenu} placement="bottomRight">
<span class={'drop-down'}>
<Icon type="global" title={this.$t('navBar.lang')} />
</span>
</Dropdown>
)
}
}
export default SelectLang
@import "~ant-design-vue/es/style/themes/default";
@header-menu-prefix-cls: ~'@{ant-prefix}-pro-header-menu';
.drop-down {
line-height: @layout-header-height;
vertical-align: top;
cursor: pointer;
> i {
font-size: 16px !important;
transform: none !important;
svg {
position: relative;
top: -1px;
}
}
}
.ant-pro-select-lang {
.ant-dropdown-menu-item {
min-width: 160px;
}
}
/**
* 项目默认配置项
* primaryColor - 默认主题色, 如果修改颜色不生效,请清理 localStorage
* navTheme - sidebar theme ['dark', 'light'] 两种主题
* colorWeak - 色盲模式
* layout - 整体布局方式 ['sidemenu', 'topmenu'] 两种布局
* fixedHeader - 固定 Header : boolean
* fixSiderbar - 固定左侧菜单栏 : boolean
* contentWidth - 内容区布局: 流式 | 固定
*
* storageOptions: {} - Vue-ls 插件配置项 (localStorage/sessionStorage)
*
*/
export default {
navTheme: 'dark', // theme for nav menu
primaryColor: '#52C41A', // primary color of ant design
layout: 'sidemenu', // nav menu position: `sidemenu` or `topmenu`
contentWidth: 'Fluid', // layout of content: `Fluid` or `Fixed`, only works when layout is topmenu
fixedHeader: false, // sticky header
fixSiderbar: false, // sticky siderbar
colorWeak: false,
menu: {
locale: true
},
title: 'Ant Design Pro',
pwa: false,
iconfontUrl: '',
production: process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW !== 'true'
}
// eslint-disable-next-line
import BasicLayout from '../layouts/BasicLayout.vue'
const RouteView = {
name: 'RouteView',
render: (h) => h('router-view')
}
const asyncRouterMap = [
{
path: '/',
name: 'index',
component: BasicLayout,
meta: { title: 'menu.home' },
redirect: '/dashboard/analysis',
children: [
{
path: '/dashboard',
name: 'dashboard',
meta: { keepAlive: true, title: 'menu.dashboard.default', icon: 'dashboard' },
redirect: '/dashboard/analysis',
component: RouteView,
children: [
{
path: '/dashboard/analysis',
name: 'analysis',
meta: {
keepAlive: true,
icon: 'smile',
title: 'menu.dashboard.analysis'
},
component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard/analysis')
},
{
path: '/dashboard/workplace/:id?',
name: 'workplace',
meta: {
global: true,
keepAlive: true,
icon: 'smile',
title: 'menu.dashboard.workplace'
},
component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard/workplace')
}
]
},
{
path: '/form',
name: 'form',
meta: {
keepAlive: true,
title: 'menu.form.default',
icon: 'video-camera'
},
component: RouteView,
children: [
{
path: '/form/basic-form',
name: 'basic-form',
meta: {
keepAlive: true,
icon: 'smile',
title: 'menu.form.basicform'
},
component: () => import(/* webpackChunkName: "about" */ '../views/form/basic-form')
},
{
path: '/form/step-form',
name: 'step-form',
meta: {
keepAlive: true,
icon: 'smile',
title: 'menu.form.stepform'
},
component: () => import(/* webpackChunkName: "about" */ '../views/form/step-form')
},
{
path: '/form/advanced-form',
name: 'advanced-form',
meta: {
keepAlive: true,
icon: 'smile',
title: 'menu.form.advancedform'
},
component: () => import(/* webpackChunkName: "about" */ '../views/form/advanced-form')
}
]
},
{
path: '/page1',
name: 'page1',
meta: {
keepAlive: true,
title: 'menu.nav1',
icon: 'smile'
},
component: () => import(/* webpackChunkName: "about" */ '../views/TestPage1')
},
{
path: '/page2',
name: 'page2',
meta: {
keepAlive: true,
title: 'menu.nav2',
icon: 'smile'
},
component: () => import(/* webpackChunkName: "about" */ '../views/TestPage2')
},
{
path: '/page3',
name: 'page3',
meta: {
keepAlive: true,
title: 'menu.nav3',
icon: 'smile'
},
component: () => import(/* webpackChunkName: "about" */ '../views/TestPage3')
}
]
}
]
export { asyncRouterMap }
/* Layout begin */
export {
default as LeftOutline
} from '@ant-design/icons/lib/outline/LeftOutline'
export {
default as RightOutline
} from '@ant-design/icons/lib/outline/RightOutline'
export {
default as ArrowLeftOutline
} from '@ant-design/icons/lib/outline/ArrowLeftOutline'
export {
default as ArrowRightOutline
} from '@ant-design/icons/lib/outline/ArrowRightOutline'
export {
default as MenuFoldOutline
} from '@ant-design/icons/lib/outline/MenuFoldOutline'
export {
default as MenuUnfoldOutline
} from '@ant-design/icons/lib/outline/MenuUnfoldOutline'
export {
default as DashboardOutline
} from '@ant-design/icons/lib/outline/DashboardOutline'
export {
default as VideoCameraOutline
} from '@ant-design/icons/lib/outline/VideoCameraOutline'
export {
default as LoadingOutline
} from '@ant-design/icons/lib/outline/LoadingOutline'
export {
default as GlobalOutline
} from '@ant-design/icons/lib/outline/GlobalOutline'
export {
default as UserOutline
} from '@ant-design/icons/lib/outline/UserOutline'
export {
default as LogoutOutline
} from '@ant-design/icons/lib/outline/LogoutOutline'
export {
default as SmileOutline
} from '@ant-design/icons/lib/outline/SmileOutline'
export {
default as CheckOutline
} from '@ant-design/icons/lib/outline/CheckOutline'
export {
default as EllipsisOutline
} from '@ant-design/icons/lib/outline/EllipsisOutline'
export {
default as NotificationOutline
} from '@ant-design/icons/lib/outline/NotificationOutline'
export {
default as SettingOutline
} from '@ant-design/icons/lib/outline/SettingOutline'
export {
default as GithubOutline
} from '@ant-design/icons/lib/outline/GithubOutline'
export {
default as CopyrightOutline
} from '@ant-design/icons/lib/outline/CopyrightOutline'
/* Layout end */
/* Feedback begin */
export {
default as QuestionCircleOutline
} from '@ant-design/icons/lib/outline/QuestionCircleOutline'
/* Feedback end */
/* MultiTab begin */
export {
default as CloseOutline
} from '@ant-design/icons/lib/outline/CloseOutline'
export {
default as ReloadOutline
} from '@ant-design/icons/lib/outline/ReloadOutline'
export {
default as DownOutline
} from '@ant-design/icons/lib/outline/DownOutline'
export {
default as AlignLeftOutline
} from '@ant-design/icons/lib/outline/AlignLeftOutline'
/* MultiTab end */
/* eslint-disable */
/**
* 该文件是为了按需加载,剔除掉了一些不需要的框架组件。
* 减少了编译支持库包大小
*
* 当需要更多组件依赖时,在该文件加入即可
*
* 注意:需要同时修改 babel.config.js 文件下的配置项
*/
import Vue from 'vue'
import {
ConfigProvider,
Layout,
Input,
InputNumber,
Button,
Switch,
Radio,
Checkbox,
Select,
Card,
Form,
Row,
Col,
Modal,
Table,
Tabs,
Icon,
Badge,
Popover,
Dropdown,
List,
Avatar,
Breadcrumb,
Steps,
Spin,
Menu,
Drawer,
Tooltip,
Alert,
Tag,
Divider,
DatePicker,
TimePicker,
Upload,
Progress,
Skeleton,
Popconfirm,
message,
notification
} from 'ant-design-vue'
Vue.use(ConfigProvider)
Vue.use(Layout)
Vue.use(Input)
Vue.use(InputNumber)
Vue.use(Button)
Vue.use(Switch)
Vue.use(Radio)
Vue.use(Checkbox)
Vue.use(Select)
Vue.use(Card)
Vue.use(Form)
Vue.use(Row)
Vue.use(Col)
Vue.use(Modal)
Vue.use(Table)
Vue.use(Tabs)
Vue.use(Icon)
Vue.use(Badge)
Vue.use(Popover)
Vue.use(Dropdown)
Vue.use(List)
Vue.use(Avatar)
Vue.use(Breadcrumb)
Vue.use(Steps)
Vue.use(Spin)
Vue.use(Menu)
Vue.use(Drawer)
Vue.use(Tooltip)
Vue.use(Alert)
Vue.use(Tag)
Vue.use(Divider)
Vue.use(DatePicker)
Vue.use(TimePicker)
Vue.use(Upload)
Vue.use(Progress)
Vue.use(Skeleton)
Vue.use(Popconfirm)
// Add to this.$xxx
Vue.prototype.$confirm = Modal.confirm
Vue.prototype.$message = message
Vue.prototype.$notification = notification
Vue.prototype.$info = Modal.info
Vue.prototype.$success = Modal.success
Vue.prototype.$error = Modal.error
Vue.prototype.$warning = Modal.warning
/* eslint-disable */
/**
* 该文件用于直接全量引入 antd
* 如果想减少依赖体积,可以使用同目录下的 lazyload.js 按需加载
* 注意:需要同时修改 babel.config.js 文件下的配置项
*/
import Vue from 'vue'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.less'
Vue.use(Antd)
import Vue from 'vue'
import store from '../store'
import defaultSettings from '@config/defaultSettings'
import {
APP_LANGUAGE,
TOGGLE_CONTENT_WIDTH,
TOGGLE_FIXED_HEADER,
TOGGLE_FIXED_SIDEBAR, TOGGLE_HIDE_HEADER,
TOGGLE_LAYOUT, TOGGLE_NAV_THEME, TOGGLE_WEAK
} from '../store/mutation-types'
export default function initializer () {
console.log('API_URL:', process.env.BASE_URL)
// store.commit(ACCESS_TOKEN, Vue.ls.get(ACCESS_TOKEN))
store.commit(TOGGLE_LAYOUT, Vue.ls.get(TOGGLE_LAYOUT, defaultSettings.layout))
store.commit(TOGGLE_FIXED_HEADER, Vue.ls.get(TOGGLE_FIXED_HEADER, defaultSettings.fixedHeader))
store.commit(TOGGLE_FIXED_SIDEBAR, Vue.ls.get(TOGGLE_FIXED_SIDEBAR, defaultSettings.fixSiderbar))
store.commit(TOGGLE_CONTENT_WIDTH, Vue.ls.get(TOGGLE_CONTENT_WIDTH, defaultSettings.contentWidth))
store.commit(TOGGLE_HIDE_HEADER, Vue.ls.get(TOGGLE_HIDE_HEADER, defaultSettings.autoHideHeader))
store.commit(TOGGLE_NAV_THEME, Vue.ls.get(TOGGLE_NAV_THEME, defaultSettings.navTheme))
store.commit(TOGGLE_WEAK, Vue.ls.get(TOGGLE_WEAK, defaultSettings.colorWeak))
store.dispatch('setLang', Vue.ls.get(APP_LANGUAGE, 'en-US'))
}
import Vue from 'vue'
import Storage from 'vue-ls'
// use antd
// import './antd/normal'
// lazyload antd
import './antd/lazyload'
import { storageOptions } from '@config/defaultSettings'
import { DocumentTitle } from '@ant-design-vue/pro-layout'
Vue.use(DocumentTitle)
Vue.use(Storage, storageOptions)
import 'core-js/features/array/from'
import 'core-js/features/array/flat'
import 'core-js/features/set'
import 'core-js/features/promise'
import 'core-js/features/object/entries'
import 'regenerator-runtime/runtime'
@import '~ant-design-vue/es/style/themes/default.less';
html,
body,
#app, #root {
height: 100%;
}
.colorWeak {
filter: invert(80%);
}
.ant-layout.layout-basic {
height: 100vh;
min-height: 100vh;
}
canvas {
display: block;
}
body {
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
ul,
ol {
list-style: none;
}
@media (max-width: @screen-xs) {
.ant-table {
width: 100%;
overflow-x: auto;
&-thead > tr,
&-tbody > tr {
> th,
> td {
white-space: pre;
> span {
display: block;
}
}
}
}
}
@import "~ant-design-vue/es/style/themes/default";
.ant-pro-global-header-index-right {
margin-right: 8px;
&.ant-pro-global-header-index-dark {
.ant-pro-global-header-index-action {
color: hsla(0,0%,100%,.85);
&:hover {
background: #1890ff;
}
}
}
&.ant-pro-global-header-topmenu {
margin-right: 0;
}
.account {
.antd-pro-global-header-index-avatar {
margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0;
margin-right: 8px;
color: @primary-color;
vertical-align: top;
background: rgba(255, 255, 255, 0.85);
}
}
.menu {
.anticon {
margin-right: 8px;
}
.ant-dropdown-menu-item {
min-width: 100px;
}
}
}
<template>
<pro-layout
title="Pro Layout"
:menus="menus"
:collapsed="collapsed"
:mediaQuery="query"
:isMobile="isMobile"
:handleMediaQuery="handleMediaQuery"
:handleCollapse="handleCollapse"
:i18nRender="i18nRender"
v-bind="settings"
>
<!-- <template v-slot:menuRender>
<div v-for="(menu, key) in menus" :key="key">
{{ menu }}
</div>
</template>-->
<template v-slot:menuHeaderRender>
<div>
<img src="../assets/logo.svg" />
<h1>Pro Layout</h1>
</div>
</template>
<template v-slot:headerContentRender>
<div>headerContentRender</div>
</template>
<template v-slot:rightContentRender>
<div :class="['ant-pro-global-header-index-right', settings.layout === 'topmenu' && `ant-pro-global-header-index-${settings.theme}`]">
rightContentRender
</div>
</template>
<template v-slot:footerRender>
<div>footerRender</div>
</template>
<setting-drawer
:settings="settings"
@change="handleSettingChange"
/>
<router-view />
</pro-layout>
</template>
<script>
import { asyncRouterMap } from '../config/router.config'
import { i18nRender } from '../locales'
import defaultSettings from '@/config/defaultSettings'
import LogoSvg from '../assets/logo.svg?inline'
import { CONTENT_WIDTH_TYPE } from '@/store/mutation-types'
export default {
name: 'BasicLayout',
data () {
return {
// base
menus: [],
// 侧栏收起状态
collapsed: false,
// 自动隐藏头部栏
autoHideHeader: false,
// 媒体查询
query: {},
// 布局类型
layout: 'sidemenu', // 'sidemenu', 'topmenu'
// 定宽: true / 流式: false
contentWidth: false, // layout of content: `Fluid` or `Fixed`, only works when layout is topmenu
// 主题 'dark' | 'light'
theme: 'dark',
// 是否手机模式
isMobile: false,
settings: {
// 布局类型
layout: defaultSettings.layout, // 'sidemenu', 'topmenu'
// CONTENT_WIDTH_TYPE
contentWidth: defaultSettings.contentWidth,
// 主题 'dark' | 'light'
theme: defaultSettings.navTheme,
// 主色调
primaryColor: defaultSettings.primaryColor,
fixedHeader: defaultSettings.fixedHeader,
fixSiderbar: defaultSettings.fixSiderbar,
colorWeak: defaultSettings.colorWeak,
hideHintAlert: false,
hideCopyButton: false
}
}
},
created () {
this.menus = asyncRouterMap.find(item => item.path === '/').children
},
methods: {
i18nRender,
handleMediaQuery (val) {
this.query = val
if (this.isMobile && !val['screen-xs']) {
this.isMobile = false
return
}
if (!this.isMobile && val['screen-xs']) {
this.isMobile = true
this.collapsed = false
}
},
handleCollapse (val) {
this.collapsed = val
},
handleSettingChange ({ type, value }) {
console.log('type', type, value)
type && (this.settings[type] = value)
switch (type) {
case 'contentWidth':
this.settings[type] = value
break
case 'layout':
if (value === 'sidemenu') {
this.settings.contentWidth = CONTENT_WIDTH_TYPE.Fluid
} else {
this.settings.fixSiderbar = false
this.settings.contentWidth = CONTENT_WIDTH_TYPE.Fixed
}
break
}
},
logoRender () {
return <LogoSvg />
},
footerRender () {
return <div>custom footer</div>
}
}
}
</script>
<style lang="less" scoped>
@import "BasicLayout.less";
</style>
import Vue from 'vue'
import VueI18n from 'vue-i18n'
// default lang
import enUS from './lang/en-US'
Vue.use(VueI18n)
export const defaultLang = 'en-US'
const messages = {
'en-US': {
...enUS
}
}
const i18n = new VueI18n({
silentTranslationWarn: true,
locale: defaultLang,
fallbackLocale: defaultLang,
messages
})
const loadedLanguages = [defaultLang]
function setI18nLanguage (lang) {
i18n.locale = lang
// request.headers['Accept-Language'] = lang
document.querySelector('html').setAttribute('lang', lang)
return lang
}
export function loadLanguageAsync (lang = defaultLang) {
return new Promise(resolve => {
// 缓存语言设置
Vue.ls.set('lang', lang)
if (i18n.locale !== lang) {
if (!loadedLanguages.includes(lang)) {
return import(/* webpackChunkName: "lang-[request]" */ `./lang/${lang}`).then(msg => {
i18n.setLocaleMessage(lang, msg.default)
loadedLanguages.push(lang)
return setI18nLanguage(lang)
})
}
return resolve(setI18nLanguage(lang))
}
return resolve(lang)
})
}
export function i18nRender (key) {
return i18n.t(`${key}`)
}
export default i18n
export default {
navBar: {
lang: 'Language'
},
layouts: {
usermenu: {
dialog: {
title: 'Log-out',
content: 'Do you want log-out?'
}
}
},
menu: {
home: 'Home',
dashboard: {
default: 'Dashboard',
analysis: 'Analysis',
workplace: 'Workplace'
},
form: {
default: 'Form',
basicform: 'Basic Form',
stepform: 'Step Form',
advancedform: 'Advanced Form'
},
nav1: 'Nav 1',
nav2: 'Nav 2',
nav3: 'Nav 3'
},
pages: {
form: {
basicform: {
headers: {
btn1: 'Button1'
},
content: 'Form pages are used to collect or verify information to users, and basic forms are common in scenarios where there are fewer data items.',
tabs: {
tab1: 'Tab1',
tab2: 'Tab2',
tab3: 'Tab3'
}
}
}
},
'navBar.lang': 'Language',
'app.setting.pagestyle': 'Page style setting',
'app.setting.pagestyle.light': 'Light style',
'app.setting.pagestyle.dark': 'Dark style',
'app.setting.pagestyle.realdark': 'RealDark style',
'app.setting.themecolor': 'Theme Color',
'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',
'app.setting.copy': 'Copy Setting',
'app.setting.loading': 'Loading theme',
'app.setting.copyinfo': 'copy success,please replace defaultSettings in src/models/setting.js',
'app.setting.production.hint': 'Setting panel shows in development environment only, please manually modify'
}
export default {
navBar: {
lang: '语言'
},
layouts: {
usermenu: {
dialog: {
title: '注销',
content: '要注销账户吗?'
}
}
},
menu: {
home: '首页',
dashboard: {
default: '仪表盘',
analysis: '分析页',
workplace: '工作台'
},
form: {
default: '表单页',
basicform: '基础表单',
stepform: '分步表单',
advancedform: '高级表单'
},
nav1: '导航1',
nav2: '导航2',
nav3: '导航3'
},
pages: {
dashboard: {
analysis: {
content: '分析页描述'
}
},
form: {
basicform: {
headers: {
btn1: '按钮1'
},
content: '表单页用于向用户收集或验证信息,基础表单常见于数据项较少的表单场景。',
tabs: {
tab1: '标签1',
tab2: '标签2',
tab3: '标签3'
}
}
}
}
}
import './core/polyfills' // with polyfills
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import i18n from './locales'
import './router/router-guards'
import './core/library'
import ProLayout, { PageHeaderWrapper, SettingDrawer } from '@ant-design-vue/pro-layout'
import initializer from './core/bootstrap'
import './global.less'
import themePluginConfig from '@config/themePluginConfig'
Vue.config.productionTip = false
Vue.use(ProLayout)
Vue.use(PageHeaderWrapper)
Vue.use(SettingDrawer)
window.umi_plugin_ant_themeVar = themePluginConfig.theme
window._vue = new Vue({
router,
store,
i18n,
beforeCreate: initializer,
render: h => h(App)
}).$mount('#app')
import Vue from 'vue'
import VueRouter from 'vue-router'
import { asyncRouterMap } from '@/config/router.config'
// hack router push/replace callback
['push', 'replace'].map(key => {
return {
k: key,
prop: VueRouter.prototype[key]
}
}).forEach(item => {
VueRouter.prototype[item.k] = function newCall (location, onResolve, onReject) {
if (onResolve || onReject) return item.prop.call(this, location, onResolve, onReject)
return item.prop.call(this, location).catch(err => err)
}
})
Vue.use(VueRouter)
const routes = asyncRouterMap
const router = new VueRouter({
mode: 'history',
routes
})
export default router
import router from './index'
router.beforeEach((to, from, next) => {
next()
})
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-restricted-globals */
/* eslint-disable no-underscore-dangle */
/* globals workbox */
workbox.core.setCacheNameDetails({
prefix: 'antd-pro',
suffix: 'v1'
})
// Control all opened tabs ASAP
workbox.clientsClaim()
/**
* Use precaching list generated by workbox in build process.
* https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching
*/
workbox.precaching.precacheAndRoute(self.__precacheManifest || [])
/**
* Register a navigation route.
* https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route
*/
workbox.routing.registerNavigationRoute('/index.html')
/**
* Use runtime cache:
* https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute
*
* Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc.
* https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies
*/
/**
* Handle API requests
*/
workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst())
/**
* Handle third party requests
*/
workbox.routing.registerRoute(
/^https:\/\/gw.alipayobjects.com\//,
workbox.strategies.networkFirst()
)
workbox.routing.registerRoute(
/^https:\/\/cdnjs.cloudflare.com\//,
workbox.strategies.networkFirst()
)
workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst())
/**
* Response to client after skipping waiting with MessageChannel
*/
addEventListener('message', event => {
const replyPort = event.ports[0]
const message = event.data
if (replyPort && message && message.type === 'skip-waiting') {
event.waitUntil(
self
.skipWaiting()
.then(
() => replyPort.postMessage({ error: null }),
error => replyPort.postMessage({ error })
)
)
}
})
const getters = {
device: state => state.app.device,
lang: state => state.app.lang
}
export default getters
import { mapState } from 'vuex'
const i18nMixin = {
computed: {
...mapState({
currentLang: state => state.app.lang
})
},
methods: {
setLang (lang) {
this.$store.dispatch('setLang', lang)
}
}
}
export default i18nMixin
import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'
import getters from './getters'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
app
},
state: {
},
mutations: {
},
actions: {
},
getters
})
import Vue from 'vue'
import {
SIDEBAR_TYPE,
TOGGLE_DEVICE,
TOGGLE_NAV_THEME,
TOGGLE_LAYOUT,
TOGGLE_FIXED_HEADER,
TOGGLE_FIXED_SIDEBAR,
TOGGLE_CONTENT_WIDTH,
TOGGLE_HIDE_HEADER,
TOGGLE_COLOR,
TOGGLE_WEAK,
// i18n
APP_LANGUAGE
} from '../mutation-types'
import { loadLanguageAsync } from '@/locales'
const App = {
state: {
sideCollapsed: true,
device: 'desktop',
theme: 'dark',
layout: '',
contentWidth: '',
fixedHeader: false,
fixedSidebar: false,
autoHideHeader: false,
color: '',
weak: false,
lang: 'en-US'
},
mutations: {
[SIDEBAR_TYPE]: (state, type) => {
state.sideCollapsed = type
Vue.ls.set(SIDEBAR_TYPE, type)
},
[TOGGLE_DEVICE]: (state, device) => {
state.device = device
},
[TOGGLE_NAV_THEME]: (state, theme) => {
state.theme = theme
Vue.ls.set(TOGGLE_NAV_THEME, theme)
},
[TOGGLE_LAYOUT]: (state, mode) => {
state.layout = mode
Vue.ls.set(TOGGLE_LAYOUT, mode)
},
[TOGGLE_FIXED_HEADER]: (state, mode) => {
state.fixedHeader = mode
Vue.ls.set(TOGGLE_FIXED_HEADER, mode)
},
[TOGGLE_FIXED_SIDEBAR]: (state, mode) => {
state.fixedSidebar = mode
Vue.ls.set(TOGGLE_FIXED_SIDEBAR, mode)
},
[TOGGLE_CONTENT_WIDTH]: (state, type) => {
state.contentWidth = type
Vue.ls.set(TOGGLE_CONTENT_WIDTH, type)
},
[TOGGLE_HIDE_HEADER]: (state, type) => {
state.autoHideHeader = type
Vue.ls.set(TOGGLE_HIDE_HEADER, type)
},
[TOGGLE_COLOR]: (state, color) => {
state.color = color
Vue.ls.set(TOGGLE_COLOR, color)
},
[TOGGLE_WEAK]: (state, mode) => {
state.weak = mode
Vue.ls.set(TOGGLE_WEAK, mode)
},
[APP_LANGUAGE]: (state, lang) => {
console.log('lang', lang)
state.lang = lang
Vue.ls.set(APP_LANGUAGE, lang)
}
},
actions: {
setLang ({ commit }, lang) {
return new Promise((resolve, reject) => {
commit(APP_LANGUAGE, lang)
loadLanguageAsync(lang).then(() => {
resolve()
}).catch((e) => {
reject(e)
})
})
}
}
}
export default App
export const ACCESS_TOKEN = 'Access-Token'
export const SIDEBAR_TYPE = 'sidebar_type'
export const TOGGLE_DEVICE = 'device'
export const TOGGLE_NAV_THEME = 'nav_theme'
export const TOGGLE_LAYOUT = 'layout'
export const TOGGLE_FIXED_HEADER = 'fixed_header'
export const TOGGLE_FIXED_SIDEBAR = 'fixed_sidebar'
export const TOGGLE_CONTENT_WIDTH = 'content_width'
export const TOGGLE_HIDE_HEADER = 'auto_hide_header'
export const TOGGLE_COLOR = 'color'
export const TOGGLE_WEAK = 'weak'
export const APP_LANGUAGE = 'app_language'
export const CONTENT_WIDTH_TYPE = {
Fluid: 'Fluid',
Fixed: 'Fixed'
}
<template>
<page-header-wrapper
:tab-list="tabList"
:tab-active-key="tabActiveKey"
:tab-change="(key) => {
this.tabActiveKey = key
console.log('PageHeader::tabChange', key)
}"
@back="() => {
console.log('PageHeader::@back')
}"
:back="() => {
// 自定义 back,不会覆盖 onBack 事件
console.log('PageHeader::.back')
}"
>
<template v-slot:content>
<span>{{ $t('pages.form.basicform.content') }}</span>
</template>
<template v-slot:extraContent>
<div><a-button>{{ $t('pages.form.basicform.headers.btn1') }}</a-button></div>
</template>
<div>
<strong>Block Page</strong>
</div>
</page-header-wrapper>
</template>
<script>
export default {
name: 'BlockPage',
data () {
return {
console: window.console,
tabList: [
{ tab: 'pages.form.basicform.tabs.tab1', key: 'tab1' },
{ tab: 'pages.form.basicform.tabs.tab2', key: 'tab2' },
{ tab: 'pages.form.basicform.tabs.tab3', key: 'tab3' }
],
tabActiveKey: 'tab1'
}
},
methods: {
handleTabChange (key) {
this.tabActiveKey = key
console.log('PageHeader::tabChange', key)
}
}
}
</script>
<style scoped>
</style>
<template>
<page-header-wrapper>
<div class="page-test-wrapper">
<h1>Test Page1 keepAlive: {{ $route.meta.keepAlive }}</h1>
<div class="box">
<div style="margin: 1em 0">
<a-input v-model="newVal" placeholder="..."/>
</div>
<a-button @click="handleClick">Click Me!</a-button>
<a-divider />
<p>{{ newVal }}</p>
</div>
</div>
</page-header-wrapper>
</template>
<script>
export default {
data () {
return {
newVal: ''
}
},
methods: {
handleClick (e) {
this.$router.push({ path: '/dashboard/analysis' })
}
}
}
</script>
<style lang="less" scoped>
.page-test-wrapper {
width: 100%;
max-width: 400px;
margin: 0 auto;
}
</style>
<template>
<page-header-wrapper>
<div class="page-test-wrapper">
<h1>Test Page2 keepAlive: {{ $route.meta.keepAlive }}</h1>
<div class="box">
<div style="margin: 1em 0">
<a-input v-model="newVal" placeholder="..."/>
</div>
<a-button @click="handleClick">Click Me!</a-button>
<a-divider />
<p>{{ newVal }}</p>
</div>
</div>
</page-header-wrapper>
</template>
<script>
export default {
data () {
return {
newVal: ''
}
},
methods: {
handleClick (e) {
this.$router.push({ path: '/dashboard/analysis' })
}
}
}
</script>
<style lang="less" scoped>
.page-test-wrapper {
width: 100%;
max-width: 400px;
margin: 0 auto;
}
</style>
<template>
<page-header-wrapper>
<div class="page-test-wrapper">
<h1>Test Page3 keepAlive: {{ $route.meta.keepAlive }}</h1>
<div class="box">
<div style="margin: 1em 0">
<a-input v-model="newVal" placeholder="..."/>
</div>
<a-button @click="handleClick">Click Me!</a-button>
<a-divider />
<p>{{ newVal }}</p>
</div>
</div>
</page-header-wrapper>
</template>
<script>
export default {
data () {
return {
newVal: ''
}
},
methods: {
handleClick (e) {
this.$router.push({ path: '/dashboard/analysis' })
}
}
}
</script>
<style lang="less" scoped>
.page-test-wrapper {
width: 100%;
max-width: 400px;
margin: 0 auto;
}
</style>
<template>
<page-header-wrapper
:tab-list="tabList"
:tab-active-key="tabActiveKey"
:tab-change="(key) => {
this.tabActiveKey = key
console.log('PageHeader::tabChange', key)
}"
@back="() => {
console.log('PageHeader::@back')
}"
:back="() => {
// 自定义 back,不会覆盖 onBack 事件
console.log('PageHeader::.back')
}"
>
<template v-slot:extra>
<a-button-group style="margin-right: 4px;">
<a-button>操作</a-button>
<a-button>操作</a-button>
<a-button><a-icon type="ellipsis"/></a-button>
</a-button-group>
<a-button type="primary" >主操作</a-button>
</template>
<template v-slot:extraContent>
<a-row class="status-list">
<a-col :xs="12" :sm="12">
<div class="text">状态</div>
<div class="heading">待审批</div>
</a-col>
<a-col :xs="12" :sm="12">
<div class="text">订单金额</div>
<div class="heading">¥ 568.08</div>
</a-col>
</a-row>
</template>
<template v-slot:content>
<span>{{ $t('pages.dashboard.analysis.content') }}</span>
</template>
<div>
<strong>Analysis Page</strong>
<p>...</p>
<p>...</p>
<p>...</p>
<p>...</p>
<p>...</p>
<p>很长的内容...</p>
<p>...</p>
<p>...</p>
<p>...</p>
<p>...</p>
<p>...</p>
<p>很长的内容...</p>
<p>...</p>
<p>...</p>
<p>...</p>
<p>...</p>
<p>...</p>
<p>很长的内容...</p>
<p>...</p>
<p>...</p>
<p>...</p>
<p>...</p>
<p>...</p>
</div>
</page-header-wrapper>
</template>
<script>
export default {
name: 'Analysis',
data () {
return {
console: window.console,
tabList: [
{ tab: 'pages.form.basicform.tabs.tab1', key: 'tab1' },
{ tab: 'pages.form.basicform.tabs.tab2', key: 'tab2' },
{ tab: 'pages.form.basicform.tabs.tab3', key: 'tab3' }
],
tabActiveKey: 'tab1'
}
},
methods: {
handleTabChange (key) {
this.tabActiveKey = key
console.log('PageHeader::tabChange', key)
}
}
}
</script>
<style scoped>
</style>
<template>
<div>
Workplace Page
</div>
</template>
<script>
export default {
name: 'Analysis',
data () {
return {
console: window.console,
tabList: [
{ tab: 'pages.form.basicform.tabs.tab1', key: 'tab1' },
{ tab: 'pages.form.basicform.tabs.tab2', key: 'tab2' },
{ tab: 'pages.form.basicform.tabs.tab3', key: 'tab3' }
],
tabActiveKey: 'tab1'
}
},
methods: {
handleTabChange (key) {
this.tabActiveKey = key
console.log('PageHeader::tabChange', key)
}
}
}
</script>
<style scoped>
</style>
<template>
<page-header-wrapper
:tab-list="tabList"
:tab-active-key="tabActiveKey"
:title="false"
:tab-change="(key) => {
this.tabActiveKey = key
console.log('PageHeader::tabChange', key)
}"
@back="() => {
console.log('PageHeader::@back')
}"
:back="() => {
// 自定义 back,不会覆盖 onBack 事件
console.log('PageHeader::.back')
}"
>
<template v-slot:content>
<span>{{ $t('pages.form.basicform.content') }}</span>
</template>
<template v-slot:extraContent>
<div><a-button>{{ $t('pages.form.basicform.headers.btn1') }}</a-button></div>
</template>
<div>
<strong>Advanced Form</strong>
</div>
</page-header-wrapper>
</template>
<script>
export default {
name: 'AdvancedForm',
data () {
return {
console: window.console,
tabList: [
{ tab: 'pages.form.basicform.tabs.tab1', key: 'tab1' },
{ tab: 'pages.form.basicform.tabs.tab2', key: 'tab2' },
{ tab: 'pages.form.basicform.tabs.tab3', key: 'tab3' }
],
tabActiveKey: 'tab1'
}
},
methods: {
handleTabChange (key) {
this.tabActiveKey = key
console.log('PageHeader::tabChange', key)
}
}
}
</script>
<style scoped>
</style>
<template>
<page-header-wrapper
:tab-list="tabList"
:tab-active-key="tabActiveKey"
:tab-change="(key) => {
this.tabActiveKey = key
console.log('PageHeader::tabChange', key)
}"
@back="() => {
console.log('PageHeader::@back')
}"
:back="() => {
// 自定义 back,不会覆盖 onBack 事件
console.log('PageHeader::.back')
}"
>
<template v-slot:title>
自定义标题
</template>
<template v-slot:tags>
<a-tag color="pink">tag1</a-tag>
<a-tag>tag2</a-tag>
</template>
<template v-slot:content>
<span>{{ $t('pages.form.basicform.content') }}</span>
</template>
<template v-slot:extraContent>
<div><a-button>{{ $t('pages.form.basicform.headers.btn1') }}</a-button></div>
</template>
<div>
<strong>Basic Form</strong>
</div>
</page-header-wrapper>
</template>
<script>
export default {
name: 'BasicForm',
data () {
return {
console: window.console,
tabList: [
{ tab: 'pages.form.basicform.tabs.tab1', key: 'tab1' },
{ tab: 'pages.form.basicform.tabs.tab2', key: 'tab2' },
{ tab: 'pages.form.basicform.tabs.tab3', key: 'tab3' }
],
tabActiveKey: 'tab1'
}
},
methods: {
handleTabChange (key) {
this.tabActiveKey = key
console.log('PageHeader::tabChange', key)
}
}
}
</script>
<style scoped>
</style>
<template>
<page-header-wrapper
:tab-list="tabList"
:tab-active-key="tabActiveKey"
:tab-change="(key) => {
this.tabActiveKey = key
console.log('PageHeader::tabChange', key)
}"
:breadcrumb="customBreadcrumb"
>
<template v-slot:content>
<span>{{ $t('pages.form.basicform.content') }}</span>
</template>
<template v-slot:extraContent>
<div><a-button>{{ $t('pages.form.basicform.headers.btn1') }}</a-button></div>
</template>
<div>
<strong>Step Form</strong>
</div>
</page-header-wrapper>
</template>
<script>
export default {
name: 'StepForm',
data () {
return {
console: window.console,
tabList: [
{ tab: 'pages.form.basicform.tabs.tab1', key: 'tab1' },
{ tab: 'pages.form.basicform.tabs.tab2', key: 'tab2' },
{ tab: 'pages.form.basicform.tabs.tab3', key: 'tab3' }
],
tabActiveKey: 'tab1'
}
},
computed: {
customBreadcrumb () {
return {
props: {
routes: this.$route.matched.concat().map(route => {
return {
path: route.path,
breadcrumbName: this.$t(route.meta.title)
}
}),
itemRender: ({ route, params, routes, paths, h }) => {
return routes.indexOf(route) === routes.length - 1 && (
<span>{route.breadcrumbName}</span>
) || (
<router-link to={{ path: route.path || '/' }}>{route.breadcrumbName}</router-link>
)
}
}
}
}
},
methods: {
handleTabChange (key) {
this.tabActiveKey = key
console.log('PageHeader::tabChange', key)
}
}
}
</script>
<style scoped>
</style>
import { createApp, reactive, watchEffect } from 'vue';
import { Card, Space, Button, Tag } from 'ant-design-vue';
import 'ant-design-vue/dist/antd.less';
const DemoComponent = {
setup() {
const state = reactive({
name: 'value',
});
return () => (
<div class="components">
<h2># Template</h2>
<Card style={{ marginBottom: '24px', background: 'rgb(244,244,244)' }}>
<Space size="middle">
<Button
type="primary"
onClick={() => {
state.name = new Date().getTime().toString()
}}
>
Change Value
</Button>
</Space>
<div style={{ margin: '12px 0' }}>
<p>state.name: { JSON.stringify(state.name) }</p>
</div>
</Card>
<div class="demo" style="background: rgb(244,244,244);">
<div class="container" style="width: 256px;">
demo in here.<br />
<Tag color="pink">{state.name}</Tag>
</div>
</div>
</div>
);
},
};
const app = createApp(DemoComponent);
app.mount('#__vue-content>div');
const path = require('path')
const { IgnorePlugin } = require('webpack')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const dynamicThemePlugin = require('./config/dynamicTheme.js')
const isProd = process.env.NODE_ENV === 'production'
const isAnalyz = process.env.IS_ANALYZ === 'true'
function resolve (dir) {
return path.join(__dirname, dir)
}
const assetsCDN = {
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'axios': 'axios'
},
assets: {
css: [],
// https://unpkg.com/browse/vue@2.6.10/
js: [
'//cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js',
'//cdn.jsdelivr.net/npm/vue-router@3.1.3/dist/vue-router.min.js',
'//cdn.jsdelivr.net/npm/vuex@3.1.1/dist/vuex.min.js',
'//cdn.jsdelivr.net/npm/axios@0.19.0/dist/axios.min.js'
]
}
}
// vue.config
const defaultConfig = {
configureWebpack: {
plugins: [
// Ignore all locale files of moment.js
new IgnorePlugin(/^\.\/locale$/, /moment$/)
],
resolve: {
alias: {
'@ant-design/icons/lib/dist$': resolve('./src/core/antd/icons.js')
}
},
externals: isProd ? assetsCDN.externals : {}
},
chainWebpack: (config) => {
config.resolve.alias
.set('@config', resolve('./config'))
.set('@ant-design-vue/pro-layout', resolve('../src'))
// if `production` env require on cdn assets
isProd && config.plugin('html').tap(args => {
args[0].cdn = assetsCDN.assets
return args
})
// if `IS_ANALYZ` env is TRUE on report bundle info
isAnalyz && config.plugin('webpack-report').use(BundleAnalyzerPlugin, [
{
analyzerMode: 'static'
}
])
const svgRule = config.module.rule('svg')
svgRule.uses.clear()
svgRule.oneOf('inline')
.resourceQuery(/inline/)
.use('vue-svg-icon-loader')
.loader('vue-svg-icon-loader')
.end()
.end()
.oneOf('external')
.use('file-loader')
.loader('file-loader')
.options({
name: 'assets/[name].[hash:8].[ext]'
})
},
css: {
loaderOptions: {
less: {
modifyVars: {
// less vars,customize ant design theme
// 'primary-color': '#F5222D',
// 'link-color': '#F5222D',
// 'border-radius-base': '4px'
},
// DO NOT REMOVE THIS LINE
javascriptEnabled: true
}
}
},
devServer: {
// default development server port 8000
port: 9001
// If you want to turn on the proxy, please remove the mockjs /src/main.jsL11
// proxy: {
// '/api': {
// target: 'https://mock.ihx.me/mock/5baf3052f7da7e07e04a5116/antd-pro',
// ws: false,
// changeOrigin: true
// }
// }
},
// disable source map in production
productionSourceMap: false,
lintOnSave: undefined,
// babel-loader no-ignore node_modules/*
transpileDependencies: []
}
if (!isProd) {
defaultConfig.configureWebpack.plugins.push(dynamicThemePlugin())
}
module.exports = defaultConfig
{
"name": "@ant-design-vue/pro-layout",
"version": "1.0.1",
"main": "./lib/index.js",
"module": "./es/index.js",
"repository": {
"type": "git",
"url": "https://github.com/vueComponent/pro-layout"
"name": "@ant-design-vue/pro-layout",
"version": "3.0.0-alpha.1",
"main": "./lib/index.js",
"module": "./es/index.js",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/ant-design/pro-layout"
},
"contributors": [
"tangjinzhou <415800467@qq.com>",
"Sendya <yladmxa@gmail.com>"
],
"license": "MIT",
"scripts": {
"clean": "./scripts/cleanup.sh",
"start": "vc-tools run server",
"lint": "eslint src/ --ext .tsx,.ts",
"compile": "vc-tools run compile",
"test": "cross-env NODE_ENV=test jest --config .jest.js",
"prepublishOnly": "npm run lint && npm run generate && npm run compile && npm run test",
"generate": "rimraf src/icons && TS_NODE_PROJECT=scripts/tsconfig.json node -r ts-node/register scripts/generate.ts --target=icon",
"postcompile": "npm run clean && TS_NODE_PROJECT=scripts/tsconfig.json node -r ts-node/register scripts/generate.ts --target=entry"
},
"peerDependencies": {
"vue": ">=3.0.0",
"ant-design-vue": ">=2.0.0"
},
"devDependencies": {
"@vue/babel-plugin-jsx": "^1.0.0-rc.3",
"@babel/runtime": "^7.11.2",
"@types/jest": "^24.0.17",
"@types/node": "^13.13.15",
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0-0",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^5.0.2",
"@vue/test-utils": "^2.0.0-beta.2",
"cross-env": "^5.2.0",
"eslint": "^6.7.2",
"eslint-config-prettier": "^6.4.0",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-vue": "^7.0.0-0",
"jest": "^25.4.0",
"jest-serializer-vue": "^2.0.2",
"prettier": "^1.19.1",
"ts-node": "^8.10.2",
"typescript": "~3.9.3",
"vc-tools": "^3.0.0",
"vue": "^3.0.0-0",
"vue-jest": "^5.0.0-alpha.3"
},
"dependencies": {
"@babel/runtime": "^7.10.4",
"ant-design-vue": "^2.0.0-beta.10",
"lodash": "^4.17.15"
},
"config": {
"port": 9528,
"entry": {
"@ant-design-vue/pro-layout": [
"./src/index.jsx"
]
},
"contributors": [
"tangjinzhou <415800467@qq.com>",
"Sendya <yladmxa@gmail.com>"
],
"license": "MIT",
"scripts": {
"start": "vc-tools run server",
"lint": "eslint -c ./.eslintrc --fix --ext .jsx,.js,.vue ./src",
"compile": "vc-tools run compile --babel-runtime",
"test": "cross-env NODE_ENV=test jest --config .jest.js",
"prepublishOnly": "npm run lint && npm run test && npm run compile && np --no-cleanup --yolo --no-publish"
},
"files": [
"es",
"lib"
],
"peerDependencies": {
"ant-design-vue": "^1.6.2",
"umi-request": "^1.2.11",
"vue": ">=2.6.0",
"vue-container-query": "^0.1.0",
"vue-template-compiler": ">=2.6.0"
},
"devDependencies": {
"@ant-design-vue/tools": "^1.0.2-beta.2",
"@babel/core": "^7.11.1",
"@babel/plugin-transform-runtime": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@types/jest": "^24.0.17",
"@types/node": "^10.5.5",
"@types/vue": "^2.0.0",
"@vue/server-test-utils": "1.0.0-beta.16",
"@vue/test-utils": "1.0.0-beta.16",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.1.0",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-object-assign": "^6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-plugin-transform-vue-jsx": "^4.0.1",
"babel-runtime": "^6.26.0",
"cross-env": "^5.2.0",
"eslint": "^6.5.1",
"eslint-config-prettier": "^6.4.0",
"eslint-plugin-vue": "^5.2.3",
"jest": "^26.3.0",
"jest-serializer-vue": "^2.0.2",
"np": "^5.1.1",
"prettier": "^1.18.2",
"regenerator-runtime": "^0.13.7",
"vue": ">=2.5.0",
"vue-jest": "^2.5.0",
"vue-template-compiler": ">=2.5.0"
},
"dependencies": {
"ant-design-vue": "^1.6.2",
"classnames": "^2.2.6",
"insert-css": "^2.0.0",
"lodash": "^4.17.15",
"omit.js": "^1.0.2",
"umi-request": "^1.2.11",
"vue-container-query": "^0.1.0",
"vue-copy-to-clipboard": "^1.0.3"
},
"config": {
"port": 9528,
"entry": {
"@ant-design-vue/pro-layout": [
"./src/index.js"
]
"loaders": [
{
"loader": "less-loader",
"options": {
"javascriptEnabled": true
}
},
"description": "ant-design-vue-pro layout, easy to use pro scaffolding."
}
]
},
"description": "Ant Design Pro Layout of Vue, easy to use pro scaffolding."
}
module.exports = {
plugins: {
autoprefixer: {}
}
}
{
"extends": [
"config:base"
]
}
#!/bin/sh
ls | grep -v .jest.js | grep -v .eslintrc.js | grep -e '.d.ts' -e '.js$' | xargs rm
# keep one d.ts to make vscode auto import working with corrcet path
rm ./es/icons/*.d.ts || echo
import * as allIconDefs from '@ant-design/icons-svg';
import { IconDefinition } from '@ant-design/icons-svg/es/types';
import * as path from 'path';
import * as fs from 'fs';
import { promisify } from 'util';
// eslint-disable-next-line import/no-extraneous-dependencies
import { template } from 'lodash';
const writeFile = promisify(fs.writeFile);
interface IconDefinitionWithIdentifier extends IconDefinition {
svgIdentifier: string;
}
function walk<T>(fn: (iconDef: IconDefinitionWithIdentifier) => Promise<T>) {
return Promise.all(
Object.keys(allIconDefs).map(svgIdentifier => {
const iconDef = (allIconDefs as { [id: string]: IconDefinition })[svgIdentifier];
return fn({ svgIdentifier, ...iconDef });
}),
);
}
async function generateIcons() {
const iconsDir = path.join(__dirname, '../src/icons');
try {
await promisify(fs.access)(iconsDir);
} catch (err) {
await promisify(fs.mkdir)(iconsDir);
}
const render = template(
`
// GENERATE BY ./scripts/generate.ts
// DON NOT EDIT IT MANUALLY
import { SetupContext } from 'vue';
import <%= svgIdentifier %>Svg from '@ant-design/icons-svg/lib/asn/<%= svgIdentifier %>';
import AntdIcon, { AntdIconProps } from '../components/AntdIcon';
const <%= svgIdentifier %> = (props: AntdIconProps, context: SetupContext) => {
const p = { ...props, ...context.attrs };
return <AntdIcon {...p} icon={<%= svgIdentifier %>Svg}></AntdIcon>;
};
<%= svgIdentifier %>.displayName = '<%= svgIdentifier %>';
<%= svgIdentifier %>.inheritAttrs = false;
export default <%= svgIdentifier %>;
`.trim(),
);
await walk(async ({ svgIdentifier }) => {
// generate icon file
await writeFile(
path.resolve(__dirname, `../src/icons/${svgIdentifier}.tsx`),
render({ svgIdentifier }),
);
});
// generate icon index
const entryText = Object.keys(allIconDefs)
.sort()
.map(svgIdentifier => {
return `export { default as ${svgIdentifier} } from './${svgIdentifier}';`;
})
.join('\n');
await promisify(fs.appendFile)(
path.resolve(__dirname, '../src/icons/index.tsx'),
`
// GENERATE BY ./scripts/generate.ts
// DON NOT EDIT IT MANUALLY
${entryText}
`.trim(),
);
}
async function generateEntries() {
const render = template(
`
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _<%= svgIdentifier %> = _interopRequireDefault(require('./lib/icons/<%= svgIdentifier %>'));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _default = _<%= svgIdentifier %>.default || _<%= svgIdentifier %>;
exports.default = _default;
module.exports = _default;
`.trim(),
);
await walk(async ({ svgIdentifier }) => {
// generate `Icon.js` in root folder
await writeFile(
path.resolve(__dirname, `../${svgIdentifier}.js`),
render({
svgIdentifier,
}),
);
// generate `Icon.d.ts` in root folder
await writeFile(
path.resolve(__dirname, `../${svgIdentifier}.d.ts`),
`export { default } from './lib/icons/${svgIdentifier}';`,
);
});
}
if (process.argv[2] === '--target=icon') {
generateIcons();
}
if (process.argv[2] === '--target=entry') {
generateEntries();
}
{
"compilerOptions": {
"module": "commonJS",
}
}
\ No newline at end of file
const { camelCase, upperFirst } = require('lodash');
const manifest = require('@ant-design/icons-svg/lib/manifest').default;
const themeMap = {
fill: 'filled',
outline: 'outlined', // default theme
twotone: 'twoTone',
};
exports.getComponentNameList = () => {
const icons = [];
Object.keys(manifest).forEach(theme => {
manifest[theme].forEach(name => {
const baseName = upperFirst(camelCase(name));
icons.push({
theme,
componentName: baseName + upperFirst(themeMap[theme]),
svgName: baseName + upperFirst(theme === 'twotone' ? 'twoTone' : theme),
});
});
});
return icons;
};
import './BasicLayout.less'
import PropTypes from 'ant-design-vue/es/_util/vue-types'
import 'ant-design-vue/es/layout/style'
import Layout from 'ant-design-vue/es/layout'
import { ContainerQuery } from 'vue-container-query'
import { SiderMenuWrapper, GlobalFooter } from './components'
import { contentWidthCheck, getComponentFromProp, isFun } from './utils/util'
import { SiderMenuProps } from './components/SiderMenu'
import HeaderView, { HeaderViewProps } from './Header'
import WrapContent from './WrapContent'
import ConfigProvider from './components/ConfigProvider'
import PageHeaderWrapper from './components/PageHeaderWrapper'
export const BasicLayoutProps = {
...SiderMenuProps,
...HeaderViewProps,
contentWidth: PropTypes.oneOf(['Fluid', 'Fixed']).def('Fluid'),
// contentWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).def('Fluid'),
locale: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).def('en-US'),
breadcrumbRender: PropTypes.func,
disableMobile: PropTypes.bool.def(false),
mediaQuery: PropTypes.object.def({}),
handleMediaQuery: PropTypes.func,
footerRender: PropTypes.func,
}
const MediaQueryEnum = {
'screen-xs': {
maxWidth: 575
},
'screen-sm': {
minWidth: 576,
maxWidth: 767
},
'screen-md': {
minWidth: 768,
maxWidth: 991
},
'screen-lg': {
minWidth: 992,
maxWidth: 1199
},
'screen-xl': {
minWidth: 1200,
maxWidth: 1599
},
'screen-xxl': {
minWidth: 1600
}
}
const getPaddingLeft = (
hasLeftPadding,
collapsed = undefined,
siderWidth
) => {
if (hasLeftPadding) {
return collapsed ? 80 : siderWidth
}
return 0
}
const headerRender = (h, props) => {
if (props.headerRender === false) {
return null
}
return <HeaderView { ...{ props } } />
}
const defaultI18nRender = (key) => key
const BasicLayout = {
name: 'BasicLayout',
functional: true,
props: BasicLayoutProps,
render (h, content) {
const { props, children } = content
const {
layout,
// theme,
isMobile,
collapsed,
mediaQuery,
handleMediaQuery,
handleCollapse,
siderWidth,
fixSiderbar,
i18nRender = defaultI18nRender
} = props
const footerRender = getComponentFromProp(content, 'footerRender')
const rightContentRender = getComponentFromProp(content, 'rightContentRender')
const collapsedButtonRender = getComponentFromProp(content, 'collapsedButtonRender')
const menuHeaderRender = getComponentFromProp(content, 'menuHeaderRender')
const breadcrumbRender = getComponentFromProp(content, 'breadcrumbRender')
const headerContentRender = getComponentFromProp(content, 'headerContentRender')
const menuRender = getComponentFromProp(content, 'menuRender')
const isTopMenu = layout === 'topmenu'
const hasSiderMenu = !isTopMenu
// If it is a fix menu, calculate padding
// don't need padding in phone mode
const hasLeftPadding = fixSiderbar && !isTopMenu && !isMobile
const cdProps = {
...props,
hasSiderMenu,
footerRender,
menuHeaderRender,
rightContentRender,
collapsedButtonRender,
breadcrumbRender,
headerContentRender,
menuRender
}
return (
<ConfigProvider i18nRender={i18nRender} contentWidth={props.contentWidth} breadcrumbRender={breadcrumbRender}>
<ContainerQuery query={MediaQueryEnum} onChange={handleMediaQuery}>
<Layout class={{
'ant-pro-basicLayout': true,
'ant-pro-topmenu': isTopMenu,
...mediaQuery
}}>
<SiderMenuWrapper
{ ...{ props: cdProps } }
collapsed={collapsed}
onCollapse={handleCollapse}
/>
<Layout class={[layout]} style={{
paddingLeft: hasSiderMenu
? `${getPaddingLeft(!!hasLeftPadding, collapsed, siderWidth)}px`
: undefined,
minHeight: '100vh'
}}>
{headerRender(h, {
...cdProps,
mode: 'horizontal',
})}
<WrapContent class="ant-pro-basicLayout-content" contentWidth={props.contentWidth}>
{children}
</WrapContent>
<Layout.Footer>
{ footerRender && (
isFun(footerRender) && footerRender(h) || footerRender
) || (
<GlobalFooter>
<template slot="links">
<a href="https://www.github.com/vueComponent/" target="_self">Github</a>
<a href="https://www.github.com/sendya/" target="_self">@Sendya</a>
</template>
<template slot="copyright">
<a href="https://github.com/vueComponent">vueComponent</a>
</template>
</GlobalFooter>
)}
</Layout.Footer>
</Layout>
</Layout>
</ContainerQuery>
</ConfigProvider>
)
}
}
BasicLayout.install = function (Vue) {
Vue.component(PageHeaderWrapper.name, PageHeaderWrapper)
Vue.component('page-container', PageHeaderWrapper)
Vue.component('pro-layout', BasicLayout)
}
export default BasicLayout
@import "~ant-design-vue/es/style/themes/default";
@import '~ant-design-vue/es/style/themes/default.less';
@basicLayout-prefix-cls: ~'@{ant-prefix}-pro-basicLayout';
@sider-menu-prefix-cls: ~'@{ant-prefix}-pro-sider-menu';
@nav-header-height: @layout-header-height;
@pro-layout-header-height: 48px;
.@{basicLayout-prefix-cls} {
&:not('.ant-pro-basicLayout-mobile') {
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.06);
border-radius: 3px;
-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.08);
}
/* 滚动条滑块 */
::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.12);
border-radius: 3px;
-webkit-box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.2);
}
}
// BFC
display: flex;
flex-direction: column;
width: 100%;
min-height: 100%;
.ant-layout-header {
&:not(.ant-pro-top-menu) {
background: @component-background;
}
&.ant-pro-fixed-header {
.@{ant-prefix}-layout-header {
&.@{ant-prefix}-pro-fixed-header {
position: fixed;
top: 0;
}
......@@ -44,58 +20,45 @@
&-content {
position: relative;
margin: 24px;
transition: all 0.2s;
.@{ant-prefix}-pro-page-header-wrap {
.@{ant-prefix}-pro-page-container {
margin: -24px -24px 0;
}
&-disable-margin {
margin: 0;
> div > .@{ant-prefix}-pro-page-header-wrap {
.@{ant-prefix}-pro-page-container {
margin: 0;
}
}
> .ant-layout {
> .@{ant-prefix}-layout {
max-height: 100%;
}
}
// append hook styles
.ant-layout-sider-children {
height: 100%;
}
.trigger {
font-size: 18px;
line-height: 64px;
padding: 0 24px;
cursor: pointer;
transition: color 0.3s;
&:hover {
color: #1890ff;
}
// children should support fixed
.@{basicLayout-prefix-cls}-is-children.@{basicLayout-prefix-cls}-fix-siderbar {
height: 100vh;
overflow: hidden;
transform: rotate(0);
}
&-content {
position: relative;
margin: 24px;
transition: all 0.2s;
.@{ant-prefix}-pro-page-header-wrap {
margin: -24px -24px 0;
.@{basicLayout-prefix-cls}-has-header {
// tech-page-container
.tech-page-container {
height: calc(100vh - @pro-layout-header-height);
}
&-disable-margin {
margin: 0;
> div > .@{ant-prefix}-pro-page-header-wrap {
margin: 0;
.@{basicLayout-prefix-cls}-is-children.@{basicLayout-prefix-cls}-has-header {
.tech-page-container {
height: calc(100vh - @pro-layout-header-height - @pro-layout-header-height);
}
.@{basicLayout-prefix-cls}-is-children {
min-height: calc(100vh - @pro-layout-header-height);
&.@{basicLayout-prefix-cls}-fix-siderbar {
height: calc(100vh - @pro-layout-header-height);
}
}
}
> .ant-layout {
max-height: 100%;
}
}
.color-picker {
margin: 10px 0;
}
}
import './BasicLayoutTest.less';
import { h, App } from 'vue';
import { Layout, Menu } from 'ant-design-vue';
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue';
import ProProvider from './ProProvider';
const defaultI18nRender = (key: string) => key
const BasicLayout = (props, { emit, slots }) => {
const handleClick = () => {
emit('collapsed', !props.collapsed)
}
return (
<ProProvider {...props} i18n={defaultI18nRender}>
<Layout class="ant-pro-basicLayout">
<Layout.Sider collapsed={props.collapsed} trigger={null} collapsible>
<div class="logo" />
<Menu theme="dark" mode="inline" selectedKeys={props.selectedKeys}>
<Menu.Item key="1">
<span>nav 1</span>
</Menu.Item>
<Menu.Item key="2">
<span>nav 2</span>
</Menu.Item>
<Menu.Item key="3">
<span>nav 3</span>
</Menu.Item>
</Menu>
</Layout.Sider>
<Layout>
<Layout.Header style="background: #fff; padding: 0">
{
props.collapsed
? <MenuUnfoldOutlined class="trigger" onClick={handleClick} />
: <MenuFoldOutlined class="trigger" onClick={handleClick} />
}
</Layout.Header>
<Layout.Content style={{ margin: '24px 16px', padding: '24px', background: '#fff', minHeight: '280px' }}>
{slots.default && slots.default()}
</Layout.Content>
</Layout>
</Layout>
</ProProvider>
)
};
BasicLayout.install = function (app: App) {
app.component('pro-layout', BasicLayout)
};
export default BasicLayout
.ant-pro-basicLayout {
.logo {
height: 32px;
background: rgba(255, 255, 255, 0.2);
margin: 16px;
}
.trigger {
font-size: 18px;
line-height: 64px;
padding: 0 24px;
cursor: pointer;
transition: color 0.3s;
&:hover {
color: #1890ff;
}
}
}
const BlockLayout = {
name: 'BlockLayout',
functional: true,
render (createElement, content) {
return content.children
}
}
export default BlockLayout
@import '~ant-design-vue/es/style/themes/default.less';
@pro-footer-bar-prefix-cls: ~'@{ant-prefix}-pro-footer-bar';
.@{pro-footer-bar-prefix-cls} {
position: fixed;
right: 0;
bottom: 0;
z-index: 99;
display: flex;
width: 100%;
align-items: center;
padding: 0 24px;
line-height: 48px;
background: @component-background;
border-top: 1px solid @border-color-split;
box-shadow: @box-shadow-base;
&-left {
flex: 1;
}
&-right {
> * {
margin-right: 8px;
&:last-child {
margin: 0;
}
}
}
}
\ No newline at end of file
import { VNodeChild } from 'vue';
import './index.less';
export interface FooterToolbarProps {
extra?: VNodeChild;
renderContent?: (
props: FooterToolbarProps & { leftWidth?: string },
dom: JSX.Element,
) => VNodeChild | JSX.Element;
prefixCls?: string;
}
const FooterToolbar = (props, context) => {
const baseClassName = `${props.prefixCls}-footer-bar`;
}
@import "~ant-design-vue/es/style/themes/default";
@import '~ant-design-vue/es/style/themes/default.less';
@global-footer-prefix-cls: ~'@{ant-prefix}-pro-global-footer';
@pro-global-footer-prefix-cls: ~'@{ant-prefix}-pro-global-footer';
.@{global-footer-prefix-cls} {
.@{pro-global-footer-prefix-cls} {
margin: 48px 0 24px 0;
padding: 0 16px;
text-align: center;
......@@ -28,4 +28,4 @@
color: @text-color-secondary;
font-size: @font-size-base;
}
}
}
\ No newline at end of file
import './index.less';
import { WithFalse } from '../typings';
import { defineComponent, PropType, SetupContext, VNodeChild } from 'vue';
export type Links = WithFalse<
{
key?: string;
title: VNodeChild | JSX.Element;
href: string;
blankTarget?: boolean;
}[]
>
export interface GlobalFooterProps {
links?: Links;
copyright?: VNodeChild | JSX.Element;
prefixCls?: string;
}
export default defineComponent({
name: 'GlobalFooter',
props: {
links: [Array, Boolean] as PropType<Links>,
copyright: [Object, Function] as PropType<VNodeChild | JSX.Element>,
prefixCls: {
type: String,
default: 'ant-pro'
}
},
setup (props: GlobalFooterProps, { slots }: SetupContext) {
if (
(props.links == null || props.links === false || (Array.isArray(props.links) && props.links.length === 0)) &&
(props.copyright == null || props.copyright === false)
) {
return null;
}
const baseClassName = `${props.prefixCls}-global-footer`;
const copyright = props.copyright || (slots.copyright && slots.copyright());
return () => (
<footer class={baseClassName}>
{props.links && (
<div class={`${baseClassName}-links`}>
{props.links.map(link => (
<a
key={link.key}
title={link.key}
target={link.blankTarget ? '_blank' : '_self'}
href={link.href}
>
{link.title}
</a>
))}
</div>
)}
{props.copyright && <div class={`${baseClassName}-copyright`}>{copyright}</div>}
</footer>
);
}
});
@import '~ant-design-vue/es/style/themes/default.less';
@pro-layout-grid-content-prefix-cls: ~'@{ant-prefix}-pro-grid-content';
.@{pro-layout-grid-content-prefix-cls} {
width: 100%;
&.wide {
max-width: 1200px;
margin: 0 auto;
}
}
\ No newline at end of file
import './GridContent.less';
import { SetupContext, CSSProperties } from 'vue'
import { PureSettings } from '../defaultSettings';
interface GridContentProps {
contentWidth?: PureSettings['contentWidth'];
prefixCls?: string;
style?: CSSProperties;
}
const GridContent = ({ prefixCls = 'ant-pro', contentWidth }: GridContentProps, { slots }: SetupContext) => {
return (
<div
class={{
[`${prefixCls}-grid-content`]: true,
wide: contentWidth === 'Fixed',
}}
>
<div class={`${prefixCls}-grid-content-children`}>{slots.default && slots.default()}</div>
</div>
);
};
export default GridContent;
import './Header.less'
import 'ant-design-vue/es/layout/style'
import Layout from 'ant-design-vue/es/layout'
import PropTypes from 'ant-design-vue/es/_util/vue-types'
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
export const HeaderViewProps = {
...GlobalHeaderProps,
...SiderMenuProps,
isMobile: PropTypes.bool.def(false),
collapsed: PropTypes.bool,
logo: PropTypes.any,
hasSiderMenu: PropTypes.bool,
autoHideHeader: PropTypes.bool,
menuRender: PropTypes.any,
headerRender: PropTypes.any,
rightContentRender: PropTypes.any,
visible: PropTypes.bool.def(true),
}
const renderContent = (h, props) => {
const isTop = props.layout === 'topmenu'
const maxWidth = 1200 - 280 - 120
const contentWidth = props.contentWidth === 'Fixed'
const baseCls = 'ant-pro-top-nav-header'
const { logo, title, theme, isMobile, headerRender, rightContentRender, menuHeaderRender } = props
const rightContentProps = { theme, isTop, isMobile }
let defaultDom = <GlobalHeader {...{ props: props }} />
if (isTop && !isMobile) {
defaultDom = (
<div class={[baseCls, theme]}>
<div class={[`${baseCls}-main`, contentWidth ? 'wide' : '']}>
{menuHeaderRender && (
<div class={`${baseCls}-left`}>
<div class={`${baseCls}-logo`} key="logo" id="logo">
{defaultRenderLogoAntTitle(h, { logo, title, menuHeaderRender })}
</div>
</div>
)}
<div class={`${baseCls}-menu`} style={{ maxWidth: `${maxWidth}px`, flex: 1 }}>
<BaseMenu {...{ props: props }} />
</div>
{isFun(rightContentRender) && rightContentRender(h, rightContentProps) || rightContentRender}
</div>
</div>
)
}
if (headerRender) {
return headerRender(h, props)
}
return defaultDom
}
const HeaderView = {
name: 'HeaderView',
props: HeaderViewProps,
render (h) {
const {
visible,
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 ? (
<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
)
}
}
export default HeaderView
@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;
width: 100%;
height: @layout-header-height;
box-shadow: @box-shadow-base;
transition: background 0.3s, width 0.2s;
.ant-menu-submenu.ant-menu-submenu-horizontal {
height: 100%;
line-height: @layout-header-height;
.ant-menu-submenu-title {
height: 100%;
}
}
&.light {
background-color: @component-background;
h1 {
color: #002140;
}
}
&-main {
display: flex;
height: @layout-header-height;
padding-left: 24px;
&.wide {
max-width: 1200px;
margin: auto;
padding-left: 0;
}
.left {
display: flex;
flex: 1;
}
.right {
width: 324px;
}
}
&-logo {
position: relative;
width: 165px;
height: @layout-header-height;
overflow: hidden;
line-height: @layout-header-height;
transition: all 0.3s;
img, svg {
display: inline-block;
height: 32px;
width: 32px;
vertical-align: middle;
}
h1 {
display: inline-block;
margin: 0 0 0 12px;
color: @btn-primary-color;
font-weight: 400;
font-size: 16px;
vertical-align: top;
}
}
&-menu {
.ant-menu.ant-menu-horizontal {
height: @layout-header-height;
line-height: @layout-header-height;
border: none;
}
}
}
.@{pro-layout-fixed-header-prefix-cls} {
z-index: 9;
width: 100%;
transition: width 0.2s;
}
.drop-down {
&.menu {
.anticon {
margin-right: 8px;
}
.ant-dropdown-menu-item {
min-width: 160px;
}
}
}
import { PageHeaderWrapper } from './components'
const PageView = {
name: 'PageView',
render () {
return (
<PageHeaderWrapper>
<router-view />
</PageHeaderWrapper>
)
}
}
export default PageView
import { defineComponent, reactive, toRefs, Ref, InjectionKey, provide, SetupContext, App, PropType } from 'vue';
import { ContentWidth } from '../typings';
export interface ProProviderData {
getPrefixCls?: (suffixCls: string, customizePrefixCls: string) => string;
i18n?: (t: string) => string;
contentWidth?: ContentWidth;
}
export const defaultProProviderProps: ProProviderData = {
getPrefixCls: (suffixCls: string, customizePrefixCls: string) => {
if (customizePrefixCls) return customizePrefixCls;
return `ant-pro-${suffixCls}`;
},
i18n: (t: string): string => t,
contentWidth: 'Fluid',
}
export const injectProConfigKey: InjectionKey<ProProviderData> = Symbol()
const ProProvider = defineComponent({
name: 'ProProvider',
props: {
prefixCls: {
type: String as PropType<string>,
default: 'ant-pro',
},
contentWidth: {
type: String as PropType<ContentWidth>,
default: 'Fluid',
},
i18n: {
type: Function as PropType<(t: string) => string>,
default: (t: string): string => t,
},
},
setup (props, { slots }: SetupContext) {
const { prefixCls, i18n, contentWidth } = toRefs(props)
const getPrefixCls = (suffixCls: string, customizePrefixCls: string): string => {
if (customizePrefixCls) return customizePrefixCls;
return suffixCls ? `${prefixCls.value}-${suffixCls}` : prefixCls.value;
}
const proConfigProvider = reactive({
i18n,
contentWidth,
getPrefixCls,
})
provide(injectProConfigKey, proConfigProvider)
return () => slots.default && slots.default()
},
})
ProProvider.install = function (app: App) {
app.component(ProProvider.name, ProProvider)
}
export default ProProvider
import './index.less';
import { defineComponent, h, resolveDynamicComponent, resolveComponent, VNode, ref, reactive, computed, Ref, watch, ComputedRef, VNodeChild, WatchStopHandle, PropType, isVNode, toRefs, defineAsyncComponent, Component } from 'vue';
// import * as Icon from '@ant-design/icons-vue';
import { createFromIconfontCN } from '@ant-design/icons-vue';
import 'ant-design-vue/es/menu/style'
import Menu from 'ant-design-vue/es/menu'
import defaultSettings, { PureSettings } from '../defaultSettings';
import { isImg, isUrl } from '../utils'
import { MenuMode, SelectInfo, OpenEventHandler } from './typings';
import { RouteProps, MenuTheme, WithFalse } from '../typings';
export { MenuMode, SelectInfo, OpenEventHandler }
export interface MenuState {
collapsed?: boolean | false;
selectedKeys?: string[];
openKeys?: string[];
}
interface MenuStated {
collapsed: boolean;
selectedKeys: string[];
openKeys: string[];
}
export interface MenuStateWatched {
state: MenuStated;
watchRef: WatchStopHandle;
}
export function useMenuState ({ collapsed = false, openKeys = [] as string[], selectedKeys = [] as string[], }: MenuState): MenuStateWatched {
const state = reactive<MenuStated>({
collapsed,
selectedKeys,
openKeys,
})
const cachedOpenKeys: Ref<string[]> = ref([] as string[])
const watchRef = watch(() => state.collapsed, (collapsed) => {
if (collapsed) {
cachedOpenKeys.value = state.openKeys.concat()
state.openKeys = []
} else {
state.openKeys = cachedOpenKeys.value.concat()
}
})
return {
state,
watchRef,
}
}
export function useRootSubmenuKeys (menus: RouteProps[]): ComputedRef<string[]> {
return computed(() => menus.map(it => it.path))
}
// ts typo
export interface BaseMenuProps extends Partial<PureSettings> {
prefixCls?: string;
collapsed?: boolean;
splitMenus?: boolean;
isMobile?: boolean;
menuData?: RouteProps[];
mode?: MenuMode;
onCollapse?: (collapsed: boolean) => void;
openKeys?: WithFalse<string[]> | undefined;
handleOpenChange?: (openKeys: string[]) => void;
theme?: MenuTheme | 'realDark';
i18n?: (t: string) => string | VNodeChild;
}
// vue props
export const VueBaseMenuProps = {
locale: Boolean,
menus: Array as PropType<RouteProps[]>,
// top-nav-header: horizontal
mode: {
type: String as PropType<MenuMode>,
default: 'inline',
},
theme: {
type: String as PropType<MenuTheme | 'realDark'>,
default: 'dark',
},
collapsed: {
type: Boolean as PropType<boolean>,
default: false,
},
openKeys: {
type: Array as PropType<string[]>,
required: true,
},
handleOpenChange: {
type: Function as PropType<(openKeys: WithFalse<string[]>) => void>,
default: () => undefined,
},
selectedKeys: {
type: Array as PropType<string[]>,
required: true,
},
}
const renderMenu = (item, i18nRender) => {
if (item && !item.hidden) {
const hasChild = item.children && !item.hideChildrenInMenu
return hasChild ? renderSubMenu(item, i18nRender) : renderMenuItem(item, i18nRender)
}
return null
}
const renderSubMenu = (item, i18nRender) => {
const renderMenuContent = (
<span>
<LazyIcon icon={item.meta.icon} />
<span>{renderTitle(item.meta.title, i18nRender)}</span>
</span>
)
return (
<Menu.SubMenu
key={item.path}
// @ts-ignore
title={renderMenuContent}
>
{!item.hideChildrenInMenu && item.children.map(cd => renderMenu(cd, i18nRender))}
</Menu.SubMenu>
)
}
const renderMenuItem = (item, i18nRender) => {
const meta = Object.assign({}, item.meta)
const target = meta.target || null
const CustomTag = target && 'a' || 'router-link'
const props = { to: { name: item.name }, href: item.path, target: target }
if (item.children && item.hideChildrenInMenu) {
// 把有子菜单的 并且 父菜单是要隐藏子菜单的
// 都给子菜单增加一个 hidden 属性
// 用来给刷新页面时, selectedKeys 做控制用
item.children.forEach(cd => {
cd.meta = Object.assign(cd.meta || {}, { hidden: true })
})
}
return (
<Menu.Item key={item.path}>
<CustomTag {...props}>
<LazyIcon icon={meta.icon} />
{renderTitle(meta.title, i18nRender)}
</CustomTag>
</Menu.Item>
)
}
let IconFont = createFromIconfontCN({
scriptUrl: defaultSettings.iconfontUrl,
});
// const LazyIcon = (props, _) => {
// const { icon } = toRefs(props)
// if (typeof icon.value === 'string' && icon.value !== '') {
// if (isUrl(icon.value) || isImg(icon.value)) {
// return <img src={icon.value} alt="icon" class="ant-pro-sider-menu-icon" />
// }
// if (icon.value.startsWith('icon-')) {
// return <IconFont type={icon.value} />
// }
// }
// if (isVNode(icon.value)) {
// return icon.value
// }
// const IconComponent = resolveComponent(`${icon.value}`)
// return h(IconComponent)
// }
const LazyIcon = defineComponent({
props: {
icon: {
type: [String, Function, Object] as PropType<string|Function|VNodeChild|JSX.Element>,
}
},
setup(props, _) {
const { icon } = toRefs(props)
if (typeof icon.value === 'string' && icon.value !== '') {
if (isUrl(icon.value) || isImg(icon.value)) {
return <img src={icon.value} alt="icon" class="ant-pro-sider-menu-icon" />
}
if (icon.value.startsWith('icon-')) {
return <IconFont type={icon.value} />
}
}
if (isVNode(icon.value)) {
return icon.value
}
const ALazyIcon = resolveComponent(`${icon.value}`)
// return ALazyIcon && ALazyIcon
return ALazyIcon && (() => (
// @ts-ignore
<ALazyIcon />
)) || null
}
})
const renderTitle = (title, i18nRender) => {
return <span>{ i18nRender && i18nRender(title) || title }</span>
}
export default defineComponent({
name: 'BaseMenu',
props: Object.assign({}, {
i18n: {
type: Function,
default: (t: string): string => t,
},
}, VueBaseMenuProps),
emits: ['update:openKeys', 'update:selectedKeys'],
setup (props, { emit } ) {
console.log('props.mode', props.mode)
const isInline = computed(() => props.mode === 'inline')
const handleOpenChange: OpenEventHandler = (openKeys): void => {
emit('update:openKeys', openKeys)
}
const handleSelect = ({ selectedKeys }: SelectInfo): void => {
emit('update:selectedKeys', selectedKeys)
}
return () => (
<Menu
inlineCollapsed={isInline.value && props.collapsed || undefined}
mode={props.mode}
theme={props.theme}
openKeys={props.openKeys}
selectedKeys={props.selectedKeys}
onOpenChange={handleOpenChange}
onSelect={handleSelect}
>
{props.menus && props.menus.map(menu => {
if (menu.hidden) {
return null
}
return renderMenu(menu, props.i18n)
})}
</Menu>
)
}
})
import './index.less';
import { VNodeChild, SetupContext } from 'vue';
import 'ant-design-vue/es/layout/style';
import Layout from 'ant-design-vue/es/layout';
import BaseMenu, { BaseMenuProps } from './BaseMenu';
import { WithFalse } from '../typings';
import { SiderProps } from './typings';
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue';
const { Sider } = Layout;
export interface SiderMenuProps extends Pick<BaseMenuProps, Exclude<keyof BaseMenuProps, ['onCollapse']>> {
logo?: VNodeChild | JSX.Element;
siderWidth?: number;
menuHeaderRender?: WithFalse<
(logo: VNodeChild | JSX.Element, title: VNodeChild | JSX.Element, props?: SiderMenuProps) => VNodeChild
>;
menuFooterRender?: WithFalse<(props?: SiderMenuProps) => VNodeChild>;
menuContentRender?: WithFalse<(props: SiderMenuProps, defaultDom: VNodeChild | JSX.Element) => VNodeChild>;
menuExtraRender?: WithFalse<(props: SiderMenuProps) => VNodeChild>;
collapsedButtonRender?: WithFalse<(collapsed?: boolean) => VNodeChild>;
breakpoint?: SiderProps['breakpoint'] | false;
onMenuHeaderClick?: (e: MouseEvent) => void;
hide?: boolean;
onOpenChange?: (openKeys: WithFalse<string[]>) => void;
}
export const defaultRenderLogo = (logo: VNodeChild | JSX.Element): VNodeChild | JSX.Element => {
if (typeof logo === 'string') {
return <img src={logo} alt="logo" />;
}
if (typeof logo === 'function') {
return logo();
}
return logo;
}
export const defaultRenderLogoAndTitle = (
props: SiderMenuProps,
renderKey: string = 'menuHeaderRender',
): VNodeChild | JSX.Element => {
const {
logo = 'https://gw.alipayobjects.com/zos/antfincdn/PmY%24TNNDBI/logo.svg',
title,
layout,
} = props
const renderFunction = props[renderKey || ''];
if (renderFunction === false) {
return null;
}
const logoDom = defaultRenderLogo(logo);
const titleDom = <h1>{title}</h1>;
if (renderFunction) {
// when collapsed, no render title
return renderFunction(logoDom, props.collapsed ? null : titleDom, props);
}
if (layout === 'mix' && renderKey === 'menuHeaderRender') {
return null;
}
return (
<a>
{logoDom}
{props.collapsed ? null : titleDom}
</a>
);
}
export const defaultRenderCollapsedButton = (collapsed?: boolean) =>
collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />;
const SiderMenu = (props: SiderMenuProps, context: SetupContext) => {
const {
collapsed,
fixSiderbar,
menuFooterRender,
onCollapse,
theme,
siderWidth,
isMobile,
onMenuHeaderClick,
breakpoint = 'lg',
layout,
menuExtraRender = false,
collapsedButtonRender = defaultRenderCollapsedButton,
menuContentRender,
prefixCls,
onOpenChange,
headerHeight,
} = props;
const baseClassName = `${props.prefixCls}-sider`;
const siderClassName = {
[baseClassName]: true,
[`${baseClassName}-fixed`]: fixSiderbar,
[`${baseClassName}-layout-${layout}`]: layout && !isMobile,
[`${baseClassName}-light`]: theme === 'light',
};
const headerDom = defaultRenderLogoAndTitle(props);
const extraDom = menuExtraRender && menuExtraRender(props);
const menuDom = menuContentRender !== false && (
<BaseMenu
{...props}
mode="inline"
handleOpenChange={onOpenChange}
style={{
width: '100%',
}}
class={`${baseClassName}-menu`}
/>
);
};
@import "~ant-design-vue/es/style/themes/default.less";
@import '../BasicLayout.less';
@pro-layout-sider-menu-prefix-cls: ~'@{ant-prefix}-pro-sider';
@nav-header-height: @pro-layout-header-height;
.@{pro-layout-sider-menu-prefix-cls} {
position: relative;
background-color: @layout-sider-background;
border-right: 0;
transition: background-color 0.3s;
z-index: 9;
&.@{ant-prefix}-menu-vertical .@{ant-prefix}-menu-item:not(:last-child),
&.@{ant-prefix}-menu-vertical-left .@{ant-prefix}-menu-item:not(:last-child),
&.@{ant-prefix}-menu-vertical-right .@{ant-prefix}-menu-item:not(:last-child),
&.@{ant-prefix}-menu-inline .@{ant-prefix}-menu-item:not(:last-child) {
margin-bottom: 4px;
}
&.@{ant-prefix}-layout-sider-light {
.@{ant-prefix}-menu-item a {
color: @heading-color;
}
.@{ant-prefix}-menu-item-selected a,
.@{ant-prefix}-menu-item a:hover {
color: @primary-color;
}
}
.@{ant-prefix}-menu-inline-collapsed > .@{ant-prefix}-menu-item .anticon + span,
.@{ant-prefix}-menu-inline-collapsed
> .@{ant-prefix}-menu-item-group
> .@{ant-prefix}-menu-item-group-list
> .@{ant-prefix}-menu-item
.anticon
+ span,
.@{ant-prefix}-menu-inline-collapsed
> .@{ant-prefix}-menu-item-group
> .@{ant-prefix}-menu-item-group-list
> .@{ant-prefix}-menu-submenu
> .@{ant-prefix}-menu-submenu-title
.anticon
+ span,
.@{ant-prefix}-menu-inline-collapsed
> .@{ant-prefix}-menu-submenu
> .@{ant-prefix}-menu-submenu-title
.anticon
+ span {
display: none;
}
ul.@{ant-prefix}-menu-sub {
li.@{ant-prefix}-menu-item,
li.@{ant-prefix}-menu-submenu {
.@{ant-prefix}-pro-menu-item {
padding-left: 8px;
}
}
}
&-logo {
position: relative;
display: flex;
align-items: center;
padding: 16px 16px;
line-height: 32px;
cursor: pointer;
> a {
display: flex;
align-items: center;
justify-content: center;
min-height: 32px;
}
img {
display: inline-block;
height: 32px;
vertical-align: middle;
transition: height 0.2s;
}
h1 {
display: inline-block;
height: 32px;
margin: 0 0 0 12px;
color: white;
font-weight: 600;
font-size: 18px;
line-height: 32px;
vertical-align: middle;
animation: fade-in;
animation-duration: 0.2s;
}
}
&-extra {
margin-bottom: 16px;
padding: 0 16px;
&-no-logo {
margin-top: 16px;
}
}
&-menu {
position: relative;
z-index: 10;
min-height: 100%;
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
}
.@{ant-prefix}-layout-sider-children {
display: flex;
flex-direction: column;
height: 100%;
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.15);
border-radius: 3px;
-webkit-box-shadow: inset 0 0 5px rgba(37, 37, 37, 0.05);
}
/* 滚动条滑块 */
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
-webkit-box-shadow: inset 0 0 5px rgba(255, 255, 255, 0.05);
}
}
&.@{ant-prefix}-layout-sider-collapsed {
.@{ant-prefix}-menu-inline-collapsed {
width: 48px;
}
.@{pro-layout-sider-menu-prefix-cls} {
&-logo {
padding: 16px 8px;
}
}
}
.@{ant-prefix}-menu-inline-collapsed {
& > .@{ant-prefix}-menu-item .sider-menu-item-img + span,
&
> .@{ant-prefix}-menu-item-group
> .@{ant-prefix}-menu-item-group-list
> .@{ant-prefix}-menu-item
.sider-menu-item-img
+ span,
&
> .@{ant-prefix}-menu-submenu
> .@{ant-prefix}-menu-submenu-title
.sider-menu-item-img
+ span {
display: inline-block;
max-width: 0;
opacity: 0;
}
.@{ant-prefix}-menu-item {
width: 48px;
padding: 0 16px !important;
}
// hide menu item text in collapsed
.@{ant-prefix}-pro-menu-item-title {
display: none;
}
.@{ant-prefix}-menu-submenu {
&-title {
width: 48px;
padding: 0 16px !important;
}
}
}
.@{ant-prefix}-menu-item,
.@{ant-prefix}-menu-submenu-title {
.anticon {
transition: none;
}
}
&-fixed {
position: fixed;
top: 0;
left: 0;
z-index: 99;
height: 100%;
overflow: auto;
overflow-x: hidden;
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
> .@{ant-prefix}-menu-root {
:not(.@{pro-layout-sider-menu-prefix-cls}-link-menu) {
height: ~'calc(100vh - @{nav-header-height})';
overflow-y: auto;
}
}
}
.@{ant-prefix}-menu-inline {
.@{ant-prefix}-menu-item,
.@{ant-prefix}-menu-submenu-title {
width: 100%;
}
}
&-light {
.@{ant-prefix}-layout-sider-children {
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.06);
border-radius: 3px;
-webkit-box-shadow: inset 0 0 5px rgba(0, 21, 41, 0.05);
}
/* 滚动条滑块 */
::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.12);
border-radius: 3px;
-webkit-box-shadow: inset 0 0 5px rgba(0, 21, 41, 0.05);
}
}
background-color: @white;
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
.@{pro-layout-sider-menu-prefix-cls}-logo {
h1 {
color: @primary-color;
}
}
.@{ant-prefix}-menu-light {
border-right-color: transparent;
}
.@{pro-layout-sider-menu-prefix-cls}-collapsed-button {
border-top: @border-width-base @border-style-base @border-color-split;
}
}
&-icon {
width: 14px;
vertical-align: baseline;
}
&-links {
width: 100%;
ul.@{ant-prefix}-menu-root {
height: auto;
}
}
&-collapsed-button {
border-top: @border-width-base @border-style-base rgba(0, 0, 0, 0.25);
.anticon {
font-size: 16px;
}
}
.top-nav-menu li.@{ant-prefix}-menu-item {
height: 100%;
line-height: 1;
}
.drawer .drawer-content {
background: @layout-sider-background;
}
.@{ant-prefix}-menu-item .sider-menu-item-img + span,
.@{ant-prefix}-menu-submenu-title .sider-menu-item-img + span {
opacity: 1;
transition: opacity 0.3s @ease-in-out, width 0.3s @ease-in-out;
}
}
@keyframes fade-in {
0% {
display: none;
opacity: 0;
}
99% {
display: none;
opacity: 0;
}
100% {
display: block;
opacity: 1;
}
}
This diff is collapsed.
This diff is collapsed.
import PropTypes from 'ant-design-vue/es/_util/vue-types'
const ProConfigProviderProps = {
i18nRender: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]).def(false),
contentWidth: PropTypes.oneOf(['Fluid', 'Fixed']).def('Fluid'),
breadcrumbRender: PropTypes.func,
}
const ConfigProvider = {
name: 'ProConfigProvider',
props: ProConfigProviderProps,
provide () {
const _self = this
return {
locale: _self.$props.i18nRender,
contentWidth: _self.$props.contentWidth,
breadcrumbRender: _self.$props.breadcrumbRender,
}
},
render () {
const { $scopedSlots } = this
const children = this.children || $scopedSlots.default
return children()
}
}
export default ConfigProvider
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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