当前位置: 首页 > news >正文

【Vue】- ref获取DOM元素和购物车案例分析

文章目录

  • 知识回顾
  • 前言
  • 源码分析
    • 1. ref
    • 2. 购物车案例分析
    • 3. 购物车计算、全选
  • 拓展知识
    • 数据持久化localStorage
  • 总结


知识回顾

前言

元素上使用 ref属性关联响应式数据,获取DOM元素

步骤

● 创建 ref => const hRef = ref(null)
● 模板中建立关联 => <h1 ref="hRef">我是标题</h1>=> hRef.value

源码分析

1. ref

const inp = ref<null | HTMLInputElement>()
const img = ref<null | HTMLImageElement>()

vue

<script setup lang="ts">
import { onMounted, ref } from 'vue'const words = ref('')
const inp = ref<null | HTMLInputElement>()
const img = ref<null | HTMLImageElement>()
onMounted(() => {console.log('onMounted')inp.value?.focus()img.valueconsole.log(img.value)console.log(inp.value)
})
</script><template><div class="container" id="app"><div class="search-container"><img ref="img" src="https://" alt="" /><div class="search-box"><input ref="inp" type="text" v-model="words" id="inp" /><button>搜索一下</button></div></div></div>
</template><style>
html,
body {height: 100%;
}.search-container {position: absolute;top: 30%;left: 50%;transform: translate(-50%, -50%);text-align: center;
}.search-container .search-box {display: flex;
}.search-container img {margin-bottom: 30px;
}.search-container .search-box input {width: 512px;height: 16px;padding: 12px 16px;font-size: 16px;margin: 0;vertical-align: top;outline: 0;box-shadow: none;border-radius: 10px 0 0 10px;border: 2px solid #c4c7ce;background: #fff;color: #222;overflow: hidden;box-sizing: content-box;-webkit-tap-highlight-color: transparent;
}.search-container .search-box button {cursor: pointer;width: 112px;height: 44px;line-height: 41px;line-height: 42px;background-color: #ad2a27;border-radius: 0 10px 10px 0;font-size: 17px;box-shadow: none;font-weight: 400;border: 0;outline: 0;letter-spacing: normal;color: white;
}body {background: no-repeat center / cover;background-color: #edf0f5;
}
</style>

2. 购物车案例分析

列表渲染、购物车增加删除
css

.app-container {padding-bottom: 300px;width: 800px;margin: 0 auto;
}
@media screen and (max-width: 800px) {.app-container {width: 600px;}
}
.app-container .banner-box {border-radius: 20px;overflow: hidden;margin-bottom: 10px;
}
.app-container .banner-box img {width: 100%;
}
.app-container .nav-box {background: #ddedec;height: 60px;border-radius: 10px;padding-left: 20px;display: flex;align-items: center;
}
.app-container .nav-box .my-nav {display: inline-block;background: #5fca71;border-radius: 5px;width: 90px;height: 35px;color: white;text-align: center;line-height: 35px;margin-right: 10px;
}.breadcrumb {font-size: 16px;color: gray;
}
.table {width: 100%;text-align: left;border-radius: 2px 2px 0 0;border-collapse: separate;border-spacing: 0;
}
.th {color: rgba(0, 0, 0, 0.85);font-weight: 500;text-align: left;background: #fafafa;border-bottom: 1px solid #f0f0f0;transition: background 0.3s ease;
}
.th.num-th {flex: 1.5;
}
.th {text-align: center;
}
.th:nth-child(4),
.th:nth-child(5),
.th:nth-child(6),
.th:nth-child(7) {text-align: center;
}
.th.th-pic {flex: 1.3;
}
.th:nth-child(6) {flex: 1.3;
}.th,
.td {position: relative;padding: 16px 16px;overflow-wrap: break-word;flex: 1;
}
.pick-td {font-size: 14px;
}
.main,
.empty {border: 1px solid #f0f0f0;margin-top: 10px;
}
.tr {display: flex;cursor: pointer;border-bottom: 1px solid #ebeef5;
}
.tr.active {background-color: #f5f7fa;
}
.td {display: flex;justify-content: center;align-items: center;
}.table img {width: 100px;height: 100px;
}button {outline: 0;box-shadow: none;color: #fff;background: #d9363e;border-color: #d9363e;color: #fff;background: #d9363e;border-color: #d9363e;line-height: 1.5715;position: relative;display: inline-block;font-weight: 400;white-space: nowrap;text-align: center;background-image: none;border: 1px solid transparent;box-shadow: 0 2px 0 rgb(0 0 0 / 2%);cursor: pointer;transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;touch-action: manipulation;height: 32px;padding: 4px 15px;font-size: 14px;border-radius: 2px;
}
button.pay {background-color: #3f85ed;margin-left: 20px;
}.bottom {height: 60px;display: flex;align-items: center;justify-content: space-between;padding-right: 20px;border: 1px solid #f0f0f0;border-top: none;padding-left: 20px;
}
.right-box {display: flex;align-items: center;
}
.check-all {cursor: pointer;
}
.price {color: hotpink;font-size: 30px;font-weight: 700;
}
.price-box {display: flex;align-items: center;
}
.empty {padding: 20px;text-align: center;font-size: 30px;color: #909399;
}
.my-input-number {display: flex;
}
.my-input-number button {height: 40px;color: #333;border: 1px solid #dcdfe6;background-color: #f5f7fa;
}
.my-input-number button:disabled {cursor: not-allowed!important;
}
.my-input-number .my-input__inner {height: 40px;width: 50px;padding: 0;border: none;border-top: 1px solid #dcdfe6;border-bottom: 1px solid #dcdfe6;
}

