1. 安装 vue-cli & 创建项目
# 安装 vue-cli 因为是全局安装,安装过一次之后再创建项目就不用安装了;
> npm i -g @vue/cli
# 创建项目
> vue create vue-web
# 进入项目目录
> cd vue-web
# 启动项目
> npm run serve
# 访问:
http://localhost:8080/
2. 安装 iview
安装 iview
> npm i iview
引入 iview main.js
import Vue from 'vue'
import iview from 'iview'
import 'iview/dist/styles/iview.css'
Vue.use(iview)
3. 安装 less
# less-loader
> npm i less-loader
# less
> npm i less -D
4. 安装并配置路由 vue-router
安装 vue-router
npm i vue-router
引入 & 配置 vue-router
import Vue from 'vue'
import vueRouter from 'vue-router'
// 安装 vue-router 插件
Vue.use(vueRouter);
// vue-router 实例化对象
const router = new vueRouter({
mode: 'history',
routes: []
})
// 暴露出去
export default router;
vue-router 官网
5. 使用路由
创建页面:
登录(Login.vue
) ,后台(Admin.vue
),
后台-欢迎页面(admin/Welcome.vue
),后台-用户管理(admin/User.vue
)等
导入路由:main.js
import router from './router/index'
new Vue({
render: h => h(App),
router
}).$mount('#app')
路由出口
main.js
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
配置路由
// vue-router 实例化对象
const router = new vueRouter({
mode: 'history',
routes: [
{
path: '/',
redirect: '/login'
},
{
path: '/login',
component: () => import('../pages/Login.vue')
},
{
path: '/sys',
component: () => import('../pages/Admin.vue'),
children: [
{
path: '',
component: ()=> import('../pages/admin/Welcome.vue')
},
{
path: 'user',
component: () => import('../pages/admin/User.vue')
}
]
},
{
path: '/404',
component: () => import('../pages/notFind.vue')
},
{
path: '*',
redirect: '/404'
}
]
})
子路由出口 pages/Admin.vue
<div>
admin
<router-view></router-view>
</div>
6. 使用 iview 搭建页面
- 登录页面
- 后台框架
- 后台欢迎页面
7. 登录 & 菜单列表
详细的登录流程见如下博客:
https://blog.youkuaiyun.com/qq_39125684/article/details/91126413
管理员部分
页面大体如下:
8. 请求管理员数据
新建接口 /api/getUserInfo.js
* 注意:GET 请求的参数是一个对象,通过这个对象的 params属性传递的!!
/**
* 管理员列表
*/
import http from './http'
function getUserInfo(data) {
return http.get('/sys/user/list', {
params: data
})
}
export default getUserInfo;
在 User.vue
页面请求数据 beforeCreate()
将要用的数据在 data
选项中定义好,然后将请求到的数据存储起来
import getUserInfo from '../../api/getUserInfo'
data(){
return {
// 表体数据
userList: [],
// 当前页码
currPage: 1,
// 每页条数
pageSize: 10,
// 总条数
totalCount: 1,
// 总页数
totalPage: 1
}
},
beforeCreate() {
getUserInfo({
page: 1,
limit: 1,
sidx: 'username',
order: 'desc',
username: ''
}).then((res)=>{
const {code, msg, page} = res.data;
if(code === 0){
const {currPage, list, pageSize, totalCount, totalPage} = page;
// 将请求到的数据存储起来
this.currPage = currPage;
this.userList = list;
this.pageSize = pageSize;
this.totalCount = totalCount;
this.totalPage = totalPage;
}else{
this.$Message.error(msg)
}
})
}
9. Table 表部分
iview中的 Table
组件循环是提供一个表头数组,提供一个表体数组;
表体数组我们已经得到了,根据表体数据写一个表头数组循环生成 Table
表
data(){
return {
// 表头
userHeader: [
/**
* 表格前边的复选框 type: 'selection',
* 还可以设置每一列的宽度,这一列是否固定在左侧或者右侧等属性
*/
{
type: 'selection',
width: 50
},
{
title: '#',
key: 'userId'
},
{
title: '用户名',
key: 'username'
},
{
title: '邮箱',
key: 'email'
},
{
title: '手机号码',
key: 'mobile'
},
{
title: '创建时间',
key: 'createTime'
}
]
}
}
<Table :columns="userHeader" :data="userList"></Table>
因为我们请求到的数据并没有操作按钮部分,所以需要我们使用 Table
组件的 render
函数生成操作按钮;
就在表头数组中;
- 方法一,使用 render函数生成结构
{
title: '操作',
align: 'center',
render: (h) => h('div', [
h('Button','编辑'),
h('Button','删除')
])
}
- 方法二,引用外部组件
创建组件 components/UserTools.vue
<template>
<div>
<Button> 编辑 </Button>
<Button> 删除 </Button>
</div>
</template>
导入组件(局部组件,不过在 render函数中,局部组件和全局组件都可以使用)
import UserTools from '../../components/UserTools.vue'
在表头数组中,通过render 函数使用导入的组件
{
title: '操作',
align: 'center',
render: (h) => h(UserTools)
}
触发事件:在组件内部触发,render函数中处理
<Button @click="$emit('edit')"> 编辑 </Button>
<Button @click="$emit('del')"> 删除 </Button>
{
title: '操作',
align: 'center',
render: (h) => h(UserTools, {
// 使用箭头函数,使用普通函数的话获取不到 this指向
on: {
edit: ()=> {
console.log('编辑')
},
del: ()=> {
console.log('删除');
}
}
})
}
但是新的问题又来了,我们怎么知道是点击的哪一行的操作按钮呢?
通过 render函数中的第二个参数(params
),可以知道点击的是哪一行的按钮
{
title: '操作',
align: 'center',
render: (h, params) => h(UserTools, {
on: {
edit: ()=> {
console.log('编辑', params)
},
del: ()=> {
console.log('删除', params);
}
}
})
}
10. 搜索功能
- 在搜索输入框绑定数据
<Input
search
v-model="usernameSearch"
/>
data() {
return {
usernameSearch: ''
}
}
- 将我们
data
中的数据和请求用户信息的时候传入的数据绑定在一起
这样我们操控 data
中的数据之后,再次请求到的就是我们想要的数据了
data() {
return {
// 表头数据
userHeader: [ ... ],
// 表体数据
userList: [],
// 当前页码
currPage: 1,
// 每页条数
pageSize: 10,
// 总条数
totalCount: 1,
// 总页数
totalPage: 1,
// 搜索框的输入
usernameSearch: ''
}
}
beforeCreate(){
// 解构 this,简化下方操作
const {currPage, pageSize, usernameSearch} = this;
// 请求的参数,绑定 data中的数据
getUserInfo({
page: currPage,
limit: pageSize,
sidx: 'username',
order: 'desc',
username: usernameSearch
}).then((res)=>{
const {code, msg, page} = res.data;
if(code === 0){
const {currPage, list, pageSize, totalCount, totalPage} = page;
// 将请求返回的数据,和 data中的数据再次绑定
this.currPage = currPage;
this.userList = list;
this.pageSize = pageSize;
this.totalCount = totalCount;
this.totalPage = totalPage;
}else{
this.$Message.error(msg)
}
})
}
- 不管是 搜索 还是 分页,都是我们改变
data
中的数据,然后再次执行getUserInfo()
函数,请求新的数据;
我们之前是在 beforeCreate()
钩子中请求初始数据,因为需要在不少地方调用请求数据的方法,就不能在 beforeCreate()
中直接请求了,要封装成一个方法;
下面我们就创建一个方法(getUserInfoList()
),用来请求数据;
在 created()
钩子中调用一下请求数据的方法,用来初始化,因为 beforeCreate()
钩子中得不到这个方法
created(){
this.getUserInfoList();
},
methods: {
getUserInfoList(){
const {currPage, pageSize, usernameSearch} = this;
getUserInfo({
page: currPage,
limit: pageSize,
sidx: 'username',
order: 'desc',
username: usernameSearch
}).then((res)=>{
const {code, msg, page} = res.data;
if(code === 0){
const {currPage, list, pageSize, totalCount, totalPage} = page;
this.currPage = currPage;
this.userList = list;
this.pageSize = pageSize;
this.totalCount = totalCount;
this.totalPage = totalPage;
}else{
this.$Message.error(msg)
}
})
}
}
- 自动搜索,输入后直接进行搜索
直接监听搜索框绑定的数据usernameSearch
的变化,如果变化就再次请求数据就好了
watch: {
usernameSearch() {
this.getUserInfoList();
}
}
- 手动搜索,可以解决多人同时搜索的时候并发量太大的问题
通过 <Input>
控件的 on-search
事件解决;
事件名 | 说明 | 返回值 |
---|---|---|
on-search | 开启 search 时可用,点击搜索或按下回车键时触发 | value |
将 <Input >
中的 v-model="usernameSearch"
去掉;添加上 @on-search
事件
<Input
search
@on-search="handleSearch"
/>
data() {
return {
// 搜索框的输入
usernameSearch: '',
}
},
methods: {
/**
* 搜索
* value 搜索框中的内容
*/
handleSearch(value){
this.usernameSearch = value
}
},
watch: {
usernameSearch() {
// 请求数据
this.getUserInfoList();
},
}
- 分页也是如此,直接监听当前页码
currPage
这个数据的变化就好了
watch: {
currPage() {
this.getUserInfoList();
}
}
- 给表格的数据切换添加 loading…
给 Table
控件添加 loading
属性,并绑定数据
<Table
:columns="userHeader"
:data="userList"
:loading="tableLoading"
></Table>
data() {
return {
// 表格加载状态
tableLoading: false
}
}
在请求数据的时候,将 tableLoading
变成 true
,请求完成之后,不管是请求成功还是请求失败,都把 tableLoading
变成 false
11. 分页功能
- 改变分页,直接监听当前页码
currPage
这个数据的变化,如果有改变请求一次数据就好了
watch: {
currPage() {
this.getUserInfoList();
}
}
- 每页展示条数的控制
<!--
total: 总条数
current: 当前页码,支持 .sync 修饰符
page-size: 每页条数
page-size-opts: Array 条数控制选项
@on-page-size-change: 切换每页条数时的回调,返回切换后的每页条数
-->
<Page
show-sizer
:total="totalCount"
:current.sync="currPage"
:page-size="pageSize"
:page-size-opts="[1, 5, 10, 20]"
@on-page-size-change="pageSizeChange"
/>
methods: {
/**
* 每页条数改变
* num 当前选中的条数
*/
pageSizeChange(num){
this.pageSize = num;
}
}
watch: {
// 当页码发生改变的时候,重新请求数据
pageSize() {
this.getUserInfoList();
}
}
12. 添加 & 编辑管理员
- 搭建布局
使用模态框组件<Modal>
搭建如下布局
其中开关组件<Switch>
需要注意: 直接使用是不会显示的,需要加上前缀 <i-Switch>
开关这里,因为我们后台需要的不是 true false
而是 1 0
,所以通过 true-value false-value
属性设置;
<Modal v-model="modalShow" :title="modalTitle" >
<Form
:label-width="40"
label-position="left"
>
<FormItem label="账号" >
<Input prefix="md-person" placeholder="username" />
</FormItem>
<FormItem label="密码" >
<Input prefix="md-lock" type="password" placeholder="password" />
</FormItem>
<FormItem label="邮箱" >
<Input prefix="md-mail" placeholder="email" />
</FormItem>
<FormItem label="手机" >
<Input prefix="md-phone-portrait" placeholder="mobile" />
</FormItem>
<FormItem label="状态" >
<i-Switch
size="large"
:true-value="1"
:false-value="0"
>
<span slot="open">激活</span>
<span slot="close">禁用</span>
</i-Switch>
</FormItem>
</Form>
</Modal>
data() {
return {
modalShow: true,
modalTitle: '添加管理员'
}
}
- 绑定数据
<Form :model="modalForm" >
<FormItem>
<Input v-model="modalForm.username" />
</FormItem>
<!--
...
-->
</Form>
data() {
return {
// 模态框表单
modalForm: {
userId: 0,
username: '',
password: '',
email: '',
mobile: '',
status: 0,
roleIdList: []
},
}
}
- 表单数据验证
iview表单验证:https://blog.youkuaiyun.com/qq_39125684/article/details/91125528
# 安装validator 库,方便自定义验证
> npm i validator
// 导入
import validator from 'validator'
<Form :rules="modalRules" >
<FormItem
label="账号"
prop="username"
>
<Input v-model="modalForm.username" />
</FormItem>
</Form>
data() {
return {
// 模态框 表单验证
modalRules: {
username: [
{required: true, message: '用户名不能为空'}
],
email: [
{required: true, message: '邮箱不能为空'},
{validator: (rule, value, callback)=>{
if(validator.isEmail(value)){
callback()
}else{
callback(new Error('邮箱格式不正确'))
}
}}
],
mobile: [
{required: true, message: '手机号码不能为空'},
{validator: (rule, value, callback)=>{
if(validator.isMobilePhone(value, 'zh-CN')) {
callback()
}else{
callback(new Error('手机号码格式不正确'))
}
}}
]
}
}
}
- 添加管理员按钮 & 编辑管理员
<Button @click="modalTitle='添加管理员',modalShow=true">添加管理员</Button>
{
title: '操作',
align: 'center',
render: (h, params) => h(UserTools, {
on: {
edit: ()=> {
this.modalShow = true;
this.modalTitle = "编辑管理员"
},
del: ()=> {
console.log('删除', params);
}
}
})
}
- 编辑管理员,模态框的表单中要有当前行的数据
{
title: '操作',
align: 'center',
render: (h, params) => h(UserTools, {
on: {
edit: ()=> {
this.modalShow = true;
this.modalTitle = "编辑管理员";
// 获取到当前行的数据,赋值给模态框表单
const {userId, email, mobile, username, password, status, roleIdList} = params.row;
this.modalForm = {
userId,
username,
password: '',
email,
mobile,
status,
roleIdList,
}
},
del: ()=> {
console.log('删除', params);
}
}
})
}
- 但是由此带来新的问题
当我们点击完编辑某个用户的信息之后,再点击添加,则添加对话框中会有刚才用户的信息
所以我们要知道模态框是什么时候关闭的,在关闭的时候,我们把模态框表单中的内容清空就好了;
Modal
事件
事件名 | 说明 | 返回值 |
---|---|---|
on-ok | 点击确定的回调 | 无 |
on-cancel | 点击取消的回调 | 无 |
Form
方法
方法名 | 说明 | 参数 |
---|---|---|
resetFields | 对整个表单进行重置,将所有字段值重置为空并移除校验结果 | 无 |
iview
提供了如上事件和方法,做起来就很简单了
首先 给 <Modal>
对话框的关闭按钮添加事件
<Modal
@on-cancel="modalCancel"
>
然后 给将要清空的 <Form>
表单添加 ref
,方便后面可以通过 this.$refs
找到
<Form
ref="modalForm"
>
最后 在modalCancel
函数中找到 <Form>
,执行一下 resetFields()
,清空掉就好了
methods: {
/**
* 模态框点击取消时 执行
*/
modalCancel(){
this.$refs['modalForm'].resetFields();
},
}
- 添加 & 编辑 管理员的业务逻辑
编写接口
addUser.js 文件
/**
* 添加管理员
*/
import http from './http'
function addUser(data){
return http.post('/sys/user/save', data)
}
export default addUser;
editUser.js 文件
/**
* 编辑管理员信息
*/
import http from './http'
function editUser(data) {
return http.post('/sys/user/update', data)
}
export default editUser;
在页面中导入接口
import addUser from '../../api/addUser'
import editUser from '../../api/editUser'
因为添加管理员和编辑管理员使用的是同一个表单,所以在点击确定按钮,提交数据的时候要做一下判断;
<Modal
@on-ok="modalSubmit"
>
点击确定按钮的时候进行一下表单验证,验证通过则调用接口,请求数据
methods: {
/**
* 模态框点击确定的时候执行
*/
modalSubmit(){
// 先做一下验证
this.$refs['modalForm'].validate((result)=>{
if(result){
// 验证通过 判断是添加还是编辑
if(this.modalTitle == '添加管理员'){
addUser({
...this.modalForm
}).then((res)=>{
const {code, msg} = res.data;
if(code === 0){
this.$Message.success('恭喜您,数据添加成功');
this.getUserInfoList();
}else{
this.$Message.error('错误:'+ msg)
}
});
}else if(this.modalTitle == '编辑管理员'){
editUser({
...this.modalForm
}).then((res)=> {
const {code, msg} = res.data;
if(code === 0){
this.$Message.success('恭喜您,数据更新成功');
this.getUserInfoList();
}else{
this.$Message.error('错误:'+ msg)
}
})
}
}
})
}
}
13. 删除管理员
- 定义接口
deleteUser.js
/**
* 删除用户
*/
import http from './http'
function deleteUser(data){
return http.post('/sys/user/delete', data)
}
export default deleteUser;
- 删除单项
在表头数据部分:
del: ()=> {
// 拿到当前行的用户的 ID
const { userId } = params.row;
// 模态框询问一下是否确定删除
this.$Modal.confirm({
title: '删除',
content: '<p>您确定要删除该数据吗?</p>',
onOk: () => {
deleteUser([userId]).then((res)=> {
if(res.data.code === 0){
this.$Message.success('删除成功');
this.getUserInfoList();
}else{
this.$Message.error('删除失败:' + res.data.msg)
}
})
},
onCancel: () => {
this.$Message.info('取消删除');
}
});
}
- 批量删除
Table
事件
事件名 | 说明 | 返回值 |
---|---|---|
on-selection-change | 在多选模式下有效,只要选中项发生变化时就会触发 | selection :已选项数据 |
找到选中的项
<Table
@on-selection-change="selectionChange"
></Table>
// 存储将要删除的用户的ID
data() {
return {
selections: []
}
}
/**
* 拿到选中数据的 userId
*/
selectionChange(selection){
this.selections = selection.map((item, index, arr)=>{
return item.userId;
})
},
点击删除按钮,删除选中的数据
<Button @click="deleteSome">批量删除</Button>
/**
* 批量删除 按钮点击事件
* 注意:先判断一下是否有选中的数据
*/
deleteSome(){
// 判断一下是否有选中的数据
if(this.selections.length > 0){
// 模态框询问一下是否确定删除
this.$Modal.confirm({
title: '删除',
content: '<p>您确定要删除选中的数据吗?</p>',
onOk: () => {
// 删除
deleteUser([
...this.selections
]).then((res)=> {
if(res.data.code === 0){
this.$Message.success('删除成功');
this.getUserInfoList();
}else{
this.$Message.error('删除失败:' + res.data.msg)
}
})
},
onCancel: () => {
this.$Message.info('取消删除');
}
});
}else {
this.$Message.info('请选择将要删除的数据')
}
}
至此,一个 vue-cli项目的,登录流程,增删改查已经完成了;
积跬步,而至千里;
共勉;