目录
后台功能——品牌管理(前端)
预期效果图:
前端到后端的完整开发
一、前端页面构建
1.1 页面内容分析
从上图可以看到,页面内容分三个部分来构建,首先是用来展示数据的表格,然后是搜索框,最后是增加和删除按钮。当点击增加按钮会弹出一个对话框用来新增品牌,所以还需要一个对话框。
1.2 data-tables组件
1.2.1 基本内容
v-data-table
中有以下核心属性:
-
dark:是否使用黑暗色彩主题,默认是false
-
expand:表格的行是否可以展开,默认是false
-
headers:定义表头的数组,数组的每个元素就是一个表头信息对象,结构:
{ text: string, // 表头的显示文本 value: string, // 表头对应的每行数据的key align: 'left' | 'center' | 'right', // 位置 sortable: boolean, // 是否可排序 class: string[] | string,// 样式 width: string,// 宽度 }
-
items:表格的数据的数组,数组的每个元素是一行数据的对象,对象的key要与表头的value一致
-
loading:是否显示加载数据的进度条,默认是false
-
no-data-text:当没有查询到数据时显示的提示信息,string类型,无默认值
-
pagination.sync:包含分页和排序信息的对象,将其与vue实例中的属性关联,表格的分页或排序按钮被触发时,会自动将最新的分页和排序信息更新。对象结构:
{ page: 1, // 当前页 rowsPerPage: 5, // 每页大小 sortBy: '', // 排序字段 descending:false, // 是否降序 }
-
total-items:分页的总条数信息,number类型,无默认值
-
select-all :是否显示每一行的复选框,Boolean类型,无默认值
-
value:当表格可选的时候,返回选中的行
在官方文档中找到一个基本符合需求的样例,所以在这个基础上进行修改
源代码:
<template>
<div>
<v-data-table
:headers="headers"
:items="desserts"
:pagination.sync="pagination"
:total-items="totalDesserts"
:loading="loading"
class="elevation-1"
>
<template slot="items" slot-scope="props">
<td>{{ props.item.name }}</td>
<td class="text-xs-right">{{ props.item.calories }}</td>
<td class="text-xs-right">{{ props.item.fat }}</td>
<td class="text-xs-right">{{ props.item.carbs }}</td>
<td class="text-xs-right">{{ props.item.protein }}</td>
<td class="text-xs-right">{{ props.item.iron }}</td>
</template>
</v-data-table>
</div>
</template>
1.2.2 代码分析
先介绍一下基本属性:
<v-data-table
:headers="headers"
:items="desserts"
:search="search"
:pagination.sync="pagination"
:total-items="totalDesserts"
:loading="loading"
class="elevation-1"
>
</v-data-table>
-
headers:表头信息,是一个数组
-
items:要在表格中展示的数据,数组结构,每一个元素是一行
-
search:搜索过滤字段,用不到,暂时不管
-
pagination.sync:分页信息,包含了当前页,每页大小,排序字段,排序方式等。加上.sync代表服务端排序,当用户点击分页条时,该对象的值会跟着变化。监控这个值,并在这个值变化时去服务端查询,即可实现页面数据动态加载了。
-
total-items:总条数
-
loading:boolean类型,true:代表数据正在加载,会有进度条。false:数据加载完毕。
-
class: 表格的样式
<template slot="items" slot-scope="props">
<td>{{ props.item.name }}</td>
<td class="text-xs-right">{{ props.item.calories }}</td>
<td class="text-xs-right">{{ props.item.fat }}</td>
<td class="text-xs-right">{{ props.item.carbs }}</td>
<td class="text-xs-right">{{ props.item.protein }}</td>
<td class="text-xs-right">{{ props.item.iron }}</td>
</template>
这段就是在渲染每一行的数据。Vue会自动遍历上面传递的items
属性,并把得到的对象传递给这段template
中的props.item
属性。我们从中得到数据,渲染在页面即可。
所以需要做的事情,主要有两件:
-
给items和total-Items赋值
-
当pagination变化时,重新获取数据,再次给items和total-Items赋值
1.2.3 补充
在每一条数据的前面加一个选择框,用来辅助删除操作。
继续在官方文档中寻找~
代码:
<template>
<v-data-table
:headers="headers"
:items="desserts"
:search="search"
v-model="selected"
item-key="name"
select-all
class="elevation-1"
>
<template slot="headerCell" slot-scope="props">
<v-tooltip bottom>
<span slot="activator">
{{ props.header.text }}
</span>
<span>
{{ props.header.text }}
</span>
</v-tooltip>
</template>
<template slot="items" slot-scope="props">
<td>
<v-checkbox
v-model="props.selected"
primary
hide-details
></v-checkbox>
</td>
<td>{{ props.item.name }}</td>
<td class="text-xs-right">{{ props.item.calories }}</td>
<td class="text-xs-right">{{ props.item.fat }}</td>
<td class="text-xs-right">{{ props.item.carbs }}</td>
<td class="text-xs-right">{{ props.item.protein }}</td>
<td class="text-xs-right">{{ props.item.iron }}</td>
</template>
</v-data-table>
</template>
v-model = "selected" :绑定一个数组用来存放已经选中的条目
select-all:可以点击表头中的选择框选中当页全部数据
1.3 页面优化
1.3.1 编辑和删除按钮
将来要对品牌进行增删改,需要给每一行数据添加 修改删除的按钮,一般放到改行的最后一列:
其实就是多了一列,只是这一列没有数据,而是两个按钮而已。
1.3.2 新增、删除(多个)
将这两个按钮放在表格外:
1.3.3 搜索
一个文本输入框
-
name:字段名,表单中会用到
-
label:提示文字
-
value:值。可以用v-model代替,实现双向绑定
1.3.4 布局调整
使用v-card
卡片v-card
包含四个基本组件:
-
v-card-media:一般放图片或视频
-
v-card-title:卡片的标题,一般位于卡片顶部
-
v-card-text:卡片的文本(主体内容),一般位于卡片正中
-
v-card-action:卡片的按钮,一般位于卡片底部
可以把新增、删除和搜索
放到v-card-title
位置,把table
放到下面,这样就成一个上下关系。
1.4 对话框
1.4.1 初步实现弹窗
当点击新增按钮,应该出现一个弹窗,然后在弹窗中出现一个表格,就可以填写品牌信息了。
另外,可以通过文档看到对话框的一些属性:
-
value:控制窗口的可见性,true可见,false,不可见
-
max-width:控制对话框最大宽度
-
scrollable :是否可滚动,要配合v-card来使用,默认是false
-
persistent :点击弹窗以外的地方不会关闭弹窗,默认是false
首先,我们在data中定义一个show属性,来控制对话框的显示状态:
然后,在页面添加一个v-dialog
<!--弹出的对话框-->
<v-dialog max-width="500" v-model="show" persistent>
<v-card>
<!--对话框的标题-->
<v-toolbar dense dark color="primary">
<v-toolbar-title>新增品牌</v-toolbar-title>
</v-toolbar>
<!--对话框的内容,表单-->
<v-card-text class="px-5">
我是表单
</v-card-text>
</v-card>
</v-dialog>
说明:
-
我们给dialog指定了3个属性,分别是
-
max-width:限制宽度
-
v-model:value值双向绑定到show变量,用来控制窗口显示
-
persisitent:控制窗口不会被意外关闭
-
-
因为可滚动需要配合
v-card
使用,因此我们在对话框中加入了一个v-card
-
在
v-card
的头部添加了一个v-toolbar
,作为窗口的头部,并且写了标题为:新增品牌-
dense:紧凑显示
-
dark:黑暗主题
-
color:颜色,primary就是整个网站的主色调,蓝色
-
-
在
v-card
的内容部分,暂时空置,等会写表单
-
-
class=“px-5"
:vuetify的内置样式,含义是padding的x轴设置为5,这样表单内容会缩进一些,而不是顶着边框基本语法:
{property}{direction}-{size}
-
property:属性,有两种
padding
和margin
-
p
:对应padding
-
m
:对应margin
-
-
direction:只padding和margin的作用方向,
-
t
- 对应margin-top
或者padding-top
属性 -
b
- 对应margin-bottom
orpadding-bottom
-
l
- 对应margin-left
orpadding-left
-
r
- 对应margin-right
orpadding-right
-
x
- 同时对应*-left
和*-right
属性 -
y
- 同时对应*-top
和*-bottom
属性
-
-
size:控制空间大小,基于
$spacer
进行倍增,$spacer
默认是16px-
0
:将margin
或padding的大小设置为0 -
1
- 将margin
或者padding
属性设置为$spacer * .25
-
2
- 将margin
或者padding
属性设置为$spacer * .5
-
3
- 将margin
或者padding
属性设置为$spacer
-
4
- 将margin
或者padding
属性设置为$spacer * 1.5
-
5
- 将margin
或者padding
属性设置为$spacer * 3
-
-
1.4.2 实现弹窗的可见和关闭
窗口可见
接下来,我们要在点击新增品牌按钮时,将窗口显示,因此要给新增按钮绑定事件。
<v-btn color="primary" @click="addBrand">新增品牌</v-btn>
然后定义一个addBrand方法:
addBrand(){
// 控制弹窗可见:
this.show = true;
}
效果:
窗口关闭
因为我们设置了persistent属性,窗口无法被关闭了。除非把show属性设置为false,因此我们需要给窗口添加一个关闭按钮:
<!--对话框的标题-->
<v-toolbar dense dark color="primary">
<v-toolbar-title>新增品牌</v-toolbar-title>
<v-spacer/>
<!--关闭窗口的按钮-->
<v-btn icon @click="closeWindow"><v-icon>close</v-icon></v-btn>
</v-toolbar>
并且,我们还给按钮绑定了点击事件,回调函数为closeWindow。
接下来,编写closeWindow函数:
closeWindow(){
// 关闭窗口
this.show = false;
}
效果:
1.4.3 新增品牌的表单页
表单的构造有两种选择:
-
直接在dialog对话框中编写表单代码
-
另外编写一个组件,组件内写表单代码。然后在对话框引用组件
选第二种方案,优点:
-
表单代码独立组件,可拔插,方便后期的维护。
-
代码分离,可读性更好。
缺点:
- 麻烦
1.4.3.1 创建新组件
将MyBrandForm引入到MyBrand中,这里使用局部组件的语法:
先导入自定义组件:
// 导入自定义的表单组件
import MyBrandForm from './MyBrandForm'
然后通过components属性来指定局部组件:
components:{
MyBrandForm
}
然后在页面中引用:
页面效果:
1.4.3.2 编写表单
效果图:
一共包含五项:两个文本框、一个级联下拉选择框(自定义组件)、一个文件上传和两个按钮(清空、保存)。具体内容请参考项目代码。
1.4.3.3 数据校验
Vuetify的表单校验,是通过rules属性来指定的:
校验规则的写法:
说明:
-
规则是一个数组
-
数组中的元素是一个函数,该函数接收表单项的值作为参数,函数返回值两种情况:
-
返回true,代表成功,
-
返回错误提示信息,代表失败
-
1.4.3.4 表单提交
在submit方法中添加表单提交的逻辑:
submit() {
// 1、表单校验
if (this.$refs.myBrandForm.validate()) {
// 2、定义一个请求参数对象,通过解构表达式来获取brand中的属性
const {categories ,letter ,...params} = this.brand;
// 3、数据库中只要保存分类的id即可,因此我们对categories的值进行处理,只保留id,并转为字符串
params.cids = categories.map(c => c.id).join(",");
// 4、将字母都处理为大写
params.letter = letter.toUpperCase();
// 5、将数据提交到后台
this.$http.post('/item/brand', this.$qs.stringfy(params))
.then(() => {
// 6、弹出提示
this.$message.success("保存成功!");
})
.catch(() => {
this.$message.error("保存失败!");
});
}
}
-
1、通过
this.$refs.myBrandForm
选中表单,然后调用表单的validate
方法,进行表单校验。返回boolean值,true代表校验通过 -
2、通过解构表达式来获取brand中的值,categories和letter需要处理,单独获取。其它的存入params对象中
-
3、品牌和商品分类的中间表只保存两者的id,而brand.categories中保存的数对象数组,里面有id和name属性,因此这里通过数组的map功能转为id数组,然后通过join方法拼接为字符串
-
4、首字母都处理为大写保存
-
5、发起请求,注意此时必须对表单数据进行转换,将json对象转换为请求参数字符串。
-
6、弹窗提示成功还是失败,这里用到的是我们的自定义组件功能message组件:
这个插件把$message
对象绑定到了Vue的原型上,因此我们可以通过this.$message
来直接调用。
包含以下常用方法:
- info、error、success、warning等,弹出一个带有提示信息的窗口,色调与为普通(灰)、错误(红色)、成功(绿色)和警告(黄色)。使用方法:this.$message.info("msg")
- confirm:确认框。用法:
this.$message.confirm("确认框的提示信息")
,返回一个Promise