Vue2 学习笔记【详解】

一.yarn和npm的区别

  1. 速度: Yarn 在安装依赖包时通常比 npm 更快。Yarn 使用了并行下载和缓存机制,可以显著提高包的安装速度。
  2. 可靠性: Yarn 通过锁定文件(yarn.lock)确保每个团队成员在不同的环境中安装相同版本的依赖包,从而减少了潜在的构建差异和问题。
  3. 离线模式: Yarn 允许你在没有网络连接的情况下使用先前下载过的包进行安装,这对于开发者在航班、旅途或其他离线环境下工作非常有用。
  4. 语义化版本控制: Yarn 使用语义化版本范围解析依赖关系,可以更精确地确定所需的包版本,以及避免潜在的兼容性问题。
  5. 并行安装: Yarn 可以同时下载多个依赖包,这加快了项目的构建时间。
  6. 更好的错误日志: Yarn 提供了更详细和易于理解的错误日志,可以帮助你更快地调试和解决依赖问题。

二.Vue项目流程

创建项目

// 安装vue cli包
yarn global add @vue/cli
// 创建vue项目
vue create 项目名称

element-ui完全注入vue

// 安装element-ui
npm i element-ui -S
// 然后在main.js中按需求引入
import { Button } from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

按需引入element-ui

// 按需引入插件下载
npm install babel-plugin-component -D
// babel.config.js中添加下面代码
{
  // 由于没有依赖项所以需要这样写
  "presets": [[""@babel/preset-env", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

路由的使用

// @之后就是版本号
npm i [email protected]

// 新建router文件夹
// 新建index.js
// 模块化,引用模块
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

// 1.创建路由组件(对这些组件进行引用)
import Home from '../views/Home.vue'
import User from '../views/User.vue'

// 2.将组件和路由进行一个映射
const routes = [
    { path: '/home', component: Home},
    { path: '/user', component: User}
]

// 3.创建router实例
const router = new VueRouter({
    routes //(缩写)相当于routes:routes
})

// 4.将当前的router实例对外进行一个暴露
export default router

new Vue({
  render: h => h(App),
  // 5.最后把router挂载到Vue上
  router
}).$mount('#app')

// 6.最后要将router-view组件渲染到div中
//   路由出口
//   把路由匹配的组件都渲染到这里
<template>
  <div>
    <router-view></router-view>
  </div>
</template>

关闭eslint校验

// 在vue.config.js中添加代码
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave: false // 关闭eslint校验
})

router嵌套路由

解决一个页面就只有一个模块需要嵌套路由的问题,不用重复写代码

使用多层路由,不变的路由作为外层路由,变化的路由作为内层路由

const routes = [
    // 主路由
    { 
        path: '/',
        component: Main,
        children: [
            // 嵌套路由
            { path: 'home', component: Home},
            { path: 'user', component: User}
        ]
    }
]
// 当然Main.vue中也需要添加路由出口,因为Home和User都是显示在Main中的
<template>
    <div>
        <h1>我是Main.vue</h1>
        <!-- 路由出口 -->
        <!-- 路由匹配到的组件将渲染到这里 -->
        <router-view></router-view>
    </div>
</template>

页面布局,使用element-ui,使用嵌套路由,将ui添加到主路由的template中

扫描二维码关注公众号,回复: 17815081 查看本文章
<template>
    <div>
        <el-container>
            <el-aside width="200px">Aside</el-aside>
            <el-container>
                <el-header>Header</el-header>
                <el-main>
                    <!-- 路由出口 -->
                    <!-- 路由匹配到的组件将渲染到这里 -->
                    <router-view></router-view>
                </el-main>
            </el-container>
        </el-container>
    </div>
</template>

将标签改成动态标签,在标签之前添加符号:

<!-- 当前所渲染的图标 -->
<i :class="`el-icon-${item.icon}`"></i>

Vue.js中的插值语法,将表达式的值插入到模板中

<span slot="title">{
   
   { item.label }}</span>

下载CSS的Less插件,这个插件可以让CSS和html一样嵌套,也可以用变量来减少CSS的代码量

yarn add [email protected] [email protected]

<style lang="less" scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
    width: 200px;
    min-height: 400px;
}
// CSS嵌套写法
.el-menu {
    height: 100vh;
    h3 {
        color: #fff;
    }
}
</style>

