一级目录

二级目录

三级目录

基本介绍

MVVM框架

M:model,模型,后端传递的数据

V:view,视图,所看到的的页面

VM:ViewModel,连接view和model的桥梁

它有两个数据传递方向:

一是将模型转化成视图,即将后端传递的数据转化成所看到的页面。实现方式:数据绑定

二是将视图转化成模型,即将所看到的页面转化成后端的数据。实现方式:DOM事件监听

在MVVM框架下视图和模型是不能直接通信的,他们通过ViewModel来通信,ViewModel通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel也能监听到视图的变化,然后通知数据做改动,实际上就是实现了数据的双向绑定

MVVM是一种前端开发的架构模式,作用就是为了让前端业务逻辑和HTML代码更加分离。它的核心思想是把每个页面分成M(Model数据模型)、V(View视图)、VM(ViewModel视图模型)。其中VM是核心,是M和V之间的调度者,M和V不直接关联,通过中间的VM。VM还提供了双向数据绑定,也就是说V发生改变M也跟着改变,M发生改变V也会跟着改变。

和MVC有什么区别

MVVM实现了View和Model的自动同步,也就是当Model的数据改变时,我们不用再自己手动操作DOM元素来改变View的显示,而是改变数据后该数据对应View层显示会自动改变。MVVM并不是用VM完全取代了C,ViewModel存在的目的是抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。

Vue的双向绑定是如何实现的

vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty() 来劫持各个属性的 setter , getter ,在数据变动时发布消息给订阅者,触发相应的监听回调

vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?vue是如何进行数据劫持的?说白了就是通过Object.defineProperty()来劫持对象属性的setter和getter操作

Object.defineProperty( )是用来做什么的?它可以来控制一个对象属性的一些特有操作,比如读写权、是否可以枚举

1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者

2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图

3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器

原生js实现类似VUE双向数据绑定

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TS原生实现双向数据绑定</title>
</head>
<body>
    <input type="text" name="" id="inputId">

    <p id="contextMsg"></p>
</body>
<script>
    let inputId = document.getElementById("inputId")
    let contextMsg = document.getElementById("contextMsg")

    let data = {
        context:''//这个变量要与输入框和P标签进行双向数据绑定
    }
    //目标:输入框得值改变,同步到mesage
    //message值改变,同步到页面
    let $data = {
        _context:''
    }
    Object.defineProperty(data,'context',{
        set(val){
            $data._context = val
            inputId.value = val
            contextMsg.innerText = val
        },
        get(){
            return $data._context
        }
    })
    data.context = "a"
    console.log(data.context);

    // 监听V层改变
	inputId.oninput = function(e) {
	    data.context= e.target.value
	}
</script>
</html>

环境搭建

安装 NodeJS

1.下载

NodeJS下载官网:https://nodejs.cn/download/

在这里插入图片描述

2.验证

下载后解压安装,运行如下命令验证安装是否成功:

node -v
npm -v

3.查看默认存放位置

查看npm默认存放位置,运行命令如下:

npm get prefix
npm get cache

在这里插入图片描述

4.修改存放路径

在 nodejs 安装目录下,创建 node_globalnode_cache 两个文件夹

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sJ8z3I0W-1691999491591)(C:\Users\PC\AppData\Roaming\Typora\typora-user-images\image-20230812145208689.png)]

修改默认文件夹,命令如下:

npm config set prefix "D:\Software\nodejs\node_global"
npm config set cache "D:\Software\nodejs\node_cache"

5.添加环境变量

在系统变量的 path 当中添加 node_global 的路径(nodejs的路径在安装时默认已存在)

6.测试安装

运行如下命令:

npm install express -g

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-plWuysyA-1691999491592)(C:\Users\PC\AppData\Roaming\Typora\typora-user-images\image-20230812145744881.png)]

无报错即为安装成功

安装 VUE

VUE安装方式有两种,一种是 npm 安装,一种是 JavaScript 标签引入,现介绍 npm 安装方式。

1、安装淘宝镜像,打开命令窗口,运行如下代码:

npm install -g cnpm --registry=https://registry.npm.taobao.org

安装位置:C:\Users\HuYao\AppData\Roaming\npm

如果报错如下:

request to https://registry.npm.taobao.org/vue3-pdf-app failed, reason: certificate has expired

可以先运行如下命令取消https认证,再运行上述命令:

npm config set strict-ssl false

2、安装 vue-cli,打开命令窗口,运行如下代码:

npm install @vue/cli -g

安装完毕后,进行检验,运行如下代码:

