vue 已经成为前端一个必备技能了, 就像当年的 jQuery, 所以好好学吧~
不多说上代码
- // index.vue 弹框主体, 可以直接组件的形式引用, 后面会介绍如何在使用 this.$xxxx
- <template>
- <div v-if="!destroyDom" class="panel">
- <transition name="fade">
- <div v-if="visible" :style="{opacity:data.mask?1:0}" class="panel_mask" @click="action({type:'mask'})" />
- </transition>
- <transition name="bounce" @after-leave="destroyDomHandle">
- <div v-if="visible" class="panel-wrap" :class="data.class">
- <div v-if="data.title" class="panel-title" v-html="data.title" />
- <div v-if="!component" class="panel-content" v-HTML="data.content" />
- <div v-if="component" class="panel-content">
- <component :is="_component" ref="component" />
- </div>
- <div class="panel-btn">
- <div v-for="(btn,index) in btns" :key="index" :class="`panel-${btn.type}`" @click="action(btn)">{{ btn.text }}</div>
- </div>
- </div>
- </transition>
- </div>
- </template>
- <script>
- export default {
- name: 'Panel',
- props: {
- component: {
- type: Function,
- default: null
- },
- data: {
- type: Object,
- required: true
- },
- btns: {
- type: Array,
- default: () => {
- return [
- {
- text: '知道了',
- type: 'cancel'
- }
- ]
- }
- },
- listener: { // 每个阶段会回调
- type: Function,
- default: null
- }
- },
- data() {
- return {
- visible: false,
- destroyDom: false
- }
- },
- computed: {
- _component() { // 可以传入 vnode 同时也保留了传 HTML
- const fn = this.component
- let vNode
- return {
- name: 'panel-component',
- render() {
- vNode = fn()
- return vNode
- }
- }
- }
- },
- created() {
- this.listener && this.listener({ acr: 'start' })
- this.data.delay && setTimeout(() => {
- this.close()
- }, this.data.delay)
- },
- methods: {
- destroyDomHandle() { // 动画结束后把这块这块销毁
- this.destroyDom = true
- this.listener && this.listener({ act: 'destroy' })
- },
- close() {
- this.visible = false
- this.listener && this.listener({ act: 'close' })
- },
- action(btn) {
- this.listener && this.listener({ act: 'click', type: btn.type })
- if (btn && btn.callback) {
- btn.callback(this.close)
- } else {
- this.close()
- }
- }
- }
- }
- </script>
- // 下面就是一些样式, 适用移动端, 并使用 rem 做的适配
- <style scoped lang="less">
- @designwidth: 750;
- .bounce-enter-active {
- animation: bounce-in 0.4s;
- }
- .bounce-leave-active {
- animation: bounce-in 0.4s reverse;
- }
- @keyframes bounce-in {
- 0% {
- transform: translate3d(-50%, -32%, 0) scale(0);
- opacity: 0;
- }
- 50% {
- transform: translate3d(-50%, -31%, 0) scale(1.1);
- opacity: 0.8;
- }
- 100% {
- transform: translate3d(-50%, -30%, 0) scale(1);
- opacity: 1;
- }
- }
- .fade-enter-active,
- .fade-leave-active {
- transition: all 0.4s;
- }
- .fade-enter,
- .fade-leave-to {
- opacity: 0;
- }
- .panel_mask {
- position: fixed;
- z-index: 99;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.3);
- }
- .panel-wrap {
- position: fixed;
- top: 30%;
- left: 50%;
- z-index: 100;
- transform: translate3d(-50%, -30%, 0);
- width: 70%;
- // max-height: 70%;
- background: #f8f8f8;
- border-radius: rem(16);
- box-sizing: border-box;
- }
- .panel-title {
- padding: rem(10) 0;
- width: 100%;
- text-align: center;
- // max-height: 1rem;
- font-size: rem(36);
- overflow: hidden;
- box-sizing: border-box;
- color: #666;
- text-overflow: ellipsis;
- border-bottom: 1px solid #d9d9d9;
- white-space: nowrap;
- }
- .panel-content {
- padding: rem(20) rem(20);
- width: 100%;
- max-height: 60vh;
- overflow-y: auto;
- font-size: rem(30);
- box-sizing: border-box;
- text-align: center;
- }
- .panel-btn {
- text-align: center;
- display: flex;
- width: 100%;
- border-top: 1px solid #d9d9d9;
- color: #4e82d7;
- }
- .panel-btn> div {
- flex: 1;
- padding: rem(10) 0;
- border-left: 1px solid #d9d9d9;
- border-radius: 0.12rem;
- font-size: rem(30);
- &:nth-child(1) {
- border-left: 0;
- }
- }
- </style>
渲染到页面
- // render.JS
- import Tpl from './index.vue'
- export default function create(Vue, { store = {}, router = {}}, options) {
- const comp = new Vue({
- name: 'Root' + Tpl.name,
- router,
- store,
- data() {
- return {
- options: { ...options }
- }
- },
- render(h) {
- return h(Tpl, {
- props: this.options
- })
- }
- })
- comp.$mount()
- document.body.appendChild(comp.$el)
- comp.$children[0].visible = true
- return {
- close: () => comp.$children[0].close() // 暴露出去一个关闭的方法
- }
- }
挂载
- // index.JS
- import create from './render'
- export default {
- install(Vue, options = {}) {
- Vue.prototype.$panel = create.bind(this, Vue, options)
- }
- }
使用
- // main.JS
- import Vue from 'vue'
- import App from './App.vue'
- import Panel from './base/panel'
- Vue.use(Panel)
- new Vue({
- render: h => h(App)
- }).$mount('#app')
- // 组件中全局使用
- this.$panel({
- data: {
- class: 'xxxx',
- mask: true,
- title: '提示',
- content: 'xxx'
- },
- btns: [
- {
- text: '知道了',
- type: 'cancel',
- callback: () => { }
- }
- ],
- component: () => <div />,
- listener: (res) => {console.log(res)}
- })
通过控制参数可以实现 toast, 可以进行二次封装
原文 https://juejin.im/post/5d648ac26fb9a06af471d0a6
来源: http://www.jianshu.com/p/b078693c430a