li.shaoyi há 3 anos atrás
pai
commit
12d3a0ccb2

+ 0 - 20
src/components/base/mask/index.less

@@ -1,20 +0,0 @@
-.app-mask {
-    top   : 0;
-    left  : 0;
-    width : 100%;
-    height: 100%;
-
-    &__block {
-        position           : absolute;
-        top                : 0;
-        left               : 0;
-        width              : 100%;
-        height             : 100%;
-        background-color   : rgba(0, 0, 0, .35);
-        transition-property: opacity;
-
-        &:not(.is-show) {
-            opacity: 0;
-        }
-    }
-}

+ 0 - 72
src/components/base/mask/index.vue

@@ -1,72 +0,0 @@
-<!-- 基础遮罩组件 -->
-<template>
-  <div class="app-mask" :style="maskStyles" v-show="visible">
-    <div :class="['app-mask__block', transitionClass]" :style="transitionStyles" @click="onMask"></div>
-    <slot></slot>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import { computed, ref, watch } from 'vue';
-import { useMask } from './index'
-
-const emit = defineEmits(['mask'])
-
-const props = defineProps({
-  show: {
-    type: Boolean,
-    default: false,
-  },
-  // 是否可以点击遮罩关闭窗口
-  closeOnClickMask: {
-    type: Boolean,
-    default: true,
-  },
-  // 绝对定位
-  fixed: {
-    type: Boolean,
-    default: true,
-  },
-  // 遮罩堆叠顺序
-  zIndex: {
-    type: Number,
-    default: 1000,
-  },
-  // 窗口动画时间
-  delay: {
-    type: Number,
-    default: 200,
-  },
-});
-
-const visible = ref(props.show);
-const { transitionClass, transitionStyles, transition } = useMask(props.show, props.delay);
-
-const maskStyles = computed(() => ({
-  position: props.fixed ? 'fixed' : 'absolute',
-  zIndex: props.zIndex,
-}));
-
-// 点击遮罩事件
-const onMask = () => {
-  if (props.closeOnClickMask) {
-    emit('mask');
-  }
-};
-
-watch(() => props.show, (isShow) => {
-  if (isShow) {
-    visible.value = isShow;
-  } else {
-    // 动画结束时再关闭窗口
-    setTimeout(() => {
-      visible.value = isShow;
-    }, props.delay);
-  }
-  transition(isShow);
-})
-</script>
-
-<style lang="less">
-@import './index.less';
-</style>

+ 0 - 8
src/components/base/mask/interface.ts

@@ -1,8 +0,0 @@
-/** 
- * 遮罩配置项
- */
-export interface MaskOptions {
-    target: string; // 挂载到指定 DOM 节点
-    fixed: boolean; // 是否全屏固定
-    zIndex: Number;// 遮罩堆叠顺序
-}

+ 57 - 33
src/components/base/modal/index.less