node list

会出现如下信息,证明安装成功:

  ★  browserify - A full-featured Browserify + vueify setup with hot-reload, linting & unit testing.
  ★  browserify-simple - A simple Browserify + vueify setup for quick prototyping.
  ★  pwa - PWA template for vue-cli based on the webpack template
  ★  simple - The simplest possible Vue setup in a single HTML file
  ★  webpack - A full-featured Webpack + vue-loader setup with hot reload, linting, testing & css extraction.
  ★  webpack-simple - A simple Webpack + vue-loader setup for quick prototyping.

3、新建一个文件夹,如 “VUEStudy” ,在 VUEStudy 文件夹中打开命令窗口,运行如下命令:

vue init webpack myvue

其中,myvue是项目文件夹名称,根据提示进行下一步,安装完成后,进入myvue文件夹

运行如下命令并等待安装:

npm install

报错后运行提示命令:

npm audit fix

5、验证安装,在myvue文件夹中,输入:

npm run dev

运行vue项目,在浏览器中输入终端指定的浏览器地址,访问vue首页成功,至此,vue基本环境搭建完成。

vue-router安装

手动安装vue-router时,最好采用指定版本号方式:

npm i vue-router@3.5.2 --save-dev

若报错,按照提示修复:

npm audit fix

上述命令会安装VUE2.X,如果不指定vue-router版本安装,

npm install vue-router --save-dev

会默认安装vue-router 4.X版本,而vue-router 4.X支持VUE3.X,不支持npm audit fix,在vue-router引用时会报错。

//main.js
import VueRouter from 'vue-router'

Vue.use(VueRouter)
vue-router卸载
npm uninstall vue-router

vue脚手架配置方式(推荐)

运行,cmd打开终端,运行如下代码:

vue ui

自动弹出网页后,在网页端安装配置 vue 项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dfYlq8bI-1691999491592)(C:\Users\PC\AppData\Roaming\Typora\typora-user-images\image-20230812150347483.png)]

目录结构简介

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UgaPV4np-1691999491592)(C:\Users\PC\AppData\Roaming\Typora\typora-user-images\image-20230812151116159.png)]

vue.config.js 文件

