小demo-定时器、标志、el-carousel-item-完整开发过程

130 阅读6分钟

step1 - 小demo页面搭建

image.png

主页面

<template>
  <div>
    <div class="colors">
      <div class="item" v-for="(item, index) in colorsData" :key="item">
        {{ item.name }}
        <span class="colors-tick-checked"
          ><el-icon><Check /></el-icon>
        </span>
      </div>
    </div>
    <div class="tip">
      请选择以下有<span class="name">{{ colorsData[0].name }}</span
      >颜色的标志
    </div>
    <div class="logos">
      <el-carousel
        ref="cardShow"
        height="480px"
        indicator-position="none"
        :autoplay="false"
        arrow="never"
        trigger="click"
      >
        <el-carousel-item v-for="(item, index) in colorsData" :key="item">
          <div class="logosImg">
            <div v-for="iitem in item.chooseData" v-if="item.chooseData.length">
              <div class="carouselImgOuter">
                <img :src="getAssets('sprites', iitem.imgName)" alt="" />
              </div>
              <div class="checkIcon">
                <el-tag type="success" round effect="dark">
                  <el-icon><Select /></el-icon>
                </el-tag>
                <!-- <el-tag type="danger" round effect="dark">
                  <el-icon><CloseBold /></el-icon>
                </el-tag> -->
              </div>
            </div>
          </div>
        </el-carousel-item>
      </el-carousel>
    </div>
  </div>
</template>

<script setup>
import { ref } from "vue";
import { getAssets } from "../utils/getAssets";
const colorsData = ref([
  {
    name: "BLUE",
    rightData: [{ imgName: "blue.png" }],
    chooseData: [
      {
        imgName: "pink.png",
      },
      {
        imgName: "blue.png",
      },
      {
        imgName: "red.png",
      },
    ],
  },
  {
    name: "PINK",
    rightData: [{ imgName: "pink.png" }],
    chooseData: [
      {
        imgName: "orange.png",
      },
      {
        imgName: "blue.png",
      },
      {
        imgName: "pink.png",
      },
    ],
  },
  {
    name: "RED",
    rightData: [{ imgName: "red.png" }],
    chooseData: [
      {
        imgName: "pink.png",
      },
      {
        imgName: "green.png",
      },
      {
        imgName: "yellow.png",
      },
    ],
  },
  {
    name: "YELLOW",
    rightData: [{ imgName: "yellow.png" }],
    chooseData: [
      {
        imgName: "yellow.png",
      },
      {
        imgName: "blue.png",
      },
      {
        imgName: "red.png",
      },
    ],
  },
]);
</script>

<style lang="scss" scoped>
.colors {
  margin: 10px 0 60px;
  padding: 0 160px;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 30px;
  .item {
    user-select: none;
    position: relative;
    font-size: 28px;
    height: 26px;
    line-height: 26px;
    padding: 30px;
    background: #efeeff;
    border-radius: 34px;
    color: #2f2e41;
    &:hover,
    &.active {
      cursor: pointer;
      background-color: #6c63ff;
      color: #ffffff;
      transition: background-color 0.8s ease;
    }
  }
  .colors-tick-checked {
    position: absolute;
    top: -4px;
    right: -2px;
    width: 29px;
    height: 29px;
    border-radius: 50%;
    background-color: #6c63ff;
    line-height: 29px;
    cursor: pointer;
    text-align: center;
    .el-icon {
      font-size: 20px;
      color: white;
    }
  }
}
.tip {
  font-weight: 400;
  font-size: 30px;
  color: #2f2e41;
  text-align: center;
  .name {
    color: #6c63ff;
  }
}
.logos {
  height: 480px;
  margin: 40px 0;
  width: 100%;
  .logosImg {
    box-sizing: border-box;
    padding: 0 160px;
    display: flex; 
    justify-content: center; 
    align-items: center; 
    flex-wrap: wrap; 
    div {
      box-sizing: border-box;
      width: 30%;
      text-align: center;
      margin: 0 auto;
      display: flex; //
      justify-content: center; //
      align-items: center; //
      flex-wrap: wrap; //
      .carouselImgOuter {
        width: 100%; //
        &:hover {
          cursor: pointer;
        }
      }
      .checkIcon {
        width: 100%; //
        display: inline-block;
        margin-top: 10px;
        height: 25px;
        text-align: center;
        .el-tag {
          box-sizing: border-box;
          padding: 0 4px;
          .el-icon {
            margin-right: 0;
          }
        }
      }
    }
  }
}
</style>