定义路由跳转的click事件

<el-menu-item v-on:click="clickMenu(item)" v-for="item in nochildren" :key="item.name" :index="item.name">
    <!-- 当前所渲染的图标 -->
    <i :class="`el-icon-${item.icon}`"></i>
    <!-- 将表达式的值插入到模板中 -->
    <span slot="title">{
   
   { item.label }}</span>
</el-menu-item>

    methods: {
        // 点击菜单
        clickMenu(item) {
            // 当页面的路径和跳转的路径不一致时候才跳转
            if (this.$route.path !== item.path && !(this.$route.path === '/home' && (item.path === '/')))
            {
                this.$router.push(item.path);
            }
        }
    },

路由重定向

const routes = [
    // 主路由
    { 
        path: '/',
        component: Main,
        redirect: '/home',
        children: [
            // 嵌套路由
            { path: 'home', component: Home}, // 首页
            { path: 'user', component: User}, // 用户管理
            { path: 'exhibits', component: Exhibits}, // 展品管理
            { path: 'exhibit', component: Exhibit}, // 展厅管理
            { path: 'page1', component: PageOne}, // 页面1
            { path: 'page2', component: PageTwo}, // 页面2
        ]
    }
]

使用Vuex3,兄弟组件通信

// 下载vuex
yarn add [email protected]

// 创建store文件夹
// 创建index.js这个文件夹用来汇总所有需要通信的vuex组件
import Vue from 'vue'
import Vuex from 'vuex'
import tab from './tab.js'
Vue.use(Vuex)
// 创建vuex的实例,并且对外暴露
export default new Vuex.Store({
    modules: {
        tab
    }
})

// 创建tab.js子文件用来写具体的需要通信的值和改变值的方法,就好比中间商一个人去改这个
值其它一方也会改变
export default {
    state: {
        // 控制菜单的展开还是收起
        isCollapse: false 
    },
    mutations: {
        // 修改菜单打开收起的方法(会传入一个变量)
        collapseMenu(state) {
            state.isCollapse = !state.isCollapse
        }
    }
}
 
 // 要记得把store挂载到vue,全局使用
 new Vue({
  store,
  // 5.最后把router挂载到Vue上
  router,
  render: h => h(App),
}).$mount('#app')

// 然后其它组件就可以使用这个方法或者得到他的变量值
<script>
export default {
    data() {
        return {}
    },
    methods: {
        handleMenu() {
            // 这里能获取$store也是因为在main.js中已经让store成为vue的一部分了
            this.$store.commit('collapseMenu')
        }
    } 
}
</script>

 isCollapse() {
   return this.$store.state.tab.isCollapse
}    

Layout内容布局

<el-row :gutter="20">
  <el-col :span="16"><div class="grid-content bg-purple"></div></el-col>
  <el-col :span="8"><div class="grid-content bg-purple"></div></el-col>
</el-row>

table组件

card组件

ajax请求

axios请求

// 安装axios
npm install axios

// 二次封装axios
// 创建工具文件夹utils
// 创建request.js 这是一个工具类用于方便连接后端接口
import axios from 'axios'
const http = axios.create({
    // 通用请求的地址前缀
    baseURL: 'http://60.247.146.86:7118/api/',
    // 超时时间,超过这个时间服务器没有给响应,就会停止请求
    timeout: 100000,
})
// 添加请求拦截器
http.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});
// 添加响应拦截器
http.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
}, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
});
export default http

// 创建api文件夹
// 创建index.js
// 写一个getData方法并且暴露给外部进行调用
import http from '../utils/request.js'
// 请求首页数据
export const getData = () => {
    // 返回后端数据库中的数据
    return http.get('UserData/GetUserData')
}

// 最后在export中调用
<script>
import { getData } from '../api/index.js'
export default {
    // vue的生命周期函数
    mounted() {
        getData().then((data) => {
            console.log(data);
        })
    }
}
</script>

