Blogger Information
Blog 54
fans 6
comment 31
visits 106571
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
购物车三种实现(原生JS、jquery和vuejs)
吾逍遥
Original
3192 people have browsed it

一、购物车功能描述及实现思路

1、购物车功能描述

  1. 全选复选框勾选时,所有商品单项全选,全选不勾选时,所有商品单项也不勾选
  2. 商品单项有一项不勾选时,全选复选框自动不勾选,当所有商品单项勾选时,全选复选框则自动勾选
  3. 商品单项数量影响商品单项总价
  4. 商品单项数量影响商品总数量
  5. 商品单项数量影响商品总价格
  6. 商品单项是否勾选影响商品总数量
  7. 商品单项是否勾选影响商品总价格

2、实现思路

虽然后面采用了原生JS、jquery和vuejs实现,但基本思路都是一样,尤其是核心问题的解决问题的思路。

第1项 其实就是将全选复选框的状态赋值给每一个单项选择即可,这样全选和单选就同步了,无论是勾选还是不选。

第2项 我的思路和老师一样,不过实现方式不一样,老师是使用计数器,通过计数器是否等于总数来决定全选是否勾选。我的是直接 统计勾选的个数,和总数比较 ,相等则自动勾选全选,不等则不勾选。

第3、4、5项 为所有商品数量输入框添加事件,在事件函数中可对商品单项总价、商品总数量和商品总价格计算。 难点在于获取是第几个商品数量输入框,这样可改变对应商品单项总价 。老师所有都是采用数组遍历,我认为不合理,因为商品单项数量改变时其单项总价只要计算它就可以,所有都计算一遍,目前只有几个商品没关系,若是几百个就会浪费不必要的时间了,降低了效率。这时就需要知道是哪个商品数量改变了,这就需要事件监听器添加索引就可以,可惜直接添加是无效的。下面是正确添加方式,大家可以试着理解为什么这样就可以了??jquery也有类似实现,至于vuejs则是双向绑定就简单多了。

  1. // 监听事件传参不能直接传参数,而是通过function((ev){函数名(ev,自定义参数)}) ******
  2. for (let [index, item] of checkItemBtns.entries()) {
  3. item.addEventListener( 'click', function (ev) { checkItem(ev, index); }, false );
  4. }

第5、6项 商品单项是否勾选将影响商品总数量和总价格,至于数量和商品单项总价则不受影响。我的解决思路就是设置开关,对每个商品设置开关,若勾选则为1时计算它,不勾选则为0不计算它。这里说明下原生JS和jquery中目前唯一问题是开关对单项商品总价有影响,当然可以加两行代码就解决了。至于vuejs则全部实现了所有功能。

二、原生JS实现购物车

这里原生JS实现购物车基本就是按上面思路来完成的,具体实现可看下面源码。这里唯一强调一点就是商品单项数量改变是只会改变它的单项总价、总数量和总价格这三个位置。实现就是用上面原理中说的给事件监听器的回调函数传递参数,也是事件监听器的进阶用法了,大家可以参考下。

