layout.vue 12.2 KB
Newer Older
fxy060608's avatar
fxy060608 已提交
1 2 3
<template>
  <uni-layout
    v-if="responsive"
fxy060608's avatar
fxy060608 已提交
4
    :class="{'uni-app--showlayout':showLayout,'uni-app--showtopwindow':showTopWindow,'uni-app--showleftwindow':showLeftWindow,'uni-app--showrightwindow':showRightWindow}"
fxy060608's avatar
fxy060608 已提交
5
  >
fxy060608's avatar
fxy060608 已提交
6 7
    <uni-top-window
      v-if="topWindow"
8
      v-show="showTopWindow || apiShowTopWindow"
fxy060608's avatar
fxy060608 已提交
9
    >
fxy060608's avatar
fxy060608 已提交
10 11 12 13 14
      <div
        ref="topWindow"
        class="uni-top-window"
        :style="topWindowStyle"
      >
fxy060608's avatar
fxy060608 已提交
15 16
        <v-uni-top-window
          ref="top"
17
          :navigation-bar-title-text="navigationBarTitleText"
fxy060608's avatar
fxy060608 已提交
18
          v-bind="bindWindow"
fxy060608's avatar
fxy060608 已提交
19 20 21 22 23 24 25 26
          @hook:mounted="onTopWindowInit"
        />
      </div>
      <div
        class="uni-top-window--placeholder"
        :style="{height:topWindowHeight}"
      />
    </uni-top-window>
fxy060608's avatar
fxy060608 已提交
27 28 29 30 31 32 33 34
    <uni-content>
      <uni-main>
        <keep-alive :include="keepAliveInclude">
          <router-view :key="routerKey" />
        </keep-alive>
      </uni-main>
      <uni-left-window
        v-if="leftWindow"
35
        v-show="showLeftWindow || apiShowLeftWindow"
fxy060608's avatar
fxy060608 已提交
36 37
        ref="leftWindow"
        :data-show="apiShowLeftWindow"
fxy060608's avatar
fxy060608 已提交
38 39
        :style="leftWindowStyle"
      >
40 41 42 43
        <div
          v-if="apiShowLeftWindow"
          class="uni-mask"
          @click="apiShowLeftWindow = false"
fxy060608's avatar
fxy060608 已提交
44
        />
45 46 47
        <div class="uni-left-window">
          <v-uni-left-window
            ref="left"
fxy060608's avatar
fxy060608 已提交
48
            v-bind="bindWindow"
49 50 51
            @hook:mounted="onLeftWindowInit"
          />
        </div>
fxy060608's avatar
fxy060608 已提交
52 53 54
      </uni-left-window>
      <uni-right-window
        v-if="rightWindow"
55
        v-show="showRightWindow || apiShowRightWindow"
fxy060608's avatar
fxy060608 已提交
56 57
        ref="rightWindow"
        :data-show="apiShowRightWindow"
fxy060608's avatar
fxy060608 已提交
58 59
        :style="rightWindowStyle"
      >
60 61 62 63
        <div
          v-if="apiShowRightWindow"
          class="uni-mask"
          @click="apiShowRightWindow = false"
fxy060608's avatar
fxy060608 已提交
64
        />
65 66 67
        <div class="uni-right-window">
          <v-uni-right-window
            ref="right"
fxy060608's avatar
fxy060608 已提交
68
            v-bind="bindWindow"
69 70 71
            @hook:mounted="onRightWindowInit"
          />
        </div>
fxy060608's avatar
fxy060608 已提交
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
      </uni-right-window>
    </uni-content>
    <!--TODO footer-->
  </uni-layout>
  <keep-alive
    v-else
    :include="keepAliveInclude"
  >
    <router-view :key="routerKey" />
  </keep-alive>
</template>

<script>
import Vue from 'vue'

fxy060608's avatar
fxy060608 已提交
87
import {
88 89
  hasOwn,
  capitalize
fxy060608's avatar
fxy060608 已提交
90 91
} from 'uni-shared'

fxy060608's avatar
fxy060608 已提交
92 93 94 95
import {
  RESPONSIVE_MIN_WIDTH
} from 'uni-helpers/constants'

fxy060608's avatar
fxy060608 已提交
96
const windowTypes = ['top', 'left', 'right']
fxy060608's avatar
fxy060608 已提交
97 98 99 100 101 102 103 104 105 106 107
const documentElement = document.documentElement

