普通表单
我们先可以创建一个普通的表单,我们知道的是表单是相对比较复杂的,antv被我们诟病为就是其表单,这个设计出来的理念就是和别的组件库不一样,我们就在这篇文章分成四个部分来讲述,现在我们的第一个部分,就是我们的普通的表单。
操作的步骤:先再 main.js 引入注册 Form 和 Input,在 BasicForm 拷贝刚才 的表单布局,这个就是最基础的表单。 接下来就是需要添加一个错误校验,有对应的属性传递对应 的状态等。拷贝 validateStatus 到 item 中加上对应的 help 文 本,如果想要动态输入的话也可以,给 input 双向绑定 fieldA 和 fieldB,在 data 里面注册这两个值,同时 validateStatus 也 变成动态的指向 fieldAStatus,文本也是动态指向 fieldAHelp。 通过监听 fieldA 的长度,如果小于 5 改变状态和文本,否则 就都置为空就行了。同样的,给按钮也绑定一个事件 handleSubmit,拷贝刚才的监听逻辑,如果通过就 console.log 输出一下。
如此就完成了一个最简单的表单校验,像这种手动添加错误 校验信息是最灵活简单但是不够渐变,最好是组件内部自动 校验。
<!--登录表单区域--> <el-form ref="loginFormRef" :model="loginForm" :rules="loginFormRules" label-width="0px" class="login_form"> <!--用户名--> <el-form-item prop="username"> <el-input v-model="loginForm.username" prefix-icon="el-icon-user"></el-input> </el-form-item> <!--密码--> <el-form-item prop="password"> <el-input v-model="loginForm.password" prefix-icon="el-icon-lock" type="password"></el-input> <!--type="password使得密码隐藏"--> </el-form-item> <!--按钮区域--> <el-form-item class="btns"> <el-button type="primary" @click="login">mua</el-button> <el-button type="info" @click="resetLoginForm">重置</el-button> </el-form-item> </el-form>
<script> export default { data () { return { // 这里是登录表单数据 loginForm: { username: 'admin', password: '991130' }, // 表单的验证规则对象 loginFormRules: { username: [ { required: true, message: '请输入登录名称', trigger: 'blur' }, { min: 3, max: 10, message: '长度在 3 到 10 个字符', trigger: 'blur' } ], password: [ { required: true, message: '请输入登录密码', trigger: 'blur' }, { min: 6, max: 15, message: '长度在 6 到 15 个字符', trigger: 'blur' } ] } } }, // 点击重置按钮,重置登录表单 methods: { resetLoginForm () { // console.log(this) this.$refs.loginFormRef.resetFields() }, login () { this.$refs.loginFormRef.validate(async valid => { if (!valid) return const { data: res } = await this.$http.post('login', this.loginForm) if (res.meta.status !== 200) return this.$message.error('登录失败!') this.$message.success('登录成功') // 1.将登陆成功之后的token,保存到客户端的sessionstorage中 // 1.1项目中除了登录之外的其他api接口,必须在登陆之后才能访问 // 1.2token只应在当前网站打开期间生效,所以将token保存在sessionstorage中 window.sessionStorage.setItem('token', res.data.token) // 2.通过编程式导航跳转到后台主页,路由地址为/home this.$router.push('home') }) } } } </script>
初始数据、自动校验、动态赋值
目前表单方案主要有两种,一种是基于双向绑定的表单校验 (iview element)另一种就是 ant。 数据加规则映射到表单视图,通过双向绑定在表单变化的时 候继续同步到数据中对比规则,这个是理想情况。但是很经 常是多个地方使用一个数据,使用双向绑定的话可以实现表 单与其它数据一起修改,这可能是想要的,但是也有可能是 我们希望表单数据提交后经过后台等等最后返回出来的数 据再进行展示,对这种情况我们就需要复制或者深复制一份 数据,这个就是双向绑定的模型。 Ant 选择把所有数据放到表单,data 只是提供一个初始化, 后面表单就是一个黑盒,里面处理的不会影响外面的数据, 后续数据如果需要同步到 data 就可以通过 API 来同步到 data 和其它组件等等。
回到 form,在 data 里面实例化一个 from(因为 main 中有 use 自动把$from 挂载到 vue 实例上,所以就可以访问到), 同时把 this 进去(用于组件底层内部,数据改变时调用这个 this 来更新当前组件也就是 basicForm)。把这个实例传递到 最外层那,如此就不需要手动监听、双向绑定和 watch 监听 了。给 input 绑定一个 v-decorator,接收数组,第一个是字段名 称、第二个是配对的规则(required 是否必须,min 最小长 度,message 报错信息)、第三个是初始值的字段。 v-decorator 的源码如下,可以看到什么都没做,仅仅是提供 一个标志位,也就是当我们渲染我们的 item 时,会查找子组 件,如果带有这个标志位就会劫持然后交由我们的 from 去 控制
export function antDecorator(Vue) { return Vue.directive('decorator',{}); } export default { // install: Vue =>{ antDecorator(Vue); } }
按钮的点击事件也要改一下,通过 form 的 validateFields,接 收 err 错误信息和 values 接收到的值,如果没有 err 说明校验 通过。如此就实现了自动校验。 接下来我们想要数据改变后同步给 data 里面的 fieldA 和 fieldB(即后台校验通过后),我们当然可以一个个赋值,也 可以通过 Object.assign 往 this 上赋值即可。 如果想通过接口动态改变表单的值也可以通过 form 的 setFieldsValue API 来动态改变其值,值得注意的是初始化的 数据只是在第一次生效,后续改变都要通过这个 API。
handleSubmit(){ this.form.validateFields((err,value)=> { if(!err) { console.log(values); object.assign(this,values)//全部赋值 } } }
data() { this.form = this.$form.createForm(this)//初始化一个表单 return { formLauout:'horizontal', fieldA:'hello', fieldB:'' }; } mounted () { setTimeout(() = >{ //定时改变一次舒适化改变 this.form.setFieldsValue({ fieldA:'byebye'}) },3000) },
发布表单
结合 vuex 和 vue-router,建一个 store 文件夹在其下面建 modules 文件夹,下面再建一个 form.js,导入 router、request, 定义一个 state 对象,下包含 step 对象里面只有 payAccount 即 可 。 定 义 一 个 actions 对 象 , 下 面 一 个 异 步 的 submitStepForm 接收{commit}和{payload},调用 request 请求, url 是‘/api/form’,方式是 post,数据是 payload,返回请求 后我们 commit 一个 mutation,命名为 saveStepFormData,把 payload 传过去,然后路由跳转到‘formep-form/result’ 结果页。
定义一个 mutations 对象,saveStepFormData 接收 state 和{payload},我们要在里面改变 state 里面的 step 值(拓 展运算符摊开 step 和 payload,因为理论上是有很多的数据)。 最后是暴露出这三个对象(同时开启命名空间!) 在 store 下面再建一个文件 index.js,导入刚才的 form.js,在 实例化中加一个 modules 对象,里面丢个 form,如此就完成 了 store,不过需要注意的是外面还有一个 store.js(无用可 以删),而之前的 mian.js 默认是找到这个 store.js(原本是找 到刚才新建的 index,被外面这个拦住了),可能会报错,重 启工程或者指明更清楚的路径就行。
既然定义了 API 就往 mock 里面加多一个 form.js,修改方法 为 POST,返回数据为 message 即可。 接下来就是正式写表单了,首先是第一个表单 Step1,手写
一个 a-form 指定为 horizontal,item 的 label 为付款账户。往 data 里面丢一个布局配置 formItemLayout 对象,里面设置 labelCol 和 wrapperCol,当然我们还需要在外面创建一个 form 实例指向当前的form,item的labelCol也指向对象的labelCol, wrapperCol 也指定一下。 再加一个 a-input,指定 decorator,名称为 payAccount,初始 化值需要从 vuex 中取(可以利用计算属性拿到),规则是必 填否则报错误提示。 再来一个 item,里面放 button,type 为 primary,触发点击 事件 handleSubmit,在方法中定义,先从 this 中拿到 form、 $router 、 $store 就 不 用 一 个 个 this. , 再 调 用 form 的 validateFields 校验一下,无错则提交给 vuex 的 store,直接 commit 而不是 dispatch,因为第一步无需异步,只是储存数 据到 step 字段,给个 type 和 payload(传递 values 字段), 完成后 router 跳转到第二步(/form ep-form-confirm),如 此就完成了第一步的表单。 接下来开发这个第二步的表单,复制第一步的,把 input 删 除了,这一步就是要显示这个付款账户的{{step.payAccount}}, 再来个item,里面放input用来输入密码,type改为password, 下一个 item 中加多一个 button 为提交,另一个改为上一步, 分别绑定 handleSubmit 和 onPrev 两个事件,上一步的按钮 给个样式(margin-left 让它们两个有点距离)。onPrev 的话是
将路由跳转到‘/form ep-form/info’,提交事件的话就是通 过 dispatch,来调 vuex 中的 actions 中的 submit 最后由其 commit 来修改 store 中的值。注意这里传值的时候记得多传 递一个第一步的信息(...step、...values),完成之后无需跳转 (因为 commit 完就在那里执行了跳转的逻辑,不用重复写)。 表单三的话给个小提示就行
<template> <div> <!--面包屑导航区--> <el-breadcrumb separator-class="el-icon-arrow-right"> <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item> <el-breadcrumb-item>商品管理</el-breadcrumb-item> <el-breadcrumb-item>商品列表</el-breadcrumb-item> </el-breadcrumb> <!-- 卡片视图区域 --> <el-card> <el-row :gutter="20"> <el-col :span="8"> <el-input placeholder="请输入内容" v-model="queryInfo.query" clearable> <el-button slot="append" icon="el-icon-search" @click="getGoodsList"></el-button> </el-input> </el-col> <el-col :span="4"> <el-button type="primary" @click="goAddpage">添加商品</el-button> </el-col> </el-row> <!-- table表格区域 --> <el-table :data="goodslist" border stripe> <el-table-column type="index"> </el-table-column> <el-table-column label="商品名称" prop="goods_name"></el-table-column> <el-table-column label="商品价格(元)" prop="goods_price" width="95px"></el-table-column> <el-table-column label="商品重量" prop="goods_weight" width="70px"></el-table-column> <el-table-column label="创建时间" prop="add_time" width="140px"> <template slot-scope="scope">{{scope.row.add_time | dateFormat}}</template> </el-table-column> <el-table-column label="操作" width="130px"> <template slot-scope="scope"> <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button> <el-button size="mini" type="danger" icon="el-icon-delete" @click="removeById(scope.row.goods_id)">删除</el-button> </template> </el-table-column> </el-table> <!-- 分页区域 --> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="queryInfo.pagenum" :page-sizes="[5, 10, 15, 20]" :page-size="queryInfo.pagesize" layout="total, sizes, prev, pager, next, jumper" :total="total" background> </el-pagination> </el-card> </div> </template> <script> export default { data () { return { queryInfo: { query: '', pagenum: 1, pagesize: 10 }, // 商品列表 goodslist: [], // 总书记条数 total: 0 } }, created () { this.getGoodsList() }, methods: { // 根据分页获取对应的商品列表 async getGoodsList () { const { data: res } = await this.$http.get('goods', { params: this.queryInfo }) if (res.meta.status !== 200) { return this.$message.error('获取商品列表失败!') } this.$message.success('获取商品列表成功!') console.log = res.data this.goodslist = res.data.goodslist this.total = res.data.total }, handleSizeChange (newSize) { this.queryInfo.pagesize = newSize this.getGoodsList() }, handleCurrentChange (newPage) { this.queryInfo.pagenum = newPage this.getGoodsList() }, async removeById (id) { const confirmResult = await this.$confirm('此操作将永久删除该商品, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).catch(err => err) if (confirmResult !== 'confirm') { return this.$message.info('已取消删除!') } // eslint-disable-next-line no-undef const { data: res } = await this.$http.delete(`goods/${id}`) if (res.meta.status !== 200) { return this.$message.error('删除失败!') } this.$message.success('删除成功!') this.getGoodsList() }, goAddpage () { this.$router.push('/goods/add') } } } </script> <style lang="less" scoped> </style>