Vue_02:详细语法以及代码示例 + 知识点练习 + 综合案例(第二期)

2023年8月4日15:25:01

Vue_02_note

在Vue中,非相应式数据,直接往实例上面挂载就可以了。

01_Vue 指令修饰符

什么是指令修饰符呢?

答: 通过 " . " 指明一些指令后缀,不同 后缀 封装了不同的处理操作 ------> 简化代码

常见的有哪些呢?

  1. 按键修饰符 @keyup.enter="执行代码" ------> 键盘回车监听

  2. v-model修饰符

    1. v-model.trim ------> 去首位空格
    2. v-model.number ------> 转数字
  3. 事件修饰符

    1. @事件名.stop ------> 阻止冒泡
    2. @事件名.prevent ------ > 阻止默认行为
代码示例

按键修饰符

html 复制代码
   <input @keyup.enter="add" v-model="todoName" placeholder="请输入任务" class="new-todo" />

同样Vue实例中需要提供add() {} ,回车需要执行的代码都需要写在add函数中


  1. v-model.trim 修饰符
html 复制代码
      <input v-moedl.trim="变量" v-model="todoName" placeholder="请输入任务" class="new-todo" />
  1. v-model.number
html 复制代码
      <input v-moedl.number="变量" v-model="todoName" placeholder="请输入年龄" class="new-todo" />

如果你输入的是字符符号,就转不了,原封不动显示,总不可能呢个转成一个NaN把


  1. @事件名.stop (阻止冒泡)

    html 复制代码
    <div @click.stop="xxx" > </div>
  2. @事件名.prevent (阻止默认行为)

    html 复制代码
    <a @click.prevent="xxx">阻止默认行为</a>

02_ 通过v-bind操作class

常见操作的方式有 : 通过class类 、 或者style 行内式等原生方式来实现。

v-bind -操作class
语法
  1. 语法: class="对象/数组"

    1. 对象 -》 键就是类名,值是布尔值。如果为true,有这个类,否则就没有这个类

    <div class="box" :class="{类名1:布尔值,类名2: 布尔值}"></div>
    2. 数组-》数组中所有的类,都会添加到盒子上,本质就是一个 class 列表

    <div class="box" :class="[类名1,类名2,类名3]"></div>


    代码演示:
    html 复制代码
        <div id="app">
            <!-- 传递对象的写法         对象注意键值对-->
            <div class="box" :class="{pink: true,big: true}">对象形式</div>
            <!-- 传递数组的写法         数组注意加引号-->
            <div class="box" :class="['pink','big']">数组形式</div>
    
        </div>
    
        <script src="http://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    
        <script>
            const app = new Vue({
                el: '#app',
                data: {
    
                }
            })
        </script>
    • 使用场景:
    • 对象适用于一个类,来回切换。
    • 数组适用于批量添加或删除。

(案例)通过操作class类实现导航栏高亮效果
核心思路
  1. 基于数据动态渲染 tab 使用v-for
  2. 准备下标记录了高亮的是哪一个 tab
  3. 基于下标,动态控制class类名 使用 v-bind:class

代码示例
html 复制代码
<div id="app">
 <ul>
   <li v-for="(item,index) in list" :key="item.id" @click="activeIndex = index">
     <a :class="{active: index === activeIndex}" href="#">{{ item.name }}</a></li>
 </ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
 const app = new Vue({
   el: '#app',
   data: {
     activeIndex: 2,
     list: [
       {id: 1, name: '京东秒杀'},
       {id :2, name: '每日特价'},
       {id :3, name: '品类秒杀'}
     ]
   }
 })
</script>

就是操作下标


03_ 通过v-bind操作style


语法
  1. 语法 :style="样式对象"
代码示例:
html 复制代码
 <div id="app">
     <div :style="{width: '400px',height: '400px',backgroundColor: 'green'}" class="box"></div>
 </div>
