This commit is contained in:
bac-joker 2021-01-07 16:31:49 +08:00
commit c2e230f784
20 changed files with 391 additions and 175 deletions

View File

@ -3,7 +3,6 @@
"version": "2.0.0-alpha.0",
"description": "@webank/fes-compiler",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib"
],
@ -28,7 +27,6 @@
"@babel/register": "^7.12.1",
"@umijs/babel-preset-umi": "3.3.3",
"@umijs/utils": "3.3.3",
"@webank/fes-runtime": "^2.0.0",
"dotenv": "8.2.0",
"joi": "17.3.0",
"set-value": "3.0.2",

View File

@ -6,7 +6,6 @@
"files": [
"lib"
],
"module": "dist/index.esm.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
@ -28,7 +27,7 @@
"access": "public"
},
"peerDependencies": {
"@webank/fes": "^2.0.0",
"@webank/fes": "^2.0.0-alpha.0",
"vue": "^3.0.4"
}
}

View File

@ -9,7 +9,6 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"module": "dist/index.esm.js",
"repository": {
"type": "git",
"url": "git+https://github.com/WeBankFinTech/fes.js.git",

View File

@ -6,7 +6,6 @@
"files": [
"lib"
],
"module": "dist/index.esm.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
@ -27,9 +26,12 @@
"publishConfig": {
"access": "public"
},
"dependencies": {
"@umijs/utils": "3.3.3"
},
"peerDependencies": {
"@ant-design/icons-vue": "^5.1.6",
"@webank/fes": "^2.0.0",
"@webank/fes": "^2.0.0-alpha.0",
"ant-design-vue": "2.0.0-rc.3",
"vue": "^3.0.4"
}

View File

@ -1,5 +1,6 @@
import { readFileSync, copyFileSync, statSync } from 'fs';
import { join } from 'path';
import { winPath } from '@umijs/utils';
const namespace = 'plugin-layout';
@ -18,16 +19,18 @@ export default (api) => {
}
});
const absRuntimeFilePath = join(namespace, 'runtime.js');
api.addRuntimePluginKey(() => 'layout');
const absFilePath = join(namespace, 'index.js');
api.onGenerateFiles(() => {
// 文件写出
const userConfig = api.config.layout || {};
api.writeTmpFile({
path: absRuntimeFilePath,
path: absFilePath,
content: Mustache.render(
readFileSync(join(__dirname, 'template/runtime.tpl'), 'utf-8'),
readFileSync(join(__dirname, 'template/index.tpl'), 'utf-8'),
{
REPLACE_USER_CONFIG: JSON.stringify(userConfig),
HAS_LOCALE: api.pkg.dependencies?.['@webank/fes-plugin-locale']
@ -58,7 +61,14 @@ export default (api) => {
});
});
api.addRuntimePluginKey(() => 'layout');
api.addRuntimePlugin(() => `@@/${absRuntimeFilePath}`);
// 把BaseLayout插入到路由配置中作为跟路由
api.modifyRoutes(routes => [
{
path: '/',
component: winPath(
join(api.paths.absTmpPath || '', absFilePath)
),
children: routes
}
]);
};

View File

@ -0,0 +1,41 @@
import { reactive, defineComponent } from "vue";
import { getRoutes, plugin, ApplyPluginsType } from "@@/core/coreExports";
import BaseLayout from "./views/BaseLayout.vue";
import { fillMenuData } from "./helpers";
const userConfig = reactive({{{REPLACE_USER_CONFIG}}});
const Layout = defineComponent({
name: 'Layout',
setup(){
const runtimeConfig = plugin.applyPlugins({
key: "layout",
type: ApplyPluginsType.modify,
initialValue: {},
});
const localeShared = plugin.getShared("locale");
const routeConfig = getRoutes();
userConfig.menus = fillMenuData(userConfig.menus, routeConfig);
return () => {
const slots = {
userCenter: () => {
if (runtimeConfig.userCenter) {
return (
<runtimeConfig.userCenter></runtimeConfig.userCenter>
);
}
return null;
},
locale: () => {
if (localeShared) {
return <localeShared.SelectLang></localeShared.SelectLang>;
}
return null;
},
};
return <BaseLayout locale={ localeShared ? true : false } {...userConfig} v-slots={slots}></BaseLayout>;
};
}
})
export default Layout;

View File

@ -1,37 +0,0 @@
import { reactive } from "vue";
import { getRoutes, plugin, ApplyPluginsType } from "@@/core/coreExports";
import BaseLayout from "./views/BaseLayout.vue";
import { fillMenuData } from "./helpers";
const userConfig = reactive({{{REPLACE_USER_CONFIG}}});
export function rootContainer(childComponent, args) {
const runtimeConfig = plugin.applyPlugins({
key: "layout",
type: ApplyPluginsType.modify,
initialValue: {},
});
const localeShared = plugin.getShared("locale");
const routeConfig = getRoutes();
userConfig.menus = fillMenuData(userConfig.menus, routeConfig);
return () => {
const slots = {
default: () => <childComponent keepAlive={userConfig.multiTabs}></childComponent>,
userCenter: () => {
if(runtimeConfig.userCenter){
return (<runtimeConfig.userCenter></runtimeConfig.userCenter>)
}
return null
},
locale: () => {
if(localeShared){
return (<localeShared.SelectLang></localeShared.SelectLang>)
}
return null
}
};
return (
<BaseLayout {...userConfig} v-slots={slots}>
</BaseLayout>
);
};
}

View File

@ -1,53 +1,74 @@
<template>
<a-layout class="main-layout">
<a-layout-sider
v-if="routeHasLayout"
v-model:collapsed="collapsed"
:width="sideWidth"
:class="{ collapsed: collapsed }"
collapsible
theme="dark"
class="layout-sider"
>
<div class="logo">
<img :src="logo" class="logo-img" />
<h1 class="logo-name">{{title}}</h1>
</div>
<Menu :menus="menus" :theme="theme" />
</a-layout-sider>
<a-layout>
<a-layout-header v-if="routeHasLayout" class="layout-header">
<div class="layout-header-user">
<a-layout
v-if="routeHasLayout"
:class="[
collapsed ? 'main-layout-collapsed' : '',
`main-layout-navigation-${navigation}`
]"
class="main-layout"
>
<template v-if="navigation !== 'top'">
<div v-if="fixedSideBar" class="layout-sider-fixed-stuff"></div>
<a-layout-sider
v-model:collapsed="collapsed"
:width="sideWidth"
:class="[
'layout-sider',
fixedSideBar ? 'layout-sider-fixed' : ''
]"
collapsible
theme="dark"
>
<div class="layout-logo">
<img :src="logo" class="logo-img" />
<h1 class="logo-name">{{title}}</h1>
</div>
<Menu :menus="menus" :theme="theme" />
</a-layout-sider>
</template>
<a-layout class="child-layout">
<a-layout-header v-if="fixedHeader" class="layout-header">
</a-layout-header>
<a-layout-header
:class="[fixedHeader ? 'layout-header-fixed' : '']"
class="layout-header"
>
<template v-if="navigation === 'top'">
<div class="layout-logo">
<img :src="logo" class="logo-img" />
<h1 class="logo-name">{{title}}</h1>
</div>
<Menu :menus="menus" :theme="theme" class="layout-menu" mode="horizontal" />
</template>
<div class="layout-header-custom">
<slot name="userCenter"></slot>
</div>
<slot name="locale"></slot>
<template v-if="locale">
<slot name="locale"></slot>
</template>
</a-layout-header>
<a-layout-content class="layout-content">
<template v-if="multiTabs">
<a-tabs :activeKey="route.path" @tabClick="switchTab" class="layout-content-tabs" hide-add type="editable-card">
<a-tab-pane v-for="page in openedPageList" :key="page.path" :tab="page.meta.title" closable>
</a-tab-pane>
</a-tabs>
</template>
<slot></slot>
<MultiTabProvider v-if="multiTabs" />
<router-view v-else></router-view>
</a-layout-content>
<a-layout-footer v-if="routeHasLayout" class="layout-footer">
<a-layout-footer class="layout-footer">
Ant Design ©2020 Created by MumbleFe
</a-layout-footer>
</a-layout>
</a-layout>
<div v-else class="layout-content">
<MultiTabProvider v-if="multiTabs" />
<router-view v-else></router-view>
</div>
</template>
<script>
import {
ref, computed, reactive, unref
} from 'vue';
import { useRoute, useRouter } from '@@/core/coreExports';
import { ref, computed } from 'vue';
import { useRoute } from '@@/core/coreExports';
import Layout from 'ant-design-vue/lib/layout';
import 'ant-design-vue/lib/layout/style';
import Tabs from 'ant-design-vue/lib/tabs';
import 'ant-design-vue/lib/tabs/style';
import Menu from './Menu';
import MultiTabProvider from './MultiTabProvider';
export default {
components: {
@ -56,9 +77,8 @@ export default {
[Layout.Content.name]: Layout.Content,
[Layout.Header.name]: Layout.Header,
[Layout.Footer.name]: Layout.Footer,
[Tabs.name]: Tabs,
[Tabs.TabPane.name]: Tabs.TabPane,
Menu
Menu,
MultiTabProvider
},
props: {
menus: {
@ -81,7 +101,7 @@ export default {
},
theme: {
type: String,
default: 'dark'
default: 'dark' // lightdark
},
navigation: {
type: String,
@ -106,26 +126,12 @@ export default {
},
setup() {
const route = useRoute();
const router = useRouter();
const openedPageList = reactive([]);
const routeHasLayout = computed(() => {
const _routeLayout = route.meta.layout;
return _routeLayout === undefined ? true : _routeLayout;
});
router.beforeEach((to) => {
if (!openedPageList.some(page => unref(page.path) === to.path)) {
openedPageList.push(to);
}
return true;
});
//
const switchTab = (path) => {
router.push(path);
};
return {
switchTab,
route,
openedPageList,
routeHasLayout,
collapsed: ref(false)
};
@ -133,29 +139,86 @@ export default {
};
</script>
<style lang="less">
<style lang="less" vars="{ sideWidth: sideWidth +'px' }">
.main-layout {
min-height: 100vh;
.layout-sider{
&.collapsed{
.logo{
&.main-layout-collapsed {
.layout-sider {
.logo {
justify-content: center;
.logo-name{
.logo-name {
display: none;
}
}
}
.logo {
.layout-sider-fixed-stuff {
overflow: hidden;
width: 80px;
}
}
&.main-layout-navigation-top {
.layout-header {
padding-left: 24px;
color: hsla(0,0%,100%,.65);
background: #001529;
.layout-menu {
line-height: 48px;
}
.layout-logo {
display: flex;
justify-content: flex-start;
align-items: center;
min-width: 165px;
height: 100%;
overflow: hidden;
transition: all .3s;
.logo-img {
height: 32px;
width: auto;
}
.logo-name {
overflow: hidden;
margin: 0 0 0 12px;
color: #fff;
font-weight: 600;
font-size: 18px;
line-height: 32px;
}
}
&.layout-header-fixed {
left: 0;
width: 100%;
}
}
}
.layout-sider-fixed-stuff {
overflow: hidden;
width: var(--sideWidth);
transition: width 0.2s;
flex-shrink: 0;
}
.child-layout {
position: relative;
}
.layout-sider {
&.layout-sider-fixed {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 200px;
}
.layout-logo {
height: 32px;
margin: 16px;
display: flex;
justify-content: flex-start;
align-items: center;
.logo-img{
.logo-img {
height: 32px;
width: auto;
}
.logo-name{
.logo-name {
overflow: hidden;
margin: 0 0 0 12px;
color: #fff;
@ -173,23 +236,22 @@ export default {
height: 48px;
line-height: 48px;
background: #fff;
box-shadow: 0 1px 4px rgba(0,21,41,.08);
padding: 0 24px;
.layout-header-user {
flex: 1
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
padding: 0;
.layout-header-custom {
flex: 1;
}
&.layout-header-fixed {
position: fixed;
top: 0;
left: var(--sideWidth);
right: 0;
z-index: 10;
width: calc(100% - var(--sideWidth));
}
}
.layout-content {
position: relative;
.layout-content-tabs {
background: rgb(255, 255, 255);
margin: 0px;
padding-top: 6px;
width: 100%;
.ant-tabs-nav-container {
padding-left: 16px;
}
}
}
.layout-footer {
text-align: center;

View File

@ -0,0 +1,172 @@
<template>
<a-tabs
:activeKey="route.path"
@tabClick="switchPage"
class="layout-content-tabs"
hide-add
type="editable-card"
>
<a-tab-pane v-for="page in pageList" :key="page.path" closable>
<template #tab>
{{page.name}}
<ReloadOutlined
v-show="route.path === page.path"
@click="reloadPage(page.path)"
class="layout-tabs-close-icon"
/>
</template>
</a-tab-pane>
<template #tabBarExtraContent>
<a-dropdown>
<div class="layout-tabs-more-icon">
<MoreOutlined />
</div>
<template #overlay>
<a-menu @click="handlerMore">
<a-menu-item key="closeOtherPage">
<a href="javascript:;">关闭其他</a>
</a-menu-item>
<a-menu-item key="reloadPage">
<a href="javascript:;">刷新当前页</a>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
</a-tabs>
<router-view v-slot="{ Component, route }">
<keep-alive>
<component :key="getPageKey(route)" :is="Component" />
</keep-alive>
</router-view>
</template>
<script>
import { reactive, unref } from 'vue';
import Tabs from 'ant-design-vue/lib/tabs';
import Dropdown from 'ant-design-vue/lib/dropdown';
import Menu from 'ant-design-vue/lib/menu';
import 'ant-design-vue/lib/menu/style';
import 'ant-design-vue/lib/dropdown/style';
import 'ant-design-vue/lib/tabs/style';
import { ReloadOutlined, MoreOutlined } from '@ant-design/icons-vue';
import { useRouter, useRoute } from '@@/core/coreExports';
let i = 0;
const getKey = () => ++i;
export default {
components: {
[Dropdown.name]: Dropdown,
[Menu.name]: Menu,
[Menu.Item.name]: Menu.Item,
[Tabs.name]: Tabs,
[Tabs.TabPane.name]: Tabs.TabPane,
ReloadOutlined,
MoreOutlined
},
setup() {
const route = useRoute();
const router = useRouter();
const pageList = reactive([
{
path: unref(route.path),
route: {
query: unref(route.query),
params: unref(route.params)
},
name: unref(route.meta).name,
key: getKey()
}
]);
const findPage = path => pageList.find(item => unref(item.path) === unref(path));
router.beforeEach((to) => {
if (!findPage(to.path)) {
pageList.push({
path: to.path,
route: to,
name: to.meta.name,
key: getKey()
});
}
return true;
});
//
const switchPage = (path) => {
const selectedPage = findPage(path);
if (selectedPage) {
router.push({
path,
query: selectedPage.route.query,
params: selectedPage.route.params
});
}
};
const reloadPage = (path) => {
const selectedPage = findPage(path || unref(route.path));
if (selectedPage) {
selectedPage.key = getKey();
}
};
const closeOtherPage = (path) => {
const selectedPage = findPage(path || unref(route.path));
pageList.length = 0;
pageList.push(selectedPage);
};
const getPageKey = (_route) => {
const selectedPage = findPage(_route.path);
if (selectedPage) {
return selectedPage.key;
}
return '';
};
const handlerMore = ({ key }) => {
console.log(key);
switch (key) {
case 'closeOtherPage':
closeOtherPage();
break;
case 'reloadPage':
reloadPage();
break;
default:
}
};
return {
route,
pageList,
getPageKey,
reloadPage,
switchPage,
handlerMore
};
}
};
</script>
<style lang="less">
.layout-content-tabs {
background: rgb(255, 255, 255);
margin: 0px;
padding-top: 6px;
width: 100%;
.ant-tabs-nav-container {
padding-left: 16px;
}
.layout-tabs-close-icon {
vertical-align: middle;
color: rgba(0, 0, 0, 0.45);
font-size: 12px;
margin-left: 6px;
margin-top: -2px;
&:hover {
color: rgba(0, 0, 0, 0.8);
}
}
.layout-tabs-more-icon {
margin-right: 8px;
padding: 0 4px;
color: rgba(0, 0, 0, 0.45);
&:hover {
color: rgba(0, 0, 0, 0.8);
}
}
}
</style>

View File

@ -6,7 +6,6 @@
"files": [
"lib"
],
"module": "dist/index.esm.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
@ -33,7 +32,7 @@
},
"peerDependencies": {
"@ant-design/icons-vue": "^5.1.6",
"@webank/fes": "^2.0.0",
"@webank/fes": "^2.0.0-alpha.0",
"ant-design-vue": "2.0.0-rc.3",
"vue": "^3.0.4"
}

View File

@ -1,6 +1,6 @@
<template>
<a-dropdown>
<GlobalOutlined />
<div class="lang-icon"><GlobalOutlined /></div>
<template #overlay>
<a-menu :selectedKeys="selectedKeys" @click="handleClick">
<a-menu-item
@ -53,6 +53,11 @@ export default {
</script>
<style lang="less">
.lang-icon {
margin: 0 8px;
padding: 0 4px;
cursor: pointer;
}
.lang-item {
display: flex;
align-items: center;

View File

@ -6,7 +6,6 @@
"files": [
"lib"
],
"module": "dist/index.esm.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
@ -31,7 +30,7 @@
"@umijs/utils": "3.3.3"
},
"peerDependencies": {
"@webank/fes": "^2.0.0",
"@webank/fes": "^2.0.0-alpha.0",
"vue": "^3.0.4"
}
}

View File

@ -9,7 +9,6 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"module": "dist/index.esm.js",
"repository": {
"type": "git",
"url": "git+https://github.com/WeBankFinTech/fes.js.git",
@ -28,7 +27,7 @@
"access": "public"
},
"peerDependencies": {
"@webank/fes": "^2.0.0"
"@webank/fes": "^2.0.0-alpha.0"
},
"dependencies": {
"axios": "0.21.1"

View File

@ -31,7 +31,7 @@
"@vue/babel-plugin-jsx": "^1.0.0-rc.5",
"@vue/compiler-sfc": "^3.0.4",
"@vue/preload-webpack-plugin": "1.1.2",
"@webank/fes-compiler": "^2.0.0",
"@webank/fes-compiler": "^2.0.0-alpha.0",
"cliui": "6.0.0",
"html-webpack-plugin": "^3.2.0",
"html-webpack-tags-plugin": "2.0.17",

View File

@ -8,7 +8,6 @@ import { plugin } from './core/plugin';
import './core/pluginRegister';
import { ApplyPluginsType } from '{{{ runtimePath }}}';
import { getRoutes } from './core/routes/routes';
import BaseView from './core/routes/baseView';
{{{ imports }}}
@ -19,9 +18,7 @@ const renderClient = (opts = {}) => {
const rootContainer = plugin.applyPlugins({
type: ApplyPluginsType.modify,
key: 'rootContainer',
initialValue: defineComponent((props) => {
return () => (<BaseView {...props}></BaseView>)
}),
initialValue: defineComponent(() => () => (<RouterView></RouterView>)),
args: {
routes: routes,
plugin: plugin

View File

@ -250,8 +250,6 @@ export default function (api) {
const absRuntimeFilePath = join(namespace, 'runtime.js');
const baseViewFilePath = join(namespace, 'baseView.vue');
api.onGenerateFiles(async () => {
const routesTpl = readFileSync(join(__dirname, 'template/routes.tpl'), 'utf-8');
const routes = await api.getRoutesJSON();
@ -269,11 +267,6 @@ export default function (api) {
path: absRuntimeFilePath,
content: readFileSync(join(__dirname, 'template/runtime.tpl'), 'utf-8')
});
api.writeTmpFile({
path: baseViewFilePath,
content: readFileSync(join(__dirname, 'template/baseView.vue'), 'utf-8')
});
});
api.addCoreExports(() => [

View File

@ -1,22 +0,0 @@
<template>
<template v-if="keepAlive">
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
</template>
<template v-else>
<router-view></router-view>
</template>
</template>
<script>
export default {
props: {
keepAlive: {
type: Boolean,
default: false
}
}
};
</script>

View File

@ -40,9 +40,9 @@
"postcss-px-to-viewport": "1.1.1"
},
"dependencies": {
"@webank/fes": "^2.0.0",
"@webank/fes-plugin-icon": "^1.0.0",
"@webank/fes-plugin-request": "^1.0.0",
"@webank/fes": "^2.0.0-alpha.0",
"@webank/fes-plugin-icon": "^2.0.0-alpha.0",
"@webank/fes-plugin-request": "^2.0.0-alpha.0",
"vue": "^3.0.4"
},
"private": true

View File

@ -43,11 +43,11 @@
"@webank/eslint-config-webank": "0.2.7"
},
"dependencies": {
"@webank/fes": "^2.0.0",
"@webank/fes-plugin-access": "^1.0.0",
"@webank/fes-plugin-layout": "^1.0.0",
"@webank/fes-plugin-locale": "^1.0.0",
"@webank/fes-plugin-model": "^1.0.0",
"@webank/fes": "^2.0.0-alpha.0",
"@webank/fes-plugin-access": "^2.0.0-alpha.0",
"@webank/fes-plugin-layout": "^2.0.0-alpha.0",
"@webank/fes-plugin-locale": "^2.0.0-alpha.0",
"@webank/fes-plugin-model": "^2.0.0-alpha.0",
"ant-design-vue": "2.0.0-rc.3",
"vue": "3.0.4"
},

View File

@ -39,9 +39,9 @@
],
"dependencies": {
"@umijs/utils": "3.3.3",
"@webank/fes-compiler": "^2.0.0",
"@webank/fes-preset-built-in": "^2.0.0",
"@webank/fes-runtime": "^2.0.0",
"@webank/fes-compiler": "^2.0.0-alpha.0",
"@webank/fes-preset-built-in": "^2.0.0-alpha.0",
"@webank/fes-runtime": "^2.0.0-alpha.0",
"resolve-cwd": "^3.0.0"
}
}