首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【Vue.js ——功能实现】趣购(蓝桥杯真题-2426)【合集】

【Vue.js ——功能实现】趣购(蓝桥杯真题-2426)【合集】

作者头像
Rossy Yan
发布2025-02-16 19:20:01
发布2025-02-16 19:20:01
4450
举报

背景介绍

在线购物几乎已经是现代生活必备的一环,每年的 618,双 11 都是购物的狂欢。我们几乎可以在线上购物商城买到一切日常所需。 本题需要在已提供的基础项目中,使用 Web 原生拖拽事件实现在线购物的功能。

准备步骤

开始答题前,需要先打开本题的项目代码文件夹,目录结构如下:

代码语言:javascript
复制
├── images
│   ├── book.jpeg
│   ├── box.jpeg
│   ├── paper.jpeg
│   ├── trolley.jpeg
│   └── tv.jpg
├── index.html
├── effect.gif
├── js
│   ├── http-vue-loader.js
│   └── vue.min.js
└── trolley.vue

其中:

  • index.html 是主页面。
  • effect.gif 是最终实现的效果图。
  • images 文件夹提供的页面所需要的商品图片。
  • js/vue.min.jsjs/http-vue-loader.js 是 vue 库相关文件。
  • trolley.vue 是需要补充代码的组件文件。

注意:打开环境后发现缺少项目代码,请复制下述命令至命令行进行下载。

代码语言:javascript
复制
cd /home/project
wget https://labfile.oss.aliyuncs.com/courses/18164/08.zip && unzip 08.zip && rm 08.zip

在浏览器中预览 index.html 页面,显示如下所示:


目标效果

请在 trolley.vue 文件中的 TODO 部分补全代码:

  • 用鼠标按下拖动图片到购物车(即 id="trolley" 的节点)中,然后松开鼠标,购物车会添加拖动的商品,并显示购物车商品数量。
  • 下方(即 class="result" 的方框)会显示购物车中商品的详情,详情以商品名 x 数量的形式展示,商品之间通过空格间隔。
  • 下方(即 class="result" 的方框)同时还会显示购物车中商品的总价。

完成后的效果见文件夹下面的 gif 图,图片名称为 effect.gif(提示:可以通过 VS Code 或者浏览器预览 gif 图片):

题目会用到的拖拽 API 参考:

全局属性 draggable 是一个枚举类型的属性,用于标识元素是否允许使用拖放操作拖动。它的取值如下:

  • true:表示元素可以被拖动。
  • false:表示元素不可以被拖动。

带有属性 draggable 的可拖放元素可用的拖放事件 api 如下:

拖动事件:

事件

事件处理程序

触发时刻

drag

ondrag

当拖拽元素或选中的文本时触发。

dragstart

ondragstart

当用户开始拖拽一个元素或选中的文本时触发

dragend

ondragend

当拖拽操作结束时触发 (比如松开鼠标按键或敲“Esc”键)

放置事件:

事件

事件处理程序

触发时刻

dragenter

ondragenter

当拖拽元素或选中的文本到一个可释放目标时触发

dragleave

ondragleave

当拖拽元素或选中的文本离开一个可释放目标时触发。

dragover

ondragover

当元素或选中的文本被拖到一个可释放目标上时触发(每 100 毫秒触发一次)

drop

ondrop

当元素或选中的文本在可释放目标上被释放时触发,想要 ondrop 能正确触发,有时需要在前置 dragover 事件中禁用默认行为

每个 drag event 都有一个 dataTransfer 属性,其中保存着事件的数据。这个属性(DataTransfer 对象)也有管理拖拽数据的方法。setData() 方法为拖拽数据添加一个项,其用法如下所示:

代码语言:javascript
复制
function dragstart_handler(ev) {
  // 添加拖拽数据,key 可以为任意字符串
  ev.dataTransfer.setData("key", "value");
  const data = ev.dataTransfer.getData("key");
}

