我想使用bootstrap vue modal component在vuejs应用程序中实现此功能:
当用户单击页面UI上的“删除”按钮时:
它显示了在主体中具有动态内容的模态: “您确定要删除客户: customer_name_here“
如果用户单击“取消”按钮:模态消失。
如果用户单击“确定”按钮:
它将模式主体内容更改为: '正在删除客户'customer_name_here'... ,则会禁用“取消”和“确定”按钮,并调用API删除客户。
从API收到成功响应后:
到目前为止的代码:
<b-button v-b-modal.modal1 variant="danger">Delete</b-button>
<b-modal id="modal1" title="Delete Customer"
@ok="deleteCustomer" centered no-close-on-backdrop -close-on-esc ref="modal">
<p class="my-4">Are you sure, you want to delete customer:</p>
<p>{{customer.name}}</p>
</b-modal>
Vue JS代码:
deleteCustomer(evt) {
evt.preventDefault()
this.$refs.modal.hide()
CustomerApi.deleteCustomer(this.customer.id).then(response => {
// successful response
})
答案 0 :(得分:4)
如果我的理解正确,您想根据不同的状态组合显示模式内容。
根据您的描述,应该有2种状态:
deletingState:指示是否开始删除
loadingState:指示是否正在等待服务器的响应
选中Bootstrap Vue Modal Guide,然后搜索关键字= 禁用内置按钮,您将看到我们可以使用cancel-disabled
和ok-disabled
道具来控制禁用状态默认的取消和确定按钮(或者您可以使用slot = modal-footer 或 modal-ok ,模态取消。)
您可能会使用的其他道具:ok-only
,cancel-only
,busy
。
最后将v-if
和道具与状态组合绑定以显示内容。
就像下面的演示一样:
Vue.config.productionTip = false
new Vue({
el: '#app',
data() {
return {
customer: {name: 'demo'},
deletingState: false, // init=false, if pop up modal, change it to true
loadingState: false // when waiting for server respond, it will be true, otherwise, false
}
},
methods: {
deleteCustomer: function() {
this.deletingState = false
this.loadingState = false
this.$refs.myModalRef.show()
},
proceedReq: function (bvEvt) {
if(!this.deletingState) {
bvEvt.preventDefault() //if deletingState is false, doesn't close the modal
this.deletingState = true
this.loadingState = true
setTimeout(()=>{
console.log('simulate to wait for server respond...')
this.loadingState = false
this.deletingState = true
}, 1500)
} else {
console.log('confirm to delete...')
}
},
cancelReq: function () {
console.log('cancelled')
}
}
})
.customer-name {
background-color:green;
font-weight:bold;
}
<!-- Add this to <head> -->
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<!-- Add this after vue.js -->
<script src="//unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>
<div id="app">
<b-button v-b-modal.modal1 variant="danger" @click="deleteCustomer()">Delete</b-button>
<b-modal title="Delete Customer" centered no-close-on-backdrop no-close-on-esc ref="myModalRef"
@ok="proceedReq($event)" @cancel="cancelReq()" :cancel-disabled="deletingState" :ok-disabled="loadingState" :ok-only="deletingState && !loadingState">
<div v-if="!deletingState">
<p class="my-4">Are you sure, you want to delete customer:<span class="customer-name">{{customer.name}}</span></p>
</div>
<div v-else>
<p v-if="loadingState">
Deleting customer <span class="customer-name">{{customer.name}}</span>
</p>
<p v-else>
Successfully deleted customer <span class="customer-name">{{customer.name}}</span>
</p>
</div>
</b-modal>
</div>
答案 1 :(得分:2)
这里是Bootstrap-vue模态的通用包装器组件,它采用状态数组并根据nextState
属性进行导航。它利用计算属性来响应状态更改。
在父级中,状态数组也可以在计算属性中定义,以便我们可以将客户(或照片)属性添加到消息中。
修改
添加了内容插槽,允许父组件在模态内容内定义确切的标记。
console.clear()
// Mock CustomerApi
const CustomerApi = {
deleteCustomer: (id) => {
console.log('id', id)
return new Promise((resolve,reject) => {
setTimeout(() => {
if (id !== 1) {
reject(new Error('Delete has failed'))
} else {
resolve('Deleted')
}
}, 3000);
});
}
}
// Wrapper component to handle state changes
Vue.component('state-based-modal', {
template: `
<b-modal
ref="innerModal"
:title="title"
:ok-disabled="okDisabled"
:cancel-disabled="cancelDisabled"
:busy="busy"
@ok="handleOk"
:ok-title="okTitle"
@hidden="hidden"
v-bind="otherAttributes"
>
<div class="content flex-grow" :style="{height: height}">
<!-- named slot applies to current state -->
<slot :name="currentState.id + 'State'" v-bind="currentState">
<!-- default content if no slot provided on parent -->
<p>{{message}}</p>
</slot>
</div>
</b-modal>`,
props: ['states', 'open'],
data: function () {
return {
current: 0,
error: null
}
},
methods: {
handleOk(evt) {
evt.preventDefault();
// save currentState so we can switch display immediately
const state = {...this.currentState};
this.displayNextState(true);
if (state.okButtonHandler) {
state.okButtonHandler()
.then(response => {
this.error = null;
this.displayNextState(true);
})
.catch(error => {
this.error = error.message;
this.displayNextState(false);
})
}
},
displayNextState(success) {
const nextState = this.getNextState(success);
if (nextState == -1) {
this.$refs.innerModal.hide();
this.hidden();
} else {
this.current = nextState;
}
},
getNextState(success) {
// nextState can be
// - a string = always go to this state
// - an object with success or fail pathways
const nextState = typeof this.currentState.nextState === 'string'
? this.currentState.nextState
: success && this.currentState.nextState.onSuccess
? this.currentState.nextState.onSuccess
: !success && this.currentState.nextState.onError
? this.currentState.nextState.onError
: undefined;
return this.states.findIndex(state => state.id === nextState);
},
hidden() {
this.current = 0; // Reset to initial state
this.$emit('hidden'); // Inform parent component
}
},
computed: {
currentState() {
const currentState = this.current;
return this.states[currentState];
},
title() {
return this.currentState.title;
},
message() {
return this.currentState.message;
},
okDisabled() {
return !!this.currentState.okDisabled;
},
cancelDisabled() {
return !!this.currentState.cancelDisabled;
},
busy() {
return !!this.currentState.busy;
},
okTitle() {
return this.currentState.okTitle;
},
otherAttributes() {
const otherAttributes = this.currentState.otherAttributes || [];
return otherAttributes
.reduce((obj, v) => { obj[v] = null; return obj; }, {})
},
},
watch: {
open: function(value) {
if (value) {
this.$refs.innerModal.show();
}
}
}
})
// Parent component
new Vue({
el: '#app',
data() {
return {
customer: {id: 1, name: 'myCustomer'},
idToDelete: 1,
openModal: false
}
},
methods: {
deleteCustomer(id) {
// Return the Promise and let wrapper component handle result/error
return CustomerApi.deleteCustomer(id)
},
modalIsHidden(event) {
this.openModal = false; // Reset to start condition
}
},
computed: {
avatar() {
return `https://robohash.org/${this.customer.name}?set=set4`
},
modalStates() {
return [
{
id: 'delete',
title: 'Delete Customer',
message: `delete customer: ${this.customer.name}`,
okButtonHandler: () => this.deleteCustomer(this.idToDelete),
nextState: 'deleting',
otherAttributes: ['centered no-close-on-backdrop close-on-esc']
},
{
id: 'deleting',
title: 'Deleting Customer',
message: `Deleting customer: ${this.customer.name}`,
okDisabled: true,
cancelDisabled: true,
nextState: { onSuccess: 'deleted', onError: 'error' },
otherAttributes: ['no-close-on-esc'],
contentHeight: '250px'
},
{
id: 'deleted',
title: 'Customer Deleted',
message: `Deleting customer: ${this.customer.name}`,
cancelDisabled: true,
nextState: '',
otherAttributes: ['close-on-esc']
},
{
id: 'error',
title: 'Error Deleting Customer',
message: `Error deleting customer: ${this.customer.name}`,
okTitle: 'Retry',
okButtonHandler: () => this.deleteCustomer(1),
nextState: 'deleting',
otherAttributes: ['close-on-esc']
},
];
}
}
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<script src="//unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>
<div id="app">
<b-button @click="openModal = true" variant="danger">Delete</b-button>
<input type="test" id="custId" v-model="idToDelete">
<label for="custId">Enter 2 to make it fail</label>
<state-based-modal
:states="modalStates"
:open="openModal"
@hidden="modalIsHidden"
>
<template slot="deleteState" scope="state">
<img alt="Mindy" :src="avatar" style="width: 150px">
<p>DO YOU REALLY WANT TO {{state.message}}</p>
</template>
<template slot="errorState" scope="state">
<p>Error message: {{state.error}}</p>
</template>
</state-based-modal>
</div>
答案 2 :(得分:1)
您可能更喜欢使用单独的模式,逻辑变得更加清晰,并且您可以轻松添加更多途径,例如重试API错误。
console.clear()
const CustomerApi = {
deleteCustomer: (id) => {
return new Promise((resolve,reject) => {
setTimeout(() => {
if (id !== 1) {
reject(new Error('Delete has failed'))
} else {
resolve('Deleted')
}
}, 3000);
});
}
}
new Vue({
el: '#app',
data() {
return {
customer: {id: 1, name: 'myCustomer'},
id: 1,
error: null
}
},
methods: {
deleteCustomer(e) {
e.preventDefault()
this.$refs.modalDeleting.show()
this.$refs.modalDelete.hide()
CustomerApi.deleteCustomer(this.id)
.then(response => {
this.$refs.modalDeleting.hide()
this.$refs.modalDeleted.show()
})
.catch(error => {
this.error = error.message
this.id = 1 // For demo, api success 2nd try
this.$refs.modalError.show()
})
}
}
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<script src="//unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>
<div id="app">
<b-button v-b-modal.modal-delete variant="danger">Delete</b-button>
<input type="test" id="custId" v-model="id">
<label for="custId">Enter 2 to make it fail</label>
<b-modal
id="modal-delete"
ref="modalDelete"
title="Delete Customer"
@ok="deleteCustomer"
centered no-close-on-backdrop close-on-esc>
<p class="my-4">Are you sure, you want to delete customer: {{customer.name}}</p>
</b-modal>
<b-modal
ref="modalDeleting"
title="Deleting Customer"
centered no-close-on-backdrop no-close-on-esc
no-fade
:busy="true">
<p class="my-4">Deleting customer: {{customer.name}}</p>
</b-modal>
<b-modal
ref="modalDeleted"
title="Customer Deleted"
centered no-close-on-backdrop close-on-esc
no-fade
:ok-only="true">
<p class="my-4">Customer '{{customer.name}}' has been deleted</p>
</b-modal>
<b-modal
ref="modalError"
title="Error Deleting Customer"
centered no-close-on-backdrop close-on-esc
no-fade
:ok-title="'Retry'"
@ok="deleteCustomer">
<p class="my-4">An error occured deleting customer: {{customer.name}}</p>
<p>Error message: {{error}}</p>
</b-modal>
</div>
答案 3 :(得分:-1)
正如我们在评论中所讨论的,另一种解决方案类似于Quasar Stepper。
设计一个组件作为步骤(在下面的演示中,名称为b-step-modal
)
然后使用一个模态步进器(以下示例中的名称为b-stepper-modal
)作为父级。
然后,您只需列出所有步骤即可成为modal-stepper
的子级。如果您想禁用按钮或跳过一个步骤等,则可以使用步骤挂钩(下面的演示提供了step-begin
和step-end
)来实现目标。
就像下面的粗略演示一样:
Vue.config.productionTip = false
let bModal = Vue.component('bModal')
Vue.component('b-stepper-modal', {
provide () {
return {
_stepper: this
}
},
extends: bModal,
render(h) {
let _self = this
return h(bModal, {props: _self.$props, ref: '_innerModal', on: {
ok: function (bvEvt) {
_self.currentStep++
if(_self.currentStep < _self.steps.length) {
bvEvt.preventDefault()
}
}
}}, _self.$slots.default)
},
data() {
return {
steps: [],
currentStep: 0
}
},
methods: {
_registerStep(step) {
this.steps.push(step)
},
show () {
this.$refs._innerModal.show()
}
}
})
Vue.component('b-step-modal', {
inject: {
_stepper: {
default () {
console.error('step must be child of stepper')
}
}
},
props: ['stepBegin', 'stepEnd'],
data () {
return {
isActive: false,
stepSeq: 0
}
},
render(h) {
return this.isActive ? h('p', {}, this.$slots.default) : null
},
created () {
this.$watch('_stepper.currentStep', function (newVal, oldVal) {
if(oldVal) {
if(typeof this.stepEnd === 'function') this.stepEnd()
} else {
if(typeof this.stepBegin === 'function') this.stepBegin()
}
this.isActive = (newVal === this.stepSeq)
})
},
mounted () {
this.stepSeq = this._stepper.steps.length
this._stepper._registerStep(this)
this.isActive = this._stepper.currentStep === this.stepSeq
}
})
new Vue({
el: '#app',
data() {
return {
customer: {
name: 'demo'
},
deletingState: false, // init=false, if pop up modal, change it to true
loadingState: false // when waiting for server respond, it will be true, otherwise, false
}
},
methods: {
deleteCustomer: function() {
this.deletingState = false
this.loadingState = false
this.$refs.myModalRef.show()
},
proceedReq: function(bvEvt) {
if (!this.deletingState) {
bvEvt.preventDefault() //if deletingState is false, doesn't close the modal
this.deletingState = true
this.loadingState = true
setTimeout(() => {
console.log('simulate to wait for server respond...')
this.loadingState = false
this.deletingState = true
}, 1500)
} else {
console.log('confirm to delete...')
}
},
cancelReq: function() {
console.log('cancelled')
},
testStepBeginHandler: function () {
this.deletingState = true
this.loadingState = true
setTimeout(() => {
console.log('simulate to wait for server respond...')
this.loadingState = false
this.deletingState = true
}, 1500)
},
testStepEndHandler: function () {
console.log('step from show to hide')
}
}
})
<!-- Add this to <head> -->
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<!-- Add this after vue.js -->
<script src="//unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>
<div id="app">
<b-button v-b-modal.modal1 variant="danger" @click="deleteCustomer()">Delete</b-button>
<b-stepper-modal title="Delete Customer" centered no-close-on-backdrop no-close-on-esc ref="myModalRef" @ok="proceedReq($event)" @cancel="cancelReq()" :cancel-disabled="deletingState" :ok-disabled="loadingState" :ok-only="deletingState && !loadingState">
<b-step-modal>
<div>
<p class="my-4">Are you sure, you want to delete customer:<span class="customer-name">{{customer.name}}</span></p>
</div>
</b-step-modal>
<b-step-modal :step-begin="testStepBeginHandler" :step-end="testStepEndHandler">
<div>
<p v-if="loadingState">
Deleting customer <span class="customer-name">{{customer.name}}</span>
</p>
<p v-else>
Successfully deleted customer <span class="customer-name">{{customer.name}}</span>
</p>
</div>
</b-step-modal>
</b-stepper-modal>
</div>