至于我勾选单项影响单项总价、总数量和总价格三个位置,如果你认为不合理可以简单修改就可以。下面jq也是如此效果,vuejs则只影响总数量和总价格了。

  1. <style>
  2. * {
  3. margin: 0;
  4. padding: 0;
  5. box-sizing: border-box;
  6. }
  7. table {
  8. border-collapse: collapse;
  9. width: 90vw;
  10. min-width: 680px;
  11. text-align: center;
  12. margin: auto;
  13. }
  14. table caption {
  15. font-size: 1.5em;
  16. margin-bottom: 15px;
  17. }
  18. table th,
  19. table td {
  20. border: 1px solid;
  21. padding: 5px;
  22. }
  23. table thead tr {
  24. background-color: lightblue;
  25. }
  26. table tbody tr:nth-child(even) {
  27. background-color: lightcyan;
  28. }
  29. table input {
  30. text-align: center;
  31. line-height: 2em;
  32. }
  33. table input[type='checkbox'] {
  34. width: 1.5em;
  35. height: 1.5em;
  36. }
  37. .btns {
  38. width: 90%;
  39. margin: 1em auto;
  40. display: flex;
  41. justify-content: flex-end;
  42. }
  43. button {
  44. width: 8em;
  45. height: 2em;
  46. outline: none;
  47. border: none;
  48. background-color: seagreen;
  49. color: white;
  50. letter-spacing: 1em;
  51. }
  52. </style>
  53. <table>
  54. <caption>购物车</caption>
  55. <thead>
  56. <tr>
  57. <th>
  58. <input type="checkbox" name="checkAll" id="check-all" checked />
  59. <label for="check-all">全选</label>
  60. </th>
  61. <th>ID</th>
  62. <th>品名</th>
  63. <th>单位</th>
  64. <th>单价/元</th>
  65. <th>数量</th>
  66. <th>金额/元</th>
  67. </tr>
  68. </thead>
  69. <tbody>
  70. <tr>
  71. <td>
  72. <input type="checkbox" name="itemId" value="SN-1010" checked />
  73. </td>
  74. <td>SN-1010</td>
  75. <td>MacBook Pro电脑</td>
  76. <td></td>
  77. <td>18999</td>
  78. <td>
  79. <input type="number" name="counter" value="1" min="1" step="1" />
  80. </td>
  81. <td></td>
  82. </tr>
  83. <tr>
  84. <td>
  85. <input type="checkbox" name="itemId" value="SN-1020" checked />
  86. </td>
  87. <td>SN-1020</td>
  88. <td>iPhone手机</td>
  89. <td></td>
  90. <td>4999</td>
  91. <td>
  92. <input type="number" name="counter" value="2" min="1" step="1" />
  93. </td>
  94. <td></td>
  95. </tr>
  96. <tr>
  97. <td>
  98. <input type="checkbox" name="itemId" value="SN-1030" checked />
  99. </td>
  100. <td>SN-1030</td>
  101. <td>智能AI音箱</td>
  102. <td></td>
  103. <td>399</td>
  104. <td>
  105. <input type="number" name="counter" value="3" min="1" step="1" />
  106. </td>
  107. <td></td>
  108. </tr>
  109. <tr>
  110. <td>
  111. <input type="checkbox" name="itemId" value="SN-1040" checked />
  112. </td>
  113. <td>SN-1040</td>
  114. <td>SSD移动硬盘</td>
  115. <td></td>
  116. <td>888</td>
  117. <td>
  118. <input type="number" name="counter" value="4" min="1" step="1" />
  119. </td>
  120. <td></td>
  121. </tr>
  122. <tr>
  123. <td>
  124. <input type="checkbox" name="itemId" value="SN-1050" checked />
  125. </td>
  126. <td>SN-1050</td>
  127. <td>黄山毛峰</td>
  128. <td></td>
  129. <td>999</td>
  130. <td>
  131. <input type="number" name="counter" value="5" min="1" step="1" />
  132. </td>
  133. <td></td>
  134. </tr>
  135. </tbody>
  136. <tfoot>
  137. <tr>
  138. <td colspan="5">总计:</td>
  139. <td id="total-num"></td>
  140. <td id="total-amount"></td>
  141. </tr>
  142. </tfoot>
  143. </table>
  144. <div class="btns">
  145. <button>结算</button>
  146. </div>
  147. <script>
  148. // 为了同步选择项,建立数组,其值为1为选中,0为未选中,(实现5,6,通过1或0判断单项是否有效来控制)
  149. let checkArr = [];
  150. // 一、全选和自选(实现1,2)
  151. // 获取全选按钮
  152. const checkAllBtn = document.querySelector('#check-all');
  153. // 获取单项选择按钮
  154. const checkItemBtns = document.querySelectorAll('input[name="itemId"]');
  155. // 全选按钮添加点击事件,将它的状态赋值给所有单项选择按钮
  156. checkAllBtn.addEventListener('click', checkAll, false);
  157. function checkAll(ev) {
  158. for (let [index, item] of checkItemBtns.entries()) {
  159. checkArr[index] = ev.target.checked ? 1 : 0;
  160. item.checked = ev.target.checked;
  161. }
  162. autoCount();
  163. }
  164. // 所有单项选择按钮添加点击事件,老师用change,这里用click事件感觉更为合理,当然效果目前是一样。
  165. // 监听事件传参不能直接传参数,而是通过function((ev){函数名(ev,自定义参数)}) ******
  166. for (let [index, item] of checkItemBtns.entries()) {
  167. // 初始化单项状态
  168. checkArr[index] = item.checked ? 1 : 0;
  169. item.addEventListener(
  170. 'click',
  171. function (ev) {
  172. checkItem(ev, index);
  173. },
  174. false
  175. );
  176. }
  177. function checkItem(ev, index) {
  178. checkArr[index] = checkItemBtns[index].checked ? 1 : 0;
  179. // 单项选择按钮点击时,就获取单项选择按钮选中的个数
  180. const selectItemBtns = document.querySelectorAll('input[name="itemId"]:checked');
  181. // 若选中个数等于总个数则全选,否则取消全选。老师是用计数器的方法,感觉没这种方法简洁明了。
  182. checkAllBtn.checked = selectItemBtns.length == checkItemBtns.length ? true : false;
  183. autoCount();
  184. }
  185. // 二、自动计算(实现3,4,5)
  186. // 获取单价元素组
  187. const unitPriceArr = document.querySelectorAll('tbody>tr>td:nth-child(5)');
  188. // console.log(unitPriceArr[0].innerHTML);
  189. // 获取数量输入框元素组
  190. const inputCounterArr = document.querySelectorAll('input[name="counter"]');
  191. // console.log(inputCounterArr[1].value);
  192. // 获取单项总价元素组
  193. const unitPriceTotalArr = document.querySelectorAll('tbody>tr>td:nth-child(7)');
  194. // console.log(unitPriceTotalArr[unitPriceTotalArr.length-1].innerHTML);
  195. // 为数量输入框添加变化事件
  196. for (let inputCounter of inputCounterArr) {
  197. inputCounter.addEventListener('change', autoCount, false);
  198. }
  199. window.addEventListener('load', autoCount, false);
  200. function autoCount() {
  201. console.log(checkArr);
  202. // 获取输入数量数组,parseInt转换为整数压入数组
  203. let inputArr = [];
  204. let unitTotal = 0;
  205. for (let [index, inputCounter] of inputCounterArr.entries()) {
  206. inputArr.push(parseInt(inputCounter.value) * checkArr[index]);
  207. }
  208. // 计算数量总和并赋值给统计的总数,数组reduce是求和的利器。
  209. document.querySelector('#total-num').innerHTML = inputArr.reduce((a, b) => a + b, 0);
  210. // 改变单项总价并累加到总价上
  211. for (let [index, unitPriceTotal] of unitPriceTotalArr.entries()) {
  212. unitTotal += inputArr[index] * unitPriceArr[index].innerHTML * checkArr[index];
  213. unitPriceTotal.innerHTML = inputArr[index] * unitPriceArr[index].innerHTML;
  214. }
  215. // 给总价赋值
  216. document.querySelector('#total-amount').innerHTML = unitTotal;
  217. }
  218. </script>