要求规定

  • 请勿修改 trolley.vue 文件外的任何内容。
  • 请严格按照考试步骤操作,切勿修改考试默认提供项目中的文件名称、文件夹路径、class 名、id 名、图片名等,以免造成无法判题通过。
  • 满足需求后,保持 Web 服务处于可以正常访问状态,点击「提交检测」系统会自动检测。

判分标准

  • 本题完全实现题目目标得满分,否则得 0 分。

通关代码✔️

代码语言:javascript
复制
<template>
  <div class="container">
    <div class="good-list">
      <div v-for="good in goods" :key="good.name" class="good" draggable="true" 
      @dragstart="dragstart($event,good)"> 
      <!-- 要绑定一个event事件 -->
        <img :src="good.cover" />
        <span>{{ good.name }}</span>
        <span>¥{{ good.price }}</span>
      </div>
    </div>
    <div id="trolley" class="trolley" @dragover.prevent 
    @drop="handleDrop">
    <!-- 绑定拖拽事件,要取消dragover的默认事件 -->
      <span id="bought" class="bought" v-if="bought.length !== 0">{{
        bought.length
      }}</span>
      <img src="./images/trolley.jpeg" />
    </div>
    <div class="result">
      <div>
        购物车商品:<span id="goods">{{ goodsDetail }}</span>
      </div>
      <div>
        购物车商品总计:<span id="total">{{ totalPrice }}</span>
      </div>
    </div>
  </div>
</template>
<style>
.container {
  width: 650px;
  position: relative;
  height: 600px;
  margin: 10px auto;
  display: flex;
  flex-direction: column;
}
.good-list {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  flex-grow: 1;
}
.good {
  width: 150px;
  height: 160px;
  border: 1px solid rgb(52, 52, 52);
  border-radius: 5px;
  padding: 5px;
}
.good:hover {
  border: 2px solid rgb(52, 52, 52);
}

.good img {
  width: 100%;
  height: 120px;
}
.trolley {
  position: absolute;
  height: 60px;
  width: 60px;
  border-radius: 50%;
  overflow: hidden;
  border: 1px solid #f4f4f4;
  background-color: #f4f4f4;
  display: flex;
  align-items: center;
  justify-content: center;
  right: 0;
  bottom: 100px;
}

.trolley img {
  width: 40px;
  height: 40px;
  pointer-events: none;
}

.bought {
  width: 16px;
  height: 16px;
  background-color: crimson;
  color: white;
  position: absolute;
  right: 8px;
  top: 10px;
  border-radius: 50%;
  text-align: center;
  line-height: 15px;
  pointer-events: none;
}
.result {
  width: 100%;
  min-height: 80px;
  border: 1px solid;
  margin-top: 20px;
  padding: 10px;
}
</style>
<script>
module.exports = {
  data() {
    return {
      goods: [
        {
          name: "畅销书",
          price: 30,
          cover: "./images/book.jpeg",
        },
        {
          name: "收纳箱",
          price: 60,
          cover: "./images/box.jpeg",
        },
        {
          name: "纸巾",
          price: 20,
          cover: "./images/paper.jpeg",
        },
        {
          name: "电视",
          price: 1000,
          cover: "./images/tv.jpg",
        },
      ],
      bought: [],
    };
  },
 computed: {
    totalPrice() {
      return this.bought.reduce((sum,e)=>sum+e.price,0);
    },
    goodsDetail() {
    // 复制一份商品信息出来
    const goodsCopy = JSON.parse(JSON.stringify(this.goods));

    // 遍历拖拽的商品
    for (let i = 0; i < this.bought.length; i++) {
        // 找到相同名称的商品
        const target = goodsCopy.filter( //依次遍历每一本书
          e => e.name === this.bought[i].name
        )[0];
          // console.log(target)
          // console.log(JSON.stringify(this.bought))
        // 存到一个新的属性 count 上
        if (target.count) { //给书对象添加数量的变量
          
            target.count += 1;
        } else {
            target.count = 1;
        }
    }

    // 去掉数量为 0 的商品,拼接字符串
    return goodsCopy
        .filter((e) => e.count)
        .map((e) => `${e.name}*${e.count}`)
        .join(" ");
}
  },
 methods: {dragstart(e,good){
      e.dataTransfer.setData('good',JSON.stringify(good))
    },
    handleDrop(e){
      const good = JSON.parse(e.dataTransfer.getData("good"))
      this.bought.push(good)
      console.log(this.bought)
    }},
};
</script>

