Vue公共結果頁面實現


需求

我希望寫一個公共結果頁面,滿足所有模塊的結果展示,頁面設計要素如下:

  1. 結果圖標type(成功圖標,失敗圖標)
  2. 標題title(如:提交成功)
  3. 描述descripton(如:您的工單已提交,請等待管理員審核)
  4. 內容content(內容不固定,樣式不固定,可自定義)
  5. 操作action(提供默認按鈕,可定制返回步數,具備自定義的能力)

我希望的結果頁面應該是這樣的

  1. 只有一個路由頁面,所有人模塊跳轉到當前頁面展示結果。
  2. 除了typetitledescription只傳入字符串即可,contentaction可擴展。

方案

框架采用vue,以下方案都是基於vue的實現思路

方案一

  • 提供一個Result.vue組件
  • typetitledescription使用props傳參
  • contentaction使用slot插槽進行擴展

弊端:每個人都要自己配一個路由頁面進行跳轉,違背設計的初衷,只有一個路由頁面滿足所有結果展示的設計思路。

方案二

  • 提供一個ResultPage.vue路由頁面,所有模塊都跳轉到該頁面
  • typetitledescription通過路由參數傳遞,結果頁面接收參數進行展示
  • contentaction傳遞全局組件的標識,結果頁面通過動態組件加載對應的標識

動態組件API

ResultPage.vue

// 偽代碼只是表達思路用
<template>
  <div>
    <img
      v-if="$route.query.type === 'success'"
      src='success.png'
    />
    <img v-else src='fail.png'/>
  </div>
  <h1>{{ $route.query.title $}}</h1>
  <h2>{{ $route.query.description $}}</h2>
  <component 
    v-if="$route.query.content"
    is="$route.query.content"
  />
  <componet 
    v-if="$route.query.action"
    is="$route.query.action"
  >
  <button v-else>返回</button>
</template>
<script>
export default {
  name: 'ResultPage'
}
</script>

跳轉示例

<script>
import Vue from 'vue'
export default {
  methods: {
    // 創建全局組件,傳遞組件標識id字符串
    Vue.component('content', {
      template: `
        <table>
          <tr>
            <th>提交人:</th>
            <td>張三</td>
          </tr>
          <tr>
            <th>提交時間:</th>
            <td>2019-07-09</td>
          </tr>
        </table>
      `
    })
    this.$router.replace({
      name: 'ResultPage',
      query: {
        type: 'success'
        title: '提交成功',
        description: '提交等待審核',
        content: 'content'
      }
    })
  }
}
</script>

弊端:無法模塊化創建組件,無法傳遞組件,只能傳遞組件標識字符串
優化:如果要傳遞組件構造器,可以用vuex傳遞參數

方案三

方案三是對方案二的優化升級,最大的難點是,怎么在路由頁面間傳遞組件

  • 兼容方案二,路由傳參+vuex傳參
  • 組件支持模塊化
  • 可傳遞局部組件
  • 組件可傳參擴展

需要了解的API

  • component 內置組件component
    • Props:
      • is - string | ComponentDefinition | ComponentConstructor
    • 用法:
      渲染一個“元組件”為動態組件。依 is 的值,來決定哪個組件被渲染。
  <!-- 動態組件由 vm 實例的屬性值 `componentId` 控制 -->
  <component :is="componentId"></component>

  <!-- 也能夠渲染注冊過的組件或 prop 傳入的組件 -->
  <component :is="$options.components.child"></component>

動態組件接收三個參數

  • string 組件標識
  • ComponentDefinition 組件定義
  • ComponentConstructor 組件構造器

以下是對組件幾個概念的示例

// 返回組件定義
import ComponentDefinition from './ComponentDefinition.vue'
// 組件標識id
const componentId = 'component-1' 
// 返回組件構造器
 const ComponentConstructor = Vue.component(componentId, {
      data() {
        return {
          msg: '你好'
        }
      },
      template: `<h1>{{msg}}</h1>`
    })
// 組件擴展返回組件構造器    
const ComponentConstructor2 = Vue.extend(ComponentDefinition)
// 創建組件實例,並給組件傳遞參數
const componentInstance = new ComponentConstructor({
  propsData: {
    detail: {
      username: '張三'
    }
  }
})

如果要實現組件模塊化,並可擴展就要用到以下方法
1、導入本地組件
2、對組件進行擴展
3、創建組件實例,並傳遞propsData