let styleObj

function updateCssVar (name, value) {
  if (!styleObj) {
    styleObj = documentElement.style
  }
  styleObj.setProperty(name, value)
}

fxy060608's avatar
fxy060608 已提交
108 109 110 111 112 113 114 115 116 117 118 119
function checkMinWidth (minWidth) {
  const screen = window.screen
  const sizes = [
    window.outerWidth,
    window.outerHeight,
    screen.width,
    screen.height,
    documentElement.clientWidth,
    documentElement.clientHeight
  ]
  return Math.max.apply(null, sizes) > minWidth
}
fxy060608's avatar
fxy060608 已提交
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136

export default {
  name: 'Layout',
  props: {
    routerKey: {
      type: String,
      default: ''
    },
    keepAliveInclude: {
      type: Array,
      default: function () {
        return []
      }
    }
  },
  data () {
    return {
fxy060608's avatar
fxy060608 已提交
137
      marginWidth: 0,
fxy060608's avatar
fxy060608 已提交
138
      leftWindowStyle: '',
fxy060608's avatar
fxy060608 已提交
139 140
      rightWindowStyle: '',
      topWindowStyle: '',
fxy060608's avatar
fxy060608 已提交
141 142 143
      topWindowMediaQuery: false,
      leftWindowMediaQuery: false,
      rightWindowMediaQuery: false,
144 145 146
      topWindowHeight: '0px',
      apiShowTopWindow: false,
      apiShowLeftWindow: false,
147
      apiShowRightWindow: false,
fxy060608's avatar
fxy060608 已提交
148 149
      navigationBarTitleText: '',
      maxWidthMeidaQuery: false
fxy060608's avatar
fxy060608 已提交
150 151
    }
  },
fxy060608's avatar
fxy060608 已提交
152
  computed: {
fxy060608's avatar
fxy060608 已提交
153 154 155 156 157 158 159 160 161 162
    bindWindow () {
      return {
        matchTopWindow: this.topWindowMediaQuery,
        showTopWindow: this.showTopWindow || this.apiShowTopWindow,
        matchLeftWindow: this.leftWindowMediaQuery,
        showLeftWindow: this.showLeftWindow || this.apiShowLeftWindow,
        matchRightWindow: this.rightWindowMediaQuery,
        showRightWindow: this.showRightWindow || this.apiShowRightWindow
      }
    },
fxy060608's avatar
fxy060608 已提交
163 164 165 166
    showLayout () {
      return this.showTopWindow || this.showLeftWindow || this.showRightWindow
    },
    showTopWindow () {
167
      this.resetApiShowWindow()
fxy060608's avatar
fxy060608 已提交
168
      return this.$route.meta.topWindow !== false && this.topWindowMediaQuery
fxy060608's avatar
fxy060608 已提交
169 170
    },
    showLeftWindow () {
171
      this.resetApiShowWindow()
fxy060608's avatar
fxy060608 已提交
172
      return this.$route.meta.leftWindow !== false && this.leftWindowMediaQuery
fxy060608's avatar
fxy060608 已提交
173
    },
fxy060608's avatar
fxy060608 已提交
174
    showRightWindow () {
175
      this.resetApiShowWindow()
fxy060608's avatar
fxy060608 已提交
176
      return this.$route.meta.rightWindow !== false && this.rightWindowMediaQuery
fxy060608's avatar
fxy060608 已提交
177 178 179
    }
  },
  watch: {
fxy060608's avatar
fxy060608 已提交
180 181 182
    $route () {
      this.checkMaxWidth()
    },
fxy060608's avatar
fxy060608 已提交
183 184 185
    showLayout () {
      this.checkLayout()
    },
fxy060608's avatar
fxy060608 已提交
186 187 188 189
    showTopWindow (newVal, val) {
      if (newVal) {
        this.$nextTick(this.onTopWindowInit)
      } else {
190
        updateCssVar('--top-window-height', '0px')
fxy060608's avatar
fxy060608 已提交
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
      }
    },
    showLeftWindow (newVal, val) {
      if (newVal) {
        this.$nextTick(this.onLeftWindowInit)
      } else {
        updateCssVar('--window-left', '0px')
      }
    },
    showRightWindow (newVal, val) {
      if (newVal) {
        this.$nextTick(this.onRightWindowInit)
      } else {
        updateCssVar('--window-right', '0px')
      }
fxy060608's avatar
fxy060608 已提交
206 207 208
    },
    marginWidth (newVal) {
      updateCssVar('--window-margin', newVal + 'px')
fxy060608's avatar
fxy060608 已提交
209 210 211
    }
  },
  beforeCreate () {
212
    updateCssVar('--top-window-height', '0px')
fxy060608's avatar
fxy060608 已提交
213 214
    updateCssVar('--window-left', '0px')
    updateCssVar('--window-right', '0px')
fxy060608's avatar
fxy060608 已提交
215
    updateCssVar('--window-margin', '0px')
fxy060608's avatar
fxy060608 已提交
216 217
  },
  created () {
fxy060608's avatar
fxy060608 已提交
218
    this.topWindow = Vue.component('VUniTopWindow')
fxy060608's avatar
fxy060608 已提交
219 220 221
    this.leftWindow = Vue.component('VUniLeftWindow')
    this.rightWindow = Vue.component('VUniRightWindow')
    if ( // 低版本不提供 responsive 支持
fxy060608's avatar
fxy060608 已提交
222
      (this.topWindow || this.leftWindow || this.rightWindow) &&
fxy060608's avatar
fxy060608 已提交
223 224 225
        uni.canIUse('css.var') &&
        window.matchMedia
    ) {
fxy060608's avatar
fxy060608 已提交
226 227
      windowTypes.forEach(type => this.initWindowMinWidth(type))
      this.responsive = checkMinWidth(this.minWidth)
fxy060608's avatar
fxy060608 已提交
228
      if (this.responsive) {
fxy060608's avatar
fxy060608 已提交
229 230 231
        if (this.topWindow && this.topWindow.options.style) {
          this.topWindowStyle = this.topWindow.options.style
        }
fxy060608's avatar
fxy060608 已提交
232 233 234 235 236 237
        if (this.leftWindow && this.leftWindow.options.style) {
          this.leftWindowStyle = this.leftWindow.options.style
        }
        if (this.rightWindow && this.rightWindow.options.style) {
          this.rightWindowStyle = this.rightWindow.options.style
        }
fxy060608's avatar
fxy060608 已提交
238
        windowTypes.forEach(type => this.initMediaQuery(type))
239 240 241 242

        UniServiceJSBridge.on('onNavigationBarChange', (navigationBar) => {
          this.navigationBarTitleText = navigationBar.titleText
        })
fxy060608's avatar
fxy060608 已提交
243 244
      }
    }
fxy060608's avatar
fxy060608 已提交
245 246 247
    this.initMaxWidth()
  },
  mounted () {
fxy060608's avatar
fxy060608 已提交
248
    this.checkLayout()
fxy060608's avatar
fxy060608 已提交
249
    this.checkMaxWidth()
fxy060608's avatar
fxy060608 已提交
250 251
  },
  methods: {
fxy060608's avatar
fxy060608 已提交
252
    resetApiShowWindow () {
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
      // 仅对 left,right 重置
      // this.apiShowTopWindow = false
      this.apiShowLeftWindow = false
      this.apiShowRightWindow = false
    },
    showWindow (type, show = true) {
      if (!this[type + 'Window']) {
        return type + 'Window not found'
      }
      const fType = capitalize(type)
      if (!this['show' + fType + 'Window']) { // 小屏下
        const apiShowName = 'apiShow' + fType + 'Window'
        if (this[apiShowName] !== show) {
          this[apiShowName] = show
          if (type === 'top') { // 特殊处理 top
            if (show) {
              this.$nextTick(this.onTopWindowInit)
            } else {
271
              updateCssVar('--top-window-height', '0px')
272 273 274 275 276
            }
          }
        }
      }
    },
fxy060608's avatar
fxy060608 已提交
277 278 279 280 281
    initMaxWidth () {
      window.addEventListener('resize', () => {
        this.checkMaxWidth()
      })
    },
fxy060608's avatar
fxy060608 已提交
282 283 284 285
    checkLayout () {
      this.$emit('layout', this.showLayout)
    },
    checkMaxWidth () {
fxy060608's avatar
fxy060608 已提交
286
      const windowWidth = document.body.clientWidth
fxy060608's avatar
fxy060608 已提交
287 288
      const maxWidth = parseInt(this.$route.meta.maxWidth)
      let showMaxWidth = false
fxy060608's avatar
fxy060608 已提交
289
      if (windowWidth > maxWidth) {
fxy060608's avatar
fxy060608 已提交
290 291 292 293 294 295
        showMaxWidth = true
      } else {
        showMaxWidth = false
      }
      this.$emit('maxWidth', showMaxWidth)
      if (!this.$containerElem) {
fxy060608's avatar
fxy060608 已提交
296
        this.$containerElem = document.querySelector('uni-app')
fxy060608's avatar
fxy060608 已提交
297 298 299 300 301
      }
      if (!this.$containerElem) {
        return
      }
      if (showMaxWidth && maxWidth) {
fxy060608's avatar
fxy060608 已提交
302
        this.marginWidth = (windowWidth - maxWidth) / 2
fxy060608's avatar
fxy060608 已提交
303 304 305
        this.$nextTick(() => {
          this.onLeftWindowInit()
          this.onRightWindowInit()
fxy060608's avatar
fxy060608 已提交
306
          this.$containerElem.setAttribute('style', 'max-width:' + maxWidth + 'px;margin:0 auto;')
fxy060608's avatar
fxy060608 已提交
307 308 309 310 311 312 313 314 315 316
        })
      } else {
        this.marginWidth = 0
        this.$nextTick(() => {
          this.onLeftWindowInit()
          this.onRightWindowInit()
          this.$containerElem.removeAttribute('style')
        })
      }
    },
fxy060608's avatar
fxy060608 已提交
317 318 319 320 321 322
    initWindowMinWidth (type) {
      const name = type + 'Window'
      if (this[name]) {
        const minWidthName = type + 'WindowMinWidth'
        this[minWidthName] = RESPONSIVE_MIN_WIDTH
        const windowOptions = __uniConfig[name]
fxy060608's avatar
fxy060608 已提交
323
        if (windowOptions && windowOptions.matchMedia && hasOwn(windowOptions.matchMedia, 'minWidth')) {
fxy060608's avatar
fxy060608 已提交
324 325
          this[minWidthName] = windowOptions.matchMedia.minWidth
        }
fxy060608's avatar
fxy060608 已提交
326
        if (typeof this.minWidth === 'undefined' || this.minWidth > this[minWidthName]) {
fxy060608's avatar
fxy060608 已提交
327 328
          this.minWidth = this[minWidthName]
        }
fxy060608's avatar
fxy060608 已提交
329 330
      }
    },
fxy060608's avatar
fxy060608 已提交
331 332 333 334 335 336 337
    initMediaQuery (type) {
      if (this[type + 'Window']) {
        const name = type + 'WindowMediaQuery'
        const mediaQueryList = window.matchMedia('(min-width: ' + this[type + 'WindowMinWidth'] + 'px)')
        mediaQueryList.addListener((e) => {
          this[name] = e.matches
          this.$nextTick(() => {
338
            this['on' + capitalize(type) + 'WindowInit']()
fxy060608's avatar
fxy060608 已提交
339
          })
fxy060608's avatar
fxy060608 已提交
340
        })
fxy060608's avatar
fxy060608 已提交
341 342
        this[name] = mediaQueryList.matches
      }
fxy060608's avatar
fxy060608 已提交
343
    },
fxy060608's avatar
fxy060608 已提交
344
    onTopWindowInit () {
fxy060608's avatar
fxy060608 已提交
345 346 347
      if (!(this.responsive && this.topWindow)) {
        return
      }
fxy060608's avatar
fxy060608 已提交
348
      // TODO page header
fxy060608's avatar
fxy060608 已提交
349
      let windowTopHeight = '0px'
fxy060608's avatar
fxy060608 已提交
350
      if (this.topWindowStyle && this.topWindowStyle.height) {
fxy060608's avatar
fxy060608 已提交
351
        windowTopHeight = this.$refs.topWindow.offsetHeight + 'px'
fxy060608's avatar
fxy060608 已提交
352
      } else {
fxy060608's avatar
fxy060608 已提交
353
        windowTopHeight = this.$refs.top.$el.offsetHeight + 'px'
fxy060608's avatar
fxy060608 已提交
354
      }
fxy060608's avatar
fxy060608 已提交
355
      this.topWindowHeight = windowTopHeight
356
      updateCssVar('--top-window-height', windowTopHeight)
fxy060608's avatar
fxy060608 已提交
357
    },
fxy060608's avatar
fxy060608 已提交
358
    onLeftWindowInit () {
fxy060608's avatar
fxy060608 已提交
359
      if (!(this.responsive && this.leftWindow)) {
fxy060608's avatar
fxy060608 已提交
360
        updateCssVar('--window-left', this.marginWidth + 'px')
fxy060608's avatar
fxy060608 已提交
361 362
        return
      }
fxy060608's avatar
fxy060608 已提交
363
      if (this.leftWindowStyle && this.leftWindowStyle.width) {
fxy060608's avatar
fxy060608 已提交
364
        updateCssVar('--window-left', this.$refs.leftWindow.offsetWidth + this.marginWidth + 'px')
fxy060608's avatar
fxy060608 已提交
365
      } else {
fxy060608's avatar
fxy060608 已提交
366
        updateCssVar('--window-left', this.$refs.left.$el.offsetWidth + this.marginWidth + 'px')
fxy060608's avatar
fxy060608 已提交
367 368 369
      }
    },
    onRightWindowInit () {
fxy060608's avatar
fxy060608 已提交
370
      if (!(this.responsive && this.rightWindow)) {
fxy060608's avatar
fxy060608 已提交
371
        updateCssVar('--window-right', this.marginWidth + 'px')
fxy060608's avatar
fxy060608 已提交
372 373
        return
      }
fxy060608's avatar
fxy060608 已提交
374
      if (this.rightWindowStyle && this.rightWindowStyle.width) {
fxy060608's avatar
fxy060608 已提交
375
        updateCssVar('--window-right', this.$refs.rightWindow.offsetWidth + this.marginWidth + 'px')
fxy060608's avatar
fxy060608 已提交
376
      } else {
fxy060608's avatar
fxy060608 已提交
377
        updateCssVar('--window-right', this.$refs.right.$el.offsetWidth + this.marginWidth + 'px')
fxy060608's avatar
fxy060608 已提交
378 379 380 381 382 383 384 385 386 387 388 389 390 391
      }
    }
  }
}
</script>

