Commit 0ef301707f2c469a024a022d163cc356ecb8b6f6

Authored by 刘汉宸
1 parent 1dca57d9
Exists in master

[新增] 弹出框

examples/router/routes.js
@@ -48,6 +48,12 @@ const _components = [ @@ -48,6 +48,12 @@ const _components = [
48 meta: { title: 'Tag 标签' }, 48 meta: { title: 'Tag 标签' },
49 component: () => import('@/views/docs/component/tag.md'), 49 component: () => import('@/views/docs/component/tag.md'),
50 }, 50 },
  51 + {
  52 + path: 'popup',
  53 + name: 'popup',
  54 + meta: { title: 'Popup 弹出框' },
  55 + component: () => import('@/views/docs/component/popup.md'),
  56 + },
51 ] 57 ]
52 }, 58 },
53 { 59 {
examples/views/docs/component/popup.md 0 → 100644
@@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
  1 +# Popup 弹出框
  2 +
  3 +参考 <a href="https://didi.github.io/mand-mobile/#/zh-CN/docs/components/feedback/popup" target="_blank">Mand Mobile - Popup</a>
  4 +
  5 +## 基础用法
  6 +
  7 +弹出框说明
  8 +
  9 +::: snippet 示例
  10 +
  11 +```html
  12 +<template>
  13 + <div class="popup-demo">
  14 + <zui-button @click="popupVisible = true">普通按钮</zui-button>
  15 + <zui-popup v-model="popupVisible">
  16 + 弹出框内容
  17 + </zui-popup>
  18 + </div>
  19 +</template>
  20 +
  21 +<script>
  22 +export default {
  23 + data() {
  24 + return {
  25 + popupVisible: false,
  26 + }
  27 + },
  28 +}
  29 +</script>
  30 +
  31 +<style lang="scss">
  32 +.popup-demo {
  33 + height: 20vh;
  34 + width: 400px;
  35 + position: relative;
  36 +}
  37 +</style>
  38 +```
  39 +
  40 +:::
0 \ No newline at end of file 41 \ No newline at end of file
packages/icon/index.vue
1 <template> 1 <template>
2 - <i class="zui-icon" :class="classRender" :style="styleRender"> 2 + <i class="zui-icon" :class="classRender" :style="styleRender" @click="onClick">
3 <div v-if="info" class="zui-info">{{ info }}</div> 3 <div v-if="info" class="zui-info">{{ info }}</div>
4 </i> 4 </i>
5 </template> 5 </template>
@@ -36,6 +36,11 @@ export default { @@ -36,6 +36,11 @@ export default {
36 color: this.color 36 color: this.color
37 } 37 }
38 }, 38 },
  39 + onClick: function() {
  40 + if (this.$listeners['click']) {
  41 + this.$emit('click');
  42 + }
  43 + }