(案例)通过操作style实现进度条效果
核心思路
  1. 底层就是将两个不同颜色的盒子重叠,修改宽度
  2. 这里我们使用v-bind操作style来实现宽度的变化
  3. 给按钮绑定点击事件,然后将宽度赋值给变量
  4. 将变量动态的设置给我们的宽度,进行拼接

html 复制代码
  <style>
    .progress {
      height: 25px;
      width: 400px;
      border-radius: 15px;
      background-color: #272425;
      border: 3px solid #272425;
      box-sizing: border-box;
      margin-bottom: 30px;
    }
    .inner {
      width: 50%;
      height: 20px;
      border-radius: 10px;
      text-align: right;
      position: relative;
      background-color: #409eff;
      background-size: 20px 20px;
      box-sizing: border-box;
      transition: all 1s;
    }
    .inner span {
      position: absolute;
      right: -20px;
      bottom: -25px;
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="progress">
      <div class="inner" :style="{width: percent + '%'}">
        <span>{{ percent }}%</span>
      </div>
    </div>
    <button @click="percent = 25">设置25%</button>
    <button @click="percent = 50">设置50%</button>
    <button @click="percent = 75">设置75%</button>
    <button @click="percent = 100">设置100%</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        percent: 30
      }
    })
  </script>
适用场景
  • style适用于某个具体属性的动态设置(就如上面案例)
  • 如果说添加class类的话,会导致所有的属性都生效,不能实现单独效果

04_ v-model应用于其他表单元素

其他表单元素:例如,文本框textarea、复选框checkedbox、单选框 input:radio 、下拉菜单select

  • 常见的表单元素都可以使用 v-model来绑定关联,快速获取或设置表单元素的值,他会根据控件类型自动选取 正确的方式来更新元素。

下面直接使用案例来学习怎么实现以及操作其他表单元素

代码示例

主要看v-model在这些表单中使用的方式,大差不差

html 复制代码
<!-- 容器 -->
<div id="app">
 <h3>小黑学习网</h3>

 姓名:
   <input type="text" v-model="username"> 
   <br><br>

 是否单身:
   <input type="checkbox" v-model="isSingle"> 
   <br><br>

 <!-- 
   前置理解:
        1. name:  给单选框加上 name 属性 可以分组 → 同一组互相会互斥
        2. value: 给单选框加上 value 属性,用于提交给后台的数据
      结合 Vue 使用 → v-model
    -->
    性别: 
      <input v-model="gender" type="radio" name="gender" value="1">男
      <input v-model="gender" type="radio" name="gender" value="2">女
      <br><br>

    <!-- 
      前置理解:
        1. option 需要设置 value 值,提交给后台
        2. select 的 value 值,关联了选中的 option 的 value 值
      结合 Vue 使用 → v-model
    -->
    所在城市:
      <select v-model="cityId">
        <option value="101">北京</option>
        <option value="102">上海</option>
        <option value="103">成都</option>
        <option value="104">南京</option>
      </select>
      <br><br>

    自我描述:
      <textarea v-model="desc"></textarea> 

    <button>立即注册</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <!-- 实例 -->
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: '',
        isSingle: false,
        gender: "1",
        cityId: '101',
        desc: ''

      }
    })
  </script>

05_计算属性

什么是计算属性呢?
  1. 概念:基于现有的数据,计算出来的新属性。依赖的数据变化,自动重新计算。

语法
  1. 声明在 computed 配置项中,一个计算属性对应一个函数。
  2. 使用起来和普通属性一样使用{{ 计算属性名 }}。

代码示例
html 复制代码
<div id="app">
 <h3>小黑的礼物清单</h3>
 <table>
   <tr>
     <th>名字</th>
     <th>数量</th>
   </tr>
   <tr v-for="(item, index) in list" :key="item.id">
     <td>{{ item.name }}</td>
     <td>{{ item.num }}个</td>
   </tr>
 </table>

 <!-- 目标:统计求和,求得礼物总数 -->
 <p>礼物总数:{{ totalCount }} 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
 const app = new Vue({
   el: '#app',
   data: {
     // 现有的数据
     list: [
       { id: 1, name: '篮球', num: 1 },
       { id: 2, name: '玩具', num: 2 },
       { id: 3, name: '铅笔', num: 5 },
     ]
   },
   computed: {
     // 使用计算属性,不要加括号,就当普通属性一样使用
     totalCount () {
       // 基于现有的数据,计算
       // 可以通过this访问到app实例,利用reduce数组求和即可
       let total = this.list.reduce((sum,item) => sum + item.num,0)
       return total
     }
   }
 })
