Feat use layout (#223)

* style: format code

* feat: 添加 useLayout 暴露 closeTab 的能力
This commit is contained in:
qlin 2023-12-18 20:22:31 +08:00 committed by GitHub
parent 33684ca21d
commit 9c5283aef0
9 changed files with 153 additions and 75 deletions

View File

@ -1,40 +1,41 @@
{ {
"name": "@fesjs/plugin-layout", "name": "@fesjs/plugin-layout",
"version": "5.1.7", "version": "5.1.7",
"description": "@fesjs/plugin-layout", "description": "@fesjs/plugin-layout",
"main": "lib/index.js", "author": "harrywan",
"files": [ "license": "MIT",
"lib", "homepage": "https://github.com/WeBankFinTech/fes.js#readme",
"types.d.ts" "repository": {
], "type": "git",
"scripts": { "url": "git+https://github.com/WeBankFinTech/fes.js.git",
"test": "echo \"Error: no test specified\" && exit 1" "directory": "packages/fes-plugin-layout"
}, },
"repository": { "bugs": {
"type": "git", "url": "https://github.com/WeBankFinTech/fes.js/issues"
"url": "git+https://github.com/WeBankFinTech/fes.js.git", },
"directory": "packages/fes-plugin-layout" "keywords": [
}, "fes"
"keywords": [ ],
"fes" "main": "lib/index.js",
], "files": [
"author": "harrywan", "lib",
"license": "MIT", "types.d.ts"
"bugs": { ],
"url": "https://github.com/WeBankFinTech/fes.js/issues" "scripts": {
}, "test": "echo \"Error: no test specified\" && exit 1"
"homepage": "https://github.com/WeBankFinTech/fes.js#readme", },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
"dependencies": { "peerDependencies": {
"@fesjs/utils": "^3.0.1" "@fesjs/fes": "^3.1.4",
}, "@fesjs/fes-design": ">=0.7.0",
"peerDependencies": { "vue": "^3.2.47",
"@fesjs/fes": "^3.1.4", "vue-router": "^4.0.1"
"@fesjs/fes-design": ">=0.7.0", },
"vue": "^3.2.47", "dependencies": {
"vue-router": "^4.0.1" "@fesjs/utils": "^3.0.1",
}, "@vueuse/core": "^10.7.0"
"typings": "./types.d.ts" },
"typings": "./types.d.ts"
} }

View File

@ -1,5 +1,5 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'node:fs';
import { join } from 'path'; import { join } from 'node:path';
import { winPath } from '@fesjs/utils'; import { winPath } from '@fesjs/utils';
import { name } from '../package.json'; import { name } from '../package.json';
@ -40,7 +40,7 @@ export default (api) => {
const iconNames = helper.getIconNamesFromMenu(userConfig.menus); const iconNames = helper.getIconNamesFromMenu(userConfig.menus);
const iconsString = iconNames.map((iconName) => `import { ${iconName} } from '@fesjs/fes-design/icon'`); const iconsString = iconNames.map(iconName => `import { ${iconName} } from '@fesjs/fes-design/icon'`);
api.writeTmpFile({ api.writeTmpFile({
path: join(namespace, 'icons.js'), path: join(namespace, 'icons.js'),
content: ` content: `
@ -84,7 +84,7 @@ export default (api) => {
api.addPluginExports(() => [ api.addPluginExports(() => [
{ {
specifiers: ['Page', 'useTabTitle'], specifiers: ['Page', 'useTabTitle', 'useLayout'],
source: join(namespace, 'index.js'), source: join(namespace, 'index.js'),
}, },
]); ]);
@ -92,7 +92,7 @@ export default (api) => {
// 把 BaseLayout插入到路由配置中作为根路由 // 把 BaseLayout插入到路由配置中作为根路由
// 添加 403 和 404 路由 // 添加 403 和 404 路由
api.modifyRoutes((routes) => { api.modifyRoutes((routes) => {
if (!routes.find((item) => item.path === '/403')) { if (!routes.find(item => item.path === '/403')) {
routes.push({ routes.push({
path: '/403', path: '/403',
name: 'Exception403', name: 'Exception403',
@ -102,7 +102,7 @@ export default (api) => {
}, },
}); });
} }
if (!routes.find((item) => item.path === '/404')) { if (!routes.find(item => item.path === '/404')) {
routes.push({ routes.push({
path: '/404', path: '/404',
name: 'Exception404', name: 'Exception404',

View File

@ -1,2 +1,3 @@
export { default as Page } from './views/page.vue'; export { default as Page } from './views/page.vue';
export { useTabTitle } from './useTitle'; export { useTabTitle } from './useTitle';
export * from './useLayout';

View File

@ -0,0 +1,12 @@
import { createSharedComposable } from '@vueuse/core';
import { shallowReactive } from 'vue';
function _useLayout() {
const state = shallowReactive({
closeTab: () => {},
});
return state;
}
export const useLayout = createSharedComposable(_useLayout);

View File

@ -3,11 +3,11 @@ import { useRoute } from '@@/core/coreExports';
const cache = reactive(new Map()); const cache = reactive(new Map());
export const getTitle = (path) => cache.get(path); export const getTitle = path => cache.get(path);
export const deleteTitle = (patch) => cache.delete(patch); export const deleteTitle = patch => cache.delete(patch);
export const useTabTitle = (title) => { export function useTabTitle(title) {
const route = useRoute(); const route = useRoute();
const titleRef = ref(title); const titleRef = ref(title);
const path = route.path; const path = route.path;
@ -15,4 +15,4 @@ export const useTabTitle = (title) => {
cache.set(path, titleRef); cache.set(path, titleRef);
return titleRef; return titleRef;
}; }

View File

@ -1,9 +1,9 @@
<template> <template>
<template v-if="multiTabs"> <template v-if="multiTabs">
<FTabs <FTabs
:modelValue="route.path" :model-value="route.path"
closable closable
:tabsPadding="24" :tabs-padding="24"
type="card" type="card"
class="layout-content-tabs" class="layout-content-tabs"
@close="handleCloseTab" @close="handleCloseTab"
@ -21,17 +21,19 @@
</FDropdown> </FDropdown>
</template> </template>
</FTabs> </FTabs>
<Page ref="pageRef" :pageKey="getPageKey" isAllKeepAlive /> <Page ref="pageRef" :page-key="getPageKey" is-all-keep-alive />
</template> </template>
<Page v-else /> <Page v-else />
</template> </template>
<script> <script>
import { computed, unref, ref } from 'vue'; import { computed, ref, unref } from 'vue';
import { FTabs, FTabPane, FDropdown } from '@fesjs/fes-design'; import { FDropdown, FTabPane, FTabs } from '@fesjs/fes-design';
import { ReloadOutlined, MoreOutlined } from '@fesjs/fes-design/icon'; import { MoreOutlined, ReloadOutlined } from '@fesjs/fes-design/icon';
import { useRouter, useRoute } from '@@/core/coreExports'; import { useRoute, useRouter } from '@@/core/coreExports';
import { transTitle } from '../helpers/pluginLocale'; import { transTitle } from '../helpers/pluginLocale';
import { getTitle, deleteTitle } from '../useTitle'; import { deleteTitle, getTitle } from '../useTitle';
import { useLayout } from '../useLayout';
import Page from './page.vue'; import Page from './page.vue';
let i = 0; let i = 0;
@ -52,6 +54,8 @@ export default {
const pageRef = ref(); const pageRef = ref();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const layoutState = useLayout();
const createPage = (_route) => { const createPage = (_route) => {
const computedTitle = computed(() => { const computedTitle = computed(() => {
const customTitle = unref(getTitle(_route.path)); const customTitle = unref(getTitle(_route.path));
@ -78,15 +82,16 @@ export default {
}, },
]; ];
const findPage = (path) => pageList.value.find((item) => unref(item.path) === unref(path)); const findPage = path => pageList.value.find(item => unref(item.path) === unref(path));
router.beforeEach((to) => { router.beforeEach((to) => {
const page = findPage(to.path); const page = findPage(to.path);
if (!page) { if (!page)
pageList.value = [...pageList.value, createPage(to)]; pageList.value = [...pageList.value, createPage(to)];
} else {
else
page.route = to; page.route = to;
}
return true; return true;
}); });
@ -102,16 +107,17 @@ export default {
} }
}; };
const handleCloseTab = async (targetKey) => { const handleCloseTab = async (targetKey) => {
targetKey = targetKey || route.path;
const selectedPage = findPage(targetKey); const selectedPage = findPage(targetKey);
const list = [...pageList.value]; const list = [...pageList.value];
const index = list.indexOf(selectedPage); const index = list.indexOf(selectedPage);
if (route.path === selectedPage.path) { if (route.path === selectedPage.path) {
if (list.length > 1) { if (list.length > 1) {
if (list.length - 1 === index) { if (list.length - 1 === index)
await switchPage(list[index - 1].path); await switchPage(list[index - 1].path);
} else {
else
await switchPage(list[index + 1].path); await switchPage(list[index + 1].path);
}
} }
} }
list.splice(index, 1); list.splice(index, 1);
@ -119,11 +125,12 @@ export default {
pageRef.value.removeKeepAlive(selectedPage.name); pageRef.value.removeKeepAlive(selectedPage.name);
deleteTitle(selectedPage.path); deleteTitle(selectedPage.path);
}; };
layoutState.closeTab = handleCloseTab;
const reloadPage = (path) => { const reloadPage = (path) => {
const selectedPage = findPage(path || unref(route.path)); const selectedPage = findPage(path || unref(route.path));
if (selectedPage) { if (selectedPage)
selectedPage.key = getKey(); selectedPage.key = getKey();
}
}; };
const closeOtherPage = (path) => { const closeOtherPage = (path) => {
const selectedPage = findPage(path || unref(route.path)); const selectedPage = findPage(path || unref(route.path));
@ -132,9 +139,9 @@ export default {
}; };
const getPageKey = (_route) => { const getPageKey = (_route) => {
const selectedPage = findPage(_route.path); const selectedPage = findPage(_route.path);
if (selectedPage) { if (selectedPage)
return selectedPage.key; return selectedPage.key;
}
return ''; return '';
}; };
const handlerMore = (key) => { const handlerMore = (key) => {
@ -163,6 +170,7 @@ export default {
}, },
}; };
</script> </script>
<style lang="less"> <style lang="less">
.layout-content-tabs { .layout-content-tabs {
background: rgb(255, 255, 255); background: rgb(255, 255, 255);

View File

@ -1,29 +1,35 @@
<template> <template>
<monaco-editor ref="editorRef" v-model="json" :language="language" height="200px" check /> <MonacoEditor ref="editorRef" v-model="json" :language="language" height="200px" check />
{{ json }} {{ json }}
</template> </template>
<config> <config>
{ {
"name": "editor", "name": "editor",
"title": "$editor" "title": "$editor"
} }
</config> </config>
<script> <script>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { MonacoEditor } from '@fesjs/fes'; import { MonacoEditor, useLayout } from '@fesjs/fes';
export default { export default {
components: { components: {
MonacoEditor, MonacoEditor,
}, },
setup() { setup() {
console.log('editor.vue'); const { closeTab } = useLayout();
const editorRef = ref(); const editorRef = ref();
const json = ref(''); const json = ref('');
const language = ref('json'); const language = ref('json');
onMounted(() => { onMounted(() => {
setTimeout(() => { setTimeout(() => {
language.value = 'html'; language.value = 'html';
}, 2000);
setTimeout(() => {
closeTab();
}, 3000); }, 3000);
}); });
return { return {

View File

@ -1,7 +1,9 @@
<template> <template>
<div class="page"> <div class="page">
home home
<FButton class="m-2" @click="go">Button</FButton> <FButton class="m-2" @click="go">
Button
</FButton>
</div> </div>
</template> </template>
@ -13,12 +15,13 @@ defineRouteMeta({
name: 'index', name: 'index',
title: '$home', title: '$home',
}); });
console.log('123123'.replaceAll('123', '234')); console.log('123123'.replaceAll('123', '234'));
const router = useRouter(); const router = useRouter();
const go = () => { function go() {
router.push('/editor'); router.push('/editor');
}; }
</script> </script>
<style> <style>

51
pnpm-lock.yaml generated
View File

@ -438,6 +438,9 @@ importers:
'@fesjs/utils': '@fesjs/utils':
specifier: ^3.0.1 specifier: ^3.0.1
version: link:../fes-utils version: link:../fes-utils
'@vueuse/core':
specifier: ^10.7.0
version: 10.7.0(vue@3.2.47)
vue: vue:
specifier: ^3.2.47 specifier: ^3.2.47
version: 3.2.47 version: 3.2.47
@ -5588,6 +5591,10 @@ packages:
/@types/web-bluetooth@0.0.16: /@types/web-bluetooth@0.0.16:
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==} resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
/@types/web-bluetooth@0.0.20:
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
dev: false
/@types/ws@8.5.10: /@types/ws@8.5.10:
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
dependencies: dependencies:
@ -6099,6 +6106,18 @@ packages:
- vue - vue
dev: true dev: true
/@vueuse/core@10.7.0(vue@3.2.47):
resolution: {integrity: sha512-4EUDESCHtwu44ZWK3Gc/hZUVhVo/ysvdtwocB5vcauSV4B7NiGY5972WnsojB3vRNdxvAt7kzJWE2h9h7C9d5w==}
dependencies:
'@types/web-bluetooth': 0.0.20
'@vueuse/metadata': 10.7.0
'@vueuse/shared': 10.7.0(vue@3.2.47)
vue-demi: 0.14.6(vue@3.2.47)
transitivePeerDependencies:
- '@vue/composition-api'
- vue
dev: false
/@vueuse/core@9.13.0(vue@3.2.47): /@vueuse/core@9.13.0(vue@3.2.47):
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==} resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
dependencies: dependencies:
@ -6115,6 +6134,10 @@ packages:
resolution: {integrity: sha512-APSjlABrV+Q74c+FR0kFETvcN9W2pAaT3XF3WwqWUuk4srmVxv7DY4WshZxK2KYk1+MVY0Fus6J1Hk/JXVm6Aw==} resolution: {integrity: sha512-APSjlABrV+Q74c+FR0kFETvcN9W2pAaT3XF3WwqWUuk4srmVxv7DY4WshZxK2KYk1+MVY0Fus6J1Hk/JXVm6Aw==}
dev: true dev: true
/@vueuse/metadata@10.7.0:
resolution: {integrity: sha512-GlaH7tKP2iBCZ3bHNZ6b0cl9g0CJK8lttkBNUX156gWvNYhTKEtbweWLm9rxCPIiwzYcr/5xML6T8ZUEt+DkvA==}
dev: false
/@vueuse/metadata@9.13.0: /@vueuse/metadata@9.13.0:
resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==} resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
dev: false dev: false
@ -6128,6 +6151,15 @@ packages:
- vue - vue
dev: true dev: true
/@vueuse/shared@10.7.0(vue@3.2.47):
resolution: {integrity: sha512-kc00uV6CiaTdc3i1CDC4a3lBxzaBE9AgYNtFN87B5OOscqeWElj/uza8qVDmk7/U8JbqoONLbtqiLJ5LGRuqlw==}
dependencies:
vue-demi: 0.14.6(vue@3.2.47)
transitivePeerDependencies:
- '@vue/composition-api'
- vue
dev: false
/@vueuse/shared@9.13.0(vue@3.2.47): /@vueuse/shared@9.13.0(vue@3.2.47):
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==} resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
dependencies: dependencies:
@ -6577,8 +6609,8 @@ packages:
peerDependencies: peerDependencies:
postcss: ^8.1.0 postcss: ^8.1.0
dependencies: dependencies:
browserslist: 4.21.5 browserslist: 4.22.1
caniuse-lite: 1.0.30001481 caniuse-lite: 1.0.30001561
fraction.js: 4.2.0 fraction.js: 4.2.0
normalize-range: 0.1.2 normalize-range: 0.1.2
picocolors: 1.0.0 picocolors: 1.0.0
@ -14283,6 +14315,21 @@ packages:
vue: 3.3.4 vue: 3.3.4
dev: true dev: true
/vue-demi@0.14.6(vue@3.2.47):
resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
peerDependencies:
'@vue/composition-api': ^1.0.0-rc.1
vue: ^3.0.0-0 || ^2.6.0
peerDependenciesMeta:
'@vue/composition-api':
optional: true
dependencies:
vue: 3.2.47
dev: false
/vue-eslint-parser@9.3.2(eslint@8.54.0): /vue-eslint-parser@9.3.2(eslint@8.54.0):
resolution: {integrity: sha512-q7tWyCVaV9f8iQyIA5Mkj/S6AoJ9KBN8IeUSf3XEmBrOtxOZnfTg5s4KClbZBCK3GtnT/+RyCLZyDHuZwTuBjg==} resolution: {integrity: sha512-q7tWyCVaV9f8iQyIA5Mkj/S6AoJ9KBN8IeUSf3XEmBrOtxOZnfTg5s4KClbZBCK3GtnT/+RyCLZyDHuZwTuBjg==}
engines: {node: ^14.17.0 || >=16.0.0} engines: {node: ^14.17.0 || >=16.0.0}