39 } 44 }
40 } 45 }
41 </script> 46 </script>
packages/popup/index.css 0 → 100644
@@ -0,0 +1,333 @@ @@ -0,0 +1,333 @@
  1 +.zui-popup {
  2 + position: absolute;
  3 + display: flex;
  4 + pointer-events: none;
  5 + z-index: 1000;
  6 + width: 100%;
  7 + height: 100%;
  8 + top: 0;
  9 + left: 0;
  10 +}
  11 +.zui-popup.center {
  12 + align-items: center;
  13 + justify-content: center;
  14 +}
  15 +.zui-popup.top {
  16 + flex-direction: column;
  17 + justify-content: flex-start;
  18 +}
  19 +.zui-popup.top .zui-popup-box {
  20 + width: 100%;
  21 +}
  22 +.zui-popup.bottom {
  23 + flex-direction: column;
  24 + justify-content: flex-end;
  25 +}
  26 +.zui-popup.bottom .zui-popup-box {
  27 + width: 100%;
  28 +}
  29 +.zui-popup.left {
  30 + justify-content: flex-start;
  31 +}
  32 +.zui-popup.left .zui-popup-box {
  33 + height: 100%;
  34 +}
  35 +.zui-popup.right {
  36 + justify-content: flex-end;
  37 +}
  38 +.zui-popup.right .zui-popup-box {
  39 + height: 100%;
  40 +}
  41 +.zui-popup.inner-popup .zui-popup-box {
  42 + background-color: #fff;
  43 + border-radius: 4px 4px 0 0;
  44 +}
  45 +.zui-popup.large-radius.inner-popup .zui-popup-box {
  46 + border-radius: 20px 20px 0 0;
  47 +}
  48 +.zui-popup-mask {
  49 + width: 100%;
  50 + height: 100%;
  51 + position: absolute;
  52 + pointer-events: auto;
  53 + z-index: 1;
  54 + background-color: rgba(0, 0, 0, 0.7);
  55 +}
  56 +.zui-popup-box {
  57 + position: relative;
  58 + pointer-events: auto;
  59 + z-index: 2;
  60 + max-width: 100%;
  61 + max-height: 100%;
  62 + overflow: auto;
  63 +}
  64 +.zui-mask-fade-enter,
  65 +.zui-mask-fade-leave-to {
  66 + opacity: 0.01;
  67 +}
  68 +.zui-mask-fade-enter-active,
  69 +.zui-mask-fade-leave-active {
  70 + transition: opacity 250ms;
  71 +}
  72 +
  73 +.zui-bounce-enter-active {
  74 + animation: bounce-in 300ms linear;
  75 +}
  76 +.zui-bounce-leave-active {
  77 + animation: zoom-out 250ms linear;
  78 +}
  79 +.zui-zoom-enter,
  80 +.zui-zoom-leave-to {
  81 + opacity: 0.01;
  82 + transform: scale(0.75);
  83 +}
  84 +.zui-zoom-enter-active {
  85 + transition: all 300ms cubic-bezier(0.215, 0.61, 0.355, 1);
  86 +}
  87 +.zui-zoom-leave-active {
  88 + transition: all 250ms linear;
  89 +}
  90 +.zui-punch-enter,
  91 +.zui-punch-leave-to {
  92 + opacity: 0.01;
  93 + transform: scale(1.35);
  94 +}
  95 +.zui-punch-enter-active {
  96 + transition: all 300ms cubic-bezier(0.215, 0.61, 0.355, 1);
  97 +}
  98 +.zui-punch-leave-active {
  99 + transition: all 250ms linear;
  100 +}
  101 +.zui-slide-up-enter,
  102 +.zui-slide-up-leave-to {
  103 + transform: translate3d(0, 100%, 0);
  104 +}
  105 +.zui-slide-up-enter-active {
  106 + transition: transform 300ms cubic-bezier(0.165, 0.84, 0.44, 1);
  107 +}
  108 +.zui-slide-up-leave-active {
  109 + transition: transform 250ms cubic-bezier(0.165, 0.84, 0.44, 1);
  110 +}
  111 +.zui-slide-right-enter,
  112 +.zui-slide-right-leave-to {
  113 + transform: translate3d(-100%, 0, 0);
  114 +}
  115 +.zui-slide-right-enter-active {
  116 + transition: transform 300ms cubic-bezier(0.165, 0.84, 0.44, 1);
  117 +}
  118 +.zui-slide-right-leave-active {
  119 + transition: transform 250ms cubic-bezier(0.165, 0.84, 0.44, 1);
  120 +}
  121 +.zui-slide-left-enter,
  122 +.zui-slide-left-leave-to {
  123 + transform: translate3d(100%, 0, 0);
  124 +}
  125 +.zui-slide-left-enter-active {
  126 + transition: transform 300ms cubic-bezier(0.165, 0.84, 0.44, 1);
  127 +}
  128 +.zui-slide-left-leave-active {
  129 + transition: transform 250ms cubic-bezier(0.165, 0.84, 0.44, 1);
  130 +}
  131 +.zui-slide-down-enter,
  132 +.zui-slide-down-leave-to {
  133 + transform: translate3d(0, -100%, 0);
  134 +}
  135 +.zui-slide-down-enter-active {
  136 + transition: transform 300ms cubic-bezier(0.165, 0.84, 0.44, 1);
  137 +}
  138 +.zui-slide-down-leave-active {
  139 + transition: transform 250ms cubic-bezier(0.165, 0.84, 0.44, 1);
  140 +}
  141 +.zui-fade-enter,
  142 +.zui-fade-leave-to {
  143 + opacity: 0.01;
  144 +}
  145 +.zui-fade-enter-active {
  146 + transition: opacity 300ms cubic-bezier(0.215, 0.61, 0.355, 1);
  147 +}
  148 +.zui-fade-leave-active {
  149 + transition: opacity 250ms linear;
  150 +}
  151 +.zui-fade-up-enter,
  152 +.zui-fade-up-leave-to {
  153 + opacity: 0.01;
  154 + transform: translate3d(0, 20%, 0);
  155 +}
  156 +.zui-fade-up-enter-active {
  157 + transition: all 300ms cubic-bezier(0.215, 0.61, 0.355, 1);
  158 +}
  159 +.zui-fade-up-leave-active {
  160 + transition: all 250ms linear;
  161 +}
  162 +.zui-fade-down-enter,
  163 +.zui-fade-down-leave-to {
  164 + opacity: 0.01;
  165 + transform: translate3d(0, -20%, 0);
  166 +}
  167 +.zui-fade-down-enter-active {
  168 + transition: all 300ms cubic-bezier(0.215, 0.61, 0.355, 1);
  169 +}
  170 +.zui-fade-down-leave-active {
  171 + transition: all 250ms linear;
  172 +}
  173 +.zui-fade-right-enter,
  174 +.zui-fade-right-leave-to {
  175 + opacity: 0.01;
  176 + transform: translate3d(-20%, 0, 0);
  177 +}
  178 +.zui-fade-right-enter-active {
  179 + transition: all 300ms cubic-bezier(0.215, 0.61, 0.355, 1);
  180 +}
  181 +.zui-fade-right-leave-active {
  182 + transition: all 250ms linear;
  183 +}
  184 +.zui-fade-left-enter,
  185 +.zui-fade-left-leave-to {
  186 + opacity: 0.01;
  187 + transform: translate3d(20%, 0, 0);
  188 +}
  189 +.zui-fade-left-enter-active {
  190 + transition: all 300ms cubic-bezier(0.215, 0.61, 0.355, 1);
  191 +}
  192 +.zui-fade-left-leave-active {
  193 + transition: all 250ms linear;
  194 +}
  195 +.zui-fly-enter-active {
  196 + animation: fly-in 600ms;
  197 + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
  198 +}
  199 +.zui-fly-leave-active {
  200 + animation: zoom-out 250ms;
  201 +}
  202 +@-moz-keyframes fly-in {
  203 + 0% {
  204 + opacity: 0.5;
  205 + transform: scale(0.5) translate3d(0, 50px, 0);
  206 + }
  207 + 45% {
  208 + opacity: 1;
  209 + transform: scale(1.05) translate3d(0, -50px, 0);
  210 + }
  211 + 100% {
  212 + transform: scale(1) translate3d(0, 0, 0);
  213 + }
  214 +}
  215 +@-webkit-keyframes fly-in {
  216 + 0% {
  217 + opacity: 0.5;
  218 + transform: scale(0.5) translate3d(0, 50px, 0);
  219 + }
  220 + 45% {
  221 + opacity: 1;
  222 + transform: scale(1.05) translate3d(0, -50px, 0);
  223 + }
  224 + 100% {
  225 + transform: scale(1) translate3d(0, 0, 0);
  226 + }
  227 +}
  228 +@-o-keyframes fly-in {
  229 + 0% {
  230 + opacity: 0.5;
  231 + transform: scale(0.5) translate3d(0, 50px, 0);
  232 + }
  233 + 45% {
  234 + opacity: 1;
  235 + transform: scale(1.05) translate3d(0, -50px, 0);
  236 + }
  237 + 100% {
  238 + transform: scale(1) translate3d(0, 0, 0);
  239 + }
  240 +}
  241 +@keyframes fly-in {
  242 + 0% {
  243 + opacity: 0.5;
  244 + transform: scale(0.5) translate3d(0, 50px, 0);
  245 + }
  246 + 45% {
  247 + opacity: 1;
  248 + transform: scale(1.05) translate3d(0, -50px, 0);
  249 + }
  250 + 100% {
  251 + transform: scale(1) translate3d(0, 0, 0);
  252 + }
  253 +}
  254 +@-moz-keyframes bounce-in {
  255 + 0% {
  256 + transform: scale(0.5);
  257 + }
  258 + 45% {
  259 + transform: scale(1.05);
  260 + }
  261 + 80% {
  262 + transform: scale(0.95);
  263 + }
  264 + 100% {
  265 + transform: scale(1);
  266 + }
  267 +}
  268 +@-webkit-keyframes bounce-in {
  269 + 0% {
  270 + transform: scale(0.5);
  271 + }
  272 + 45% {
  273 + transform: scale(1.05);
  274 + }
  275 + 80% {
  276 + transform: scale(0.95);
  277 + }
  278 + 100% {
  279 + transform: scale(1);
  280 + }
  281 +}
  282 +@-o-keyframes bounce-in {
  283 + 0% {
  284 + transform: scale(0.5);
  285 + }
  286 + 45% {
  287 + transform: scale(1.05);
  288 + }
  289 + 80% {
  290 + transform: scale(0.95);
  291 + }
  292 + 100% {
  293 + transform: scale(1);
  294 + }
  295 +}
  296 +@keyframes bounce-in {
  297 + 0% {
  298 + transform: scale(0.5);
  299 + }
  300 + 45% {
  301 + transform: scale(1.05);
  302 + }
  303 + 80% {
  304 + transform: scale(0.95);
  305 + }
  306 + 100% {
  307 + transform: scale(1);
  308 + }
  309 +}
  310 +@-moz-keyframes zoom-out {
  311 + to {
  312 + opacity: 0.01;
  313 + transform: scale(0.75);
  314 + }
  315 +}
  316 +@-webkit-keyframes zoom-out {
  317 + to {
  318 + opacity: 0.01;
  319 + transform: scale(0.75);
  320 + }
  321 +}
  322 +@-o-keyframes zoom-out {
  323 + to {
  324 + opacity: 0.01;
  325 + transform: scale(0.75);
  326 + }
  327 +}
  328 +@keyframes zoom-out {
  329 + to {
  330 + opacity: 0.01;
  331 + transform: scale(0.75);
  332 + }
  333 +}