配件 - getAssets.js

function getAssets(task, name) {
  return new URL(`../assets/images/${task}/${name}`, import.meta.url).href;
}
export { getAssets };

将相对路径改为绝对路径,避免部署上服务器后找不到图片资源

如果是vue-cli搭建,可使用require引用资源 如

image.png

step2 - 上面按钮与el-carousel-item里的图片联系起来

1717813199063.gif

<template>
  <div>
    <div class="colors">
      <div
        class="item"
        v-for="(item, index) in colorsData"
        :key="item"
        @click="select(index)" //1、添加点击事件
        :class="computedClass(index)" //2、添加样式判断逻辑
      >
        {{ item.name }}
        <span class="colors-tick-checked"
          ><el-icon><Check /></el-icon>
        </span>
      </div>
    </div>
    <div class="tip">
      请选择以下有<span class="name">{{ colorsData[0].name }}</span
      >颜色的标志
    </div>
    <div class="logos">
      <el-carousel
        ref="cardShow"
        height="480px"
        indicator-position="none"
        :autoplay="false"
        arrow="never"
        trigger="click"
      >
        <el-carousel-item
          v-for="(item, index) in colorsData"
          :key="item"
          :name="'i' + index" //新增name属性,与cardShow的setActiveItem对应
        >
          <div class="logosImg">
            <div v-for="iitem in item.chooseData" v-if="item.chooseData.length">
              <div class="carouselImgOuter">
                <img :src="getAssets('sprites', iitem.imgName)" alt="" />
              </div>
              <div class="checkIcon">
                <el-tag type="success" round effect="dark">
                  <el-icon><Select /></el-icon>
                </el-tag>
                <!-- <el-tag type="danger" round effect="dark">
                  <el-icon><CloseBold /></el-icon>
                </el-tag> -->
              </div>
            </div>
          </div>
        </el-carousel-item>
      </el-carousel>
    </div>
  </div>
</template>
<script setup> //只展示修改部分
    const cardShow = ref(null);
    const selected = ref([0]); //记录被选择的盒子
    const select = (index) => {
      selected.value.length >= 1
        ? (selected.value[0] = index)
        : selected.value.push(index);
      cardShow.value.setActiveItem("i" + index); //与轮播页展示内容连接
    };
    function computedClass(index) {
      let list = [];
      if (selected.value.indexOf(index) > -1) {
        list.push("selected");
      }
      return list.join(" ");
    }
</script>
<style lang="scss" scoped>
    .item {
        &.selected {
          cursor: pointer;
          background: #6c63ff;
          color: #ffffff;
          transition: background-color 0.8s ease;
        }
    }
</style>    

step3 - 点击图片进行判断,然后图标显示

1717814202265.gif

<template> //只展示修改部分
    <span class="colors-tick-checked" v-if="item.isright" //新增判断
      ><el-icon><Check /></el-icon>
    </span>
    <div class="carouselImgOuter">
        <img
          :src="getAssets('sprites', iitem.imgName)"
          alt=""
          @click="check($event, item, iitem)" //新增点击事件
        />
    </div>
     <div class="checkIcon">
        <el-tag
          type="success"
          round
          effect="dark"
          v-if="iitem.ischecked == 'right'" //新增判断
        >
          <el-icon><Select /></el-icon>
        </el-tag>
        <el-tag
          type="danger"
          round
          effect="dark"
          v-else-if="iitem.ischecked == 'wrong'" //新增判断
        >
          <el-icon><CloseBold /></el-icon>
        </el-tag>
  </div>