三、jquery实现购物车

jquery中页面和CSS都和原生一样,这里就不重复了。这里重点说下几个注意点:

  1. input的checked获取和设置问题 我们知道input的checked是布尔值,但attr()获取是字符串,这里是使用prop代替解决问题,记得也有问这个问题,查了网上资料也是难懂,还是拿出我们的利器:自己测试吗!
  1. <label for="c1">c1:</label>
  2. <input id="c1" name="checkbox" type="checkbox" checked="checked" />
  3. <label for="c2">c2:</label>
  4. <input id="c2" name="checkbox" type="checkbox" checked="true" />
  5. <label for="c3">c3:</label>
  6. <input id="c3" name="checkbox" type="checkbox" checked="" />
  7. <label for="c4">c4:</label>
  8. <input id="c4" name="checkbox" type="checkbox" checked />
  9. <label for="c5">c5:</label>
  10. <input id="c5" name="checkbox" type="checkbox" />
  11. <label for="c6">c6:</label>
  12. <input id="c6" name="checkbox" type="checkbox" checked="false" />
  13. <script>
  14. // attr
  15. console.log('c1 attr => ',$('#c1').attr('checked'));
  16. console.log('c2 attr => ',$('#c2').attr('checked'));
  17. console.log('c3 attr => ',$('#c3').attr('checked'));
  18. console.log('c4 attr => ',$('#c4').attr('checked'));
  19. console.log('c5 attr => ',$('#c5').attr('checked'));
  20. console.log('c6 attr => ',$('#c6').attr('checked'));
  21. // prop
  22. console.log('c1 prop => ',$('#c1').prop('checked'));
  23. console.log('c2 prop => ',$('#c2').prop('checked'));
  24. console.log('c3 prop => ',$('#c3').prop('checked'));
  25. console.log('c4 prop => ',$('#c4').prop('checked'));
  26. console.log('c5 prop => ',$('#c5').prop('checked'));
  27. console.log('c6 prop => ',$('#c6').prop('checked'));
  28. </script>