@@ -1,112 +1,136 @@
 .app-modal {
-    display: flex;
+    top   : 0;
+    left  : 0;
+    width : 100%;
+    height: 100%;
+
+    &__mask {
+        position           : absolute;
+        top                : 0;
+        left               : 0;
+        width              : 100%;
+        height             : 100%;
+        background-color   : rgba(0, 0, 0, .35);
+        transition-property: opacity;
+
+        &:not(.is-show) {
+            opacity: 0;
+        }
+    }
+
+    &__wrapper {
+        position : relative;
+        z-index  : 1;
+        display  : flex;
+        flex-wrap: wrap;
+        height   : 100%;
+        overflow : hidden;
+    }
 
     &__container {
-        position           : relative;
-        z-index            : 1;
-        display            : flex;
-        flex-direction     : column;
-        color              : #000;
-        background-color   : #fff;
-        transition-property: transform;
-        overflow           : hidden;
+        display       : inline-flex;
+        flex-direction: column;
+        max-width     : 100%;
+        max-height    : 100%;
     }
 
-    &__body {
-        flex       : 1;
-        overflow-y : auto;
-        line-height: normal;
+    &__container &__body {
+        flex      : 1;
+        overflow-y: auto;
     }
 
     /* 全屏 */
-    &.full {
+    &__wrapper.full {
         justify-content: center;
         align-items    : center;
     }
 
-    &.full &__container {
-        width : 100%;
-        height: 100%;
+    &__wrapper.full &__container {
+        width              : 100%;
+        height             : 100%;
+        border-radius      : 0;
+        transition-property: transform, opacity;
 
         &:not(.is-show) {
+            opacity  : .5;
             transform: scale(0);
         }
     }
 
-    /* 中 */
-    &.center {
+    /* 中 */
+    &__wrapper.center {
         justify-content: center;
         align-items    : center;
     }
 
-    &.center &__container {
+    &__wrapper.center &__container {
         transition-property: transform, opacity;
 
         &:not(.is-show) {
             opacity  : 0;
-            transform: translate3d(0, -50%, 0);
+            transform: translate3d(0, -15%, 0);
         }
     }
 
     /* 上 */
-    &.top {
+    &__wrapper.top {
         flex-direction: column;
         align-items   : center;
     }
 
-    &.top &__container:not(.is-show) {
+    &__wrapper.top &__container:not(.is-show) {
         transform: translate3d(0, -100%, 0);
     }
 
     /* 下 */
-    &.bottom {
+    &__wrapper.bottom {
         flex-direction : column;
         justify-content: flex-end;
         align-items    : center;
     }
 
-    &.bottom &__container:not(.is-show) {
+    &__wrapper.bottom &__container:not(.is-show) {
         transform: translate3d(0, 100%, 0);
     }
 
     /* 左 */
-    &.left {
+    &__wrapper.left {
         align-items: center;
     }
 
     /* 左上 */
-    &.left-top {
+    &__wrapper.left-top {
         align-items: flex-start;
     }
 
     /* 左下 */
-    &.left-bottom {
+    &__wrapper.left-bottom {
         align-items: flex-end;
     }
 
-    &[class*='left'] &__container:not(.is-show) {
+    &__wrapper[class*='left'] &__container:not(.is-show) {
         transform: translate3d(-100%, 0, 0);
     }
 
     /* 右 */
-    &.right {
+    &__wrapper.right {
         justify-content: flex-end;
         align-items    : center;
     }
 
     /* 右上 */
-    &.right-top {
+    &__wrapper.right-top {
         justify-content: flex-end;
         align-items    : flex-start;
     }
 
     /* 右下 */
-    &.right-bottom {
+    &__wrapper.right-bottom {
         justify-content: flex-end;
         align-items    : flex-end;
     }
 
-    &[class*='right'] &__container:not(.is-show) {
+    &__wrapper[class*='right'] &__container:not(.is-show) {
         transform: translate3d(100%, 0, 0);
     }
 }

+ 8 - 14
src/components/base/mask/index.ts → src/components/base/modal/index.ts

@@ -1,26 +1,18 @@
-import { createApp, ref, computed } from 'vue'
-import { MaskOptions } from './interface'
-import component from './index.vue'
+import { ref, computed } from 'vue'
 
-/**
- * 动态挂载遮罩组件(未完成) 
- */
-export function Mask(options: MaskOptions): void {
-    createApp(component, {
-        options,
-    }).mount('body');
-}
-
-export function useMask(isShow: boolean, delay = 200) {
+export function useModal(isShow: boolean, delay: number) {
+    // 显示隐藏
+    const visible = ref(isShow);
     // 动画类名
     const transitionClass = ref('');
     // 是否阻止鼠标事件
     const pointerEvents = ref(false);
+
     // 动画样式
     const transitionStyles = computed(() => ({
         transitionDuration: delay + 'ms',
         pointerEvents: pointerEvents.value ? 'none' : 'auto',
-    }));
+    }))
 
     // 动画切换
     const transition = (isShow: boolean, callback?: () => void) => {
@@ -30,6 +22,7 @@ export function useMask(isShow: boolean, delay = 200) {
             pointerEvents.value = true;
             window.setTimeout(() => {
                 pointerEvents.value = false;
+                visible.value = isShow;
                 callback && callback();
             }, delay);
         }, 20);
@@ -41,6 +34,7 @@ export function useMask(isShow: boolean, delay = 200) {
     }
 
     return {
+        visible,
         transitionClass,
         transitionStyles,
         transition,

+ 66 - 37
src/components/base/modal/index.vue

@@ -1,54 +1,83 @@
 <!-- 基础模态框组件 -->
 <template>
-  <app-mask :class="['app-modal', direction]" :show="show" :delay="delay" @mask="emit('mask')">
-    <div :class="['app-modal__container', transitionClass]" :style="transitionStyles">
-      <div class="app-modal__header">
-        <slot name="header"></slot>
-      </div>
-      <div class="app-modal__body">
-        <slot></slot>
-      </div>
-      <div class="app-modal__footer">
-        <slot name="footer"></slot>
-      </div>
+    <div class="app-modal" :style="modalStyles" v-show="visible">
+        <div :class="['app-modal__mask', transitionClass]" :style="transitionStyles"></div>
+        <div :class="['app-modal__wrapper', direction]" @click.self="onMask">
+            <div :class="['app-modal__container', transitionClass]" :style="transitionStyles">
+                <div class="app-modal__header">
+                    <slot name="header"></slot>
+                </div>
+                <div class="app-modal__body">
+                    <slot></slot>
+                </div>
+                <div class="app-modal__footer">
+                    <slot name="footer"></slot>
+                </div>
+            </div>
+        </div>
     </div>
-  </app-mask>
 </template>
 
 <script lang="ts" setup>
-import { watch, PropType } from 'vue'
-import { useMask } from '../mask'
-import AppMask from '../mask/index.vue'
+import { computed, watch, PropType } from 'vue';
+import { useModal } from './index'
 
 const emit = defineEmits(['mask', 'opened', 'closed']);
 
 const props = defineProps({
-  show: {
-    type: Boolean,
-    default: false,
-  },
-  // 窗口动画时间
-  delay: {
-    type: Number,
-    default: 200,
-  },
-  // 窗口弹出方向
-  direction: {
-    type: String as PropType<'full' | 'center' | 'left' | 'right' | 'top' | 'bottom' | 'left-top' | 'left-bottom' | 'right-top' | 'right-bottom'>,
-    default: 'center',
-  },
+    show: {
+        type: Boolean,
+        default: false,
+    },
+    // 是否可以点击遮罩关闭窗口
+    closeOnClickMask: {
+        type: Boolean,
+        default: true,
+    },
+    // 绝对定位
+    fixed: {
+        type: Boolean,
+        default: true,
+    },
+    // 遮罩堆叠顺序
+    zIndex: {
+        type: Number,
+        default: 1000,
+    },
+    // 窗口动画时间
+    delay: {
+        type: Number,
+        default: 250,
+    },
+    // 窗口弹出方向
+    direction: {
+        type: String as PropType<'full' | 'center' | 'left' | 'right' | 'top' | 'bottom' | 'left-top' | 'left-bottom' | 'right-top' | 'right-bottom'>,
+        default: 'center',
+    },
 })
 
-const { transitionClass, transitionStyles, transition } = useMask(props.show, props.delay);
+const { visible, transitionClass, transitionStyles, transition } = useModal(props.show, props.delay);
 
-watch(() => props.show, (isShow) => {
-  transition(isShow, () => {
-    if (isShow) {
-      emit('opened');
-    } else {
-      emit('closed');
+const modalStyles = computed(() => ({
+    position: props.fixed ? 'fixed' : 'absolute',
+    zIndex: props.zIndex,
+}))
+
+// 点击遮罩事件
+const onMask = () => {
+    if (props.closeOnClickMask) {
+        emit('mask');
     }
-  })
+}
+
+watch(() => props.show, (isShow) => {
+    transition(isShow, () => {
+        if (isShow) {
+            emit('opened');
+        } else {
+            emit('closed');
+        }
+    })
 })
 </script>
 

+ 14 - 14
src/packages/mobile/components/layouts/page/index.less

@@ -1,12 +1,6 @@
-.slide-right-enter-active,
-.slide-right-leave-active,
-.slide-left-enter-active,
-.slide-left-leave-active {
-    will-change     : transform;
-    transition      : transform 250ms;
-    position        : absolute;
-    pointer-events  : none;
-    background-color: #fff;
+.slide-left-enter-from {
+    z-index  : 1;
+    transform: translate3d(100%, 0, 0);
 }
 
 .slide-right-enter-from {
@@ -14,16 +8,22 @@
     transform: translate3d(-100%, 0, 0);
 }
 
+.slide-left-enter-active,
+.slide-left-leave-active,
+.slide-right-enter-active,
+.slide-right-leave-active {
+    pointer-events  : none;
+    position        : absolute;
+    will-change     : transform;
+    transition      : transform 300ms;
+    background-color: #fff;
+}
+
 .slide-right-leave-active {
     transition-delay: 35ms;
     transform       : translate3d(100%, 0, 0);
 }
 
-.slide-left-enter-from {
-    z-index  : 1;
-    transform: translate3d(100%, 0, 0);
-}
-
 .slide-left-leave-active {
     transition-delay: 35ms;
     transform       : translate3d(-100%, 0, 0);

+ 1 - 1
src/packages/mobile/components/layouts/page/index.vue

@@ -1,7 +1,7 @@
 <template>
   <router-view class="app-page" v-slot="{ Component, route }">
     <transition :name="state.transitionName">
-      <keep-alive :exclude="state.excludeName">
+      <keep-alive :exclude="state.excludeViews">
         <component :is="handleComponent(Component, route)" :key="$route.fullPath" />
       </keep-alive>
     </transition>

+ 9 - 9
src/packages/mobile/router/animateRouter.ts

@@ -3,7 +3,7 @@ import { createRouter, RouterOptions, RouteRecordRaw, RouteLocationNormalized }
 
 interface HistoryState {
     historyStacks: RouteLocationNormalized[]; // 已访问的路由列表
-    excludeName: string[]; // 不缓存的组件名称
+    excludeViews: string[]; // 不缓存的页面
     actionName: '' | 'push' | 'replace' | 'forward' | 'back'; // 当前路由动作
     transitionName: '' | 'slide-right' | 'slide-left'; // 前进后退动画
 }
@@ -11,7 +11,7 @@ interface HistoryState {
 export default new (class {
     private _state = ref<HistoryState>({
         historyStacks: [],
-        excludeName: [],
+        excludeViews: [],
         actionName: '',
         transitionName: '',
     })
@@ -77,8 +77,8 @@ export default new (class {
         })
 
         router.afterEach(() => {
-            const { excludeName } = toRefs(this._state.value);
-            excludeName.value = [];
+            const { excludeViews } = toRefs(this._state.value);
+            excludeViews.value = [];
         })
 
         return router;
@@ -89,15 +89,15 @@ export default new (class {
      * @param route 
      */
     private addHistory = (route: RouteLocationNormalized) => {
-        const { historyStacks, excludeName, actionName, transitionName } = toRefs(this._state.value);
+        const { historyStacks, excludeViews, actionName, transitionName } = toRefs(this._state.value);
 
         // 如果是替换动作,必定是前进
         if (actionName.value === 'replace') {
             const lastIndex = historyStacks.value.length - 1;
-            const lastPage = historyStacks.value[lastIndex];
+            const lastView = historyStacks.value[lastIndex];
 
-            if (lastPage) {
-                excludeName.value.push(lastPage.name as string);
+            if (lastView) {
+                excludeViews.value.push(lastView.name as string);
                 historyStacks.value[lastIndex] = route; // 更新最后一条记录
             } else {
                 historyStacks.value.push(route);
@@ -123,7 +123,7 @@ export default new (class {
                         const i = index + 1;
                         const n = historyStacks.value.length - i;
 
-                        excludeName.value = historyStacks.value.map((e) => e.name).slice(-n) as string[]; // 返回数组最后位置开始的n个元素
+                        excludeViews.value = historyStacks.value.map((e) => e.name).slice(-n) as string[]; // 返回数组最后位置开始的n个元素
                         historyStacks.value.splice(i, n); // 从i位置开始删除后面所有元素(包括i)
                     }
                     transitionName.value = 'slide-right'; //后退动画

+ 3 - 2
src/utils/client/index.ts

@@ -2,8 +2,9 @@ import { reactive, toRefs, readonly } from 'vue'
 
 export default new (class {
     private _state = reactive({
+        layout: 'default', // 页面布局
         clientWidth: 0, // 客户端宽度
-        isMobile: false, // 是否移动
+        isMobile: false, // 是否移动设备
     })
 
     /** 只读状态 */
@@ -42,7 +43,7 @@ export default new (class {
                 isMobile.value = true;
             }
 
-            el.setAttribute('mode', isMobile.value ? 'mobile' : 'pc');
+            el.setAttribute('screen', isMobile.value ? 'small' : 'normal');
         }
 
         clientWidth.value = body.clientWidth;