</script>
  1. 现在computed配置项里面提供声明,然后编写求和逻辑函数,使用的时候就和普通的属性一样使用差值表达式使用,切记这个不要加小括号。
computed计算属性和methods方法
  1. computed计算属性

    1. 作用: 封装了一段对于数据的处理,求得一个结果
    2. 语法:写在computed配置项中,作为属性:直接使用 this.计算属性 {{ 计算属性 }}
  2. methods方法:

    1. 作用: 给实例提供一个方法。调用以处理业务逻辑。
    2. 语法:写在methods配置项中。作为方法需要调用 , this.方法名() {{ 方法名 }} @事件名="方法名"

注意 :最大的差别就是在于使用,,属性使用直接使用this.属性就可以了,但是方法需要调用哦!

同样都能实现计算逻辑,为什么选择computed呢?

因为computed有缓存特性

计算属性会对计算出来的结果进行缓存,再次使用知己诶读取缓存。当数据依赖项变化了,会自动计算结果,并在在次缓存。便于下次使用。


06_计算属性的完整写法


  • 计算属性默认的简写,只能读取访问,不能"修改"。

  • 如果需要"修改",--> 需要写计算属性的完整写法

computed: {

计算属性名: {

get(){

​ 一段代码逻辑 (计算逻辑)

return 结果

​ },

set修改的值) {

​ 一段代码逻辑(修改逻辑)

​ }

​ }

​ }


