需求
我希望寫一個公共結果頁面,滿足所有模塊的結果展示,頁面設計要素如下:
- 結果圖標
type
(成功圖標,失敗圖標) - 標題
title
(如:提交成功) - 描述
descripton
(如:您的工單已提交,請等待管理員審核) - 內容
content
(內容不固定,樣式不固定,可自定義) - 操作
action
(提供默認按鈕,可定制返回步數,具備自定義的能力)
我希望的結果頁面應該是這樣的
- 只有一個路由頁面,所有人模塊跳轉到當前頁面展示結果。
- 除了
type
、title
和description
只傳入字符串即可,content
和action
可擴展。
方案
框架采用
vue
,以下方案都是基於vue
的實現思路
方案一
- 提供一個
Result.vue
組件 type
、title
和description
使用props
傳參content
和action
使用slot
插槽進行擴展
弊端:每個人都要自己配一個路由頁面進行跳轉,違背設計的初衷,只有一個路由頁面滿足所有結果展示的設計思路。
方案二
- 提供一個
ResultPage.vue
路由頁面,所有模塊都跳轉到該頁面 type
、title
和description
通過路由參數傳遞,結果頁面接收參數進行展示content
和action
傳遞全局組件的標識,結果頁面通過動態組件加載對應的標識
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 的值,來決定哪個組件被渲染。
- Props:
<!-- 動態組件由 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>