Skip to content

每日一题:会员卡定制

作者:江月迟迟
发表于:2024-12-10
字数统计:2315 字
预计阅读8分钟

介绍

随着数字化时代的来临,线上平台成为商家与用户之间的重要连接点,线上 VIP 卡定制功能可以满足用户的个性化需求、提升品牌形象和用户体验,并通过数据分析和个性化推荐实现更好的营销效果。这是一个与时俱进的方式,使商家能够与用户建立更紧密的关系,赢得市场竞争的优势。

本题需要你运用所学实现一个线上 VIP 卡的定制功能。

准备

本题已经内置了初始代码,打开实验环境,目录结构如下:

txt
├── css
├── effect1.gif
├── effect2.gif
├── effect3.gif
├── images
├── index.html
└── js
    ├── index.js
    └── vue3.global.js

其中:

  • css 是需要样式文件夹。
  • effect1.gif ~ effect3.gif 为三个目标的效果图。
  • images 是图片文件夹。
  • js/vue3.global.js 是 vue3.x 文件。
  • js/index.js 是需要补充代码的 js 文件。
  • index.html 是需要补充代码的主页面。

注意:打开环境后发现缺少项目代码,请手动键入下述命令进行下载:

bash
cd /home/project
wget https://labfile.oss.aliyuncs.com/courses/5340/vip-card.zip && unzip  vip-card.zip && rm vip-card.zip

选中 index.html 右键启动 Web Server 服务(Open with Live Server),让项目运行起来。接着,打开环境右侧的【Web 服务】,就可以在浏览器中看到如下效果:

图片描述

目标

  1. 完善 index.html<label for="cardNumber"> 标签下的 TODO 代码,实现将 Card Number 输入框中输入的卡号以一定规则显示在会员卡最终效果显示区域的效果。具体需求为:
  • 卡号显示输入值的前 19 位,多出 19 个字符直接忽略不做处理。
  • 卡号显示效果为:使用空格将上面输入值的前 19 位分为 4 组,前 3 组分别由 5 个字符组成,最后 1 组由 4 个字符组成。
  • 其中第一组和最后一组显示具体输入的值,中间两组每组的前 4 位由 * 号代替,最后一位显示对应的输入值。例如:输入的卡号为 20 位字符串 “12345678901234567890” 显示效果为 “12345 ****0 ****5 6789”。

图片描述

最终效果可参考文件夹下面的 gif 图,图片名称为 effect1.gif(提示:可以通过 VS Code 或者浏览器预览 gif 图片)。

需要注意的是,分组空格的 <div> 标签(.card-item__numberItem)元素需要添加 -active 类,其最终使用的 DOM 结构为:

html
<span><div class="card-item__numberItem -active"> </div></span>

最终渲染在浏览器中的 DOM 结构为:

html
    <span><div class="card-item__numberItem">1</div></span>
    <span><div class="card-item__numberItem">2</div></span>
    <span><div class="card-item__numberItem">3</div></span>
    <span><div class="card-item__numberItem">4</div></span>
    <span><div class="card-item__numberItem">5</div></span>
    <span><div class="card-item__numberItem -active"> </div></span>
    <span><div class="card-item__numberItem">*</div></span>
    <span><div class="card-item__numberItem">*</div></span>
    <span><div class="card-item__numberItem">*</div></span>
    <span><div class="card-item__numberItem">*</div></span>
    <span><div class="card-item__numberItem">0</div></span>
    <span><div class="card-item__numberItem -active"> </div></span>
    <span><div class="card-item__numberItem">*</div></span>
    <span><div class="card-item__numberItem">*</div></span>
    <span><div class="card-item__numberItem">*</div></span>
    <span><div class="card-item__numberItem">*</div></span>
    <span><div class="card-item__numberItem">5</div></span>
    <span><div class="card-item__numberItem -active"> </div></span>
    <span><div class="card-item__numberItem">6</div></span>
    <span><div class="card-item__numberItem">7</div></span>
    <span><div class="card-item__numberItem">8</div></span>
    <span><div class="card-item__numberItem">9</div></span>
  1. 完善 index.html<div class="card-item__name"> 标签下的 TODO 代码,实现将输入框 Card Holders 中输入的用户名以一定规则显示在会员卡最终效果显示区域的效果。具体需求为:完成输入卡片持有者(Card Holders)Full Name 的显示效果。Full Name 的长度不限,若输入值中有 1 到多个连续的空格,则只保留 1 个空格,且英文字母均为大写。例如:输入值为 "T   o  n ney"(即,字母 T 后面有 3 个空格,o 后面有两个空格,n 后面有一个空格),则显示效果为 "T O N NEY"。其最终 DOM 结构为:
html
<!-- 输入值为字符串 “T   o  n ney” 时,最终 DOM 结构如下:-->
  <span class="card-item__nameItem">T</span>
  <span class="card-item__nameItem"> </span>
  <span class="card-item__nameItem">o</span>
  <span class="card-item__nameItem"> </span>
  <span class="card-item__nameItem">n</span>
  <span class="card-item__nameItem"> </span>
  <span class="card-item__nameItem">n</span>
  <span class="card-item__nameItem">e</span>
  <span class="card-item__nameItem">y</span>