attr-prop

这里要说明下,上面是测试代码是改进于脚本之家网站一篇文章,但是文章作者的结论我不赞同,我增加了内置属性和自定义属性测试,得出了attr和prop的区别:

  • 元素 内置属性并且指明 时,attr和prop都是返回属性值。
  • 元素 内置属性未指明时,attr返回undefined,prop返回false。
  • 元素 内置属性的属性值只有true或false时,如checked、selected和disabled等,attr返回属性名字符串,指明属性名时prop返回true,未指明时返回false。
  • 元素 自定义属性如data-xxx等,attr正常获取属性值,而prop则无效,返回undefined。
  1. jQuery自带迭代(自带循环)如何获取第几个成员? 当然可以使用原生JS中for…of方法获取,这里说下jq本身就支持的简便方法 index ,它 返回数组中某成员的索引 。代码就是: let index=$('selector').index(this);

该解决的问题都进行了单独说明,下面就是实现了,基本是由原生JS转换过来的,除了上面两点,其它也没有什么了。

  1. let checkArr = [];
  2. $('input[name="itemId"]').each((index, item) => (checkArr[index] = $(item).prop('checked') ? 1 : 0));
  3. // 一、全选和自选(实现1、2)
  4. // 获取单值如checked
  5. $('#check-all').click(function () {
  6. $('input[name="itemId"]').prop('checked', $(this).prop('checked'));
  7. $('input[name="itemId"]').each((index, item) => (checkArr[index] = $(item).prop('checked') ? 1 : 0));
  8. autoCount();
  9. });
  10. $('input[name="itemId"]').click(function () {
  11. // jq中快速获取是第几个成员的方法
  12. let index = $('input[name="itemId"]').index(this);
  13. checkArr[index] = $(this).prop('checked') ? 1 : 0;
  14. $('#check-all').prop('checked', $('input[name="itemId"]:checked').length == $('input[name="itemId"]').length);
  15. autoCount();
  16. });
  17. // 二、自动计算(实现3,4,5)
  18. window.addEventListener('load', autoCount, false);
  19. // 获取单价元素组
  20. let unitPriceArr = [];
  21. $('tbody>tr>td:nth-child(5)').each((index, item) => (unitPriceArr[index] = item.innerHTML * 1));
  22. $('input[name="counter"]').each((index, item) => { $(item).change(function (ev) { autoCount(ev, index); }); });
  23. function autoCount(ev, index) {
  24. let goodNumArr = [];
  25. let totalAmount = 0;
  26. $('input[name="counter"]').each((index, item) => (goodNumArr[index] = $(item).val() * 1 * checkArr[index]));
  27. $('#total-num').html(goodNumArr.reduce((a, b) => a + b, 0));
  28. $('tbody>tr>td:nth-child(7)').each((index, item) => {
  29. totalAmount += unitPriceArr[index] * goodNumArr[index] * checkArr[index];
  30. $(item).html(unitPriceArr[index] * goodNumArr[index]);
  31. });
  32. $('#total-amount').html(totalAmount);
  33. }

四、vuejs实现购物车

本来是完成老师作业就完事了,后来又听了老师vuejs的课,尤其是老师用原生JS模拟实现和解释vuejs本质后,让我思路一下就开阔了,原生JS是这些一切的根基,jquery函数库、vuejs、react都是在其基础上进行了封装,更便于开发者开发,同时也降低了开发者对原生JS理解。在这些库或框架中都可混用原生JS,也可用原生JS实现,掌握了原生JS学习这些框架或库都是随手拈来。通过这几天原生JS学习,算是原生JS真正入门了,学习jquery和vuejs比以前只知道使用深刻多了,不再出现使用什么解决问题了,具体查手册就可以了,随着累积相信可以应付绝大部分问题。