</template>
<script setup>
    const colorsData = ref([ //改善一下数据结构
      {
        name: "BLUE",
        rightData: [{ imgName: "blue.png", isImgRight: false }],
        rightCnt: 0,
        chooseData: [
          {
            imgName: "pink.png",
            ischecked: "",
          },
          {
            imgName: "blue.png",
            ischecked: "",
          },
          {
            imgName: "red.png",
            ischecked: "",
          },
        ],
      },
      {
        name: "PINK",
        rightData: [{ imgName: "pink.png", isImgRight: false }],
        rightCnt: 0,
        chooseData: [
          {
            imgName: "orange.png",
            ischecked: "",
          },
          {
            imgName: "blue.png",
            ischecked: "",
          },
          {
            imgName: "pink.png",
            ischecked: "",
          },
        ],
      },
      {
        name: "RED",
        rightData: [{ imgName: "red.png", isImgRight: false }],
        rightCnt: 0,
        chooseData: [
          {
            imgName: "red.png",
            ischecked: "",
          },
          {
            imgName: "green.png",
            ischecked: "",
          },
          {
            imgName: "yellow.png",
            ischecked: "",
          },
        ],
      },
      {
        name: "YELLOW",
        rightData: [{ imgName: "yellow.png", isImgRight: false }],
        rightCnt: 0,
        chooseData: [
          {
            imgName: "yellow.png",
            ischecked: "",
          },
          {
            imgName: "blue.png",
            ischecked: "",
          },
          {
            imgName: "red.png",
            ischecked: "",
          },
        ],
      },
    ]);
    const check = (ev, item, iitem) => {
      iitem.ischecked = "wrong"; //先全部设置成错误
      item.rightData.forEach((el) => {
        //对比该index正确答案数组
        if (el.imgName == iitem.imgName) {
          //一出现就正确
          iitem.ischecked = "right";
          item.rightCnt++;
          el.isImgRight = true;
        }
      });
      if (item.rightCnt == item.rightData.length) {  //如果全部正确选项选择完成
        item.isright = true;
      }
    };
</script>

step4 - 优化-部分浏览器点击图片会有预览效果,将img标签变成背景图片引用

