之前一直在用element-ui
,这次我们改用iView
框架。
关于修改表单未保存离开页面这里我们只考虑两种情况,一种是直接关闭浏览器(直接关闭页面),一种是路由跳转(包含浏览器前进后退),以下是两种提示页面的截图:
直接关闭浏览器的提示方式:
当浏览器窗口关闭或者刷新时,会触发beforeunload
事件。当前页面不会直接关闭,可以点击确定按钮关闭或刷新,也可以取消关闭或刷新。
| Bubbles | No |
| Cancelable | Yes |
| Interface |Event
|
| Event handler property |onbeforeunload
|
事件使网页能够触发一个确认对话框,询问用户是否真的要离开该页面。如果用户确认,浏览器将导航到新页面,否则导航将会取消。
根据规范,要显示确认对话框,事件处理程序需要在事件上调用preventDefault()
。
但是请注意,并非所有浏览器都支持此方法,而有些浏览器需要事件处理程序实现两个遗留方法中的一个作为代替:
- 将字符串分配给事件的
returnValue
属性 - 从事件处理程序返回一个字符串。
了解了这个方法,我们在data
里定义这三个属性:
data () {
return {
isEdited: false, // 判断表单是否被修改
formData: {}, // 表单保存的数据
initData: {} // 拷贝一份初始表单数据,这里的数据不会改变
}
}
在methods
里定义beforeunload
方法:
methods: {
beforeunload (e) {
// 表单被修改
if (this.isEdited) {
const confirmationMessage = '你确定离开此页面吗?'
e.returnValue = confirmationMessage
return confirmationMessage
}
}
}
在进入页面和销毁页面前绑定beforeunload
方法:
mounted () {
// 初始表单数据赋值
this.initData = Object.assign({}, this.formData)
// 拦截判断是否离开当前页面
window.addEventListener('beforeunload', this.beforeunload)
},
beforeDestroy () {
// 销毁拦截判断是否离开当前页面
window.removeEventListener('beforeunload', this.beforeunload)
}
我们判断的逻辑是对比初始数据和修改表单后的数据,如果一致说明没有改变表单数据,否则就说明用户编辑过表单。通过watch
去监听formData
:
watch: {
formData: {
deep: true,
handler (val) {
// 把两个对象转换成字符串比较
if (JSON.stringify(this.initData) === JSON.stringify(val)) {
this.isEdited = false
} else {
this.isEdited = true
}
}
}
}
vue
路由跳转的提示方式:
这需要你有另一个vue
的页面,然后我们只需要在上面的基础上加上beforeRouteLeave
方法就行了。
beforeRouteLeave (to, from, next) {
// 表单被修改
if (this.isEdited) {
this.$Modal.confirm({
title: '系统监测到你有未保存表单,是否直接离开?',
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('创建成功')
} 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)
// 拦截判断是否离开当前页面
window.addEventListener('beforeunload', this.beforeunload)
},
beforeDestroy () {
// 销毁拦截判断是否离开当前页面
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: '系统监测到你有未保存表单,是否直接离开?',
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>
这里我们还结合了Element.scrollIntoView()
方法,当表单验证未通过时可以让页面直接滚动到报错的那个位置。
scrollIntoViewOptions 可选
一个包含下列属性的对象:
behavior 可选
定义动画过渡效果, "auto"或 "smooth" 之一。默认为 "auto"。
block 可选
定义垂直方向的对齐, "start", "center", "end", 或 "nearest"之一。默认为 "start"。
inline 可选
定义水平方向的对齐, "start", "center", "end", 或 "nearest"之一。默认为 "nearest"。