代码示例
javascript 复制代码
   const app = new Vue({
   el: '#app',
   data: {
     Xing: '',
     Ming: ''
   },
   methods: {
     changeName () {
       // 这里是修改了fullName的值,set方法可以拿到修改后的值
       this.fullName = '吕小布'
     }
   },

		// 简写 -> 获取
     // fullName () {
     //   return this.Xing + this.Ming
     // }

     // 完整的写法   -> 获取 + -> 设置
     fullName: {
       // (1)获取   有缓存先执行缓存  将return返回值作为求值结果
       get() {
         return this.Xing + this.Ming
       },
       // (2)设置(修改),当fullName被修改时,就执行set,将修改的值返回给set形参
       set (value) {
         // console.log(value)
         // 字符串截取
         // value.slice(0,1)
         // value.slice(1)
         this.Xing = value.slice(0,1)
         this.Ming = value.slice(1)

       }
     }

完整写法,就是能够修改了,需要在computed配置项里面 写一个计算属性, 里面需要提供两个方法,一个get获取 一个set修改,当我们计算属性名被修改后,我们set方法的形参就会拿到修改后的值, 然后再进行计算逻辑处理。在使用差值表达式对页面进行渲染,记住不需要添加小括号。


07_计算属性实现成绩统计和品均分案例

功能需求:
  1. 渲染功能: 不及格高亮、使用v-if v-else结局盒子互斥,使用v-bind:class解决高亮。
  2. 删除功能:点击传递id ,通过filter过滤器过滤然后覆盖原数组,使用指令修饰符。prevent阻止默认行为。
  3. 添加功能:v-model双向绑定,使用指令修饰符 (trim, number)修饰功能。在使用unshift修改数组,v-for会根据数组自动更新视图。
  4. **统计总分,求平均分:**使用reduce求和(需要在computed配置项中使用计算属性函数),将计算逻辑返回的结果通过return暴露出来,然后使用差值表达式 通过 属性的方式进行渲染即可。切记计算属性和函数方法长的一样都是函数,但是,使用计算属性必须写属性。{{ 计算属性名 }},后面不要小括号。

代码示例:
  • 看完需求,直接在代码中找侧重点,哪怕你能找到所有功能需求,代码所处在、使用的位置。也行!哈哈。
html 复制代码
    <div id="app" class="score-case">
      <div class="table">
        <table>
          <thead>
            <tr>
              <th>编号</th>
              <th>科目</th>
              <th>成绩</th>
              <th>操作</th>
            </tr>
          </thead>
          <tbody v-if="list.length > 0">
            <tr v-for="(item,index) in list" :key="item.id">
              <td>{{ index + 1 }}</td>
              <td>{{ item.subject }}</td>
              <td :class="{red: item.score < 60}" >{{ item.score}}</td>
              <td><a @click.prevent="del(item.id)" href="#">删除</a></td>
            </tr>
          </tbody>
          <tbody v-else>
            <tr>
              <td colspan="5">
                <span class="none">暂无数据</span>
              </td>
            </tr>
          </tbody>

          <tfoot>
            <tr>
              <td colspan="5">
                <span>总分:{{ totalCount }}</span>
                <span style="margin-left: 50px">平均分:{{ averageCount }}</span>
              </td>
            </tr>
          </tfoot>
        </table>
      </div>
      <div class="form">
        <div class="form-item">
          <div class="label">科目:</div>
          <div class="input">
            <input v-model.trim="subject"
              type="text"
              placeholder="请输入科目"
            />
          </div>
        </div>
        <div class="form-item">
          <div class="label">分数:</div>
          <div class="input">
            <input v-model.number="score"
              type="text"
              placeholder="请输入分数"
            />
          </div>
        </div>
        <div class="form-item">
          <div class="label"></div>
          <div class="input">
            <button @click="add" class="submit" >添加</button>
          </div>
        </div>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
      const app = new Vue({
        el: '#app',
        data: {
          list: [
            { id: 1, subject: '语文', score: 20 },
            { id: 7, subject: '数学', score: 80 },
            { id: 12, subject: '英语', score: 70 },
          ],
          subject: '',
          score: ''
        },
        methods: {
          del (id) {
            // 判断id,进行过滤不需要删除的项,然后将新数组重新赋值给list
            this.list = this.list.filter(item => item.id !== id)
            // 这句代码的含义就是,当我对数组一次遍历,当前项的id不等于我传过来的全部过滤放到新数组,然后重新赋值,相等的不过滤,就不会再渲染他,===删除。
          },
          add () {
            // 在v-model双向绑定后,我们获取到数据后,使用unshift在数组前面增加即可,因为v-for会自动检测数据个数然后动态渲染
            this.list.unshift({
              // 切记,list对象里面的格式是怎样的,这里也需要保持一致
              id: +new Date(),
              subject: this.subject,
              score: this.score
            })
          }
        },
        computed: {
          // 这里不需要修改,所有我们直接使用简单的计算属性写法即可,注意差值使用属性,不要写成调用方法
          totalCount () {
            // 属性是可以直接通过this访问,甚至都不需要声明
            return  this.list.reduce((sum,item) => sum + item.score ,0)
            // 求和后有返回值,用变量total接受,然后使用return返回给函数,哪里使用我们该函数,就能访问到结果,切记计算属性里面的函数,可以理解为调用函数,但是不要加小括号,切记
          },
          averageCount () {
            // 解决品均分为 NaN的问题,数组没有分数操作会显示,这里判断,如果长度为0,直接返回0 且终止后面代码。
            if (this.list.length === 0) {
              return 0
            }
            // 将返回值,通过return暴露出去,谁使用我们的计算属性名,谁就能得到结果。
            return (this.totalCount / this.list.length).toFixed(2)

          }
        }
      })
    </script>

08_watch 监听器 (监视器)


作用
  1. 作用:监视数据变化,执行一些 业务逻辑 或 异步操作。

语法
  1. 语法

    1. 简单写法 --> 简单类型数据,直接监视

    2. 完整写法 --> 添加额外的配置项(监视多个)

      depp: true 对复杂数据类型的深度监视

      immediate:true 初始化立即执行一次 handler方法


简单写法和完整写法就和计算属性一样,根据需求来写,能写简单的就不必写完整的哈!

这里那需求为例,展开啦学习

需求
  • 输入内容,实时翻译

核心步骤

代码示例
html 复制代码
  <div id="app">
    <!-- 条件选择框 -->
    <div class="query">
      <span>翻译成的语言:</span>
      <select>
        <option value="italy">意大利</option>
        <option value="english">英语</option>
        <option value="german">德语</option>
      </select>
    </div>

    <!-- 翻译框 -->
    <div class="box">
      <div class="input-wrap">
        <textarea v-model="obj.words"></textarea>
        <span><i>⌨️</i>文档翻译</span>
      </div>
      <div class="output-wrap">
        <div class="transbox">{{ result }}</div>
      </div>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        // words: ''
        obj: {
          words: ''
        },
        // 翻译结果
        result: '',
        // 延迟器的id名字,this表示的就是我们的Vue实例对象,可以直接this.属性名,当对象使用即可。
        // timer: null
      },
      // 方法实现:(1) watch语法 (2) 具体业务实现
      watch: {
        // 改方法会再数据变化时 调用执行
        // newValue新值,oldValue老值(一般不用)
        'obj.words'(newValue) {   //配置对象中,访问对象里面的属性要用引号哦!不然报错
          // console.log('变化了', newValue)

          
          // 做防抖节流

          // 每次有新的值输入,都清除延迟器,什么时候不输入并且达到延迟时间在发请求
          clearTimeout(this.timer)
          this.timer = setTimeout(async() => {
            const res = await axios({
              url: 'https://applet-base-api-t.itheima.net/api/translate',
              params: {
                words: newValue
              }
            })
            this.result = res.data.data
            console.log(res.data.data)

          }, 300);
        }
      }
    })
  </script>