vue

<script setup lang="ts">
import { ref } from 'vue'// 定义水果对象的类型
interface Fruit {id: numbericon: stringisChecked: booleannum: numberprice: number
}// 水果列表
const fruitList = ref<Fruit[]>([{id: 1,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/ll.png',isChecked: true,num: 2,price: 6},{id: 2,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/smt.png',isChecked: false,num: 7,price: 20},{id: 3,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/tg.png',isChecked: false,num: 3,price: 40},{id: 4,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/xg.png',isChecked: false,num: 10,price: 3},{id: 5,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/yl.png',isChecked: false,num: 20,price: 34}
])// 删除商品
const delGoods = (id: number) => {fruitList.value = fruitList.value.filter((item) => item.id !== id)
}
// 添加
const addGoods = (id: number) => {const fruit = fruitList.value.find((item) => item.id === id)fruit && fruit.num++
}
// 减少
const subGoods = (id: number) => {const fruit = fruitList.value.find((item) => item.id === id)fruit && fruit.num--
}
</script><template><div class="app-container" id="app"><!-- 顶部banner --><div class="banner-box"><img src="https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/sg_top.png" alt="" /></div><!-- 面包屑 --><div class="breadcrumb"><span>🏠</span>/<span>购物车</span></div><!-- 购物车主体 --><div v-if="fruitList.length > 0" class="main"><div class="table"><!-- 头部 --><div class="thead"><div class="tr"><div class="th">选中</div><div class="th th-pic">图片</div><div class="th">单价</div><div class="th num-th">个数</div><div class="th">小计</div><div class="th">操作</div></div></div><!-- 身体 --><div class="tbody"><divv-for="item in fruitList":key="item.id"class="tr":class="{ active: item.isChecked }"><div class="td"><input type="checkbox" v-model="item.isChecked" /></div><div class="td"><img :src="item.icon" alt="" /></div><div class="td">{{ item.price }}</div><div class="td"><div class="my-input-number"><button :disabled="item.num <= 1" class="decrease" @click="subGoods(item.id)">-</button><span class="my-input__inner">{{ item.num }}</span><button class="increase" @click="addGoods(item.id)">+</button></div></div><div class="td">{{ item.price * item.num }}</div><div class="td" @click="delGoods(item.id)"><button>删除</button></div></div></div></div><!-- 底部 --><div class="bottom"><!-- 全选 --><label class="check-all"><input type="checkbox" />全选</label><div class="right-box"><!-- 所有商品总价 --><span class="price-box">总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price">24</span></span><!-- 结算按钮 --><button class="pay">结算( 6 )</button></div></div></div><!-- 空车 --><div v-else class="empty">🛒空空如也</div></div>
</template><style scoped>
@import './style/inputnumber.css';
@import './style/index.css';
</style>

3. 购物车计算、全选

<script setup lang="ts">
import { computed, ref } from 'vue'// 定义水果对象的类型
interface Fruit {id: numbericon: stringisChecked: booleannum: numberprice: number
}// 水果列表
const fruitList = ref<Fruit[]>([{id: 1,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/ll.png',isChecked: true,num: 2,price: 6},{id: 2,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/smt.png',isChecked: false,num: 7,price: 20},{id: 3,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/tg.png',isChecked: false,num: 3,price: 40},{id: 4,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/xg.png',isChecked: false,num: 10,price: 3},{id: 5,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/yl.png',isChecked: false,num: 20,price: 34}
])// 删除商品
const delGoods = (id: number) => {fruitList.value = fruitList.value.filter((item) => item.id !== id)
}
// 添加
const addGoods = (id: number) => {const fruit = fruitList.value.find((item) => item.id === id)fruit && fruit.num++
}
// 减少
const subGoods = (id: number) => {const fruit = fruitList.value.find((item) => item.id === id)fruit && fruit.num--
}// 全选
const checkAll = computed({get() {return fruitList.value.every((item) => item.isChecked)},set(value) {fruitList.value.forEach((item) => (item.isChecked = value))}
})// 统计选中的总数 reduce
const totalCount = computed(() => {return fruitList.value.reduce((sum, item) => {if (item.isChecked) {// 选中 → 需要累加return sum + item.num} else {// 没选中 → 不需要累加return sum}}, 0)
})// 总计选中的总价 num * price
const totalPrice = computed(() => {return fruitList.value.reduce((sum, item) => {if (item.isChecked) {return sum + item.num * item.price} else {return sum}}, 0)
})
</script><template><div class="app-container" id="app"><!-- 顶部banner --><div class="banner-box"><img src="https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/sg_top.png" alt="" /></div><!-- 面包屑 --><div class="breadcrumb"><span>🏠</span>/<span>购物车</span></div><!-- 购物车主体 --><div v-if="fruitList.length > 0" class="main"><div class="table"><!-- 头部 --><div class="thead"><div class="tr"><div class="th">选中</div><div class="th th-pic">图片</div><div class="th">单价</div><div class="th num-th">个数</div><div class="th">小计</div><div class="th">操作</div></div></div><!-- 身体 --><div class="tbody"><divv-for="item in fruitList":key="item.id"class="tr":class="{ active: item.isChecked }"><div class="td"><input type="checkbox" v-model="item.isChecked" /></div><div class="td"><img :src="item.icon" alt="" /></div><div class="td">{{ item.price }}</div><div class="td"><div class="my-input-number"><button :disabled="item.num <= 1" class="decrease" @click="subGoods(item.id)">-</button><span class="my-input__inner">{{ item.num }}</span><button class="increase" @click="addGoods(item.id)">+</button></div></div><div class="td">{{ item.price * item.num }}</div><div class="td" @click="delGoods(item.id)"><button>删除</button></div></div></div></div><!-- 底部 --><div class="bottom"><!-- 全选 --><label class="check-all"><input type="checkbox" v-model="checkAll" />全选</label><div class="right-box"><!-- 所有商品总价 --><span class="price-box">总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price">{{ totalPrice }}</span></span><!-- 结算按钮 --><button class="pay">结算( {{ totalCount }} )</button></div></div></div><!-- 空车 --><div v-else class="empty">🛒空空如也</div></div>
</template><style scoped>
@import './style/inputnumber.css';
@import './style/index.css';
</style>

拓展知识

数据持久化localStorage

<script setup lang="ts">
import { computed, ref, watch } from 'vue'// 定义水果对象的类型
interface Fruit {id: numbericon: stringisChecked: booleannum: numberprice: number
}
const defaultData = [{id: 1,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/ll.png',isChecked: true,num: 2,price: 6},{id: 2,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/smt.png',isChecked: false,num: 7,price: 20},{id: 3,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/tg.png',isChecked: false,num: 3,price: 40},{id: 4,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/xg.png',isChecked: false,num: 10,price: 3},{id: 5,icon: 'https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/yl.png',isChecked: false,num: 20,price: 34}
]// 水果列表
let fruitList = ref<Fruit[]>(defaultData)
const localStorageFruitList = localStorage.getItem('fruitList')
if (localStorageFruitList && localStorageFruitList !== '[]') {fruitList.value = JSON.parse(localStorageFruitList)
}// 删除商品
const delGoods = (id: number) => {fruitList.value = fruitList.value.filter((item) => item.id !== id)
}
// 添加
const addGoods = (id: number) => {const fruit = fruitList.value.find((item) => item.id === id)fruit && fruit.num++
}
// 减少
const subGoods = (id: number) => {const fruit = fruitList.value.find((item) => item.id === id)fruit && fruit.num--
}// 全选
const checkAll = computed({get() {return fruitList.value.every((item) => item.isChecked)},set(value) {fruitList.value.forEach((item) => (item.isChecked = value))}
})// 统计选中的总数 reduce
const totalCount = computed(() => {return fruitList.value.reduce((sum, item) => {if (item.isChecked) {// 选中 → 需要累加return sum + item.num} else {// 没选中 → 不需要累加return sum}}, 0)
})// 总计选中的总价 num * price
const totalPrice = computed(() => {return fruitList.value.reduce((sum, item) => {if (item.isChecked) {return sum + item.num * item.price} else {return sum}}, 0)
})// 数据持久化方法
const saveData = () => {localStorage.setItem('fruitList', JSON.stringify(fruitList.value))
}
const getData = () => {const fruitListStr = localStorage.getItem('fruitList')
}
// 删除持久化
const delData = () => {localStorage.removeItem('fruitList')
}
// 清空持久化数据
const clearData = () => {localStorage.clear()
}
// 监听
watch(() => fruitList.value, saveData, { deep: true, immediate: true })
</script><template><div class="app-container" id="app"><!-- 顶部banner --><div class="banner-box"><img src="https://hongm-1312445828.cos.ap-nanjing.myqcloud.com/sg_top.png" alt="" /></div><!-- 面包屑 --><div class="breadcrumb"><span>🏠</span>/<span>购物车</span></div><!-- 购物车主体 --><div v-if="fruitList.length > 0" class="main"><div class="table"><!-- 头部 --><div class="thead"><div class="tr"><div class="th">选中</div><div class="th th-pic">图片</div><div class="th">单价</div><div class="th num-th">个数</div><div class="th">小计</div><div class="th">操作</div></div></div><!-- 身体 --><div class="tbody"><divv-for="item in fruitList":key="item.id"class="tr":class="{ active: item.isChecked }"><div class="td"><input type="checkbox" v-model="item.isChecked" /></div><div class="td"><img :src="item.icon" alt="" /></div><div class="td">{{ item.price }}</div><div class="td"><div class="my-input-number"><button :disabled="item.num <= 1" class="decrease" @click="subGoods(item.id)">-</button><span class="my-input__inner">{{ item.num }}</span><button class="increase" @click="addGoods(item.id)">+</button></div></div><div class="td">{{ item.price * item.num }}</div><div class="td" @click="delGoods(item.id)"><button>删除</button></div></div></div></div><!-- 底部 --><div class="bottom"><!-- 全选 --><label class="check-all"><input type="checkbox" v-model="checkAll" />全选</label><div class="right-box"><!-- 所有商品总价 --><span class="price-box">总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price">{{ totalPrice }}</span></span><!-- 结算按钮 --><button class="pay">结算( {{ totalCount }} )</button></div></div></div><!-- 空车 --><div v-else class="empty">🛒空空如也</div></div>
</template><style scoped>
@import './style/inputnumber.css';
@import './style/index.css';
</style>

总结

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 情感识别系统源码分享
  • 源码编译llama.cpp 、ggml 后端启用自定义BLAS加速
  • python实现冒泡排序的算法
  • 分布式技术概览
  • 数据库常规操作
  • 小众语言ruby在苹果中的初步应用
  • 【jvm】记一次hive堆heap内存溢出的排查
  • Spring自定义注解
  • C++中使用分治法求最大值
  • 为什么要用docker?
  • 函数题 6-2 多项式求值【PAT】
  • MySQL——数据库的高级操作(二)用户管理(1)uer表
  • C++ | Leetcode C++题解之第405题数字转换为十六进制数
  • 搭建Eureka高可用集群 - day03
  • ollama语言大模型部署使用
  • 5分钟即可掌握的前端高效利器:JavaScript 策略模式
  • angular学习第一篇-----环境搭建
  • CSS魔法堂:Absolute Positioning就这个样
  • HTTP传输编码增加了传输量,只为解决这一个问题 | 实用 HTTP
  • JavaScript-Array类型
  • JS题目及答案整理
  • maven工程打包jar以及java jar命令的classpath使用
  • MobX
  • ng6--错误信息小结(持续更新)
  • Node + FFmpeg 实现Canvas动画导出视频
  • React中的“虫洞”——Context
  • 将回调地狱按在地上摩擦的Promise
  • 数组大概知多少
  • 文本多行溢出显示...之最后一行不到行尾的解决
  • 延迟脚本的方式
  • 异常机制详解
  • 原生JS动态加载JS、CSS文件及代码脚本
  • 最近的计划
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • 关于Android全面屏虚拟导航栏的适配总结
  • 新年再起“裁员潮”,“钢铁侠”马斯克要一举裁掉SpaceX 600余名员工 ...
  • ​3ds Max插件CG MAGIC图形板块为您提升线条效率!
  • ​LeetCode解法汇总2182. 构造限制重复的字符串
  • ​secrets --- 生成管理密码的安全随机数​
  • # Swust 12th acm 邀请赛# [ E ] 01 String [题解]
  • (7)摄像机和云台
  • (ros//EnvironmentVariables)ros环境变量
  • (二)学习JVM —— 垃圾回收机制
  • (翻译)Entity Framework技巧系列之七 - Tip 26 – 28
  • (附源码)springboot太原学院贫困生申请管理系统 毕业设计 101517
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (几何:六边形面积)编写程序,提示用户输入六边形的边长,然后显示它的面积。
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (五)Python 垃圾回收机制
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转载)Google Chrome调试JS
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .net framework 4.0中如何 输出 form 的name属性。
  • .net mvc actionresult 返回字符串_.NET架构师知识普及