vuejs在数据处理方面最强大就是双向绑定了,而购物车则也是这方面需求,我就试着改造成vuejs实现,通过测试终于完成了,下面进行简单说明

  1. 页面中数据双向绑定 数据双向绑定一定要使用v-model ,我开发过uniapp,使用最多的变量方式是:冒号,结果测试时只能随着vuejs中data数据而改变,它不能改变绑定该变量的值,后来还是使用v-model实现的,这里只是强调,双向绑定v-model指令不可少。

  2. 计算属性如何传参数 老师演示了计算属性并解释了本质,在本案例中计算单项商品总价格时,只想改变单项商品对应的总价格,如文章开头所说,需要知道索引,查了vuejs官方文档,知道了计算属性默认是get方法,还有set方法,但是不满足本题要求,后来突然想到不能传递参数是因为 计算属性一般情况下返回值是表达式,所以不能接受参数 ,那么 如果定义为返回值是函数呢?测试了下,成功 。一个非常有用的技巧呢!!!乍一看和上面监听事件中的回调函数传递参数实现类似,都是将返回值转成函数,从而接受参数。

  3. 本实现方法中全选和自选都是能完监听数据变化来完成,这样就存在一个问题,就是自选导致全选监听再一次执行全选监听,所有我们必须知道全选变化是手动还是自动,通过给全选添加自定义方法,设置开关就解决了。

  1. <table>
  2. <caption>购物车</caption>
  3. <thead>
  4. <tr>
  5. <th>
  6. <input type="checkbox" name="checkAll" id="check-all" v-model="checkAll" v-on:input="chekcAllStatus" />
  7. <label for="check-all">全选</label>
  8. </th>
  9. <th>ID</th>
  10. <th>品名</th>
  11. <th>单位</th>
  12. <th>单价/元</th>
  13. <th>数量</th>
  14. <th>金额/元</th>
  15. </tr>
  16. </thead>
  17. <tbody>
  18. <tr>
  19. <td>
  20. <input type="checkbox" name="itemId" value="SN-1010" v-model="checkArr[0]" />
  21. </td>
  22. <td>SN-1010</td>
  23. <td>MacBook Pro电脑</td>
  24. <td></td>
  25. <td>{{unitPriceArr[0]}}</td>
  26. <td>
  27. <input type="number" name="counter" v-model:value="inputArr[0]" min="1" step="1" />
  28. </td>
  29. <td>{{unitTotalPrice(0)}}</td>
  30. </tr>
  31. <tr>
  32. <td>
  33. <input type="checkbox" name="itemId" value="SN-1020" v-model="checkArr[1]" />
  34. </td>
  35. <td>SN-1020</td>
  36. <td>iPhone手机</td>
  37. <td></td>
  38. <td>{{unitPriceArr[1]}}</td>
  39. <td>
  40. <input type="number" name="counter" v-model:value="inputArr[1]" min="1" step="1" />
  41. </td>
  42. <td>{{unitTotalPrice(1)}}</td>
  43. </tr>
  44. <tr>
  45. <td>
  46. <input type="checkbox" name="itemId" value="SN-1030" v-model="checkArr[2]" />
  47. </td>
  48. <td>SN-1030</td>
  49. <td>智能AI音箱</td>
  50. <td></td>
  51. <td>{{unitPriceArr[2]}}</td>
  52. <td>
  53. <input type="number" name="counter" v-model:value="inputArr[2]" min="1" step="1" />
  54. </td>
  55. <td>{{unitTotalPrice(2)}}</td>
  56. </tr>
  57. <tr>
  58. <td>
  59. <input type="checkbox" name="itemId" value="SN-1040" v-model="checkArr[3]" />
  60. </td>
  61. <td>SN-1040</td>
  62. <td>SSD移动硬盘</td>
  63. <td></td>
  64. <td>{{unitPriceArr[3]}}</td>
  65. <td>
  66. <input type="number" name="counter" v-model:value="inputArr[3]" min="1" step="1" />
  67. </td>
  68. <td>{{unitTotalPrice(3)}}</td>
  69. </tr>
  70. <tr>
  71. <td>
  72. <input type="checkbox" name="itemId" value="SN-1050" v-model="checkArr[4]" />
  73. </td>
  74. <td>SN-1050</td>
  75. <td>黄山毛峰</td>
  76. <td></td>
  77. <td>{{unitPriceArr[4]}}</td>
  78. <td>
  79. <input type="number" name="counter" v-model:value="inputArr[4]" min="1" step="1" />
  80. </td>
  81. <td>{{unitTotalPrice(4)}}</td>
  82. </tr>
  83. </tbody>
  84. <tfoot>
  85. <tr>
  86. <td colspan="5">总计:</td>
  87. <td id="total-num">{{totalNum}}</td>
  88. <td id="total-amount">{{totalPrice}}</td>
  89. </tr>
  90. </tfoot>
  91. </table>
  92. <script>
  93. const vm = new Vue({
  94. el: 'table',
  95. data: {
  96. checkAll: true,
  97. checkArr: [true, true, true, true, true],
  98. // 开关,防止全选和单选冲突
  99. checkStatus: false,
  100. unitPriceArr: [18999, 4999, 399, 888, 999],
  101. inputArr: [3, 2, 3, 4, 5],
  102. },
  103. computed: {
  104. totalNum: function () {
  105. // return this.inputArr.reduce((a, b) => a * 1 + b * 1, 0);
  106. let total = 0;
  107. for (let [index, input] of this.inputArr.entries()) {
  108. total += input * 1 * (this.checkArr[index] ? 1 : 0);
  109. }
  110. return total;
  111. },
  112. unitTotalPrice: function () {
  113. return index => {
  114. return this.unitPriceArr[index] * 1 * this.inputArr[index];
  115. };
  116. },
  117. totalPrice: function () {
  118. let total = 0;
  119. for (let [index, input] of this.inputArr.entries()) {
  120. total += this.unitPriceArr[index] * 1 * input * (this.checkArr[index] ? 1 : 0);
  121. }
  122. return total;
  123. },
  124. },
  125. watch: {
  126. checkAll(newVal) {
  127. // 双向绑定数组无法直接赋值,需要转换为普通的,计算完后,使用vuejs改变即可
  128. if (this.checkStatus) {
  129. let checkArr = JSON.parse(JSON.stringify(this.checkArr));
  130. for (let index of checkArr.keys()) {
  131. checkArr[index] = newVal;
  132. }
  133. this.checkArr = checkArr;
  134. this.checkStatus = false;
  135. }
  136. },
  137. checkArr() {
  138. let checkArr = JSON.parse(JSON.stringify(this.checkArr));
  139. let checkOkArr = checkArr.filter(item => item == true);
  140. this.checkAll = checkArr.length == checkOkArr.length ? true : false;
  141. },
  142. },
  143. methods: {
  144. chekcAllStatus: function () {
  145. // 区分是用户选择全选,还是单项导致自动全选
  146. this.checkStatus = true;
  147. },
  148. },
  149. });
  150. </script>

Codepen演示 https://codepen.io/woxiaoyao81/pen/zYBeEoO

五、感受

原生JS、jquery函数库和vuejs框架基本学完了,前端也快基本告以段落了,朱老师深入浅出的授课让我对前端也完成了系统的梳理,相比报前最烦恼的就是前端,无论是CSS还是JS,都感觉乱,需要就百度来东拼本西凑,最后一团乱麻。现在对前端已经有了系统的认识,现在无论是语义化元素,还是CSS布局,或是JS的使用,都能快速找到解决思路,个别不清楚查下手册就可以了。我的理解都已经写在我的博文中了,可以说好多都比网上大部分文章介绍更系统更深入,相信你看了之后也会对前端有了系统的认识。

Correcting teacher:天蓬老师天蓬老师

Correction status:qualified

Teacher's comments:其实,全选案例的判断单个复选框状态,使用every()方法一行就可以搞定了, 不需要计算, 我昨天将源码放到了群中, 不过,你的思路也非常的棒, 毕竟代码的可读性应该放在第一位
Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!