Vue组件的生命周期

Vue 组件的生命周期是指组件从创建到销毁的整个过程中,会经历一系列的阶段和钩子函数。这些钩子函数允许你在不同的时机执行代码逻辑,以便控制组件的行为和交互。

下面是 Vue 2.x 中常见的生命周期钩子函数:

  1. beforeCreate:在实例初始化之后,数据观测 (data observer) 和事件配置 (event/watcher setup) 之前被调用。此时组件的选项和初始数据都未被设置。
  2. created:在实例创建完成后被立即调用。此时已完成了数据观测、属性和方法的运算,但尚未挂载到 DOM 上。
  3. beforeMount:在组件挂载之前被调用。此时模板编译完成,但尚未将生成的 DOM 替换页面中的相应元素。
  4. mounted:在组件挂载到 DOM 后被调用。此时组件已经挂载,可以访问到 DOM 元素,并且可以进行 DOM 相关的操作。
  5. beforeUpdate:在响应式数据更新之前被调用,即在重新渲染之前被触发。此时数据已经更新,但尚未重新渲染。
  6. updated:在组件重新渲染后被调用。此时组件已经更新,DOM 也已经更新完成。
  7. beforeDestroy:在组件销毁之前被调用。在这个阶段,实例仍然完全可用。
  8. destroyed:在组件销毁后被调用。此时组件已经被销毁,所有的事件监听器和子组件都被移除。

另外,Vue 3.x 引入了 beforeUnmount 和 unmounted 替代了 Vue 2.x 中的 beforeDestroy 和 destroyed。

生命周期钩子函数可以用于执行一些特定的操作,例如初始化数据、发送网络请求、处理 DOM 元素、订阅事件等。通过合理利用这些钩子函数,你可以控制组件在不同阶段的行为,并进行相应的处理。

需要注意的是,随着 Vue 3 中 Composition API 的引入,推荐使用 setup() 函数和 onMounted()、onUpdated()、onUnmounted() 等函数来代替传统的生命周期钩子函数。这样可以更灵活地组织代码,并提供更好的开发体验和复用性。

下面是 Vue 组件生命周期函数和 Unity 游戏对象的生命周期回调函数之间的对应关系:

  • beforeCreate

:对应 Unity 中的 Awake 函数,用于在组件/对象创建前进行初始化设置。

  • created

:在 Vue 组件中没有直接对应的 Unity 生命周期回调函数。可以将其视为 Awake 后的一些初始化逻辑,例如数据的加载或订阅事件的建立。

  • beforeMount

:对应 Unity 中的 OnEnable 函数,表示组件/对象即将被激活并准备开始渲染或响应用户交互。

  • mounted

:对应 Unity 中的 Start 函数,指示组件/对象已经完成初始化,并开始正常运行和渲染。

  • beforeUpdate

:对应 Unity 中的 Update 函数,在组件/对象更新之前被调用,用于执行一些预处理逻辑。

  • updated

:对应 Unity 中的 Update 函数,表示组件/对象已经更新完成,可以执行相应的操作,如更新UI、处理输入等。

  • beforeDestroy

:对应 Unity 中的 OnDestroy 函数,表示组件/对象即将被销毁,通常在该阶段进行资源的释放和清理操作。

  • destroyed

:对应 Unity 中的 OnDestroy 函数,表示组件/对象已经被销毁,此时应确保所有引用和事件都被移除,避免内存泄漏。

需要注意的是,Vue 组件的生命周期函数更加细粒度和灵活,可以通过组合使用 Composition API 或混入 (mixin) 来实现更丰富的生命周期管理。而 Unity 的生命周期回调函数是通过统一的接口来约定对象在不同阶段的行为。

总体而言,Vue 组件的生命周期函数和 Unity 游戏对象的生命周期回调函数都提供了类似的机制,以便在不同阶段执行相应的逻辑代码,从而管理和控制组件/对象的行为。

echarts图表

npm i [email protected]
// 折线图表
<!-- 折线图 -->
<div ref="echarts1" style="height: 270px"></div>