09_watch 监听器 -完整写法


完整写法可以理解为监视一个对象里面的所有子属性

语法
  1. 完整写法 --> 添加额外的配置项

    depp: true 对复杂数据类型的深度监视(监视多个),(对对象里面的所有子属性进行监视)

    immediate:true 初始化立即执行一次 handler方法,(一进入页面,handler就立即执行一次)


需求:输入内容,修改语言,以及一进页面就执行一次,都实时翻译。


代码示例
html 复制代码
<script>
const app = new Vue({
      el: '#app',
      data: {
        // words: ''
        obj: {
          words: 'Vue',
          lang: 'italy'
        },
        // 翻译结果
        result: '',
        // 延迟器的id名字,this表示的就是我们的Vue实例对象,可以直接this.属性名,当对象使用即可。
        // timer: null
      },
      // 方法实现:(1) watch语法 (2) 具体业务实现
      watch: {
        obj: {
          deep: true,  //深度监视(对对象里面的所有子属性进行监视)
          immediate: true,   // 立刻执行  (一进入页面,handler就立即执行一次)
          handler(newValue) {
          console.log('对象被修改了', newValue)
          // 改方法会再数据变化时 调用执行
          // newValue新值,oldValue老值(一般不用)
          //配置对象中,访问对象里面的属性要用引号哦!不然报错
            // console.log('变化了', newValue)


            // 做防抖节流

            // 每次有新的值输入,都清除延迟器,什么时候不输入并且达到延迟时间在发请求
            clearTimeout(this.timer)
            this.timer = setTimeout(async() => {
              const res = await axios({
                url: 'https://applet-base-api-t.itheima.net/api/translate',
                params: newValue   //因为newValue是一个对象,刚好包含携带的查询参数。
              })
              this.result = res.data.data
              console.log(res.data.data)

            }, 300);
          
        }
      }

</script>

完整写法可以理解为监视一个对象里面的所有子属性,需要监视的都放在改对象里面,然后使用deep:true深度监视,记得进页面就执行一次immediate: true;记得别忘了使用 v-model 将数据视图双向绑定。这样watch监听器才可以实时监测到变化,同时还需要加上防抖节流。


10_ 水果购物车业务


需求说明
  1. 渲染功能
  2. 删除功能
  3. 修改功能
  4. 全选反选功能
  5. 统计 选中的 总价 和 总数量
  6. 持久化到本地

业务技术点总结:
  1. 渲染功能: v-if/v-else v-for :class
  2. 删除功能: 点击传参 filter过滤覆盖原数组
  3. 修改个数: 点击传参 find找对象
  4. 全选反选:计算属性computed 完整写法 get/set
  5. 总价和总数量: 计算属性 computed reduce条件求和
  6. 持久化到本地: watch监视,localStorage,JSON.stringify存 ,JSON.parse读取
代码示例
javascript 复制代码
<scritp> 
const defaultArr = [
          {
            id: 1,
            icon: 'http://autumnfish.cn/static/火龙果.png',
            isChecked: true,
            num: 2,
            price: 6,
          },
          {
            id: 2,
            icon: 'http://autumnfish.cn/static/荔枝.png',
            isChecked: false,
            num: 7,
            price: 20,
          },
          {
            id: 3,
            icon: 'http://autumnfish.cn/static/榴莲.png',
            isChecked: false,
            num: 3,
            price: 40,
          },
          {
            id: 4,
            icon: 'http://autumnfish.cn/static/鸭梨.png',
            isChecked: true,
            num: 10,
            price: 3,
          },
          {
            id: 5,
            icon: 'http://autumnfish.cn/static/樱桃.png',
            isChecked: false,
            num: 20,
            price: 34,
          },
        ]
    const app = new Vue({
      el: '#app',
      data: {
        // 水果列表
        fruitList: JSON.parse(localStorage.getItem('list')) || defaultArr,
      },
      computed: {
        // 计算属性默认只能获取,需要设置需要写完整写法

        isAll: {
          // 所有的小选框的选中状态,全选才选中
          get() {
            return this.fruitList.every(item => item.isChecked)
          },
          set(value) {
            // 基于拿到的value布尔值,要让所有的小选框, 同步状态
            this.fruitList.forEach(item => item.isChecked = value)
          },
        },
        // 统计选中的总数   reduce
        totalCount() {
          return this.fruitList.reduce((sum, item) => {
            if (item.isChecked) {
              // 选中  需要累加
              return sum + item.num
            } else {
              // 没选中 不需要累加 返回sum即可
              return sum
            }
          }, 0)
        },
        // 统计选中的总价
        totalPrice() {
          return this.fruitList.reduce((sum, item) => {
            // 选中   需要价格
            if (item.isChecked) {
              return sum + item.num * item.price
            } else {
              return sum
            }
          },0)
        }

      },
      methods: {
        del(id) {
          this.fruitList = this.fruitList.filter(item => item.id !== id)

        },
        reduce(id) {
          // 1. 根据id 找到数组中的对应项
          const fruit = this.fruitList.find(item => item.id === id)
          // 2. 操作 num 数量
          fruit.num--
        },
        add(id) {
          // 1. 根据id 找到数组中的对应项
          const fruit = this.fruitList.find(item => item.id === id)
          // 2. 操作 num 数量
          fruit.num++

        }
      },
      // 监视器,监视所有数据是否发生变化
      watch: {
        fruitList: {
          deep: true,
          handler (newValue) {
            // 需要将变化后的   newValue  存入本地  (存进去前需要转 JSON)
            localStorage.setItem('list',JSON.stringify(newValue))
          }
        }
      }
    })
  </script>

当前购物车业务,除了没有使用axios与服务器存储交互以及支付功能。其他的都完成了

相关推荐
Fan_web11 分钟前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常13 分钟前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇1 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr1 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho2 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常3 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记4 小时前
【复习】HTML常用标签<table>
前端·html
程序员大金4 小时前
基于SpringBoot+Vue+MySQL的装修公司管理系统
vue.js·spring boot·mysql
丁总学Java4 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js