代码解析📑

一、HTML 部分

代码语言:javascript
复制
<!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>趣购</title>
    <style>
      * {
        box-sizing: border-box;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <trolley></trolley>
    </div>
    <script src="./js/vue.min.js"></script>
    <script src="./js/http-vue-loader.js"></script>
    <script>
      Vue.use(httpVueLoader);
      const app = new Vue({
        el: "#app",
        components: {
          trolley: "url:./trolley.vue",
        },
      });
    </script>
  </body>
</html>
  • HTML 结构部分
    • <!DOCTYPE html>:声明文档类型为 HTML5。
    • <html lang="zh-CN">:指定文档语言为中文。
    • <head> 标签内:
      • <meta> 标签设置字符编码、浏览器兼容性和视口信息。
      • <title> 标签设置页面标题为 “趣购”。
      • <style> 标签内的 * { box-sizing: border-box; } 样式将所有元素的盒模型计算方式设置为 border-box,这样元素的宽度和高度包含内容、内边距和边框,但不包含外边距。
    • <body> 标签内:
      • <div id="app"> 是 Vue 实例的挂载点。
      • <trolley></trolley> 是自定义组件,后续会被 trolley.vue 文件中的内容替换。
  • JavaScript 部分
    • <script src="./js/vue.min.js"></script>:引入 Vue.js 库的压缩版本,用于创建和管理 Vue 实例。
    • <script src="./js/http-vue-loader.js"></script>:引入 http-vue-loader 插件,该插件允许直接通过 URL 加载 Vue 组件。
    • Vue.use(httpVueLoader);:使用 http-vue-loader 插件,使 Vue 能够处理 url: 格式的组件路径。
    • const app = new Vue({ ... }):创建一个新的 Vue 实例,将其挂载到 #app 元素上,并注册 trolley 组件,该组件的内容从 ./trolley.vue 文件加载。

二、Vue组件 部分

代码语言:javascript
复制
/* trolley.vue */
<template>
  <div class="container">
    <div class="good-list">
      <div v-for="good in goods" :key="good.name" class="good" draggable="true" 
      @dragstart="dragstart($event,good)"> 
        <img :src="good.cover" />
        <span>{{ good.name }}</span>
        <span>¥{{ good.price }}</span>
      </div>
    </div>
    <div id="trolley" class="trolley" @dragover.prevent 
    @drop="handleDrop">
      <span id="bought" class="bought" v-if="bought.length !== 0">{{
        bought.length
      }}</span>
      <img src="./images/trolley.jpeg" />
    </div>
    <div class="result">
      <div>
        购物车商品:<span id="goods">{{ goodsDetail }}</span>
      </div>
      <div>
        购物车商品总计:<span id="total">{{ totalPrice }}</span>
      </div>
    </div>
  </div>
</template>
<style>
/* 样式代码省略,前面已分析 */
</style>
<script>
module.exports = {
  data() {
    return {
      goods: [
        {
          name: "畅销书",
          price: 30,
          cover: "./images/book.jpeg",
        },
        {
          name: "收纳箱",
          price: 60,
          cover: "./images/box.jpeg",
        },
        {
          name: "纸巾",
          price: 20,
          cover: "./images/paper.jpeg",
        },
        {
          name: "电视",
          price: 1000,
          cover: "./images/tv.jpg",
        },
      ],
      bought: [],
    };
  },
  computed: {
    totalPrice() {
      return this.bought.reduce((sum, e) => sum + e.price, 0);
    },
    goodsDetail() {
      const goodsCopy = JSON.parse(JSON.stringify(this.goods));
      for (let i = 0; i < this.bought.length; i++) {
        const target = goodsCopy.filter(
          e => e.name === this.bought[i].name
        )[0];
        if (target.count) {
          target.count += 1;
        } else {
          target.count = 1;
        }
      }
      return goodsCopy
        .filter((e) => e.count)
        .map((e) => `${e.name} x ${e.count}`) 
        .join(" ");
    }
  },
  methods: {
    dragstart(event, good) {
      event.dataTransfer.setData('text/plain', JSON.stringify(good));
    },
    handleDrop(event) {
      event.preventDefault();
      const data = event.dataTransfer.getData('text/plain');
      const good = JSON.parse(data);
      this.bought.push(good);
    }
  }
};
</script>
  • 模板部分(<template>
    • <div class="container">:作为整个组件的容器。
    • <div class="good-list">:用于展示商品列表,使用 v-for 指令遍历 goods 数组,为每个商品生成一个 <div class="good"> 元素。
      • <div class="good">:每个商品的容器,设置 draggable="true" 使其可拖动,并绑定 @dragstart 事件到 dragstart 方法。
      • <img :src="good.cover" />:显示商品的封面图片。
      • <span>{{ good.name }}</span><span>¥{{ good.price }}</span>:分别显示商品的名称和价格。
    • <div id="trolley" class="trolley">:购物车容器,绑定 @dragover.prevent 阻止默认的 dragover 行为,绑定 @drop 事件到 handleDrop 方法。
      • <span id="bought" class="bought">:当购物车中有商品时,显示购物车中的商品数量。
      • <img src="./images/trolley.jpeg" />:显示购物车图片。
    • <div class="result">:显示购物车的结果信息,包括商品详情和总价。
      • <span id="goods">{{ goodsDetail }}</span>:显示购物车中商品的详情,通过 goodsDetail 计算属性获取。
      • <span id="total">{{ totalPrice }}</span>:显示购物车中商品的总价,通过 totalPrice 计算属性获取。
  • 样式部分(<style>:定义了页面的布局和样式,包括商品列表、购物车和结果信息的样式。
  • 脚本部分(<script>
    • data() 方法:返回组件的数据对象,包含 goods 数组(存储商品信息)和 bought 数组(存储购物车中的商品信息)。
    • computed 属性:
      • totalPrice():计算购物车中所有商品的总价,使用 reduce 方法对 bought 数组中的商品价格进行累加。
      • goodsDetail():生成购物车中商品的详情字符串,先复制一份 goods 数组,然后遍历 bought 数组,统计每种商品的数量,最后过滤掉数量为 0 的商品,将商品名称和数量拼接成字符串。
    • methods 方法:
      • dragstart(event, good):在拖动商品开始时触发,将商品信息以 JSON 字符串的形式存储到 event.dataTransfer 中。
      • handleDrop(event):在商品被拖放到购物车并释放鼠标时触发,阻止默认的 drop 行为,从 event.dataTransfer 中获取商品信息并解析为对象,然后将该商品对象添加到 bought 数组中。

三、工作流程▶️

  1. 页面加载:浏览器加载 HTML 页面,引入 Vue.js 和 http-vue-loader 插件,创建 Vue 实例并挂载到 #app 元素上,同时加载 trolley.vue 组件。
  2. 商品展示trolley.vue 组件渲染商品列表,每个商品元素可拖动。
  3. 拖动商品:用户鼠标按下商品元素并开始拖动,触发 dragstart 方法,将商品信息存储到 dataTransfer 中。
  4. 拖入购物车:用户将商品拖动到购物车上方,触发 dragover 事件,由于阻止了默认行为,继续拖动。
  5. 释放商品:用户在购物车上方释放鼠标,触发 handleDrop 方法,从 dataTransfer 中获取商品信息并添加到 bought 数组中。
  6. 更新购物车信息bought 数组更新后,totalPricegoodsDetail 计算属性重新计算,页面上的购物车商品数量、详情和总价信息随之更新。

测试结果👍

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景介绍
  • 准备步骤
  • 目标效果
  • 要求规定
  • 判分标准
  • 通关代码✔️
  • 代码解析📑
    • 一、HTML 部分
    • 二、Vue组件 部分
  • 测试结果👍
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档