// 折线图
            // 基于准备好的DOM,初始化echarts实例
            // Vue的DOM实例在mounted中获取--this.$refs.echarts1
            this.echarts1 = echarts.init(this.$refs.echarts1);
            // 获取对象 this.echarts1Data.data[0] 的所有可枚举属性名,并将它们存储在 xAxis 变量中
            var xAxis = Object.keys(this.echarts1Data[0])
            // 指定图表的配置项和数据
            var echarts1Option = {}
            echarts1Option.legend = {
                data: xAxis
            }
            echarts1Option.xAxis = {
                data: xAxis
            }
            echarts1Option.yAxis = {}
            echarts1Option.series = []
            xAxis.forEach(key => {
                echarts1Option.series.push({
                    name: key,
                    data: this.echarts1Data.map(item => item[key]),
                    type: 'line'
                })
            })
            // 根据配置显示图表
            this.echarts1.setOption(echarts1Option)
// 柱状图
            // 基于准备好的DOM,初始化echarts实例
            // Vue的DOM实例在mounted中获取--this.$refs.echarts2
            this.echarts2 = echarts.init(this.$refs.echarts2);
            // 指定图表的配置项和数据
            var echarts2Option = {
                legend: {
                    textStyle: {
                        color: "#333",
                    },
                },
                grid: {
                    left: "20%",
                },
                // 提示框
                tooltip: {
                    trigger: "axis",
                },
                xAxis: {
                    // 类目轴
                    type: "category",
                    data: this.echarts2Data.map(item => item.data),
                    axisLine: {
                        lineStyle: {
                            color: "#17b3a3",
                        },
                    },
                    axisLabel: {
                        interval: 0,
                        color: "#333",
                    },
                },
                yAxis: [
                    {
                        type: "value",
                        axisLine: {
                            lineStyle: {
                                color: "#17b3a3",
                            },
                        },
                    },
                ],
                color: ["#2ec7c9", "#b6a2de"],
                series: [
                    {
                        name: '新增用户',
                        data: this.echarts2Data.map(item => item.new),
                        type: 'bar'
                    },
                    {
                        name: '活跃用户',
                        data: this.echarts2Data.map(item => item.active),
                        type: 'bar'
                    }
                ],
            }
            // 根据配置显示图表
            this.echarts2.setOption(echarts2Option)
// 饼状图
            this.echarts3 = echarts.init(this.$refs.echarts3);
            var echarts3Option = {
                tooltip: {
                    trigger: "item",
                },
                color: [
                    "#0f78f4",
                    "#f53f65",
                    "#8e4cf9",
                    "#a1f32b",
                    "#f94791",
                    "#16aef0",
                    "#fa8d11",
                ],
                series: [
                    {
                        data: this.echarts3Data,
                        type: 'pie'
                    }
                ],
            }
            // 根据配置显示图表
            this.echarts3.setOption(echarts3Option)

表单提交

// 新增用户弹窗
        <el-dialog title="提示" :visible.sync="dialogVisible" width="50%" :before-close="handleClose">
            <!-- 用户的表单信息 -->
            <el-form ref="form" :rules="rules" :inline="true" :model="form" label-width="80px">
                <el-form-item label="姓名" prop="name">
                    <el-input placeholder="请输入姓名" v-model="form.name"></el-input>
                </el-form-item>
                <el-form-item label="出生年份" prop="year">
                    <el-date-picker v-model="form.year" type="year" placeholder="选择年份">
                    </el-date-picker>
                </el-form-item>
                <el-form-item label="性别" prop="gender">
                    <el-select placeholder="请选择性别" v-model="form.gender">
                        <el-option label="男" value="1"></el-option>
                        <el-option label="女" value="0"></el-option>
                    </el-select>
                </el-form-item>
                <el-form-item label="工作状态" prop="status">
                    <el-select placeholder="请选择工作状态" v-model="form.status">
                        <el-option label="实习" value="0"></el-option>
                        <el-option label="在职" value="1"></el-option>
                        <el-option label="非在职员工" value="2"></el-option>
                    </el-select>
                </el-form-item>
                <el-form-item label="联系电话" prop="phone_number">
                    <el-input placeholder="请输入联系电话" v-model="form.phone_number"></el-input>
                </el-form-item>
                <el-form-item label="权限" prop="permission">
                    <el-select placeholder="请选择用户权限" v-model="form.permission">
                        <el-option label="管理人员" value="0"></el-option>
                        <el-option label="业务人员" value="1"></el-option>
                        <el-option label="客户" value="2"></el-option>
                    </el-select>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="handleClose">取 消</el-button>
                <el-button type="primary" @click="submit">确 定</el-button>
            </span>
        </el-dialog>