const {defineConfig} = require('@vue/cli-service')
module.exports = defineConfig({
   /*如果处在低级浏览器范围内,那么会把node_modules里用得到的高级语法进行babel编译,如果transpileDependencies为false,则会把node_modules里用到的高级语法原封不动的打包(无视browserslist范围),会造成在低级浏览器访问报错的情况*/
  transpileDependencies:true,
  lintOnSave: false,// eslint校验,关闭后方便调试
  devServer: {
    host: 'localhost',// 主机名
    port: 8888,// 端口号
    open: true, // 配置自动启动浏览器
    proxy: {
      '/api': {
        target: 'https://api.apiopen.top/',// 后台接口域名
        ws: true,//如果要代理 websockets,配置这个参数
        secure: false,  // 如果是https接口,需要配置这个参数
        changeOrigin: true,//是否跨域
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }
})

main.js 文件

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import VCharts from 'vue-echarts'
import axios from 'axios'
Vue.prototype.$http = axios //正确的使用
axios.defaults.baseURL = '/io'

Vue.config.productionTip = false
Vue.use(ElementUI);

/*render函数是vue通过js渲染dom结构的函数createElement,约定可以简写为h,createElement 是 Vue.js 里面的 函数,这个函数的作用就是生成一个 VNode节点,render 函数得到这个 VNode 节点之后,返回给 Vue.js 的 mount 函数,渲染成真实 DOM 节点,并挂载到根节点上*/
new Vue({
  el: '#app',
  router,
  render: h => h(App)
}).$mount('#app')// 动挂载到id为app的dom中,应用实例必须在调用了,mount() 方法后才会渲染出来
Vue.component('v-chart', VCharts)

index.html 文件

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
    <!-- vue 视图会在这里展示 -->
  </body>
</html>

component 基础代码结构

<template>
  <div>
    <!-- 仅适用于 npm 工程方式 -->
    <HelloWorld/>
    <!-- <HelloWorld></HelloWorld> -->
  </div>
</template>

<script>
export default {
  name: 'App',
  components: {// 子组件区域
    HelloWorld
  },
  data(){
      return{
          //数据准备部分
      }
  },
  methods:{
      // 方法区域
  },
  
}
</script>

<style>
// 样式区域
</style>

基础代码讲解

1.数据准备——data()函数

数据准备在data函数中,JS中的实例是通过构造函数来创建的,每个构造函数可以new出很多个实例,那么每个实例都会继承原型上的方法或属性。vue的data数据其实是vue原型上的属性,数据存在于内存当中。vue为了保证每个实例上的data数据的独立性,规定了必须使用函数,而不是对象。

具体形式为:

data(){
      return{
          // 数据准备部分
        name:'Tom',// 基本数据类型
        friends:[
            {name:'Jack',age:18},
            {name:'张三',age:17},
            //....
        ],// 数组类型
		objectA:{}// 对象类型
		// ......
	  }
},

2.方法准备——methods:{}对象

methods:{}对象保存了所要调用的方法,具体形式为:

methods:{
	f1(){
		// 方法体
	},
	f2(){
		// 方法体
	},
	// ......
}

3.计算属性——compute:{}

一个能够将计算结果缓存起来的属性(将行为转化成了静态的属性)

computed: {
    //计算属性:methods,computed 方法名不能重名,重名字后,只会调用methods的方法
    currentTime2: function () {
         this.message;
         // 返回一个时间戳
         return Date.now();
     },
     // ......
}

生命周期钩子

Vue实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载DOM—>渲染,更新—>渲染、卸载等一系列过程,称之为vue的生命周期

  • beforeCreate()

    • 在实例创建以前调用
    • 没有实例化,无法通过vm访问到data中的数据、methods中的方法
  • created()

    • 实例被创建完成后调用
    • 能拿到数据
    • 能修改数据,且修改数据不会触发updated、beforeUpdate钩子函数
    • 可以在这个钩子函数里发请求,访问后端接口拿到数据
    • 挂载阶段还没有开始,$el属性还不可见
  • beforeMount()

    • 真实的dom节点挂载到页面之前
    • 编译模板已经结束,虚拟dom已经存在
    • 可以访问数据,也可以更改数据,且修改数据不会触发updated、beforeUpdate钩子函数
    • 在beforeMount和mounted之间隐藏了一个render函数,千万不能写,会覆盖系统函数
  • mounted()

    • 真实的dom节点挂载到页面以后
    • this.$refs找到ref表示的节点,操作dom
    • 可以访问和更改数据,且修改数据会触发updated、beforeUpdate钩子函数
  • beforeUpdate()

    • 修改之前被调用
    • 数据是新的,但页面是旧的。即:页面尚未和数据保持同步
  • updated()

    • 数据修改之后调用,数据是新的,页面也是新的
    • beforeUpdate、updated可以监控data里的所有数据变化
    • 不要在updated、beforeUpdate修改不定数据,否则会引起死循环
    • 监听data中的所有数据,非updated莫属

    由于数据更新导致的虚拟DOM重新渲染和打补丁,在这之后会调用updated钩子函数

  • beforeDestroy()

    • 实例卸载之前调用,可以清理非vue资源防止内存泄漏
    • 一般在此阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作
  • destroyed()

    对data改变不会再触发生命周期函数,说明此时Vue实例已经结束事件监听和dom的绑定,但是dom结构依然存在。

  • activated()

    • keep-alive缓存组件时候调用
  • deactivated()

    • 缓存组件卸载的时候调用

控件基础操作

文本

数据绑定最常见的形式就是使用 “Mustache” 语法(双大括号)的文本插值:

<span>Message: {{ msg }}</span>
<!-- msg可以是data()函数中的一个数据也可以说是compute:{}对象中的函数 -->
属性

Mustache 不能在 HTML 属性中使用,应使用 v-bind 指令:

<div v-bind:id="dynamicId"></div>
<!-- 或者 -->
<div :id="dynamicId"></div>
双向绑定值

v-model 指令在表单控件元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。

<input v-model="message" placeholder="edit me">
<!-- 用于输入框、下拉框、单选框、多选框等组件 -->
修饰符

.number

如果想自动将用户的输入值转为 Number 类型(如果原值的转换结果为 NaN 则返回原值),可以添加一个修饰符 numberv-model 来处理输入值:

<input v-model.number="age" type="number">

这通常很有用,因为在 type="number" 时 HTML 中输入的值也总是会返回字符串类型。

.trim

如果要自动过滤用户输入的首尾空格,可以添加 trim 修饰符到 v-model 上过滤输入:

<input v-model.trim="msg">
class与style绑定
<!-- 对象语法 -->
<div class="static"
     v-bind:class="{ active: isActive, 'text-danger': hasError }">
</div>
<!-- 数组语法 -->
<div v-bind:class="[activeClass, errorClass]">
    
<style>
data: {
    activeClass: 'active',
    errorClass: 'text-danger'
}
</style>

条件渲染

控制内容是否展示

v-if 是真实的条件渲染,因为它会确保条件块在切换当中适当地销毁与重建条件块内的事件监听器和子组件

v-show 简单得多——元素始终被编译并保留,只是简单地基于 CSS 切换

v-if

<h1 v-if=true>Yes</h1>
<h1 v-else>No</h1>

v-show

<h1 v-show=true>Hello!</h1>
<h1 v-show=false>World!</h1>
列表渲染

v-for

我们用 v-for 指令根据一组数组的选项列表进行渲染。 v-for 指令需要以 item in items 形式的特殊语法, items 是源数据数组并且 item 是数组元素迭代的别名

<ul id="example-1">
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>
<ul id="example-1">
  <li v-for="(item,index) in items" :key=index>
    {{ item.val }}
  </li>
</ul>
监听事件

可以用 v-on 指令监听 DOM 事件来触发一些 JavaScript 代码

按钮button

<div id="example-1">
  <button v-on:click="counter += 1">增加 1</button>
  <p>这个按钮被点击了 {{ counter }} 次。</p>
</div>
<!-- 简写形式 -->
<div id="example-2">
  <button @click="counter += 1">增加 1</button>
  <p>这个按钮被点击了 {{ counter }} 次。</p>
</div>

按键

<!-- 同上 -->
<input v-on:keyup.enter="submit">

<!-- 缩写语法 -->
<input @keyup.enter="submit">
  • .enter
  • .tab
  • .delete (捕获 “删除” 和 “退格” 键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

鼠标时间修饰符

可以用如下修饰符在相应按键响应时进行鼠标事件监听

  • .ctrl
  • .alt
  • .shift
  • .meta

组件

组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素, Vue.js 的编译器为它添加特殊功能

组件分父组件子组件

<!-- Test 父组件 -->
<template>
    <div>
        姓名:<input name="name" placeholder="请输入姓名" type="text" v-model="name"><br><br>
        年龄:<input name="age" placeholder="请输入年龄" type="text" v-model.number="age"><br><br>
        <button @click="add">添加</button>
        <h2>数据显示</h2>
        <!-- 绑定Students 传值给子组件MyUl -->
        <MyUl :Students="Students"></MyUl>
        <h1 v-if=true>Yes</h1>
        <h1 v-else>No</h1>
    </div>
</template>

<script>
import MyUl from './MyUl.vue';
export default {
    name: 'Test',
    data() {
        return {
            name: '',
            age: '',
            Students: [{ name: 'Tom', age: 18 }]
        };
    },
    methods: {
        add() {
            if (this.name !== '' && this.age !== '') {
                this.Students.push({ name: this.name, age: this.age });
            }
            console.log(this.Students);
        }
    },
    components: { MyUl }
}
</script>

<style scoped></style>
<!-- MyUL 子组件 -->
<template>
    <div>
        <MyLi :Students="Students"></MyLi>
    </div>
</template>

<script>
import MyLi from './MyLi.vue';
export default {
    name: 'MyUL',
    props:['Students'],// 子组件MyUl接收来自父组件Test的数据
    components: { MyLi }
}
</script>

<style lang="scss" scoped></style>

传值

Prop/$emit

父传子——使用 Prop 传递数据

组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。可以使用 props 把数据传给子组件。

prop 是父组件用来传递数据的一个自定义属性。子组件需要显式地用 props 选项声明 “prop”:

component('child', {
  // 声明 props
  props: ['message'],
  // 就像 data 一样,prop 可以用在模板内
  // 同样也可以在 vm 实例中像 “this.message” 这样使用
  template: '<span>{{ message }}</span>'
})

父组件传入一个普通字符串:

<child message="hello!"></child>

子传父——使用$emit(‘functionName’,params)函数

<!-- 父组件 -->
<template>
	<buttonCounter @increment="incrementTotal"></buttonCounter>
</template>
<script>
import buttonCounter from './buttonCounter.vue';
export default {
    methods: {
        incrementTotal (val) {
            console.log(val,'-----------')
        }
    },
    components: { buttonCounter}
}
</script>
<!-- 子组件 -->
<template>
    <div>
        <button @click="increment">{{ counter }}</button>
    </div>
</template>

<script >
export default {
    name: 'butttonCounter',
    data() {
        return {
            counter: 0
        }
    },
    methods: {
        increment () {
            this.counter += 1
            this.$emit('increment',this.counter)
        }
    },
}
</script>

插件 Element UI

安装
npm i element-ui -S
// main.js
import Vue from 'vue';
import ElementUI from 'element-ui';// 引入
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';

Vue.use(ElementUI);// 全局应用

new Vue({
  el: '#app',
  render: h => h(App)
});
示例
<template>
  <el-main>
    <el-row>
      <el-col :span="24" align="left">
        <el-input v-model="input" placeholder="请输入内容"></el-input>
        <el-select filterable v-show="select" v-model="value" placeholder="请选择">
          <el-option
            v-for="item in options"
            :key="item.value"
            :label="item.label"
            :value="item.value">
            <span style="float: left">{{ item.label }}</span>
            <span style="float: right; color: #8492a6; font-size: 13px">{{ item.value }}</span>
          </el-option>
        </el-select>
        <el-button type="primary" @click="Update">确定</el-button>
      </el-col>
    </el-row>
  </el-main>
</template>

<script>
export default {
  name: "FormPage",
  data() {
    return {
      input: '',
      options: [
        {value: '选项1', label: '黄金糕'},
        {value: '选项2', label: '双皮奶'},
        {value: '选项3', label: '蚵仔煎'},
        {value: '选项4', label: '龙须面'},
        {value: '选项5', label: '北京烤鸭'}
      ],
      value: '',
      select: true,
      i: 6,
    }
  },
  created() {//自动渲染数据

  },
  methods: {//方法
    Update() {
      if (this.input !== '') {
        this.options.push({value: '选项' + this.i, label: this.input})
        this.i++
      }
    }
  },
}
</script>

<style scoped>
.el-input {
  width: 20%;
}

.el-select {
  margin-left: 10px;
}

.el-button {
  margin-left: 10px;
}
</style>

插件 echarts

安装
npm install echarts vue-echarts
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import VCharts from 'vue-echarts'
import axios from 'axios'
Vue.prototype.$http = axios //正确的使用
axios.defaults.baseURL = '/io'

Vue.config.productionTip = false
Vue.use(ElementUI);
new Vue({
  el: '#app',
  router,
  render: h => h(App)
}).$mount('#app')
Vue.component('v-chart', VCharts)

示例
<template>
  <main>
    <el-row>
      <el-col :span="24">
        <!-- 必须指定一个固定大小区域 area -->
        <div class="area">
          <v-chart class="echarts" :option="option" @click="getDate"/>
        </div>
      </el-col>
    </el-row>
  </main>
</template>

<script>
import VChart, {THEME_KEY} from "vue-echarts";
import * as echarts from 'echarts/core';
import {ToolboxComponent, LegendComponent} from 'echarts/components';
import {PieChart} from 'echarts/charts';
import {LabelLayout} from 'echarts/features';
import {CanvasRenderer} from 'echarts/renderers';

echarts.use([
  ToolboxComponent,
  LegendComponent,
  PieChart,
  CanvasRenderer,
  LabelLayout
]);


export default {
  name: "elements",
  components: {
    'v-chart': VChart,
  },
  // provide: {
  //   [THEME_KEY]: 'dark'
  // },

  data() {
    return {
      option: {
        legend: {
          top: 'bottom'
        },
        toolbox: {
          show: true,
          feature: {
            mark: {show: true},
            dataView: {show: true, readOnly: false},
            restore: {show: true},
            saveAsImage: {show: true}
          }
        },
        series: [
          {
            name: 'Nightingale Chart',
            type: 'pie',
            radius: [50, 250],
            center: ['50%', '50%'],
            roseType: 'area',
            itemStyle: {
              borderRadius: 8
            },
            data: [
              {value: 40, name: 'rose 1'},
              {value: 38, name: 'rose 2'},
              {value: 32, name: 'rose 3'},
              {value: 30, name: 'rose 4'},
              {value: 28, name: 'rose 5'},
              {value: 26, name: 'rose 6'},
              {value: 22, name: 'rose 7'},
              {value: 18, name: 'rose 8'}
            ]
          }
        ]
      }
    }
  },
  created() {//自动渲染数据

  },
  methods: {//方法
    getDate(value) {
      console.log(value.name, value.value)
      this.$http.get('api/getImages?type=car&page=0&size=10').then(res=>{
        console.log(res.data.result.list)
      })
    }
  },
}

</script>

<style scoped>
.el-row {
  margin-bottom: 20px;
}

.el-col {
  border-radius: 4px;
}

.area {
  width: 80%;
  height: 80vh;
  background-size: 100% 100%;
  padding: 8%;
  margin-left: 5%;
}
</style>

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