├── example ├── snapshot.png ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── assets │ │ └── logo.png │ ├── views │ │ ├── bar │ │ │ └── index.vue │ │ ├── foo │ │ │ └── index.vue │ │ ├── base │ │ │ ├── elem │ │ │ │ ├── index.vue │ │ │ │ ├── EleDrawerExample.vue │ │ │ │ └── EleExample.vue │ │ │ ├── antd │ │ │ │ ├── index.vue │ │ │ │ ├── AntdDrawerExample.vue │ │ │ │ └── AntdExample.vue │ │ │ └── view │ │ │ │ ├── index.vue │ │ │ │ ├── ViewDrawerExample.vue │ │ │ │ └── ViewExample.vue │ │ └── match │ │ │ └── index.vue │ ├── components │ │ ├── ExampleCard.vue │ │ ├── Footer.vue │ │ ├── Title.vue │ │ └── HelloWorld.vue │ ├── router.js │ ├── main.js │ └── App.vue ├── babel.config.js ├── vue.config.js ├── .eslintrc.js ├── .gitignore ├── package.json └── README.md ├── babel.config.js ├── docs ├── .vuepress │ ├── public │ │ └── logo.png │ └── config.js ├── README.md └── guide │ ├── README.md │ ├── api.md │ ├── install.md │ ├── micro.md │ └── use.md ├── micro-frontend-example ├── sub-app1 │ ├── .browserslistrc │ ├── babel.config.js │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── src │ │ ├── assets │ │ │ └── logo.png │ │ ├── views │ │ │ ├── 404.vue │ │ │ ├── About.vue │ │ │ └── Home.vue │ │ ├── App.vue │ │ ├── router │ │ │ └── index.js │ │ ├── main.js │ │ └── components │ │ │ └── HelloWorld.vue │ ├── .gitignore │ ├── .eslintrc.js │ ├── package.json │ └── vue.config.js └── sub-app2 │ ├── .browserslistrc │ ├── babel.config.js │ ├── public │ ├── favicon.ico │ └── index.html │ ├── src │ ├── assets │ │ └── logo.png │ ├── views │ │ ├── 404.vue │ │ ├── Home.vue │ │ └── About.vue │ ├── App.vue │ ├── store │ │ └── index.js │ ├── components │ │ └── HelloWorld.vue │ ├── router │ │ └── index.js │ └── main.js │ ├── .gitignore │ ├── .eslintrc.js │ ├── package.json │ └── vue.config.js ├── README.md ├── .gitignore ├── rollup.config.js ├── src ├── modifyOptions.js ├── createCreateSlot.js ├── getSlotPayload.js ├── setGlobalHeader.js ├── locationMatcher.js ├── event.js ├── index.js ├── drawer.js └── modal.js ├── deploy.sh ├── package.json └── index.d.ts /example/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzyhbk/vue-create-dm/HEAD/example/snapshot.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzyhbk/vue-create-dm/HEAD/example/public/favicon.ico -------------------------------------------------------------------------------- /example/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzyhbk/vue-create-dm/HEAD/example/src/assets/logo.png -------------------------------------------------------------------------------- /docs/.vuepress/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzyhbk/vue-create-dm/HEAD/docs/.vuepress/public/logo.png -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 5 versions 3 | Firefox ESR 4 | not dead 5 | not ie < 11 6 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 5 versions 3 | Firefox ESR 4 | not dead 5 | not ie < 11 6 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /example/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: 3 | process.env.NODE_ENV === 'production' ? '/vue-create-dm/example/' : '/', 4 | }; 5 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzyhbk/vue-create-dm/HEAD/micro-frontend-example/sub-app1/public/favicon.ico -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzyhbk/vue-create-dm/HEAD/micro-frontend-example/sub-app1/src/assets/logo.png -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzyhbk/vue-create-dm/HEAD/micro-frontend-example/sub-app2/public/favicon.ico -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzyhbk/vue-create-dm/HEAD/micro-frontend-example/sub-app2/src/assets/logo.png -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/src/views/404.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/src/views/404.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 4 | 12 | 13 | -------------------------------------------------------------------------------- /example/src/views/bar/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | extends: ['plugin:vue/essential', 'eslint:recommended'], 7 | rules: { 8 | 'no-debugger': 'off', 9 | 'no-empty': 'off', 10 | }, 11 | parserOptions: { 12 | parser: 'babel-eslint', 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/src/components/ExampleCard.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /example/src/views/foo/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | 24 | package-lock.json -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | 24 | package-lock.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-create-dm 2 | 3 | [![vue-create-dm](https://img.shields.io/badge/dynamic/json?color=007ec6&label=npm&query=$['dist-tags'].latest&url=https://registry.npmjs.org/vue-create-dm)](https://www.npmjs.com/package/vue-create-dm) 4 | 5 | 使用函数优雅地创建弹框抽屉 6 | 7 | ## [文档地址](https://hzyhbk.github.io/vue-create-dm/) 8 | 9 | ## [在线例子地址](https://hzyhbk.github.io/vue-create-dm/example/#/antd) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | docs/.vuepress/dist 5 | docs/.vuepress/public/example 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | pnpm-debug.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | *.tgz 26 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | Vue.use(Vuex); 5 | 6 | const store = new Vuex.Store({ 7 | state: { 8 | count: 0, 9 | }, 10 | mutations: { 11 | increment(state) { 12 | state.count++; 13 | }, 14 | decrement(state) { 15 | state.count--; 16 | }, 17 | }, 18 | }); 19 | 20 | export default store; 21 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const { uglify } = require('rollup-plugin-uglify'); 2 | const babel = require('rollup-plugin-babel'); 3 | 4 | module.exports = { 5 | input: 'src/index.js', 6 | output: { 7 | file: 'dist/vue-create-dm.umd.min.js', 8 | name: 'vueCreateDM', 9 | format: 'umd', 10 | exports: 'named', 11 | }, 12 | plugins: [ 13 | babel({ runtimeHelpers: true, exclude: 'node_modules/**' }), 14 | uglify(), 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /src/modifyOptions.js: -------------------------------------------------------------------------------- 1 | // arg1 {} arg2 "" 2 | // arg1 "" arg2 {} 3 | // return location "" coverBaseOption {} 4 | export function modifyOptions(baseOption, arg1 = {}, arg2 = '') { 5 | let coverBaseOption = arg1; 6 | let location = arg2; 7 | if (typeof arg1 === 'string') { 8 | location = arg1; 9 | coverBaseOption = arg2; 10 | } 11 | return { 12 | location, 13 | baseOption: { ...baseOption, ...coverBaseOption }, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | parserOptions: { 11 | parser: 'babel-eslint' 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'vue/no-parsing-error': [2, { "x-invalid-end-tag": false }] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | parserOptions: { 11 | parser: 'babel-eslint' 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'vue/no-parsing-error': [2, { "x-invalid-end-tag": false }] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 9 | 23 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 17 | 33 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /example/src/views/base/elem/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/src/views/base/antd/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/src/views/base/view/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 当发生错误时中止脚本 4 | set -e 5 | 6 | # cd到example 7 | cd example 8 | 9 | # 构建 10 | npm run build 11 | 12 | #复制 dist 到 docs 下 13 | cp -Rf dist/* ../docs/.vuepress/public/example/ 14 | 15 | # 执行完退出 16 | cd - 17 | 18 | # 构建 19 | npm run docs:build 20 | 21 | # cd 到构建输出的目录下 22 | cd docs/.vuepress/dist 23 | 24 | # 部署到自定义域域名 25 | # echo 'www.example.com' > CNAME 26 | 27 | git init 28 | git add -A 29 | git commit -m 'deploy' 30 | 31 | # 部署到 https://.github.io 32 | # git push -f git@github.com:/.github.io.git master 33 | 34 | # 部署到 https://.github.io/ 35 | git push -f git@github.com:hzyhbk/vue-create-dm.git master:gh-pages 36 | 37 | cd - -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import foo from './views/foo/index.vue'; 4 | import bar from './views/bar/index.vue'; 5 | import antd from './views/base/antd/index.vue'; 6 | import view from './views/base/view/index.vue'; 7 | import elem from './views/base/elem/index.vue'; 8 | import match from './views/match/index.vue'; 9 | 10 | Vue.use(VueRouter); 11 | const routes = [ 12 | { path: '/foo', component: foo }, 13 | { path: '/bar', component: bar }, 14 | { path: '/antd', component: antd }, 15 | { path: '/view', component: view }, 16 | { path: '/elem', component: elem }, 17 | { path: '/match', component: match }, 18 | ]; 19 | const router = new VueRouter({ routes }); 20 | 21 | export default router; 22 | -------------------------------------------------------------------------------- /example/src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | base: '/vue-create-dm/', 3 | title: 'VueCreateDM', 4 | themeConfig: { 5 | nav: [ 6 | { 7 | text: '在线地址', 8 | link: 'https://hzyhbk.github.io/vue-create-dm/example/#/antd', 9 | }, 10 | { 11 | text: 'Github', 12 | link: 'https://github.com/hzyhbk/vue-create-dm', 13 | }, 14 | { 15 | text: 'ReactCreateDM', 16 | link: 'https://hzyhbk.github.io/react-create-dm', 17 | }, 18 | ], 19 | sidebar: { 20 | '/guide/': [ 21 | { 22 | title: '指南', 23 | collapsable: false, 24 | children: ['', 'install', 'use', 'api'], 25 | }, 26 | { 27 | title: '进阶', 28 | collapsable: false, 29 | children: ['micro'], 30 | }, 31 | ], 32 | }, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import Home from '../views/Home.vue'; 4 | 5 | Vue.use(VueRouter); 6 | 7 | const routes = [ 8 | { 9 | path: '/', 10 | name: 'Home', 11 | component: Home, 12 | }, 13 | { 14 | path: '/about', 15 | name: 'About', 16 | // route level code-splitting 17 | // this generates a separate chunk (about.[hash].js) for this route 18 | // which is lazy-loaded when the route is visited. 19 | component: () => 20 | import(/* webpackChunkName: "about" */ '../views/About.vue'), 21 | }, 22 | { 23 | path: '*', 24 | component: () => import(/* webpackChunkName: "404" */ '../views/404.vue'), 25 | }, 26 | ]; 27 | 28 | const router = new VueRouter({ 29 | mode: 'history', 30 | base: '/sub1', 31 | routes, 32 | }); 33 | 34 | export default router; 35 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import Home from '../views/Home.vue'; 4 | 5 | Vue.use(VueRouter); 6 | 7 | const routes = [ 8 | { 9 | path: '/', 10 | name: 'Home', 11 | component: Home, 12 | }, 13 | { 14 | path: '/about', 15 | name: 'About', 16 | // route level code-splitting 17 | // this generates a separate chunk (about.[hash].js) for this route 18 | // which is lazy-loaded when the route is visited. 19 | component: () => 20 | import(/* webpackChunkName: "about" */ '../views/About.vue'), 21 | }, 22 | { 23 | path: '*', 24 | component: () => import(/* webpackChunkName: "404" */ '../views/404.vue'), 25 | }, 26 | ]; 27 | 28 | const router = new VueRouter({ 29 | mode: 'history', 30 | base: '/sub2', 31 | routes, 32 | }); 33 | 34 | export default router; 35 | -------------------------------------------------------------------------------- /example/src/components/Title.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 35 | 36 | 47 | -------------------------------------------------------------------------------- /src/createCreateSlot.js: -------------------------------------------------------------------------------- 1 | export function createDrawerSlot(createElement, slotVnMap, close) { 2 | return (options, slot = 'default') => { 3 | const slotElement = createElement(options.template, { 4 | ...options, 5 | on: { 6 | ...options.on, 7 | close, 8 | }, 9 | slot, 10 | }); 11 | slotVnMap[slot] = slotElement; 12 | return slotElement; 13 | }; 14 | } 15 | export function createModalSlot( 16 | createElement, 17 | slotVnMap, 18 | confirmLoading, 19 | close, 20 | ok 21 | ) { 22 | return (options, slot = 'default') => { 23 | const slotElement = createElement(options.template, { 24 | ...options, 25 | props: { 26 | ...options.props, 27 | confirmLoading, 28 | }, 29 | on: { 30 | ...options.on, 31 | close, 32 | ok, 33 | }, 34 | slot, 35 | }); 36 | slotVnMap[slot] = slotElement; 37 | return slotElement; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sub-app1", 3 | "version": "0.1.0", 4 | "license": "ISC", 5 | "private": true, 6 | "scripts": { 7 | "serve": "vue-cli-service serve --port 8091", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "ant-design-vue": "^1.6.5", 13 | "core-js": "^3.6.5", 14 | "single-spa-vue": "^1.8.2", 15 | "vue": "^2.6.11", 16 | "vue-create-dm": "^0.0.20", 17 | "vue-router": "^3.3.4", 18 | "vuex": "^3.4.0" 19 | }, 20 | "devDependencies": { 21 | "@vue/cli-plugin-babel": "~4.4.0", 22 | "@vue/cli-plugin-eslint": "~4.4.0", 23 | "@vue/cli-service": "~4.4.0", 24 | "babel-eslint": "^10.1.0", 25 | "eslint": "^6.7.2", 26 | "eslint-plugin-vue": "^6.2.2", 27 | "less": "^3.0.4", 28 | "less-loader": "^5.0.0", 29 | "vue-template-compiler": "^2.6.11", 30 | "webpack-manifest-plugin": "^2.2.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sub-app2", 3 | "version": "0.1.0", 4 | "license": "ISC", 5 | "private": true, 6 | "scripts": { 7 | "serve": "vue-cli-service serve --port 8092", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "ant-design-vue": "^1.6.5", 13 | "core-js": "^3.6.5", 14 | "single-spa-vue": "^1.8.2", 15 | "vue": "^2.6.11", 16 | "vue-create-dm": "^0.0.20", 17 | "vue-router": "^3.3.4", 18 | "vuex": "^3.4.0" 19 | }, 20 | "devDependencies": { 21 | "@vue/cli-plugin-babel": "~4.4.0", 22 | "@vue/cli-plugin-eslint": "~4.4.0", 23 | "@vue/cli-service": "~4.4.0", 24 | "babel-eslint": "^10.1.0", 25 | "eslint": "^6.7.2", 26 | "eslint-plugin-vue": "^6.2.2", 27 | "less": "^3.0.4", 28 | "less-loader": "^5.0.0", 29 | "vue-template-compiler": "^2.6.11", 30 | "webpack-manifest-plugin": "^2.2.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/getSlotPayload.js: -------------------------------------------------------------------------------- 1 | // 获取子组件实例,调用约定的函数,获取返回值 2 | export async function getSlotPayload(slotVnMap, payloadSlot) { 3 | if (payloadSlot) { 4 | let slotInstance; 5 | // 传 true 默认从 default slot 取 6 | if (typeof payloadSlot === 'boolean') { 7 | slotInstance = slotVnMap.default; 8 | } 9 | if (typeof payloadSlot === 'string') { 10 | slotInstance = slotVnMap[payloadSlot]; 11 | } 12 | if (slotInstance) { 13 | // 约定 子组件需要提供 providePayload 方法 14 | if (!slotInstance.componentInstance.providePayload) { 15 | console.warn('子组件需要提供 providePayload 方法来返回参数.'); 16 | return null; 17 | } else { 18 | const fn = slotInstance.componentInstance.providePayload; 19 | let slotPayload; 20 | if (typeof fn.then === 'function') { 21 | slotPayload = await slotInstance.componentInstance.providePayload(); 22 | } else { 23 | slotPayload = slotInstance.componentInstance.providePayload(); 24 | } 25 | return slotPayload; 26 | } 27 | } 28 | return null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import spVue from 'single-spa-vue'; 4 | import router from './router'; 5 | import VueCreateDM from 'vue-create-dm'; 6 | import Antd, { Modal as antdModal, Drawer as antdDrawer } from 'ant-design-vue'; 7 | import 'ant-design-vue/dist/antd.css'; 8 | Vue.use(Antd); 9 | Vue.use(VueCreateDM, { 10 | antdModal, 11 | antdDrawer, 12 | }); 13 | 14 | Vue.config.productionTip = false; 15 | 16 | const vueOptions = { 17 | render: (h) => h(App), 18 | router, 19 | }; 20 | 21 | const lifecycles = spVue({ 22 | Vue, 23 | appOptions: vueOptions, 24 | }); 25 | 26 | export const bootstrap = function (props) { 27 | return lifecycles.bootstrap(props); 28 | }; 29 | 30 | export const mount = function (props) { 31 | return lifecycles.mount(props); 32 | }; 33 | 34 | export const unmount = function (props) { 35 | return lifecycles.unmount(props); 36 | }; 37 | 38 | export const update = function (props) { 39 | console.error(JSON.stringify(props)); 40 | return lifecycles.update(props); 41 | }; 42 | 43 | export const $router = router; 44 | export const $Vue = new Vue(); 45 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/vue.config.js: -------------------------------------------------------------------------------- 1 | const packageName = require('./package.json').name; 2 | const ManifestPlugin = require('webpack-manifest-plugin'); 3 | 4 | module.exports = { 5 | publicPath: 6 | process.env.NODE_ENV === 'production' 7 | ? 'http://localhost:8091' 8 | : 'http://localhost:8091', 9 | 10 | configureWebpack: (config) => { 11 | config.output.libraryTarget = 'umd'; 12 | config.output.library = packageName; 13 | config.output.jsonpFunction = `webpackJsonp_${packageName}`; 14 | 15 | config.optimization.splitChunks.cacheGroups = {}; 16 | 17 | // 外部依赖,通用包从root模块加载 18 | config.externals = ['vue-router', 'vuex']; 19 | config.plugins.push( 20 | new ManifestPlugin({ 21 | fileName: 'manifest-initial.json', 22 | filter: function (option) { 23 | return option.isInitial; 24 | }, 25 | }) 26 | ); 27 | }, 28 | 29 | devServer: { 30 | headers: { 31 | // 子应用如果与root应用不在同一个域名下,需要devops配置允许跨域 32 | 'Access-Control-Allow-Origin': '*', 33 | }, 34 | }, 35 | 36 | css: { 37 | extract: false, 38 | }, 39 | 40 | // filenameHashing: false 41 | }; 42 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/vue.config.js: -------------------------------------------------------------------------------- 1 | const packageName = require('./package.json').name; 2 | const ManifestPlugin = require('webpack-manifest-plugin'); 3 | 4 | module.exports = { 5 | publicPath: 6 | process.env.NODE_ENV === 'production' 7 | ? 'http://localhost:8092' 8 | : 'http://localhost:8092', 9 | 10 | configureWebpack: (config) => { 11 | config.output.libraryTarget = 'umd'; 12 | config.output.library = packageName; 13 | config.output.jsonpFunction = `webpackJsonp_${packageName}`; 14 | 15 | config.optimization.splitChunks.cacheGroups = {}; 16 | 17 | // 外部依赖,通用包从root模块加载 18 | config.externals = ['vue-router', 'vuex']; 19 | config.plugins.push( 20 | new ManifestPlugin({ 21 | fileName: 'manifest-initial.json', 22 | filter: function (option) { 23 | return option.isInitial; 24 | }, 25 | }) 26 | ); 27 | }, 28 | 29 | devServer: { 30 | headers: { 31 | // 子应用如果与root应用不在同一个域名下,需要devops配置允许跨域 32 | 'Access-Control-Allow-Origin': '*', 33 | }, 34 | }, 35 | 36 | css: { 37 | extract: false, 38 | }, 39 | 40 | // filenameHashing: false 41 | }; 42 | -------------------------------------------------------------------------------- /example/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import Antd from 'ant-design-vue'; 4 | import { Modal as antdModal, Drawer as antdDrawer } from 'ant-design-vue'; 5 | import { Modal as viewModal, Drawer as viewDrawer } from 'view-design'; 6 | import { Dialog as eleModal, Drawer as eleDrawer } from 'element-ui'; 7 | import Vuex from 'vuex'; 8 | import router from './router'; 9 | import 'ant-design-vue/dist/antd.css'; 10 | import 'view-design/dist/styles/iview.css'; 11 | import 'element-ui/lib/theme-chalk/index.css'; 12 | import VueCreateDM from '../../src'; 13 | import globalHeader from './components/Title'; 14 | 15 | Vue.config.productionTip = false; 16 | Vue.use(Antd); 17 | Vue.use(Vuex); 18 | export const store = new Vuex.Store({ 19 | state: { 20 | count: 0, 21 | }, 22 | mutations: { 23 | increment(state) { 24 | state.count++; 25 | }, 26 | }, 27 | }); 28 | Vue.use(VueCreateDM, { 29 | antdModal, 30 | antdDrawer, 31 | viewModal, 32 | viewDrawer, 33 | eleModal, 34 | eleDrawer, 35 | // store, 36 | // router, 37 | drawerGlobalHeader: globalHeader, 38 | }); 39 | 40 | new Vue({ 41 | render: (h) => h(App), 42 | store, 43 | router, 44 | }).$mount('#app'); 45 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app2/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import spVue from 'single-spa-vue'; 4 | import router from './router'; 5 | import store from './store'; 6 | import VueCreateDM from 'vue-create-dm'; 7 | import Antd, { Modal as antdModal, Drawer as antdDrawer } from 'ant-design-vue'; 8 | import 'ant-design-vue/dist/antd.css'; 9 | Vue.use(Antd); 10 | Vue.use(VueCreateDM, { 11 | antdModal, 12 | antdDrawer, 13 | router, 14 | store, 15 | }); 16 | 17 | Vue.config.productionTip = false; 18 | 19 | const vueOptions = { 20 | render: (h) => h(App), 21 | router, 22 | store, 23 | }; 24 | 25 | const lifecycles = spVue({ 26 | Vue, 27 | appOptions: vueOptions, 28 | }); 29 | 30 | export const bootstrap = function(props) { 31 | return lifecycles.bootstrap(props); 32 | }; 33 | 34 | export const mount = function(props) { 35 | return lifecycles.mount(props); 36 | }; 37 | 38 | export const unmount = function(props) { 39 | return lifecycles.unmount(props); 40 | }; 41 | 42 | export const update = function(props) { 43 | console.error(JSON.stringify(props)); 44 | return lifecycles.update(props); 45 | }; 46 | 47 | export const $router = router; 48 | export const $Vue = new Vue(); 49 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-create-dm", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "ant-design-vue": "^1.6.3", 12 | "core-js": "^3.6.5", 13 | "element-ui": "^2.13.2", 14 | "view-design": "^4.3.2", 15 | "vue": "^2.6.11", 16 | "vue-create-dm": "^0.0.9", 17 | "vue-router": "^3.3.4", 18 | "vuex": "^3.5.1" 19 | }, 20 | "devDependencies": { 21 | "@vue/cli-plugin-babel": "^4.4.0", 22 | "@vue/cli-plugin-eslint": "^4.4.0", 23 | "@vue/cli-service": "^4.4.0", 24 | "babel-eslint": "^10.1.0", 25 | "eslint": "^6.7.2", 26 | "eslint-plugin-vue": "^6.2.2", 27 | "vue-template-compiler": "^2.6.11" 28 | }, 29 | "eslintConfig": { 30 | "root": true, 31 | "env": { 32 | "node": true 33 | }, 34 | "extends": [ 35 | "plugin:vue/essential", 36 | "eslint:recommended" 37 | ], 38 | "parserOptions": { 39 | "parser": "babel-eslint" 40 | }, 41 | "rules": {} 42 | }, 43 | "browserslist": [ 44 | "> 1%", 45 | "last 2 versions", 46 | "not dead" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroText: VueCreateDM 4 | tagline: 使用函数优雅地创建弹框抽屉 5 | actionText: 了解更多 → 6 | actionLink: /guide/ 7 | features: 8 | - title: 函数式编程 9 | details: 使用函数优雅地创建弹框抽屉,远离让人头疼的 isXXVisible。 10 | - title: 正常触发生命周期 11 | details: 弹框抽屉子组件的 created、mounted、destoryed 生命周期按照正常逻辑触发。 12 | - title: 与父组件通信 13 | details: 约定子组件提供 providePayload 函数来和创建它的父组件通信。 14 | --- 15 | 16 | ## 当前版本 17 | ![vue-create-dm](https://img.shields.io/badge/dynamic/json?color=007ec6&label=npm&query=$['dist-tags'].latest&url=https://registry.npmjs.org/vue-create-dm) 18 | ## 安装 19 | 20 | ``` bash 21 | yarn add vue-create-dm 22 | ``` 23 | ## 使用 24 | 25 | ``` js 26 | import Vue from 'vue'; 27 | import VueCreateDM from 'vue-create-dm'; 28 | import { Modal as antdModal, Drawer as antdDrawer } from 'ant-design-vue'; 29 | import { Modal as viewModal, Drawer as viewDrawer } from 'view-design'; 30 | import { Dialog as eleModal, Drawer as eleDrawer } from 'element-ui'; 31 | import store from './store' 32 | import router from './router'; 33 | import modalGlobalHeader from './components/modalGlobalHeader'; 34 | import drawerGlobalHeader from './components/drawerGlobalHeader'; 35 | 36 | Vue.use(VueCreateDM, { 37 | antdModal, 38 | antdDrawer, 39 | viewModal, 40 | viewDrawer, 41 | eleModal, 42 | eleDrawer, 43 | store, 44 | router, 45 | modalGlobalHeader, 46 | drawerGlobalHeader, 47 | }); 48 | ``` -------------------------------------------------------------------------------- /docs/guide/README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | 使用函数优雅地创建 [ant-design-vue](https://www.antdv.com/docs/vue/introduce-cn/)、[view-design](https://www.iviewui.com/)、[ElementUI](https://element.eleme.cn/#/zh-CN) 的 Drawer 和 Modal。 4 | 5 | ## 例子 6 | [在线地址](https://hzyhbk.github.io/vue-create-dm/example/#/antd) 7 | 8 | 代码见 `example` 文件夹。 9 | 10 | ## 特性 11 | * 通过函数来创建`Modal`或`Drawer`组件 12 | * `Modal`、`Drawer`的内容子组件的`created`、`mounted`、`destoryed`生命周期按照正常逻辑触发 13 | * `Modal`、`Drawer`支持分别注册全局头部组件(需要接收名为 `title` 的 `props`) 14 | * 支持传入 `title`、`content`、`footer` 插槽 15 | * 支持`Modal`、`Drawer`与父组件通信 16 | * 支持子组件获取 `this.$store` 和 `this.$router` 17 | * 支持传入路由来匹配**内容**组件, 18 | * 若传入`url`比如 https://www.baidu.com ,则以 `iframe`形式展示 19 | * 若传入 `相对路由`(比如 /foo, /bar),则获取匹配的路由组件展示 20 | 21 | ## 为什么 22 | 在使用弹窗抽屉组件的过程中,你是否也曾遇到过以下场景: 23 | 1. 一个项目里有许多的弹窗和抽屉类型的交互,有时甚至一个页面组件里就有许多弹窗和抽屉组件,原生的使用方式是先在父组件中写好弹框抽屉组件,然后通过`visible`变量来控制弹窗的显示隐藏,当弹窗抽屉一多,看着各种`xxVisible`让人感觉很混乱。 24 | 2. 弹窗抽屉内包含的子组件的生命周期并没有按我们预想的逻辑触发,我们想打开弹窗抽屉的时候才触发内容子组件的`created`和`mounted`生命周期,然而实际上却并不是;我们希望关闭的时候可以调用子组件的`destoryed`生命周期,可是目前的UI框架大多只是把组件设置为`display:none`了,并没有完全卸载子组件, 25 | 3. `antd`提供了`destroyOnClose`参数支持关闭时销毁子元素,但也没法解决上面说到的1,2两点问题。组件库虽然也有提供通过函数打开弹窗的方法,但那些都是一些简单的弹框,可配置的参数不多,自由度也不够高。 26 | 27 | 因此就有了`vue-create-dm`这个库,`dm`就是分别取了`Drawer`和`Modal`的第一个字母组合在一起(为什么不是`md`呢,因为`md`是`markdown`的缩写...)。目前内置支持了`ant-design-vue`、`view-design`和`ElementUI`三个组件库的的弹框抽屉组件,并且提供了各种工具函数可以自己支持其他组件库的弹框抽屉组件。 28 | 29 | -------------------------------------------------------------------------------- /src/setGlobalHeader.js: -------------------------------------------------------------------------------- 1 | export function setGlobalHeader(baseOption, oldOptions) { 2 | //如果有此选项,设置全局header 3 | if (baseOption.globalHeader) { 4 | if (oldOptions.title) { 5 | // title 如果传 string,就用这个作为props 6 | if (typeof oldOptions.title === 'string') { 7 | return { 8 | ...oldOptions, 9 | title: { 10 | template: baseOption.globalHeader, 11 | props: { 12 | // 如果需要更多的props呢 13 | title: oldOptions.title, 14 | }, 15 | }, 16 | }; 17 | } 18 | return oldOptions; 19 | } 20 | // 如果没传 title,而传了 modalProps.title 或 drawerProps.title 21 | // 取这个 title 作为props 22 | if (oldOptions.modalProps && oldOptions.modalProps.title) { 23 | return { 24 | ...oldOptions, 25 | title: { 26 | template: baseOption.globalHeader, 27 | props: { 28 | title: oldOptions.modalProps.title, 29 | }, 30 | }, 31 | }; 32 | } 33 | if (oldOptions.drawerProps && oldOptions.drawerProps.title) { 34 | return { 35 | ...oldOptions, 36 | title: { 37 | template: baseOption.globalHeader, 38 | props: { 39 | title: oldOptions.drawerProps.title, 40 | }, 41 | }, 42 | }; 43 | } 44 | return { 45 | ...oldOptions, 46 | title: { 47 | template: baseOption.globalHeader, 48 | }, 49 | }; 50 | } 51 | return oldOptions; 52 | } 53 | -------------------------------------------------------------------------------- /src/locationMatcher.js: -------------------------------------------------------------------------------- 1 | const URL_REG = /^(((ht|f)tps?):\/\/)?[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?$/; 2 | 3 | // 不要直接调用,this会获取不到 4 | export function locationMatcher(location, baseOptions, oldOptions) { 5 | if (location) { 6 | // 直接传链接 7 | if (URL_REG.test(location)) { 8 | return { 9 | ...oldOptions, 10 | content: { 11 | template: 'iframe', 12 | attrs: { 13 | src: location, 14 | frameborder: 0, 15 | }, 16 | }, 17 | }; 18 | } 19 | // 传的是路由地址 20 | let getMatchedComponents; 21 | let router; 22 | // 这里的this是vm实例 23 | if (this.$router) { 24 | getMatchedComponents = this.$router.getMatchedComponents; 25 | router = this.$router; 26 | } else if (baseOptions.router) { 27 | getMatchedComponents = baseOptions.router.getMatchedComponents; 28 | router = baseOptions.router; 29 | } 30 | if (getMatchedComponents) { 31 | // 这里需要调 router 本身的 this 32 | const [template] = getMatchedComponents.call(router, location); 33 | if (template) { 34 | return { 35 | ...oldOptions, 36 | content: { 37 | ...oldOptions.content, 38 | template, 39 | }, 40 | }; 41 | } 42 | console.warn('[vue-create-dm]: 未匹配到路由对应的组件'); 43 | return oldOptions; 44 | } 45 | console.warn( 46 | `[vue-create-dm]: 未获取到 getMatchedComponents 方法,检查是否正确使用 VueRouter 并且将 router 实例传入 VueCreateDM` 47 | ); 48 | return oldOptions; 49 | } 50 | return oldOptions; 51 | } 52 | -------------------------------------------------------------------------------- /docs/guide/api.md: -------------------------------------------------------------------------------- 1 | 2 | # 类型签名 3 | 4 | ```ts 5 | type SlotOption = { template: Component } & VNodeData; 6 | 7 | type ICallbackParams = { 8 | payload: any; 9 | slotPayload: any; 10 | }; 11 | 12 | interface IBaseOption { 13 | title?: SlotOption | string; 14 | content?: SlotOption; 15 | beforeClose?: (params: ICallbackParams) => void | Promise; 16 | afterClose?: (params: ICallbackParams) => void | Promise; 17 | } 18 | 19 | interface IModalOption extends IBaseOption { 20 | modalProps: Object; 21 | footer?: SlotOption; 22 | onOk?: (params: ICallbackParams) => Boolean | Promise | void | Promise; 23 | payloadSlot?: boolean | 'default' | 'title' | 'footer'; 24 | } 25 | 26 | interface IDrawerOption extends IBaseOption { 27 | drawerProps: Object; 28 | payloadSlot?: boolean | 'default' | 'title'; 29 | } 30 | 31 | type IArgObj = { 32 | component: Component; 33 | globalHeader: Component; 34 | router: any; 35 | store: any; 36 | } 37 | 38 | type ICreateModalFn = (options: IModalOption, arg1?: IArgObj, arg2?: String) => VNode 39 | | (options: IModalOption, arg1?: string, arg2?: IArgObj) => VNode 40 | 41 | type ICreateDrawerFn = (options: IDrawerOption, arg1?: IArgObj, arg2?: String) => VNode 42 | | (options: IDrawerOption, arg1?: string, arg2?: IArgObj) => VNode 43 | 44 | declare module 'vue/types/vue' { 45 | interface Vue { 46 | $createAntdModal: ICreateModalFn; 47 | $createAntdDrawer: ICreateDrawerFn 48 | $createViewModal: ICreateModalFn; 49 | $createViewDrawer: ICreateDrawerFn 50 | $createEleModal: ICreateModalFn; 51 | $createEleDrawer: ICreateDrawerFn 52 | } 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-create-dm", 3 | "version": "0.0.20", 4 | "author": "hzyhbk", 5 | "main": "./dist/vue-create-dm.umd.min.js", 6 | "files": [ 7 | "src", 8 | "dist/*.js", 9 | "index.d.ts" 10 | ], 11 | "scripts": { 12 | "docs:dev": "vuepress dev docs", 13 | "docs:build": "vuepress build docs", 14 | "build": "vue-cli-service build --target lib --inline-vue --name vue-create-dm ./src/index.js", 15 | "build:rollup": "rimraf dist && rollup --config rollup.config.js" 16 | }, 17 | "typings": "index.d.ts", 18 | "keywords": [ 19 | "ant", 20 | "design", 21 | "antd", 22 | "vue", 23 | "vueComponent", 24 | "component", 25 | "components", 26 | "frontend", 27 | "iview", 28 | "viewui" 29 | ], 30 | "dependencies": { 31 | "core-js": "^3.6.5", 32 | "vue": "^2.6.11" 33 | }, 34 | "devDependencies": { 35 | "@vue/cli-plugin-babel": "^4.4.0", 36 | "@vue/cli-plugin-eslint": "^4.4.0", 37 | "@vue/cli-service": "^4.4.0", 38 | "babel-eslint": "^10.1.0", 39 | "eslint": "^6.7.2", 40 | "eslint-plugin-vue": "^6.2.2", 41 | "rollup": "^2.23.0", 42 | "rollup-plugin-babel": "^4.4.0", 43 | "rollup-plugin-uglify": "^6.0.4", 44 | "vue-template-compiler": "^2.6.11", 45 | "vuepress": "^1.5.3" 46 | }, 47 | "eslintConfig": { 48 | "root": true, 49 | "env": { 50 | "node": true 51 | }, 52 | "extends": [ 53 | "plugin:vue/essential", 54 | "eslint:recommended" 55 | ], 56 | "parserOptions": { 57 | "parser": "babel-eslint" 58 | }, 59 | "rules": { 60 | "no-unused-vars": "off", 61 | "no-empty": "off", 62 | "no-undef": "off" 63 | } 64 | }, 65 | "browserslist": [ 66 | "> 1%", 67 | "last 2 versions", 68 | "not dead" 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /docs/guide/install.md: -------------------------------------------------------------------------------- 1 | # 安装 2 | ```bash 3 | yarn add vue-create-dm 4 | ``` 5 | 6 | ## 统一注册 7 | ::: warning 注意 8 | 如果要在子组件内获取 `this.$store` 和 `this.$router` 请把 `VueCreateDM` 的注册放到 `Vuex` 和 `VueRouter` 实例生成之后,并且传入这两个实例 9 | ::: 10 | ::: warning 注意 11 | 如果要自定义全局头部组件,请传入`modalGlobalHeader`,`drawerGlobalHeader`这两个参数,分别对应`Modal`组件的全局头部和`Drawer`组件的全局头部 12 | ::: 13 | 下面演示如何进行全量注册: 14 | ```js 15 | import Vue from 'vue'; 16 | import VueCreateDM from 'vue-create-dm'; 17 | import { Modal as antdModal, Drawer as antdDrawer } from 'ant-design-vue'; 18 | import { Modal as viewModal, Drawer as viewDrawer } from 'view-design'; 19 | import { Dialog as eleModal, Drawer as eleDrawer } from 'element-ui'; 20 | import store from './store' 21 | import router from './router'; 22 | import modalGlobalHeader from './components/modalGlobalHeader'; 23 | import drawerGlobalHeader from './components/drawerGlobalHeader'; 24 | 25 | Vue.use(VueCreateDM, { 26 | antdModal, 27 | antdDrawer, 28 | viewModal, 29 | viewDrawer, 30 | eleModal, 31 | eleDrawer, 32 | store, 33 | router, 34 | modalGlobalHeader, 35 | drawerGlobalHeader, 36 | }); 37 | ``` 38 | ## 单个注册 39 | ::: warning 注意 40 | 如果要在子组件内获取 `this.$store` 和 `this.$router` 请把 `VueCreateDM` 的注册放到 `Vuex` 和 `VueRouter` 实例生成之后,并且传入这两个实例 41 | ::: 42 | ::: warning 注意 43 | 如果要自定义全局头部组件,请传入`globalHeader`参数 44 | ::: 45 | 46 | 下面演示如何单个注册,**其中`component`属性必传**,其余几个都是可选参数 47 | ```js 48 | import Vue from 'vue'; 49 | import store from './store'; 50 | import router from './router'; 51 | import { createAntdDrawer } from 'vue-create-dm'; 52 | import { Drawer } from 'ant-design-vue'; 53 | import globalHeader from '../components/globalHeader'; 54 | 55 | Vue.use(createAntdDrawer, { 56 | component: Drawer, 57 | router, // 子组件需要用到 this.$router 就传 58 | store // 子组件需要用到 this.$store 就传 59 | globalHeader, 60 | }); 61 | ``` 62 | -------------------------------------------------------------------------------- /example/src/views/match/index.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 82 | 83 | 89 | -------------------------------------------------------------------------------- /src/event.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | export const event = new Vue(); 4 | export const VCDM_OPEN_DRAWER = 'VCDM_OPEN_DRAWER'; 5 | export const VCDM_OPEN_MODAL = 'VCDM_OPEN_MODAL'; 6 | 7 | function open(item, options, methodName) { 8 | // 加载对应的子应用 9 | item.app().then((res) => { 10 | if (!res.$router || !res.$Vue) { 11 | console.warn(`子应用[${item.name}]请导出 $router 和 $Vue.`); 12 | return; 13 | } 14 | if (!res.$Vue[methodName]) { 15 | console.warn( 16 | `子应用[${item.name}]的 $Vue 上不存在 ${methodName} 方法,请检查子应用导出的$Vue.` 17 | ); 18 | return; 19 | } 20 | // 通过导出的 router 实例上的 getMatchedComponents 方法获取到要打开的组件 21 | const [component] = res.$router.getMatchedComponents(options.path); 22 | const params = { 23 | content: { 24 | ...options.content, 25 | template: component, 26 | props: options.content.props || {}, 27 | }, 28 | }; 29 | Object.assign(options, params); 30 | // 调用导出的 $Vue.$createAntdDrawer 或者 $Vue.$createAntdModal 31 | res.$Vue[methodName](options); 32 | }); 33 | } 34 | 35 | export function listen(type, microAppConfig, methodName) { 36 | event.$on(type, (options) => { 37 | const { appName, path } = options; 38 | if (!appName) { 39 | console.warn(`请传入子应用名称`); 40 | return; 41 | } 42 | if (!path) { 43 | console.warn(`请传入要打开的子应用页面路由`); 44 | return; 45 | } 46 | microAppConfig.forEach((item) => { 47 | if (item.name === appName) { 48 | open(item, options, methodName); 49 | } 50 | }); 51 | }); 52 | } 53 | export function trigger(...args) { 54 | event.$emit(...args); 55 | } 56 | 57 | // 监听全局打开抽屉事件 58 | export function listenOpenDrawerAction(microAppConfig, methodName) { 59 | listen(VCDM_OPEN_DRAWER, microAppConfig, methodName); 60 | } 61 | // 监听全局打开弹框事件 62 | export function listenOpenModalAction(microAppConfig, methodName) { 63 | listen(VCDM_OPEN_MODAL, microAppConfig, methodName); 64 | } 65 | export function triggerOpenDrawerAction(props) { 66 | trigger(VCDM_OPEN_DRAWER, props); 67 | } 68 | export function triggerOpenModalAction(props) { 69 | trigger(VCDM_OPEN_MODAL, props); 70 | } 71 | -------------------------------------------------------------------------------- /docs/guide/micro.md: -------------------------------------------------------------------------------- 1 | # 微前端中使用 2 | 3 | 基于single-spa,vue-create-dm也支持在微前端项目中**跨项目调用抽屉、弹框**。 4 | 5 | :::tip 前提条件 6 | 1. 需要互相调用抽屉、弹框的子项目**必须**都是 vue 技术栈 7 | 2. 并且都安装注册了vue-create-dm 8 | 3. 要跨项目调用的页面注册成路由页面 9 | ::: 10 | 11 | ## 示例项目 12 | 参考 [micro-frontend-example](https://github.com/hzyhbk/vue-create-dm/tree/master/micro-frontend-example) 文件夹 13 | 14 | ## 主项目使用 15 | 1. 注册子应用的方式需要改成config配置文件的方式。 16 | ```js 17 | const config = [{ 18 | name: 'sub1', 19 | app: () => loadSubApp('sub1', 'http://localhost:8081/manifest-initial.json'), 20 | activeWhen: (location) => location.pathname.startsWith('/sub1'), 21 | customProps: { 22 | domElement: '#app-sub-wrapper', 23 | }, 24 | }] 25 | export default config; 26 | ``` 27 | 2. main.js加入如下代码 28 | ```js 29 | import { listenOpenDrawerAction, triggerOpenDrawerAction } from 'vue-create-dm'; 30 | import config from './micro-frontend/config/config'; 31 | // 监听打开抽屉事件 32 | listenOpenDrawerAction(config, '$createAntdDrawer'); 33 | // 子项目调用打开抽屉的函数 34 | window.triggerOpenDrawerAction = triggerOpenDrawerAction; 35 | ``` 36 | ## 子项目使用 37 | 1. 除了 single-spa 要求导出的生命周期方法之外,子项目的`main.js`需要额外导出一个`router`实例和一个空的`vue`实例。 38 | ```js 39 | import Vue from 'vue'; 40 | import router from './router'; 41 | import store from './store'; 42 | import VueCreateDM from 'vue-create-dm'; 43 | import { Modal as antdModal, Drawer as antdDrawer } from 'ant-design-vue'; 44 | 45 | Vue.use(VueCreateDM, { 46 | antdDrawer, 47 | antdModal, 48 | router, 49 | store, 50 | }); 51 | 52 | export const bootstrap = function(){...} 53 | export const mount = function(){...} 54 | export const unmount = function(){...} 55 | export const update = function(){...} 56 | // 额外导出 57 | export const $router = router; 58 | export const $Vue = new Vue(); 59 | ``` 60 | 2. 需要被跨项目调用的组件,请在子项目的VueRouter路由配置文件中声明。 61 | 3. 子项目**触发**`openDrawer`、`openModal`事件 62 | ```js 63 | window.triggerOpenDrawerAction({ 64 | appName: 'sub2', 65 | path: '/about', 66 | drawerProps: { 67 | title: '子应用二抽屉', 68 | width: '50%', 69 | }, 70 | content: {}, 71 | }); 72 | ``` 73 | ## API 74 | ```ts 75 | type IBaseTriggerOption = { 76 | appName: string; // 子应用名称 77 | path: string; // 子应用要被打开的页面 78 | } 79 | type IModalTriggerOption = IBaseTriggerOption & IModalOption; 80 | type IDrawerTriggerOption = IBaseTriggerOption & IDrawerOption; 81 | 82 | ``` -------------------------------------------------------------------------------- /example/src/App.vue: -------------------------------------------------------------------------------- 1 | 53 | 72 | 98 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | createAntdDrawer, 3 | createViewDrawer, 4 | createDrawer, 5 | createEleDrawer, 6 | } from './drawer'; 7 | import { 8 | createAntdModal, 9 | createViewModal, 10 | createModal, 11 | createEleModal, 12 | } from './modal'; 13 | import { getSlotPayload } from './getSlotPayload'; 14 | import { createDrawerSlot, createModalSlot } from './createCreateSlot'; 15 | import { locationMatcher } from './locationMatcher'; 16 | import { setGlobalHeader } from './setGlobalHeader'; 17 | import { modifyOptions } from './modifyOptions'; 18 | import { 19 | event, 20 | VCDM_OPEN_DRAWER, 21 | VCDM_OPEN_MODAL, 22 | listen, 23 | trigger, 24 | listenOpenDrawerAction, 25 | listenOpenModalAction, 26 | triggerOpenDrawerAction, 27 | triggerOpenModalAction, 28 | } from './event'; 29 | 30 | export { 31 | createAntdDrawer, 32 | createAntdModal, 33 | createViewDrawer, 34 | createViewModal, 35 | createEleDrawer, 36 | createEleModal, 37 | getSlotPayload, 38 | createDrawerSlot, 39 | createModalSlot, 40 | locationMatcher, 41 | setGlobalHeader, 42 | modifyOptions, 43 | createDrawer, 44 | createModal, 45 | VCDM_OPEN_DRAWER, 46 | VCDM_OPEN_MODAL, 47 | event, 48 | listen, 49 | trigger, 50 | listenOpenDrawerAction, 51 | listenOpenModalAction, 52 | triggerOpenDrawerAction, 53 | triggerOpenModalAction, 54 | }; 55 | 56 | export default { 57 | install( 58 | Vue, 59 | { 60 | antdModal, 61 | antdDrawer, 62 | viewModal, 63 | viewDrawer, 64 | eleModal, 65 | eleDrawer, 66 | modalGlobalHeader, 67 | drawerGlobalHeader, 68 | ...restOptions 69 | } 70 | ) { 71 | const modalComponents = [ 72 | { 73 | component: antdModal, 74 | plugin: createAntdModal, 75 | }, 76 | { 77 | component: viewModal, 78 | plugin: createViewModal, 79 | }, 80 | 81 | { 82 | component: eleModal, 83 | plugin: createEleModal, 84 | }, 85 | ]; 86 | const drawerComponents = [ 87 | { 88 | component: antdDrawer, 89 | plugin: createAntdDrawer, 90 | }, 91 | { 92 | component: viewDrawer, 93 | plugin: createViewDrawer, 94 | }, 95 | { 96 | component: eleDrawer, 97 | plugin: createEleDrawer, 98 | }, 99 | ]; 100 | 101 | modalComponents.forEach((cpt) => { 102 | if (cpt.component) { 103 | cpt.plugin.install(Vue, { 104 | component: cpt.component, 105 | globalHeader: modalGlobalHeader, 106 | ...restOptions, 107 | }); 108 | } 109 | }); 110 | drawerComponents.forEach((cpt) => { 111 | if (cpt.component) { 112 | cpt.plugin.install(Vue, { 113 | component: cpt.component, 114 | globalHeader: drawerGlobalHeader, 115 | ...restOptions, 116 | }); 117 | } 118 | }); 119 | }, 120 | }; 121 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 105 | 106 | -------------------------------------------------------------------------------- /micro-frontend-example/sub-app1/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 98 | 99 | 100 | 116 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # 通过函数创建 ant-design-vue 或者 view-design 的 drawer 和 modal 2 | 3 | ## 功能亮点 4 | - [x] 通过函数来创建 Modal 或者 Drawer 组件 5 | - [x] Modal、Drawer 的内容子组件的 created、mounted 生命周期按照正常逻辑触发 6 | - [x] 支持传入 title、content、footer 插槽 7 | - [x] 支持 Modal、Drawer 与父应用通信 8 | 9 | ![image](https://user-images.githubusercontent.com/42671099/87777895-e7386280-c85c-11ea-8dbf-82b115604073.png) 10 | 11 | ## createAntdDrawer、createViewDrawer 12 | 13 | * 会往 title 和 content 子组件内传入一个名为 close 的函数,在子组件内使用 `this.$emit('close', payload)` 触发关闭行为。 14 | * 如果传入 **payloadSlot** 参数,并且相应的子组件存在 `providePayload` 方法,在抽屉关闭时,会先调用此函数获取返回值,然后作为 `beforeClose` 和 `afterClose` 方法的参数传入 15 | 16 | ```js 17 | createAntdDrawer({ 18 | // antd drawer 的所有props 19 | drawerProps: {}, 20 | /* 21 | * 不传 或者 传false 22 | * 传 'title' 调用 标题子组件 的 providePayload 方法 23 | * 传 true 或者 'default' 调用 内容子组件 的 providePayload 方法 24 | */ 25 | payloadSlot: true | false | 'title' | 'default', 26 | // title slot 27 | // drawerProps.title 和 title 同时存在的话,优先使用 title 28 | title: { 29 | template: Title, 30 | props: { 31 | title: '自定义标题组件', 32 | }, 33 | }, 34 | // default slot 35 | content: { 36 | template: HelloWorld, 37 | props: { 38 | msg: 'Welcome to Your Vue.js App', 39 | }, 40 | }, 41 | // 关闭前调用 支持async 42 | beforeClose: function({ payload, slotPayload }) { 43 | console.log('我要关闭了'); 44 | }, 45 | // 关闭后调用 支持async 46 | afterClose: function({ payload, slotPayload }) { 47 | console.log('我已经关闭了'); 48 | }, 49 | }); 50 | ``` 51 | 52 | ## createAntdModal、createViewModal 53 | 54 | * 会往 title、content 和 foter 子组件内传入 close 和 ok 函数和 confirmLoading props,在子组件内使用 `this.$emit('close', payload)` 触发关闭动作, 使用 `this.$emit('ok', payload)` 触发 onOk 回调。子组件内可以使用 confirmLoading 来管理确定按钮的 loading 状态 55 | * 如果传入 **payloadSlot** 参数,并且相应的子组件如果存在 `providePayload` 方法,在点击确定按钮时,会先调用此函数获取返回值,然后作为 `onOk` 方法的一个参数传入 56 | 57 | ```js 58 | createAntdModal({ 59 | // antd modal 的所有props 60 | modalProps: {}, 61 | /* 62 | * 不传 或者 传 false,不调用 63 | * 传 'title' 调用 标题子组件 的 providePayload 方法 64 | * 传 'footer' 调用 页脚子组件 的 providePayload 方法 65 | * 传 true 或者 'default' 调用 内容子组件 的 providePayload 方法 66 | */ 67 | payloadSlot: true | false | 'title' | 'footer' | 'default', 68 | // title slot 69 | // modalProps.title 和 title 同时存在的话,优先使用 title 70 | title: { 71 | template: Title, 72 | props: { 73 | title: '自定义标题组件', 74 | }, 75 | }, 76 | // content slot 77 | content: { 78 | template: HelloWorld, 79 | props: { 80 | msg: 'Welcome to Your Vue.js App', 81 | }, 82 | }, 83 | // footer slot 84 | footer: { 85 | template: Footer, 86 | props: { 87 | cancelText: 'hahah取消', 88 | okText: 'hahah确定', 89 | }, 90 | }, 91 | // 关闭前调用 支持async 92 | beforeClose: function() { 93 | console.log('我要关闭了'); 94 | }, 95 | // 关闭后调用 支持async 96 | afterClose: function() { 97 | console.log('我已经关闭了'); 98 | }, 99 | // 确定按钮回调 支持async 100 | async onOk({ payload, slotPayload}) { 101 | await new Promise((resolve) => { 102 | setTimeout(() => { 103 | console.log('点了确定'); 104 | resolve(); 105 | }, 3000); 106 | }); 107 | }, 108 | }); 109 | ``` -------------------------------------------------------------------------------- /example/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 109 | 110 | 111 | 127 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | VNodeData, 4 | VNode, 5 | CreateElement, 6 | PluginFunction, 7 | } from 'vue'; 8 | import { Vue as _Vue } from 'vue/types/vue'; 9 | 10 | export type IPluginOption = { 11 | antdModal: Component; 12 | antdDrawer: Component; 13 | viewModal: Component; 14 | viewDrawer: Component; 15 | eleModal: Component; 16 | eleDrawer: Component; 17 | router: any; 18 | store: any; 19 | modalGlobalHeader: Component; 20 | drawerGlobalHeader: Component; 21 | }; 22 | 23 | export type SlotOption = { template: Component } & VNodeData; 24 | 25 | export type ICallbackParams = { 26 | payload: any; 27 | slotPayload: any; 28 | }; 29 | 30 | export interface IBaseOption { 31 | title?: SlotOption | string; 32 | content?: SlotOption; 33 | beforeClose?: (params: ICallbackParams) => void | Promise; 34 | afterClose?: (params: ICallbackParams) => void | Promise; 35 | } 36 | export interface IBaseCreateOption { 37 | component: Component; 38 | globalHeader: Component; 39 | titleSlotName: string; //原来组件提供的标题插槽名称 40 | visiblePropName: string; //原来控制抽屉组件显隐的属性名称 41 | router: any; 42 | store: any; 43 | } 44 | export interface ICreateDrawerOptions extends IBaseCreateOption { 45 | closeCbName: string; // 原来组件的关闭回调事件名称 46 | } 47 | export interface IDrawerOption extends IBaseOption { 48 | drawerProps: { [k: string]: any }; 49 | payloadSlot?: boolean | 'default' | 'title'; 50 | } 51 | export type createDrawer = ( 52 | Vue: _Vue, 53 | createOptions: ICreateDrawerOptions, 54 | options: IDrawerOption 55 | ) => VNode; 56 | 57 | export interface IModalOption extends IBaseOption { 58 | modalProps: { [k: string]: any }; 59 | footer?: SlotOption; 60 | onOk?: (params: ICallbackParams) => void | Promise; 61 | payloadSlot?: boolean | 'default' | 'title' | 'footer'; 62 | } 63 | export interface ICreateModalOptions extends IBaseCreateOption { 64 | footerSlotName: string; //原来组件提供的footer插槽名称 65 | visiblePropName: string; //原来控制弹框组件显隐的属性名称 66 | cancelCbName: string; // 原来组件的关闭回调事件名称 67 | okCbName: string; // 原来组件的确定回调事件名称 68 | } 69 | export type createModal = ( 70 | Vue: _Vue, 71 | createOptions: ICreateModalOptions, 72 | options: IModalOption 73 | ) => VNode; 74 | 75 | export type getSlotPayload = ( 76 | slotVnMap: { [k: string]: VNode }, 77 | payloadSlot: string 78 | ) => any; 79 | 80 | export type createDrawerSlot = ( 81 | createElement: CreateElement, 82 | slotVnMap: { [k: string]: VNode }, 83 | close: Promise 84 | ) => (option: SlotOption, slot: string) => VNode; 85 | 86 | export type createModalSlot = ( 87 | createElement: CreateElement, 88 | slotVnMap: { [k: string]: VNode }, 89 | confirmLoading: boolean, 90 | close: Promise, 91 | ok: Promise 92 | ) => (option: SlotOption, slot: string) => VNode; 93 | 94 | declare module 'vue/types/vue' { 95 | interface Vue { 96 | $createAntdModal: (options: IModalOption, location: string) => VNode; 97 | $createAntdDrawer: (options: IDrawerOption, location: string) => VNode; 98 | $createViewModal: (options: IModalOption, location: string) => VNode; 99 | $createViewDrawer: (options: IDrawerOption, location: string) => VNode; 100 | $createEleModal: (options: IModalOption, location: string) => VNode; 101 | $createEleDrawer: (options: IDrawerOption, location: string) => VNode; 102 | } 103 | } 104 | 105 | declare class VueCreateDM { 106 | static install: PluginFunction; 107 | } 108 | 109 | export default VueCreateDM; 110 | -------------------------------------------------------------------------------- /example/src/views/base/antd/AntdDrawerExample.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /example/src/views/base/view/ViewDrawerExample.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /example/src/views/base/elem/EleDrawerExample.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /docs/guide/use.md: -------------------------------------------------------------------------------- 1 | # 使用 2 | ## 创建抽屉 3 | ### $createAntdDrawer、$createViewDrawer、$createEleDrawer 4 | ```js 5 | this.$createAntdDrawer(options, arg1, arg2); 6 | this.$createViewDrawer(options, arg1, arg2); 7 | this.$createEleDrawer(options, arg1, arg2); 8 | ``` 9 | ::: tip 提示 10 | `title` 和 `content` 子组件内会被传入一个名为 `close` 的回调函数,在子组件内使用 `this.$emit('close', payload)` 触发关闭行为。 11 | ::: 12 | ::: tip 提示 13 | 如果传入 **payloadSlot** 参数,并且相应的子组件存在 **`providePayload`** 方法,在抽屉关闭时,会先调用此函数获取返回值,然后作为 `beforeClose` 和 `afterClose` 方法的参数传入。 14 | ::: 15 | 16 | ### options参数介绍 17 | 18 | | 参数 | 说明 | 类型 | 19 | | :-----| :---- | :---- | 20 | | drawerProps | Drawer 组件的所有 props
[Antd Drawer Props](https://www.antdv.com/components/drawer-cn/#API)
[iView Drawer props](https://www.iviewui.com/components/drawer#API)
[ElementUI Drawer props](https://element.eleme.cn/#/zh-CN/component/drawer) | Object | 是 21 | | payloadSlot | 是否需要和获取子组件的返回值,**子组件需要在methods中提供`providePayload`方法。**
  • 不传或者传`false`,不调用;
  • 传`'title'`调用**标题子组件**的`providePayload`方法;
  • 传`true`或者`'default'`调用**内容子组件**的`providePayload`方法
| true \| false \| 'title' \| 'default' | 否 22 | | title | 标题自定义组件
  • 如果注册了全局头部,优先使用全局头部组件。此时此项可直接传 String 类型;如果此项不传,那就使用`drawerProps.title`作为标题。如果传对象,那就不使用全局头部组件,走自定义头部的逻辑。
  • 如果未注册全局头部,`title`需要传对象,其中`title.template`必填,类型是一个vue组件,`title`对象的其他参数可参考vue官方文档[深入数据对象](https://cn.vuejs.org/v2/guide/render-function.html#%E6%B7%B1%E5%85%A5%E6%95%B0%E6%8D%AE%E5%AF%B9%E8%B1%A1)。如果 `drawerProps.title` 和 `title` 同时存在的话,优先使用 `title`。
| Object \| String | 23 | | content | 内容自定义组件。其中`content.template`必填,类型是一个vue组件,`content`对象的其他参数可参考vue官方文档[深入数据对象](https://cn.vuejs.org/v2/guide/render-function.html#%E6%B7%B1%E5%85%A5%E6%95%B0%E6%8D%AE%E5%AF%B9%E8%B1%A1)。 | Object | 24 | | beforeClose | 抽屉关闭之前触发。其中`payload`代表调用子组件内调用`$eimt('close', params)`时携带的第二个参数;`slotPayload`表示从子组件中约定好的方法里获取到的返回值 | function({ payload, slotPayload }) | 25 | | afterClose | 抽屉关闭之前触发。 |function({ payload, slotPayload }) | 26 | |stopPropagation| 是否阻止原生`click`事件冒泡 | Boolean | 27 | |onClick| 原生`click`事件 | function(event) | 28 | 29 | ### 代码示例 30 | ```js 31 | this.$createAntdDrawer({ 32 | drawerProps: { 33 | title: '标题', 34 | width: '500px', 35 | mask: false, 36 | }, 37 | content: { 38 | template: HelloWorld, 39 | props: { 40 | msg: 'Welcome to Your Vue.js App', 41 | }, 42 | }, 43 | beforeClose: function() { 44 | console.log('我要关闭了'); 45 | }, 46 | afterClose: function() { 47 | console.log('我已经关闭了'); 48 | }, 49 | }); 50 | ``` 51 | ## 创建弹框 52 | ### $createAntdModal、$createViewModal、$createEleModal 53 | ```js 54 | this.$createAntdModal(options, arg1, arg2); 55 | this.$createViewModal(options, arg1, arg2); 56 | this.$createEleModal(options, arg1, arg2); 57 | ``` 58 | ::: tip 提示 59 | `title`、`content`和`foter`子组件内会被传入`close`和`ok`两个回调函数和 一个名为`confirmLoading`的 props 60 | * 在子组件内使用 `this.$emit('close', payload)` 触发关闭动作 61 | * 使用 `this.$emit('ok', payload)` 触发 onOk 回调 62 | * 子组件内可以使用 `confirmLoading` 来管理确定按钮的 loading 状态 63 | * **antd的modal组件可以使用默认footer,iview需要自定义footer** 64 | ::: 65 | ::: tip 提示 66 | 如果传入 **payloadSlot** 参数,并且相应的子组件如果存在 `providePayload` 方法,在点击确定按钮时,会先调用此函数获取返回值,然后作为 `onOk` 方法的一个参数传入 67 | ::: 68 | ::: tip 提示 69 | 如果`onOk`函数返回`false`,并且是 antd 的 Modal 组件,会阻止关闭弹框;iView需要自定义 footer 组件来实现。 70 | ::: 71 | 72 | ### options参数介绍 73 | 74 | 和上面说过的创建抽屉时传的差不多,只介绍以下几个不同的地方 75 | | 参数 | 说明 | 类型 | 76 | | :-----| :---- | :---- | 77 | |modalProps| Modal 的所有props
[Antd Modal](https://www.antdv.com/components/modal-cn/#API)
[iView Modal](https://www.iviewui.com/components/modal#API)
[ElementUI Dialog](https://element.eleme.cn/#/zh-CN/component/dialog)| Object | 78 | | payloadSlot | 同Drawer,多了一个 'footer' 选项 | true \| false \| 'title' \| 'footer' \| 'default' 79 | |footer| footer自定义组件 | Object | 80 | |onOk| 点击确认按钮的回调。
如果`onOk`函数返回`false`,并且是antd的Modal组件,会阻止关闭弹框;iView需要自定义footer组件来实现。 | function({ payload, slotPayload }) | 81 | 82 | ### 代码示例 83 | 84 | ```js 85 | this.$createAntdModal({ 86 | modalProps: { 87 | title: '标题', 88 | width: '500px', 89 | mask: false, 90 | }, 91 | content: { 92 | template: HelloWorld, 93 | props: { 94 | msg: 'Welcome to Your Vue.js App', 95 | }, 96 | }, 97 | beforeClose: function() { 98 | console.log('我要关闭了'); 99 | }, 100 | afterClose: function() { 101 | console.log('我已经关闭了'); 102 | }, 103 | async onOk() { 104 | const res = await new Promise((resolve) => { 105 | setTimeout(() => { 106 | console.log('点了确定'); 107 | resolve(false); 108 | }, 3000); 109 | }); 110 | return res; 111 | }, 112 | }); 113 | ``` 114 | -------------------------------------------------------------------------------- /example/src/views/base/antd/AntdExample.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /example/src/views/base/elem/EleExample.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /example/src/views/base/view/ViewExample.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /src/drawer.js: -------------------------------------------------------------------------------- 1 | import { createDrawerSlot } from './createCreateSlot'; 2 | import { getSlotPayload } from './getSlotPayload'; 3 | import { locationMatcher } from './locationMatcher'; 4 | import { setGlobalHeader } from './setGlobalHeader'; 5 | import { modifyOptions } from './modifyOptions'; 6 | 7 | /** 8 | * drawerProps 就是组件库的 drawer 支持的props 9 | * title 和 content 都是对象,其中 template 属性代表组件,其他属性同 vue 的原生属性 https://cn.vuejs.org/v2/guide/render-function.html#%E6%B7%B1%E5%85%A5%E6%95%B0%E6%8D%AE%E5%AF%B9%E8%B1%A1 10 | */ 11 | // 创建抽屉的主方法 12 | export function createDrawer( 13 | Vue, 14 | { 15 | component: Drawer, 16 | titleSlotName = 'title', //原来组件提供的标题插槽名称 17 | visiblePropName = 'visible', //原来控制抽屉组件显隐的属性名称 18 | closeCbName = 'close', // 原来组件的关闭回调事件名称 19 | router, 20 | store, 21 | }, 22 | options 23 | ) { 24 | const { 25 | title, 26 | content, 27 | drawerProps, 28 | beforeClose, 29 | afterClose, 30 | payloadSlot, // 'default', 'title', false, true 31 | onClick, 32 | stopPropagation, 33 | } = options; 34 | const el = document.createElement('div'); 35 | document.body.appendChild(el); 36 | let firstRender = true; // hack iview modal创建时没有动画的问题 37 | 38 | const vn = new Vue({ 39 | data: { 40 | visible: false, 41 | slotVnMap: {}, 42 | }, 43 | render(createElement) { 44 | const self = this; 45 | if (firstRender) { 46 | setTimeout(() => { 47 | self.$data.visible = true; 48 | firstRender = false; 49 | }, 0); 50 | } 51 | const handleNativeClick = (event) => { 52 | if (stopPropagation) { 53 | event.stopPropagation(); 54 | } 55 | onClick && onClick(event); 56 | }; 57 | const handleClose = async function(payload) { 58 | const slotPayload = await getSlotPayload( 59 | self.$data.slotVnMap, 60 | payloadSlot 61 | ); 62 | beforeClose && (await beforeClose({ payload, slotPayload })); 63 | self.$data.visible = false; 64 | // 因为antd关闭动画是 0.3s 所以稍微晚点再销毁组件 65 | setTimeout(async () => { 66 | self.$destroy(); 67 | 68 | try { 69 | // 手动删除节点 70 | document.body.removeChild(self.$el); 71 | } catch (e) {} 72 | 73 | afterClose && (await afterClose({ payload, slotPayload })); 74 | }, 400); 75 | }; 76 | const createSlot = createDrawerSlot( 77 | createElement, 78 | self.$data.slotVnMap, 79 | handleClose 80 | ); 81 | const children = []; 82 | // 如果传了内容 83 | if (content && content.template) { 84 | children.push(createSlot(content)); 85 | } 86 | // 如果title传了组件,默认用这个 87 | if (title && title.template) { 88 | // 如果是插槽的话,就要加slot 89 | children.push(createSlot(title, titleSlotName)); 90 | drawerProps.title && delete drawerProps.title; 91 | } 92 | return createElement( 93 | Drawer, 94 | { 95 | props: { 96 | ...drawerProps, 97 | [visiblePropName]: self.$data.visible, 98 | }, 99 | on: { 100 | [closeCbName]: handleClose, 101 | }, 102 | nativeOn: { 103 | click: handleNativeClick, 104 | }, 105 | }, 106 | children 107 | ); 108 | }, 109 | router, 110 | store, 111 | }).$mount(el); 112 | return vn; 113 | } 114 | 115 | // 创建 antd drawer 的扩展方法 116 | export const createAntdDrawer = { 117 | install(Vue, originBaseOption) { 118 | Vue.prototype.$createAntdDrawer = function(options, argObj, argLocation) { 119 | const { location, baseOption } = modifyOptions( 120 | originBaseOption, 121 | argObj, 122 | argLocation 123 | ); 124 | const newOptions = locationMatcher.call( 125 | this, 126 | location, 127 | baseOption, 128 | options 129 | ); 130 | const optionsWithGH = setGlobalHeader(baseOption, newOptions); 131 | return createDrawer( 132 | Vue, 133 | { 134 | ...baseOption, 135 | titleSlotName: 'title', 136 | visiblePropName: 'visible', 137 | closeCbName: 'close', 138 | }, 139 | optionsWithGH 140 | ); 141 | }; 142 | }, 143 | }; 144 | // 创建 iview drawer 的扩展方法 145 | export const createViewDrawer = { 146 | install(Vue, originBaseOption) { 147 | Vue.prototype.$createViewDrawer = function(options, argObj, argLocation) { 148 | const { location, baseOption } = modifyOptions( 149 | originBaseOption, 150 | argObj, 151 | argLocation 152 | ); 153 | const newOptions = locationMatcher.call( 154 | this, 155 | location, 156 | baseOption, 157 | options 158 | ); 159 | const optionsWithGH = setGlobalHeader(baseOption, newOptions); 160 | return createDrawer( 161 | Vue, 162 | { 163 | ...baseOption, 164 | titleSlotName: 'header', 165 | visiblePropName: 'value', 166 | closeCbName: 'on-close', 167 | }, 168 | optionsWithGH 169 | ); 170 | }; 171 | }, 172 | }; 173 | // 创建 ele drawer 的扩展方法 174 | export const createEleDrawer = { 175 | install(Vue, originBaseOption) { 176 | Vue.prototype.$createEleDrawer = function(options, argObj, argLocation) { 177 | const { location, baseOption } = modifyOptions( 178 | originBaseOption, 179 | argObj, 180 | argLocation 181 | ); 182 | const newOptions = locationMatcher.call( 183 | this, 184 | location, 185 | baseOption, 186 | options 187 | ); 188 | const optionsWithGH = setGlobalHeader(baseOption, newOptions); 189 | return createDrawer( 190 | Vue, 191 | { 192 | ...baseOption, 193 | titleSlotName: 'title', 194 | visiblePropName: 'visible', 195 | closeCbName: 'close', 196 | }, 197 | optionsWithGH 198 | ); 199 | }; 200 | }, 201 | }; 202 | -------------------------------------------------------------------------------- /src/modal.js: -------------------------------------------------------------------------------- 1 | import { createModalSlot } from './createCreateSlot'; 2 | import { getSlotPayload } from './getSlotPayload'; 3 | import { locationMatcher } from './locationMatcher'; 4 | import { setGlobalHeader } from './setGlobalHeader'; 5 | import { modifyOptions } from './modifyOptions'; 6 | /** 7 | * modalProps 就是组件库的 modal 支持的props 8 | * title、content、footer 都是对象,其中 template 属性代表组件,其他属性同 vue 的原生属性 https://cn.vuejs.org/v2/guide/render-function.html#%E6%B7%B1%E5%85%A5%E6%95%B0%E6%8D%AE%E5%AF%B9%E8%B1%A1 9 | */ 10 | // 创建弹框的主方法 11 | export function createModal( 12 | Vue, 13 | { 14 | component: Modal, 15 | titleSlotName = 'title', //原来组件提供的标题插槽名称 16 | footerSlotName = 'footer', //原来组件提供的footer插槽名称 17 | visiblePropName = 'visible', //原来控制抽屉组件显隐的属性名称 18 | btnLoadingPropName = 'confirmLoading', 19 | cancelCbName = 'cancel', // 原来组件的关闭回调事件名称 20 | okCbName = 'ok', // 原来组件的确定回调事件名称 21 | router, 22 | store, 23 | }, 24 | options 25 | ) { 26 | const { 27 | title, 28 | content, 29 | footer, 30 | modalProps, 31 | beforeClose, 32 | afterClose, 33 | onOk, 34 | payloadSlot, 35 | onClick, 36 | // 是否阻止整个modal组件的点击事件冒泡 37 | stopPropagation, 38 | } = options; 39 | const el = document.createElement('div'); 40 | document.body.appendChild(el); 41 | let firstRender = true; // hack iview modal创建时没有动画的问题 42 | 43 | const vn = new Vue({ 44 | data: { 45 | visible: false, 46 | confirmLoading: false, 47 | slotVnMap: {}, 48 | }, 49 | render(createElement) { 50 | const self = this; 51 | if (firstRender) { 52 | setTimeout(() => { 53 | self.$data.visible = true; 54 | firstRender = false; 55 | }, 0); 56 | } 57 | const handleNativeClick = (event) => { 58 | if (stopPropagation) { 59 | event.stopPropagation(); 60 | } 61 | onClick && onClick(event); 62 | }; 63 | const handleClose = async (payload) => { 64 | beforeClose && (await beforeClose(payload)); 65 | self.$data.visible = false; 66 | setTimeout(async () => { 67 | self.$destroy(); 68 | 69 | try { 70 | // 手动销毁dom 71 | document.body.removeChild(self.$el); 72 | } catch (e) {} 73 | 74 | afterClose && (await afterClose(payload)); 75 | }, 400); 76 | }; 77 | // 直接关闭不传slotPayload,通过ok关闭可以取到 78 | const handleOk = async (payload) => { 79 | const slotPayload = await getSlotPayload( 80 | self.$data.slotVnMap, 81 | payloadSlot 82 | ); 83 | self.$data.confirmLoading = true; 84 | // 如果返回false表示不关闭,其他情况关闭 85 | const res = onOk && (await onOk({ payload, slotPayload })); 86 | self.$data.confirmLoading = false; 87 | if (res === false) { 88 | } else { 89 | await handleClose({ payload, slotPayload }); 90 | } 91 | }; 92 | const createSlot = createModalSlot( 93 | createElement, 94 | self.$data.slotVnMap, 95 | self.$data.confirmLoading, 96 | handleClose, 97 | handleOk 98 | ); 99 | const children = []; 100 | // 如果传了内容 101 | if (content && content.template) { 102 | children.push(createSlot(content)); 103 | } 104 | // 如果title传了组件,默认用这个 105 | if (title && title.template) { 106 | // 如果是插槽的话,就要加slot 107 | children.push(createSlot(title, titleSlotName)); 108 | modalProps.title && delete modalProps.title; 109 | } 110 | // 如果title传了footer,用这个 111 | if (footer && footer.template) { 112 | children.push(createSlot(footer, footerSlotName)); 113 | } 114 | return createElement( 115 | Modal, 116 | { 117 | props: { 118 | ...modalProps, 119 | [btnLoadingPropName]: self.$data.confirmLoading, 120 | [visiblePropName]: self.$data.visible, 121 | }, 122 | on: { 123 | [cancelCbName]: handleClose, 124 | [okCbName]: handleOk, 125 | }, 126 | nativeOn: { 127 | click: handleNativeClick, 128 | }, 129 | }, 130 | children 131 | ); 132 | }, 133 | router, 134 | store, 135 | }).$mount(el); 136 | return vn; 137 | } 138 | // 创建 antd modal 的扩展方法 139 | export const createAntdModal = { 140 | install(Vue, originBaseOption) { 141 | Vue.prototype.$createAntdModal = function(options, argObj, argLocation) { 142 | const { location, baseOption } = modifyOptions( 143 | originBaseOption, 144 | argObj, 145 | argLocation 146 | ); 147 | const newOptions = locationMatcher.call( 148 | this, 149 | location, 150 | baseOption, 151 | options 152 | ); 153 | const optionsWithGH = setGlobalHeader(baseOption, newOptions); 154 | return createModal( 155 | Vue, 156 | { 157 | ...baseOption, 158 | titleSlotName: 'title', 159 | footerSlotName: 'footer', 160 | visiblePropName: 'visible', 161 | btnLoadingPropName: 'confirmLoading', 162 | cancelCbName: 'cancel', 163 | okCbName: 'ok', 164 | }, 165 | optionsWithGH 166 | ); 167 | }; 168 | }, 169 | }; 170 | // 创建 iview modal 的扩展方法 171 | export const createViewModal = { 172 | install(Vue, originBaseOption) { 173 | Vue.prototype.$createViewModal = function(options, argObj, argLocation) { 174 | const { location, baseOption } = modifyOptions( 175 | originBaseOption, 176 | argObj, 177 | argLocation 178 | ); 179 | const newOptions = locationMatcher.call( 180 | this, 181 | location, 182 | baseOption, 183 | options 184 | ); 185 | const optionsWithGH = setGlobalHeader(baseOption, newOptions); 186 | return createModal( 187 | Vue, 188 | { 189 | ...baseOption, 190 | titleSlotName: 'header', 191 | footerSlotName: 'footer', 192 | visiblePropName: 'value', 193 | btnLoadingPropName: 'loading', 194 | cancelCbName: 'on-cancel', 195 | okCbName: 'on-ok', 196 | }, 197 | optionsWithGH 198 | ); 199 | }; 200 | }, 201 | }; 202 | // 创建 ele modal 的扩展方法 203 | export const createEleModal = { 204 | install(Vue, originBaseOption) { 205 | Vue.prototype.$createEleModal = function(options, argObj, argLocation) { 206 | const { location, baseOption } = modifyOptions( 207 | originBaseOption, 208 | argObj, 209 | argLocation 210 | ); 211 | const newOptions = locationMatcher.call( 212 | this, 213 | location, 214 | baseOption, 215 | options 216 | ); 217 | const optionsWithGH = setGlobalHeader(baseOption, newOptions); 218 | return createModal( 219 | Vue, 220 | { 221 | ...baseOption, 222 | titleSlotName: 'title', 223 | footerSlotName: 'footer', 224 | visiblePropName: 'visible', 225 | btnLoadingPropName: 'loading', 226 | cancelCbName: 'close', 227 | okCbName: 'ok', 228 | }, 229 | optionsWithGH 230 | ); 231 | }; 232 | }, 233 | }; 234 | --------------------------------------------------------------------------------