// 新增用户按钮表单提交 然后只要连接后端数据库直接post就可以了
<el-button type="primary" @click="dialogVisible = true">+ 新增</el-button>

// JS代码
data() {
        return {
            dialogVisible: false,
            form: {
                // 初始数据
            },
            // 填写规则,一个对象限制一个条件
            rules: {
                name: [
                    {
                        required: true,
                        message: '请输入姓名',
                    }
                ],
                year: [
                    {
                        required: true,
                        message: '请选择出生年份'
                    }
                ],
                gender: [
                    {
                        required: true,
                        message: '请选择性别'
                    }
                ],
                status: [
                    {
                        required: true,
                        message: '请选择工作状态'
                    }
                ],
                phone_number: [
                    {
                        required: true,
                        message: '请输入联系电话'
                    },
                    {
                        min: 11,
                        max: 11,
                        message: '联系电话必须为11位'
                    },
                    {
                        type: 'number',
                        message: '年龄必须为数字值'
                    }
                ],
                permission: [
                    {
                        required: true,
                        message: '请选择权限'
                    }
                ]
            },
            tableData: [],
        };
    },
    methods: {
        // 提交用户表单
        submit() {
            // 利用ref标签获取form实例
            this.$refs.form.validate((isValidate) => {
                if (isValidate) {
                    // 后续对表单数据的处理
                    console.log(this.form)

                    // 当处理关闭dialog页面之前先清空form列表
                    this.handleClose()
                }
            })
        },
        // 当处理关闭dialog页面之前先清空form列表
        handleClose() {
            this.dialogVisible = false
            this.$refs.form.resetFields()
        },
        
// 过滤后端发送来的数据 进行处理显示出来
computed: {
        // 设置过滤 将后端发送来的数据过滤成可以使用的数据
        formattedTableData() {
            return this.tableData.map(item => {
                let status;
                let permission;
                let date = new Date();
                switch (item.status) {
                    case 0:
                        status = '实习'
                        break;
                    case 1:
                        status = '在职'
                        break;
                    case 2:
                        status = '非在职员工'
                        break;
                }
                switch (item.permission) {
                    case 0:
                        permission = '管理人员'
                        break;
                    case 1:
                        permission = '业务人员'
                        break;
                    case 2:
                        permission = '客户'
                        break;
                }
                return {
                    ...item,
                    year: parseInt(date.getFullYear()) - item.year,
                    gender: item.gender === 1 ? '男' : '女',
                    status: status,
                    permission: permission
                }
            });
        }
    }

table表格编辑和删除

编辑复用了表单提交的dialog对话框

在这个之前先了解vue.js这个插槽功能


首先,让我们来解释 `<template slot-scope="scope">` 这一行代码。在 Vue.js 中,插槽(slot)用于向子组件传递内容或功能。通过 `slot-scope` 属性,我们可以访问父组件中传递给插槽的数据或方法。在这种情况下,`scope` 是一个代表插槽作用域的变量名。

在这个插槽中,我们使用了两个按钮组件:一个是 "编辑" 按钮,另一个是 "删除" 按钮。现在我们来逐个解释它们:

```html
<el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
```


- `@click="handleEdit(scope.row)"` 定义了按钮点击事件的处理函数为 `handleEdit`,并传递了 `scope.row` 作为参数。`scope.row` 是由父组件传递给插槽的数据,具体含义可能需要根据上下文来确定。在点击按钮时,会执行 `handleEdit` 方法,并将 `scope.row` 作为参数传入。



总结来说,这段代码中的插槽通过 `scope` 变量限制了对父组件传递数据或方法的访问。其中,`handleEdit` 和 `handleDelete` 方法是由父组件定义和实现的,用于处理按钮的点击事件,并可能涉及到传入的数据 `scope.row`。

深拷贝和浅拷贝

// 浅拷贝示例
let obj1 = { name: 'John', age: 25 };
let obj2 = { ...obj1 };
obj2.name = 'Alice';
console.log(obj1); // { name: 'John', age: 25 }
console.log(obj2); // { name: 'Alice', age: 25 }

// 深拷贝示例
let obj3 = { name: 'John', address: { city: 'New York' } };
let obj4 = JSON.parse(JSON.stringify(obj3));
obj4.address.city = 'San Francisco';
console.log(obj3); // { name: 'John', address: { city: 'New York' } }
console.log(obj4); // { name: 'John', address: { city: 'San Francisco' } }

正则表达式

正则表达式(Regular Expression)是一种用于匹配、查找和操作文本的强大工具。它由字符和特殊符号组成,可用于执行高级的模式匹配和字符串处理。

以下是一些基本的正则表达式元字符和用法:

1. 普通字符:直接匹配文本中相应的字符,例如 `abc` 匹配文本中的 "abc"。
2. 元字符`.`:匹配任意字符,例如 `a.c` 可以匹配 "abc"、"adc"、"afc" 等。
3. 字符集`[]`:匹配指定范围内的字符,例如 `[aeiou]` 匹配任意一个元音字母。
4. 重复匹配:
   - `*`:匹配前面的元素零次或多次,例如 `ab*c` 可以匹配 "ac"、"abc"、"abbc" 等。
   - `+`:匹配前面的元素至少一次,例如 `ab+c` 可以匹配 "abc"、"abbc"、"abbbc" 等。
   - `?`:匹配前面的元素零次或一次,例如 `colou?r` 可以匹配 "color" 或 "colour"。
5. 定位符:
   - `^`:匹配行的开头,例如 `^Hello` 匹配以 "Hello" 开头的行。
   - `$`:匹配行的结尾,例如 `world$` 匹配以 "world" 结尾的行。
   - `\b`:匹配单词的边界,例如 `\btest\b` 可以匹配独立的单词 "test"。
6. 转义字符:
   - 使用`\`进行转义,例如 `\$10` 可以匹配 "$10"。
   - 使用`\`将元字符转为普通字符,例如 `\.` 可以匹配实际的点号而不是任意字符。

以上是一些常用的正则表达式元字符和用法。正则表达式还有更多的语法和功能,如分组、反向引用、预查等。你可以参考正则表达式的文档或教程,深入学习和理解其用法和技巧。同时,在线的正则表达式测试工具可帮助你实时验证和调试正则表达式。

导航守卫

// 阻止登录之前的跳转操作
router.beforeEach((to, from, next) => {
    const isLoggedIn = store.state.tab.isLogin; // 检查登录状态的函数,返回布尔值
    if (from.path === '/login' && !isLoggedIn) {
        // 如果用户未登录且试图导航到主页,则阻止导航
        next('/login'); // 重定向到登录页面或其他适当的位置
    } else if (from.path != '/login' && to.path === '/login') {
        store.commit('changeLoginState', false);
        next();
    } else {
        // 允许导航继续
        next();
    }
});

Cookie存储小文本数据

npm i [email protected]

在前端应用中全局设置请求头

import axios from 'axios';

axios.interceptors.request.use(config => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
}, error => {
  return Promise.reject(error);
});

// 现在所有通过 axios 发送的请求都会带上 token
axios.get('https://example.com/api/endpoint')
  .then(response => {
    console.log('Success:', response.data);
  })
  .catch(error => {
    console.error('Error:', error);
  });

axios.post('https://example.com/api/endpoint', { data: 'example' })
  .then(response => {
    console.log('Success:', response.data);
  })
  .catch(error => {
    console.error('Error:', error);
  });