从0开始使用 vue-cli 搭建一个项目

本文详细记录了使用vue-cli创建项目,安装iview、less和vue-router等步骤。介绍了如何使用路由、用iview搭建页面,实现登录、菜单列表、请求管理员数据等功能。还阐述了Table表、搜索、分页功能,以及管理员的添加、编辑和删除操作,完成了项目的增删改查流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 官网

https://router.vuejs.org/zh/

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 搭建页面

  1. 登录页面
  2. 后台框架
  3. 后台欢迎页面

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函数生成操作按钮;

就在表头数组中;

  1. 方法一,使用 render函数生成结构
{
    title: '操作',
    align: 'center',
    render: (h) => h('div', [
        h('Button','编辑'),
        h('Button','删除')
    ])
}
  1. 方法二,引用外部组件

创建组件 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. 搜索功能

  1. 在搜索输入框绑定数据
<Input 
    search 
    v-model="usernameSearch"
/>
data() {
    return {
        usernameSearch: ''
    }
}
  1. 将我们 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)
        }
    })
}
  1. 不管是 搜索 还是 分页,都是我们改变 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)
            }
        })
    }
}
  1. 自动搜索,输入后直接进行搜索

直接监听搜索框绑定的数据usernameSearch 的变化,如果变化就再次请求数据就好了

watch: {
    usernameSearch() {
        this.getUserInfoList();
    }
}
  1. 手动搜索,可以解决多人同时搜索的时候并发量太大的问题

通过 <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();
    },
}
  1. 分页也是如此,直接监听当前页码currPage这个数据的变化就好了
watch: {
    currPage() {
        this.getUserInfoList();
    }
}
  1. 给表格的数据切换添加 loading…

Table控件添加 loading属性,并绑定数据

<Table 
    :columns="userHeader" 
    :data="userList"
    :loading="tableLoading"
></Table>
data() {
    return {
        // 表格加载状态
        tableLoading: false
    }
}

在请求数据的时候,将 tableLoading变成 true,请求完成之后,不管是请求成功还是请求失败,都把 tableLoading变成 false

11. 分页功能

  1. 改变分页,直接监听当前页码currPage这个数据的变化,如果有改变请求一次数据就好了
watch: {
    currPage() {
        this.getUserInfoList();
    }
}
  1. 每页展示条数的控制
<!-- 
    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. 添加 & 编辑管理员

  1. 搭建布局

使用模态框组件<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: '添加管理员'
    }
}
  1. 绑定数据
<Form :model="modalForm" >
    <FormItem>
        <Input v-model="modalForm.username" />
    </FormItem>
    <!--
        ...
    -->
</Form>
data() {
    return {
        // 模态框表单
        modalForm: {
            userId: 0,
            username: '',
            password: '',
            email: '',
            mobile: '',
            status: 0,
            roleIdList: []
        },
    }
}
  1. 表单数据验证

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('手机号码格式不正确'))
                    }
                }}
            ]
        }
    }
}
  1. 添加管理员按钮 & 编辑管理员
<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);
            }
        }
    })
}
  1. 编辑管理员,模态框的表单中要有当前行的数据
{
    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);
            }
        }
    })
}
  1. 但是由此带来新的问题

当我们点击完编辑某个用户的信息之后,再点击添加,则添加对话框中会有刚才用户的信息

所以我们要知道模态框是什么时候关闭的,在关闭的时候,我们把模态框表单中的内容清空就好了;

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();
    },
}
  1. 添加 & 编辑 管理员的业务逻辑

编写接口

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. 删除管理员

  1. 定义接口
    deleteUser.js
/**
 * 删除用户
 */
import http from './http'

function deleteUser(data){
    return http.post('/sys/user/delete', data)
}

export default deleteUser;
  1. 删除单项

在表头数据部分:

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('取消删除');
        }
    });
}
  1. 批量删除

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项目的,登录流程,增删改查已经完成了;








积跬步,而至千里;
共勉;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值