图片描述

最终效果可参考文件夹下面的 gif 图,图片名称为 effect2.gif(提示:可以通过 VS Code 或者浏览器预览 gif 图片)。

  1. 补充 js/index.js 中的 TODO 代码,实现 computed 计算属性 minCardMonthwatch 监听 cardYear,最终实现本年只能选择当前月份及以后的月份的功能。即,当页面中Expiration Date 日期选择下拉框中的 Year 下拉框为当前年份时,minCardMonth 的值只能选择当前月份及其之后的月份。例如:当前年月为 2023 年 7 月,且 Year 下拉框选中的为 2023(即 minCardYear 值为 "2023"),则 Month 下拉框只能选 7~12月。

图片描述

最终效果可参考文件夹下面的 gif 图,图片名称为 effect3.gif(提示:可以通过 VS Code 或者浏览器预览 gif 图片)。

规定

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

判分标准

  • 完成目标 1,得 5 分。
  • 完成目标 2,得 10 分。
  • 完成目标 3,得 5 分。

总通过次数: 23 | 总提交次数: 56 | 通过率: 41.1%

题解:

js
req.on("end", () => {
      let { interested = [] } = qs.parse(body);
      // TODO: 补充个性化页面处理代码
      let html = ''
      if (!interested.length) {
        html = `<div class="unselect">你还未选择任何感兴趣的标签!</div>`
      } else {
        // 处理interested只有一个,不是数组的情况
        const hashMap = {}
        const result = []
        if (!Array.isArray(interested)) interested = [interested]
        for(let i = 0; i < interested.length; i++) {
          // 外层遍历
          for(let j = 0; j < data.length; j++) {
            // 内层遍历匹配
            if (interested[i] === data[j]['tag'] && !hashMap[interested[i]]) {
              // 使用tag匹配,如果匹配,和不重复出现(hashMap)就填入数据
              result.push(data[j])
              hashMap[interested[i]] = 1
              // 深度优先,在此遍历re
              for(let tag of data[j]['relevance']) {
                // 这里也可以用hashmap,都行
                if (interested.indexOf(tag) === -1) {
                  interested.push(tag)
                }
              }
            }
          }
        }
        // 处理html
        for(let item of result) 
        html += `<div class="interest">
        <div class="tag">${item.tag}</div>
        <div>${item.content}</div>
    </div>`
    // console.log(html);
      }
      let customized = fs.readFileSync(path.join(__dirname, "../customized.html"), {
        encoding: "utf8",
      });
      customized = customized.replace('<body></body>', `<body>${html}</body>`)
      // 将读取的 index.html 内容写入响应中,并返回给前端展示
      res.writeHead(200, { "Content-Type": "text/html" });
      res.write(customized);
      res.end();
    });
js
 <!-- 目标 1 TODO:在当前 <label for="cardNumber"> 标签中补充代码,完成输入卡号的显示效果。-->
  <span v-for="(n, $index) in otherCardMask" :key="$index">
  <transition name="slide-fade-up">
  <!-- 目标 1 TODO Start -->
  <div class="card-item__numberItem" v-if="cardNumber && $index < cardNumber.length && $index > 4 && $index < 15 && n.trim() !== ''">*</div>
  <div class="card-item__numberItem" v-else-if='cardNumber && $index < cardNumber.length' :class="n.trim() === '' ? '-active' : ''">{{cardNumber[$index]}}</div>
  <div class="card-item__numberItem" v-else :class="n.trim() === '' ? '-active' : ''">{{n}}</div>
  <!-- 目标 1 TODO End -->
js
 <!-- 目标 2 TODO:在下面补充代码,完成输入卡片持有者(Card Holders)Full Name 的显示效果。-->
 <!-- 目标 2 TODO Start -->
 <span class="card-item__nameItem" v-for="(item, $index) in cardName.replaceAll(/\s+/g, ' ')" :key="$index">{{ item }}</span> 
 <!-- 目标 2 TODO End -->
js
// 目标 3 TODO Start:补充计算属性 minCardMonth 及 cardYear 的 watch 监听,最终实现本年只能选择当前月份及以后的月份。即,当页面中选中的 Year 下拉框为当前年份时,minCardMonth 的值只能为包含当前月份及其之后的月份。例如:当前年月为 2023 年 7 月,且 Year 下拉框选中的为 2023(即 minCardYear 为 2023),则只能选 7~12月。
    const minCardMonth = computed(() => {
      console.log(data.cardYear);
      console.log(data.minCardYear);
      if (data.cardYear === data.minCardYear) {
        data.cardMonth = ''
        return new Date().getMonth() + 1
      }
      return 1;
    })
    watch(
      () => {
        if (data.cardMonth < minCardMonth.value) {
          data.cardMonth = ''
        }
        return data.cardYear.value; // 返回想要监听的响应式数据,ref 定义的数据需要返回其 value 属性
      },
      () => {
        console.log("data.cardYear 发生了变化");
      },
      {
        immediate: true,
      }
    );
    // 目标 3 TODO End