packages/popup/index.vue 0 → 100644
@@ -0,0 +1,227 @@ @@ -0,0 +1,227 @@
  1 +<template>
  2 + <div
  3 + v-show="isPopupShow"
  4 + class="zui-popup"
  5 + :class="popupClassRender"
  6 + >
  7 + <transition name="zui-mask-fade">
  8 + <div
  9 + v-show="hasMask && isPopupBoxShow"
  10 + @click="$_onPopupMaskClick"
  11 + class="zui-popup-mask"
  12 + ></div>
  13 + </transition>
  14 + <zui-transition
  15 + :name="transition"
  16 + @before-enter="$_onPopupTransitionStart"
  17 + @before-leave="$_onPopupTransitionStart"
  18 + @after-enter="$_onPopupTransitionEnd"
  19 + @after-leave="$_onPopupTransitionEnd"
  20 + >
  21 + <div
  22 + v-show="isPopupBoxShow"
  23 + class="zui-popup-box"
  24 + :class="[transition]"
  25 + >
  26 + <slot></slot>
  27 + </div>
  28 + </zui-transition>
  29 + </div>
  30 +</template>
  31 +
  32 +<script>
  33 +export default {
  34 + name: 'Popup',
  35 + components: {
  36 + 'zui-transition': {
  37 + name: 'zui-transition',
  38 + functional: true,
  39 + render: function(h, context) {
  40 + return h('transition', context.data, context.children);
  41 + },
  42 + },
  43 + },
  44 + props: {
  45 + position: {
  46 + type: String,
  47 + default: 'center',
  48 + },
  49 + transition: {
  50 + type: String,
  51 + default: function() {
  52 + switch (this.position) {
  53 + case 'bottom':
  54 + return 'md-slide-up'
  55 + /* istanbul ignore next */
  56 + case 'top':
  57 + return 'md-slide-down'
  58 + /* istanbul ignore next */
  59 + case 'left':
  60 + return 'md-slide-right'
  61 + /* istanbul ignore next */
  62 + case 'right':
  63 + return 'md-slide-left'
  64 + default:
  65 + return 'md-fade' // fade/fade-bounce/fade-slide/fade-zoom
  66 + }
  67 + },
  68 + },
  69 + preventScroll: {
  70 + type: Boolean,
  71 + default: false,
  72 + },
  73 + preventScrollExclude: {
  74 + type: [String, Function],
  75 + default: function() {
  76 + return ''
  77 + },
  78 + },
  79 + // Mixin Props
  80 + value: {
  81 + type: Boolean,
  82 + default: false,
  83 + },
  84 + hasMask: {
  85 + type: Boolean,
  86 + default: true,
  87 + },
  88 + maskClosable: {
  89 + type: Boolean,
  90 + default: true,
  91 + },
  92 + },
  93 + data: function() {
  94 + return {
  95 + // controle popup mask & popup box
  96 + isPopupShow: false,
  97 + // controle popup box
  98 + isPopupBoxShow: false,
  99 + // transtion lock
  100 + isAnimation: false,
  101 + largeRadius: false,
  102 + }
  103 + },
  104 + computed: {
  105 + popupClassRender: function() {
  106 + return [
  107 + this.hasMask ? 'with-mask' : '',
  108 + this.largeRadius ? 'large-radius' : '',
  109 + this.position
  110 + ]
  111 + }
  112 + },
  113 + watch: {
  114 + value: function(val) {
  115 + /* istanbul ignore next */
  116 + if (val) {
  117 + if (this.isAnimation) {
  118 + var self = this;
  119 + setTimeout(function() {
  120 + self.$_showPopupBox()
  121 + }, 50);
  122 + } else {
  123 + this.$_showPopupBox()
  124 + }
  125 + } else {
  126 + this.$_hidePopupBox()
  127 + }
  128 + },
  129 + preventScrollExclude: function(val, oldVal) {
  130 + // remove old listener before add
  131 + /* istanbul ignore next */
  132 + this.$_preventScrollExclude(false, oldVal)
  133 + /* istanbul ignore next */
  134 + this.$_preventScrollExclude(true, val)
  135 + },
  136 + },
  137 + mounted: function() {
  138 + this.value && this.$_showPopupBox()
  139 + },
  140 + methods: {
  141 + // MARK: private methods
  142 + $_showPopupBox: function() {
  143 + this.isPopupShow = true
  144 + this.isAnimation = true
  145 + // popup box enter the animation after popup show
  146 + this.isPopupBoxShow = true
  147 + /* istanbul ignore if */
  148 + if (window.process && window.process.env.NODE_ENV == 'test') {
  149 + this.$_onPopupTransitionStart()
  150 + this.$_onPopupTransitionEnd()
  151 + }
  152 + this.preventScroll && this.$_preventScroll(true)
  153 + },
  154 + $_hidePopupBox: function() {
  155 + this.isAnimation = true
  156 + this.isPopupBoxShow = false
  157 + this.preventScroll && this.$_preventScroll(false)
  158 + this.$emit('input', false)
  159 + /* istanbul ignore if */
  160 + if (window.process && window.process.env.NODE_ENV == 'test') {
  161 + this.$_onPopupTransitionStart()
  162 + this.$_onPopupTransitionEnd()
  163 + }
  164 + },
  165 + $_preventScroll: function(isBind) {
  166 + const handler = isBind ? 'addEventListener' : 'removeEventListener'
  167 + const masker = this.$el.querySelector('.md-popup-mask')
  168 + const boxer = this.$el.querySelector('.md-popup-box')
  169 + masker && masker[handler]('touchmove', this.$_preventDefault, false)
  170 + boxer && boxer[handler]('touchmove', this.$_preventDefault, false)
  171 + this.$_preventScrollExclude(isBind)
  172 + },
  173 + $_preventScrollExclude: function(isBind, preventScrollExclude) {
  174 + const handler = isBind ? 'addEventListener' : 'removeEventListener'
  175 + preventScrollExclude = preventScrollExclude || this.preventScrollExclude
  176 + const excluder =
  177 + preventScrollExclude && typeof preventScrollExclude == 'string'
  178 + ? this.$el.querySelector(preventScrollExclude)
  179 + : preventScrollExclude
  180 + excluder && excluder[handler]('touchmove', this.$_stopImmediatePropagation, false)
  181 + },
  182 + $_preventDefault: function(event) {
  183 + event.preventDefault()
  184 + },
  185 + $_stopImmediatePropagation: function(event) {
  186 + /* istanbul ignore next */
  187 + event.stopImmediatePropagation()
  188 + },
  189 + // MARK: event handler
  190 + $_onPopupTransitionStart: function() {
  191 + if (!this.isPopupBoxShow) {
  192 + this.$emit('beforeHide')
  193 + this.$emit('before-hide')
  194 + } else {
  195 + this.$emit('beforeShow')
  196 + this.$emit('before-show')
  197 + }
  198 + },
  199 + $_onPopupTransitionEnd: function() {
  200 + /* istanbul ignore next */
  201 + if (!this.isAnimation) {
  202 + return
  203 + }
  204 + /* istanbul ignore next */
  205 + if (!this.isPopupBoxShow) {
  206 + // popup hide after popup box finish animation
  207 + this.isPopupShow = false
  208 + this.$emit('hide')
  209 + } else {
  210 + this.$emit('show')
  211 + }
  212 + /* istanbul ignore next */
  213 + this.isAnimation = false
  214 + },
  215 + $_onPopupMaskClick: function() {
  216 + if (this.maskClosable) {
  217 + this.$_hidePopupBox()
  218 + this.$emit('maskClick')
  219 + }
  220 + },
  221 + },
  222 +}
  223 +</script>
  224 +
  225 +<style>
  226 +@import "./index.css";
  227 +</style>