<style>
  uni-content {
    display: flex;
    flex: 1 0 auto;
    height: 100%;
  }

  uni-main {
fxy060608's avatar
fxy060608 已提交
392
    flex: 1;
fxy060608's avatar
fxy060608 已提交
393
    width: 100%;
fxy060608's avatar
fxy060608 已提交
394 395
  }

fxy060608's avatar
fxy060608 已提交
396
  uni-top-window+uni-content {
397
    height: calc(100vh - var(--top-window-height));
fxy060608's avatar
fxy060608 已提交
398 399
  }

fxy060608's avatar
fxy060608 已提交
400 401 402 403 404 405 406 407 408 409 410 411 412
  uni-left-window {
    position: relative;
    width: var(--window-left);
    order: -1;
    overflow-x: hidden;
  }

  uni-right-window {
    position: relative;
    width: var(--window-right);
    overflow-x: hidden;
  }

413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
  uni-left-window[data-show],
  uni-right-window[data-show] {
    position: absolute;
  }

  uni-right-window[data-show] {
    right: 0;
  }

  uni-content .uni-mask,
  .uni-left-window,
  .uni-right-window {
    z-index: 997;
  }

  .uni-mask+.uni-left-window,
  .uni-mask+.uni-right-window {
fxy060608's avatar
fxy060608 已提交
430
    position: fixed;
431 432
  }

fxy060608's avatar
fxy060608 已提交
433 434
  .uni-top-window {
    position: fixed;
fxy060608's avatar
fxy060608 已提交
435 436
    left: var(--window-margin);
    right: var(--window-margin);
fxy060608's avatar
fxy060608 已提交
437
    top: 0;
fxy060608's avatar
fxy060608 已提交
438
    z-index: 998;
fxy060608's avatar
fxy060608 已提交
439
    overflow: hidden;
fxy060608's avatar
fxy060608 已提交
440
  }
fxy060608's avatar
fxy060608 已提交
441
</style>