面试被问到用Vue手写一个类似淘宝类型功能,直接懵了
Sonder
2022-08-16
11042字
28分钟
浏览 (2.5k)
具体功能
如下gif:
![10bedf754fc9665cb6a344d33aec1137.gif](https://s2.loli.net/2023/03/30/vDQOyK9sxEecXBl.gif)
需求设计
差点凌乱,结果一想,这难吗? 难就难在没有思路。于是这时的我两腿一蹬,大脑开始高速运转,生成如下流程图:
![image.png](https://s2.loli.net/2023/03/30/o7xFT1fEYGBnQv4.png)
这么看来好像就清晰很多了,果然设计是编程中不可或缺的一步!
正式coding
首先我问面试官,后台响应回来的详情数据是什么结构,于是他给了我这么一坨
mockData: {
json1: [
["红色", "黄色", "蓝色"],
["S", "M"],
["棉的", "涤纶"],
],
json2: [
{
color: "红色",
type: "S",
mianliao: "棉的",
price: 100,
},
{
color: "红色",
type: "M",
mianliao: "棉的",
price: 200,
},
{
color: "红色",
type: "S",
mianliao: "涤纶",
price: 300,
},
{
color: "红色",
type: "M",
mianliao: "涤纶",
price: 400,
},
{
color: "黄色",
type: "S",
mianliao: "棉的",
price: 500,
},
{
color: "黄色",
type: "M",
mianliao: "棉的",
price: 600,
},
{
color: "黄色",
type: "S",
mianliao: "涤纶",
price: 700,
},
{
color: "黄色",
type: "M",
mianliao: "涤纶",
price: 800,
},
{
color: "蓝色",
type: "S",
mianliao: "棉的",
price: 900,
},
{
color: "蓝色",
type: "M",
mianliao: "棉的",
price: 1000,
},
{
color: "蓝色",
type: "S",
mianliao: "涤纶",
price: 1100,
},
{
color: "蓝色",
type: "M",
mianliao: "涤纶",
price: 1200,
},
]
}
我们从这个结构不难看出,json2就是具体的数据,而json1里面存放是三个维度的分类,实现效果差不多是这样
![image.png](https://s2.loli.net/2023/03/30/wbtAFQjRJB7WK1U.png)
,所以问题不大,我们先把json1的数据渲染出来再说。
<div v-for="(rowArr, index) in mockData.json1" :key="index">
<a v-for="(item, i) in rowArr" :key="i" @click="changeData(index,item)">{{item}}</a>
</div>
上述数据我们保存在mockData中,只遍历json1,效果出来了,适当加点样式
#app a {
text-decoration: none;
border: 1px solid grey;
margin: 8px;
padding: 6px;
display: inline-block;
color: grey;
}
#app a.active {
border: 1px solid red;
color: red;
}
出来了,beautiful
![image.png](https://s2.loli.net/2023/03/30/Lrs8HRo6wOEQMig.png)
逻辑处理及代码优化
面试官在告知我需求的时候,强调又强调数据不能写死,聪明的我当然明白,意思就是要尽可能让我们的前端代码有更高的复用性,说白了就是可能会匹配颜色、尺码、面料,也可能会用金额、风格来匹配。
设计一个万能属性匹配器
可以想象,不管他是什么属性,但只要是【条件对象】的abc属性,与【数据对象】的abc属性一致,我就可以认为筛选出来的就是这个对象,因此不要在乎abc这个名,岂不就通用性很高啦。接着我们在data下声明一个condition对象来存储当前选中的各项属性,再给他默认值。
先回顾一下数据结构
data() {
return {
types:[], // 保存从json2中获取的动态属性名称
condition: {}, // 有json1.length个属性,分别是前三项color、type、mianliao
mockData: {
json1: [
["红色", "黄色", "蓝色"],
["S", "M"],
["棉的", "涤纶"],
],
json2: [
{
color: "红色",
type: "S",
mianliao: "棉的",
price: 100,
}
/*....省略数据*/
]
}
}
}
模拟created中动态获取,并动态生成types和condition
created(){
this.types = Object.keys(this.mockData.json2[0]);
for (let i = 0; i < this.types.length; i ++) {
// 由于分类有可能没有数据的属性多,所以判断一下
if (!this.mockData.json1[i])return;
let propName = this.types[i];
this.$set(this.condition,propName,this.mockData.json1[i][0]);
}
},
- 注意注意
- 第2行是讲具体商品数据中的所有属性名存储到数组中
- 第7行则是动态添加响应式属性,这是Vue2中动态添加属性必须要做的,否则更改数据页面那个属性不变化,Vue3这个问题已经解决
点中后的激活效果
<a :class="{ active: condition[types[index]] === item }"
href="#"
v-for="(item, i) in rowArr"
:key="i"
>{{ item }}</a>
>
这个比较简单!
核心代码(过滤数据)
- 首先我们给a标签添加点击事件,点了那项,就更改其中颜色或者尺码或者面料的值,因此相同函数我们必须要知道点了哪个类别,这里我们可以用【外层的index】做区分,给a标签加上事件
<a @click="changeData(types[index], item)" a>{{item}}</a>
- 保存这个数据变化
changeData(prop, data) {
let self = this;
self.condition[prop] = data;
}
- 有了数据,就可以做过滤显示了,computed是最佳人选
computed: {
showGoodsInfo() {
return his.mockData.json2.filter((e) => {
return diffObjectByKeys(this.condition, e);
});
},
}
- 核心的工具函数,对比两个对象的函数还没实现,我们去实现它,这个函数接收俩对象,对比其属性值,全都相同,返回true,否则false。
function diffObjectByKeys(obj1, obj2) {
let isEqual = true;
for (let key in obj1) {
let v = obj1[key];
if (obj2[key] && obj2[key] === v) {
continue;
} else return false;
}
return isEqual;
}
- 别忘了使用这个计算属性:
价格是: {{ showGoodsInfo }}
最终效果
![f1659a23b31a68d0809cd5e7d2ab698c.gif](https://s2.loli.net/2023/03/30/hPG6gRqzycHSNeu.gif)
完整代码
HelloWorld.vue,这是个组件
<template>
<div class="hello">
<div v-for="(rowArr, index) in mockData.json1" :key="index">
<span :class="{ active: condition[types[index]] === item }" class="goods-item" v-for="(item, i) in rowArr" :key="i" @click="changeData(types[index], item)">{{item}}</span>
</div>
所选的:{{ showGoodsInfo }}
<button @click="isShow1 = !isShow1">结算</button>
<MsgBox :show.sync="isShow1" :conf="{showGoodsInfo:showGoodsInfo[0]}"/>
</div>
</template>
<script>
// https://blog.csdn.net/tc1124692/article/details/123814949
import MsgBox from "@/components/MsgBox";
// 核心的工具函数,对比两个对象的函数还没实现,我们去实现它,这个函数接收俩对象,对比其属性值,全都相同,返回true,否则false。
function diffObjectByKeys(obj1, obj2) {
let isEqual = true;
for (let key in obj1) {
let v = obj1[key];
if (obj2[key] && obj2[key] === v) continue;
return false;
}
return isEqual;
}
export default {
components: {
MsgBox
},
data() {
return {
isShow1: false,
types: [], // 保存从json2中获取的动态属性名称
condition: {}, // 有json1.length个属性,分别是前三项color、type、mianliao
mockData: {
json1: [
["红色", "黄色", "蓝色"],
["S", "M"],
["棉的", "涤纶"],
],
json2: [
{
color: "红色",
type: "S",
mianliao: "棉的",
price: 100,
},
{
color: "红色",
type: "M",
mianliao: "棉的",
price: 200,
},
{
color: "红色",
type: "S",
mianliao: "涤纶",
price: 300,
},
{
color: "红色",
type: "M",
mianliao: "涤纶",
price: 400,
},
{
color: "黄色",
type: "S",
mianliao: "棉的",
price: 500,
},
{
color: "黄色",
type: "M",
mianliao: "棉的",
price: 600,
},
{
color: "黄色",
type: "S",
mianliao: "涤纶",
price: 700,
},
{
color: "黄色",
type: "M",
mianliao: "涤纶",
price: 800,
},
{
color: "蓝色",
type: "S",
mianliao: "棉的",
price: 900,
},
{
color: "蓝色",
type: "M",
mianliao: "棉的",
price: 1000,
},
{
color: "蓝色",
type: "S",
mianliao: "涤纶",
price: 1100,
},
{
color: "蓝色",
type: "M",
mianliao: "涤纶",
price: 1200,
},
]
}
}
},
computed: {
showGoodsInfo() {
return this.mockData.json2.filter((e) => {
return diffObjectByKeys(this.condition, e);
});
},
},
methods: {
//过滤数据
changeData(prop, data) {
let self = this;
self.condition[prop] = data;
},
},
created(){
//模拟created中动态获取,并动态生成types和condition
this.types = Object.keys(this.mockData.json2[0]);
for (let i = 0; i < this.types.length; i ++) {
// 由于分类有可能没有数据的属性多,所以判断一下
if (!this.mockData.json1[i])return;
let propName = this.types[i];
this.$set(this.condition,propName,this.mockData.json1[i][0]);
}
},
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.hello .goods-item {
text-decoration: none;
border: 1px solid grey;
margin: 8px;
padding: 6px;
display: inline-block;
color: grey;
}
.hello .goods-item.active {
border: 1px solid red;
color: red;
}
</style>
MsgBox.vue,这也是个组件
<script>
export default {
props: {
show: {
type: Boolean,
default: false
},
conf: {
type: Object
}
},
// 组件为了更轻便,可以考虑去除组件内的生命周期以及 DOM 结构。让组件更优化,取舍 this
functional: true, // 函数式组件
render(h, context) {
const {type, color, price} = context.props.conf.showGoodsInfo;
const {show} = context.props; // 是否显示当前组件
let update = context.listeners['update:show']; // 显示当前组件的事件
return (
<transition name="fade">
{show && (
<div>
<h2>颜色:{color}</h2>
<div>尺寸:{type}</div>
<div>价格:{price}</div>
<button onClick={ () => update(false) }>关闭</button>
</div>
)}
</transition>
)
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
</style>
本文转自 https://blog.csdn.net/tc1124692/article/details/123814949,如有侵权,请联系删除。