0 \ No newline at end of file 228 \ No newline at end of file
packages/tag/index.vue
1 <template> 1 <template>
2 - <div class="zui-tag"> 2 + <div class="zui-tag" @click="onClick">
3 <template v-if="showQuarter"> 3 <template v-if="showQuarter">
4 <div :class="computedClass"> 4 <div :class="computedClass">
5 <div class="quarter-content"> 5 <div class="quarter-content">
@@ -69,7 +69,7 @@ export default { @@ -69,7 +69,7 @@ export default {
69 }, 69 },
70 mounted: function() { 70 mounted: function() {
71 this.$nextTick(function() { 71 this.$nextTick(function() {
72 - if (this.shape === "circle") { 72 + if (this.shape == "circle") {
73 var radius = this.$el.offsetHeight / 2; 73 var radius = this.$el.offsetHeight / 2;
74 this.$set(this.sizeStyle, "paddingLeft", radius / 2 + "px"); 74 this.$set(this.sizeStyle, "paddingLeft", radius / 2 + "px");
75 this.$set(this.sizeStyle, "paddingRight", radius / 2 + "px"); 75 this.$set(this.sizeStyle, "paddingRight", radius / 2 + "px");
@@ -96,12 +96,12 @@ export default { @@ -96,12 +96,12 @@ export default {
96 }, 96 },
97 colorStyle: function() { 97 colorStyle: function() {
98 var style = {}; 98 var style = {};
99 - if (this.type === "fill") { 99 + if (this.type == "fill") {
100 // eslint-disable-next-line 100 // eslint-disable-next-line
101 this.fillColor && (style.background = this.fillColor); 101 this.fillColor && (style.background = this.fillColor);
102 } 102 }
103 if (this.fontColor) { 103 if (this.fontColor) {
104 - if (this.type === "ghost") { 104 + if (this.type == "ghost") {
105 style.borderColor = this.fontColor; 105 style.borderColor = this.fontColor;
106 } 106 }
107 style.color = this.fontColor; 107 style.color = this.fontColor;
@@ -127,10 +127,10 @@ export default { @@ -127,10 +127,10 @@ export default {
127 }; 127 };
128 }, 128 },
129 showQuarter: function() { 129 showQuarter: function() {
130 - return this.shape === "quarter"; 130 + return this.shape == "quarter";
131 }, 131 },
132 showCoupon: function() { 132 showCoupon: function() {
133 - return this.shape === "coupon"; 133 + return this.shape == "coupon";
134 } 134 }
135 }, 135 },
136 methods: { 136 methods: {
@@ -139,6 +139,11 @@ export default { @@ -139,6 +139,11 @@ export default {
139 return str.replace(re, function($0, $1) { 139 return str.replace(re, function($0, $1) {
140 return $1.toUpperCase(); 140 return $1.toUpperCase();
141 }); 141 });
  142 + },
  143 + onClick: function() {
  144 + if (this.$listeners['click']) {
  145 + this.$emit('click');
  146 + }
142 } 147 }
143 } 148 }
144 }; 149 };