之前一直在用element-ui
,這次我們改用iView
框架导犹。
關(guān)于修改表單未保存離開頁面這里我們只考慮兩種情況唱凯,一種是直接關(guān)閉瀏覽器(直接關(guān)閉頁面),一種是路由跳轉(zhuǎn)(包含瀏覽器前進(jìn)后退)谎痢,以下是兩種提示頁面的截圖:
直接關(guān)閉瀏覽器的提示方式:
當(dāng)瀏覽器窗口關(guān)閉或者刷新時磕昼,會觸發(fā)beforeunload
事件。當(dāng)前頁面不會直接關(guān)閉节猿,可以點擊確定按鈕關(guān)閉或刷新票从,也可以取消關(guān)閉或刷新。
| Bubbles | No |
| Cancelable | Yes |
| Interface |Event
|
| Event handler property |onbeforeunload
|
事件使網(wǎng)頁能夠觸發(fā)一個確認(rèn)對話框,詢問用戶是否真的要離開該頁面峰鄙。如果用戶確認(rèn)浸间,瀏覽器將導(dǎo)航到新頁面,否則導(dǎo)航將會取消吟榴。
根據(jù)規(guī)范发框,要顯示確認(rèn)對話框,事件處理程序需要在事件上調(diào)用preventDefault()
煤墙。
但是請注意梅惯,并非所有瀏覽器都支持此方法,而有些瀏覽器需要事件處理程序?qū)崿F(xiàn)兩個遺留方法中的一個作為代替:
- 將字符串分配給事件的
returnValue
屬性 - 從事件處理程序返回一個字符串仿野。
了解了這個方法铣减,我們在data
里定義這三個屬性:
data () {
return {
isEdited: false, // 判斷表單是否被修改
formData: {}, // 表單保存的數(shù)據(jù)
initData: {} // 拷貝一份初始表單數(shù)據(jù),這里的數(shù)據(jù)不會改變
}
}
在methods
里定義beforeunload
方法:
methods: {
beforeunload (e) {
// 表單被修改
if (this.isEdited) {
const confirmationMessage = '你確定離開此頁面嗎脚作?'
e.returnValue = confirmationMessage
return confirmationMessage
}
}
}
在進(jìn)入頁面和銷毀頁面前綁定beforeunload
方法:
mounted () {
// 初始表單數(shù)據(jù)賦值
this.initData = Object.assign({}, this.formData)
// 攔截判斷是否離開當(dāng)前頁面
window.addEventListener('beforeunload', this.beforeunload)
},
beforeDestroy () {
// 銷毀攔截判斷是否離開當(dāng)前頁面
window.removeEventListener('beforeunload', this.beforeunload)
}
我們判斷的邏輯是對比初始數(shù)據(jù)和修改表單后的數(shù)據(jù)葫哗,如果一致說明沒有改變表單數(shù)據(jù),否則就說明用戶編輯過表單球涛。通過watch
去監(jiān)聽formData
:
watch: {
formData: {
deep: true,
handler (val) {
// 把兩個對象轉(zhuǎn)換成字符串比較
if (JSON.stringify(this.initData) === JSON.stringify(val)) {
this.isEdited = false
} else {
this.isEdited = true
}
}
}
}
vue
路由跳轉(zhuǎn)的提示方式:
這需要你有另一個vue
的頁面劣针,然后我們只需要在上面的基礎(chǔ)上加上beforeRouteLeave
方法就行了。
beforeRouteLeave (to, from, next) {
// 表單被修改
if (this.isEdited) {
this.$Modal.confirm({
title: '系統(tǒng)監(jiān)測到你有未保存表單亿扁,是否直接離開捺典?',
content: '',
onOk: () => {
next()
},
onCancel: () => {
next(false)
}
})
} else {
next()
}
}
完整代碼:
<template>
<div class="main">
<h3>基本信息</h3>
<!-- 表單 -->
<Form
ref="form"
:model="formData"
:rules="formValidate"
label-position="top"
>
<!-- 姓名 -->
<Divider
orientation="left"
id="name"
>
<h5>姓名</h5>
</Divider>
<Row
:gutter="20"
class="main-block"
>
<Col span="12">
<FormItem
label="姓名"
prop="name"
>
<Input
placeholder="請輸入姓名"
v-model.trim="formData.name"
clearable
></Input>
</FormItem>
</Col>
</Row>
<!-- 年齡 -->
<Divider
orientation="left"
id="age"
>
<h5>年齡</h5>
</Divider>
<Row
:gutter="20"
class="main-block"
>
<Col span="12">
<FormItem
label="年齡"
prop="age"
>
<Input
placeholder="請輸入年齡"
v-model.trim="formData.age"
clearable
></Input>
</FormItem>
</Col>
</Row>
<!-- 性別 -->
<Divider
orientation="left"
id="gender"
>
<h5>性別</h5>
</Divider>
<Row
:gutter="20"
class="main-block"
>
<Col span="12">
<FormItem
label="性別"
prop="gender"
>
<RadioGroup v-model="formData.gender">
<Radio label="男"></Radio>
<Radio label="女"></Radio>
</RadioGroup>
</FormItem>
</Col>
</Row>
<!-- 稱呼 -->
<Divider
orientation="left"
id="tag"
>
<h5>稱呼</h5>
</Divider>
<Row
:gutter="20"
class="main-block"
>
<Col span="12">
<FormItem
label="稱呼"
prop="tag"
>
<Select
placeholder="請選擇稱呼"
v-model="formData.tag"
clearable
>
<Option
v-for="(item, index) in tagList"
:value="item.value"
:key="index"
>{{item.label}}</Option>
</Select>
</FormItem>
</Col>
</Row>
</Form>
<div class="btn-layer">
<Button
type="primary"
style="margin-right:10px;"
@click="onSubmit"
>保存</Button>
<Button @click="onCancel">重置</Button>
</div>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
data () {
return {
isEdited: false, // 判斷表單是否被修改
initData: {},
formData: {
name: '',
age: '',
gender: '男',
tag: ''
},
formValidate: {
name: [{ required: true, message: '請輸入姓名', trigger: 'blur' }],
age: [{ required: true, message: '請輸入年齡', trigger: 'blur' }],
gender: [{ required: true, message: '請輸入性別', trigger: 'change' }],
tag: [{ required: true, message: '請輸入子會會議名稱', trigger: 'change' }]
},
tagList: [
{ label: '先生', value: 'Mr.' },
{ label: '女士', value: 'Ms.' }
]
}
},
methods: {
onSubmit () {
this.$refs.form.validate(valid => {
if (valid) {
this.isEdited = false
this.$Message.success('創(chuàng)建成功')
} else {
this.$nextTick(() => {
document.getElementsByClassName('ivu-form-item-error')[0].scrollIntoView({ behavior: 'smooth' })
})
}
})
},
onCancel () {
this.$refs.form.resetFields()
},
beforeunload (e) {
if (this.isEdited) {
const confirmationMessage = '你確定離開此頁面嗎?'
e.returnValue = confirmationMessage
return confirmationMessage
}
}
},
mounted () {
this.initData = Object.assign({}, this.formData)
// 攔截判斷是否離開當(dāng)前頁面
window.addEventListener('beforeunload', this.beforeunload)
},
beforeDestroy () {
// 銷毀攔截判斷是否離開當(dāng)前頁面
window.removeEventListener('beforeunload', this.beforeunload)
},
watch: {
formData: {
deep: true,
handler (val) {
if (JSON.stringify(this.initData) === JSON.stringify(val)) {
this.isEdited = false
} else {
this.isEdited = true
}
}
}
},
beforeRouteLeave (to, from, next) {
if (this.isEdited) {
this.$Modal.confirm({
title: '系統(tǒng)監(jiān)測到你有未保存表單从祝,是否直接離開襟己?',
content: '',
onOk: () => {
next()
},
onCancel: () => {
next(false)
}
})
} else {
next()
}
}
}
</script>
<style scoped>
.main {
padding: 50px;
max-width: 1200px;
margin: 0 auto;
border: 1px solid #ccc;
}
.main h3 {
text-align: center;
margin-bottom: 50px;
}
.main .btn-layer {
margin: 30px 0;
text-align: center;
}
.main .main-block {
margin-bottom: 100px;
}
</style>
這里我們還結(jié)合了Element.scrollIntoView()
方法,當(dāng)表單驗證未通過時可以讓頁面直接滾動到報錯的那個位置牍陌。
scrollIntoViewOptions 可選
一個包含下列屬性的對象:
behavior 可選
定義動畫過渡效果擎浴, "auto"或 "smooth" 之一。默認(rèn)為 "auto"毒涧。
block 可選
定義垂直方向的對齊贮预, "start", "center", "end", 或 "nearest"之一。默認(rèn)為 "start"契讲。
inline 可選
定義水平方向的對齊仿吞, "start", "center", "end", 或 "nearest"之一。默認(rèn)為 "nearest"怀泊。