<template> //只展示修改部分
    <div class="logosImg">
        <div v-for="iitem in item.chooseData" v-if="item.chooseData.length">
          <div
            class="carouselImgOuter"
            :style="`background-image: url(${getAssets(
              'sprites',
              iitem.imgName
            )})`" //
            @click="check($event, item, iitem)"
          ></div>
          <div class="checkIcon">
            <el-tag
              type="success"
              round
              effect="dark"
              v-if="iitem.ischecked == 'right'"
            >
              <el-icon><Select /></el-icon>
            </el-tag>
            <el-tag
              type="danger"
              round
              effect="dark"
              v-else-if="iitem.ischecked == 'wrong'"
            >
              <el-icon><CloseBold /></el-icon>
            </el-tag>
          </div>
        </div>
    </div>
</template>
<style lang="scss" scoped> //只展示修改部分
     .carouselImgOuter { //设置固定宽度高度,方便居中
        width: 64px; 
        height: 64px;
        background-size: 100% 100%;
        background-repeat: no-repeat;
        &:hover {
          cursor: pointer;
        }
      }
</style>

:style="background-image: url(${getAssets('sprites', iitem.imgName )})" ,注意搭配url使用

step5 - 功能-如果有答错情况直接重做

1717827916359.gif

<script setup> //只展示修改部分
    const colorsData = ref([ //增加部分测试数据
      {
        name: "BLUEGREEN",
        rightData: [
          { imgName: "blue.png", isImgRight: false },
          { imgName: "green.png", isImgRight: false },
        ],
        rightCnt: 0,
        chooseData: [
          {
            imgName: "pink.png",
            ischecked: "",
          },
          {
            imgName: "blue.png",
            ischecked: "",
          },
          {
            imgName: "red.png",
            ischecked: "",
          },
          {
            imgName: "green.png",
            ischecked: "",
          },
        ],
      },
      {
        name: "PINK",
        rightData: [{ imgName: "pink.png", isImgRight: false }],
        rightCnt: 0,
        chooseData: [
          {
            imgName: "orange.png",
            ischecked: "",
          },
          {
            imgName: "blue.png",
            ischecked: "",
          },
          {
            imgName: "pink.png",
            ischecked: "",
          },
        ],
      },
      {
        name: "RED",
        rightData: [{ imgName: "red.png", isImgRight: false }],
        rightCnt: 0,
        chooseData: [
          {
            imgName: "red.png",
            ischecked: "",
          },
          {
            imgName: "green.png",
            ischecked: "",
          },
          {
            imgName: "yellow.png",
            ischecked: "",
          },
        ],
      },
      {
        name: "YELLOW",
        rightData: [{ imgName: "yellow.png", isImgRight: false }],
        rightCnt: 0,
        chooseData: [
          {
            imgName: "yellow.png",
            ischecked: "",
          },
          {
            imgName: "blue.png",
            ischecked: "",
          },
          {
            imgName: "red.png",
            ischecked: "",
          },
        ],
      },
    ]);

    const check = (ev, item, iitem) => {
      iitem.ischecked = "wrong"; //先全部设置成错误
      item.rightData.forEach((el) => {
        //对比该index正确答案数组
        if (el.imgName == iitem.imgName) {
          //一出现就正确
          iitem.ischecked = "right";
          item.rightCnt++;
          el.isImgRight = true;
        }
      });
      //如果全部正确选项选择完成
      if (item.rightCnt == item.rightData.length) {
        item.chooseData.forEach((eel) => {
          //其他错误选项全部出现
          if (eel.ischecked != "right") {
            eel.ischecked = "wrong";
          }
        });
        item.isright = true;
      }
      //一出现错误清空退出
      if (iitem.ischecked == "wrong") {
        //colorsData状态就要恢复默认
        item.rightCnt = 0;
        item.rightData.forEach((el) => {
          el.isImgRight = false;
        });
        setTimeout(() => { //这里选择使用定时器,使错误标志出现一会儿后消失
          item.chooseData.forEach((eel) => {
            eel.ischecked = "";
          });
        }, 1000);
      }
    };
</script>

setTimeout(() => { //这里选择使用定时器,使错误标志出现一会儿后消失 item.chooseData.forEach((eel) => { eel.ischecked = ""; }); }, 500);

step6 - 优化-如果点击过快-定时器带来一点问题

1717828239453.gif

<script setup> //只展示修改部分
    let flag = false; //标志
    const check = (ev, item, iitem) => {
      if (flag) return; //还在定时器期间点击不会有反应
      iitem.ischecked = "wrong"; 
      item.rightData.forEach((el) => {
        if (el.imgName == iitem.imgName) {
          iitem.ischecked = "right";
          item.rightCnt++;
          el.isImgRight = true;
        }
      });
      if (item.rightCnt == item.rightData.length) {
        item.chooseData.forEach((eel) => {
          if (eel.ischecked != "right") {
            eel.ischecked = "wrong";
          }
        });
        item.isright = true;
      }
      if (iitem.ischecked == "wrong") {
        flag = true; //标志状态更改
        item.rightCnt = 0;
        item.rightData.forEach((el) => {
          el.isImgRight = false;
        });
        setTimeout(() => {
          item.chooseData.forEach((eel) => {
            eel.ischecked = "";
          });
          flag = false; //标志状态更改
        }, 500);
      }
    };
</script>

灵活使用标志来对不进行判断的情况及时return

step7 - 优化-已成功的关卡不能再进行点击

1717828729156.gif

<script setup> //只展示修改部分
    const check = (ev, item, iitem) => {
      if (item.isright || iitem.ischecked == "right" || flag) { //新增判断
    return;
    }
      iitem.ischecked = "wrong"; 
      item.rightData.forEach((el) => {
        if (el.imgName == iitem.imgName) {
          iitem.ischecked = "right";
          item.rightCnt++;
          el.isImgRight = true;
        }
      });
      if (item.rightCnt == item.rightData.length) {
        item.chooseData.forEach((eel) => {
          if (eel.ischecked != "right") {
            eel.ischecked = "wrong";
          }
        });
        item.isright = true;
      }
      if (iitem.ischecked == "wrong") {
        flag = true; 
        item.rightCnt = 0;
        item.rightData.forEach((el) => {
          el.isImgRight = false;
        });
        setTimeout(() => {
          item.chooseData.forEach((eel) => {
            eel.ischecked = "";
          });
          flag = false; 
        }, 500);
      }
    };
</script>

step8 - 成功!完整代码!快跟着一起试试吧!

<template>
  <div>
    <div class="colors">
      <div
        class="item"
        v-for="(item, index) in colorsData"
        :key="item"
        @click="select(index)"
        :class="computedClass(index)"
      >
        {{ item.name }}
        <span class="colors-tick-checked" v-if="item.isright"
          ><el-icon><Check /></el-icon>
        </span>
      </div>
    </div>
    <div class="tip">
      请选择以下有<span class="name">{{ colorsData[0].name }}</span
      >颜色的标志
    </div>
    <div class="logos">
      <el-carousel
        ref="cardShow"
        height="480px"
        indicator-position="none"
        :autoplay="false"
        arrow="never"
        trigger="click"
      >
        <el-carousel-item
          v-for="(item, index) in colorsData"
          :key="item"
          :name="'i' + index"
        >
          <div class="logosImg">
            <div v-for="iitem in item.chooseData" v-if="item.chooseData.length">
              <div
                class="carouselImgOuter"
                :style="`background-image: url(${getAssets(
                  'sprites',
                  iitem.imgName
                )})`"
                @click="check($event, item, iitem)"
              ></div>
              <div class="checkIcon">
                <el-tag
                  type="success"
                  round
                  effect="dark"
                  v-if="iitem.ischecked == 'right'"
                >
                  <el-icon><Select /></el-icon>
                </el-tag>
                <el-tag
                  type="danger"
                  round
                  effect="dark"
                  v-else-if="iitem.ischecked == 'wrong'"
                >
                  <el-icon><CloseBold /></el-icon>
                </el-tag>
              </div>
            </div>
          </div>
        </el-carousel-item>
      </el-carousel>
    </div>
  </div>
</template>

<script setup>
import { ref } from "vue";
import { getAssets } from "../utils/getAssets";
const cardShow = ref(null);
const selected = ref([0]);
const colorsData = ref([
  {
    name: "BLUEGREEN",
    rightData: [
      { imgName: "blue.png", isImgRight: false },
      { imgName: "green.png", isImgRight: false },
    ],
    rightCnt: 0,
    chooseData: [
      {
        imgName: "pink.png",
        ischecked: "",
      },
      {
        imgName: "blue.png",
        ischecked: "",
      },
      {
        imgName: "red.png",
        ischecked: "",
      },
      {
        imgName: "green.png",
        ischecked: "",
      },
    ],
  },
  {
    name: "PINK",
    rightData: [{ imgName: "pink.png", isImgRight: false }],
    rightCnt: 0,
    chooseData: [
      {
        imgName: "orange.png",
        ischecked: "",
      },
      {
        imgName: "blue.png",
        ischecked: "",
      },
      {
        imgName: "pink.png",
        ischecked: "",
      },
    ],
  },
  {
    name: "RED",
    rightData: [{ imgName: "red.png", isImgRight: false }],
    rightCnt: 0,
    chooseData: [
      {
        imgName: "red.png",
        ischecked: "",
      },
      {
        imgName: "green.png",
        ischecked: "",
      },
      {
        imgName: "yellow.png",
        ischecked: "",
      },
    ],
  },
  {
    name: "YELLOW",
    rightData: [{ imgName: "yellow.png", isImgRight: false }],
    rightCnt: 0,
    chooseData: [
      {
        imgName: "yellow.png",
        ischecked: "",
      },
      {
        imgName: "blue.png",
        ischecked: "",
      },
      {
        imgName: "red.png",
        ischecked: "",
      },
    ],
  },
]);
const select = (index) => {
  selected.value.length >= 1
    ? (selected.value[0] = index)
    : selected.value.push(index);
  cardShow.value.setActiveItem("i" + index);
};
function computedClass(index) {
  let list = [];
  if (selected.value.indexOf(index) > -1) {
    list.push("selected");
  }
  return list.join(" ");
}
let flag = false; //标志
const check = (ev, item, iitem) => {
  if (item.isright || iitem.ischecked == "right" || flag) {
    return;
  }
  iitem.ischecked = "wrong"; //先全部设置成错误
  item.rightData.forEach((el) => {
    //对比该index正确答案数组
    if (el.imgName == iitem.imgName) {
      //一出现就正确
      iitem.ischecked = "right";
      item.rightCnt++;
      el.isImgRight = true;
    }
  });
  //如果全部正确选项选择完成
  if (item.rightCnt == item.rightData.length) {
    item.chooseData.forEach((eel) => {
      //其他错误选项全部出现
      if (eel.ischecked != "right") {
        eel.ischecked = "wrong";
      }
    });
    item.isright = true;
  }
  //一出现错误清空退出
  if (iitem.ischecked == "wrong") {
    flag = true;
    //colorsData状态就要恢复默认
    item.rightCnt = 0;
    item.rightData.forEach((el) => {
      el.isImgRight = false;
    });
    setTimeout(() => {
      item.chooseData.forEach((eel) => {
        eel.ischecked = "";
      });
      flag = false;
    }, 500);
  }
};
</script>

<style lang="scss" scoped>
.colors {
  margin: 10px 0 60px;
  padding: 0 160px;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 30px;
  .item {
    user-select: none;
    position: relative;
    font-size: 28px;
    height: 26px;
    line-height: 26px;
    padding: 30px;
    background: #efeeff;
    border-radius: 34px;
    color: #2f2e41;
    &:hover,
    &.active {
      cursor: pointer;
      background-color: #6c63ff;
      color: #ffffff;
      transition: background-color 0.8s ease;
    }
    &.selected {
      cursor: pointer;
      background: #6c63ff;
      color: #ffffff;
      transition: background-color 0.8s ease;
    }
  }
  .colors-tick-checked {
    position: absolute;
    top: -4px;
    right: -2px;
    width: 29px;
    height: 29px;
    border-radius: 50%;
    background-color: #6c63ff;
    line-height: 29px;
    cursor: pointer;
    text-align: center;
    .el-icon {
      font-size: 20px;
      color: white;
    }
  }
}
.tip {
  font-weight: 400;
  font-size: 30px;
  color: #2f2e41;
  text-align: center;
  .name {
    color: #6c63ff;
  }
}
.logos {
  height: 480px;
  margin: 40px 0;
  width: 100%;
  .logosImg {
    box-sizing: border-box;
    padding: 0 160px;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap: wrap;
    div {
      box-sizing: border-box;
      width: 30%;
      text-align: center;
      margin: 0 auto;
      display: flex;
      justify-content: center;
      align-items: center;
      flex-wrap: wrap;
      .carouselImgOuter {
        width: 64px;
        height: 64px;
        background-size: 100% 100%;
        background-repeat: no-repeat;
        &:hover {
          cursor: pointer;
        }
      }
      .checkIcon {
        width: 100%;
        display: inline-block;
        margin-top: 10px;
        height: 25px;
        text-align: center;
        .el-tag {
          box-sizing: border-box;
          padding: 0 4px;
          .el-icon {
            margin-right: 0;
          }
        }
      }
    }
  }
}
</style>