但是細心的朋友會發現,動態組件並不支持加載組件實例,組件實例需要手動掛載到頁面才行

// 創建實例
const componentInstance = new ComponentConstructor({
  propsData: {
    detail: {
      username: '張三'
    }
  }
})
// 掛載實例
componentInstance.$mount('#content');

方案三要對結果頁面進行擴展,支持組件模塊化,在結果頁面要對組件實例區分開,組件實例使用掛載的方式顯示,其他用動態組件顯示,好在Vue實例有_isVue屬性能區分開。

以上說明了原理,下面放出最終版本實現

  • ResultPage.vue
<template>
  <q-content>
    <a-card
      :bordered="false"
    >
      <q-result
        :type="type"
        :title="title"
        :description="description"
      >
        <template
          v-if="!!content"
        >
          <template
            v-if="content._isVue"
          >
            <div id="content" />
          </template>
          <template
            v-else
          >
            <component
              :is="content"
            />
          </template>
        </template>
        <template
          v-if="action"
          slot="action"
        >
          <div
            v-if="action._isVue"
            id="action"
          />
          <component
            :is="action"
            v-else
          />
        </template>
        <template
          v-else
          slot="action"
        >
          <a-button
            @click="backHandler"
          >
            {{ goTitle }}
          </a-button>
        </template>
      </q-result>
    </a-card>
  </q-content>
</template>

<script>
  import QContent from '../../components/page/QContent'
  import QResult from '../../components/result/QResult'

  export default {
    name: 'PageResult',
    components: { QResult, QContent },
    mixins: [],
    computed: {
      type () {
        return this.$route.query.type || this.result.type
      },
      title () {
        return decodeURIComponent(this.$route.query.title || this.result.title || '')
      },
      description () {
        return decodeURIComponent(this.$route.query.description || this.result.description || '')
      },
      content () {
        return this.$route.query.content || this.result.content
      },
      action () {
        return this.$route.query.action || this.result.action
      },
      go () {
        return this.$route.query.go || this.result.go
      },
      goTitle () {
        return this.go ? '返回' : '返回首頁'
      },
    },
    beforeDestroy () {
      this.cacheResult({})
    },
    mounted () {
      // 掛載組件實例
      if (this.content && this.content._isVue) {
        this.content.$mount('#content')
      }
      if (this.action && this.action._isVue) {
        this.action.$mount('#action')
      }
    },
    methods: {
      backHandler: function () {
        if (this.go) {
          this.$router.go(this.go)
        } else {
          this.$router.replace('/')
        }
      },
    },
  }
</script>

<style scoped>

</style>

  • result.js vuex傳遞參數
import { RESULT_UPDATE } from '../mutation-types/result'

export default {
  namespaced: true,
  state: {
    result: {},
  },
  mutations: {
    [RESULT_UPDATE] (state, obj) {
      state.result = obj
    },
  },
  getters: {
    getResult (state) {
      return state.result
    },
  },
  actions: {
    cacheResult ({ commit }, obj) {
      commit(RESULT_UPDATE, obj)
    },
  },
}

  • result.js全局混入
import Vue from 'vue'
import { mapActions, mapState } from 'vuex'

export default {
  computed: {
    ...mapState('result', [
      'result',
    ]),
  },
  methods: {
    ...mapActions('result', [
      'cacheResult',
    ]),
    changeResultPage: function (options = {}) {
      this.cacheResult(options)
      this.$router.replace({ name: 'pageResult' })
    },
    renderComponent: function (component, options) {
      if (options) { // 有參數
        if (typeof component !== 'string') {
          const componentConstructor = Vue.extend(component)
          return new componentConstructor(options)
        } else {
          return component
        }
      } else {
        return component
      }
    },
  },
}

  • 模塊調用
<script>
import TestResultContent from './components/TestResultContent'
export default {
  methods: {
    toHandler: function() {
      this.changeResultPage({
          type: 'success',
          title: '創建成功',
          description: '使用默認方法跳轉頁面',
          content: this.renderComponent(TestResultContent, {
            propsData: {
              detail: {
                username: '張三',
                production: 'office365',
                datetime: '2019-09-09',
                order: 'E128888292939293',
                between: '2019-08-07 ~ 2022-12-11',
                remark: '盡快購買',
              },
            },
          })
        })
    }
  }
}
</script>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM