Skip to content

表单生成器(大学组)

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

介绍

小蓝现在遇到一个需求,要求是设计在线动态表单生成器,通过请求到的 JSON 数据即快速生成网页表单,这可把他难倒了。请你按照题目中给出的 Vue 组件与 JSON 数据格式,帮助小蓝完成多选框、下拉框的代码编写。

准备

开始答题前,需要先打开本题的项目代码文件夹,目录结构如下:

txt
├── components
│   ├── checkbox.js
│   ├── rate.js
│   └── select.js
├── css
│   ├── icon
│   │   ├── star.svg
│   │   └── star_full.svg
│   ├── index.css
│   ├── rate.css
│   └── reset.css
├── data.json
├── effect-1.gif
├── effect-2.gif
├── index.html
└── lib
    ├── axios.min.js
    └── vue.min.js

其中:

  • index.html 是主页面。
  • components 是项目组件文件夹。
  • lib 是存放项目依赖的文件夹。
  • css 是存放项目样式的文件夹。
  • data.json 是请求需要用到 JSON 数据。
  • effect-1.gifeffect-2.gif 是项目完成后的效果图。

请通过 VS Code 中的 live server 插件启动本项目,让项目运行起来,效果如下:

初始效果

注意:一定要通过 live server 插件启动项目,避免项目无法访问,影响做题。

项目中的评分组件(rate.js)已完成。

目标

找到 index.html 以及 components 下面的多选框组件(checkbox.js)和下拉框组件(select.js)中的 TODO 部分,达成以下目标:

  1. 请仔细阅读接口设计,完成 index.html 中的 TODO 部分,根据请求回来的 JSON 数据中 type 的不同,动态生成对应的表单组件。完成多选框组件(checkbox.js)和下拉框组件(select.js)初始化时需要定义的 data 以及方法,将数据正确显示到对应的组中,确保页面无报错且组件正常显示。

接口数据设计如下:

  • type 表示组件类型
  • title 为表单项的名称
  • field 为数据所对应的原始字段名
  • value 为表单项对应的值,有多种类型
  • options 有选项的表单选择内容

具体各个组件的数据结构说明如下:

  • 多选框
js
{
    type: 'checkbox'
    title: 表单项名称, 例:选择美食
    field: 对应接口字段, 例:likes
    value: 多选选中的值, 例:["spn","oaj"]
    options: 选项值,例: [
        {
          "name": "黄金糕",
          "label": "hjg"
        },
        {
          "name": "双皮奶",
          "label": "spn"
        },
        {
          "name": "蚵仔煎",
          "label": "oaj"
        },
        {
          "name": "龙须面",
          "label": "lxm"
        }
      ]
}
  • 下拉框
js
{
    type: 'select'
    title: 表单项名称, 例:参加活动
    field: 对应接口字段, 例:activity
    value: 选中的值, 例:online
    options: 选项值,例: [
        { "label": "online", "name": "线上活动" },
        { "label": "ground", "name": "地推活动" },
        { "label": "topic", "name": "线下主题活动" }
      ]
}
  • 评分组件
js
{
    type: 'rate'
    title: 表单项名称, 例:评分
    field: 对应接口字段, 例:score
    value: 分值 1~5, 例:5
}

目标 1 完成后效果如下:

目标1

  1. 继续完善多选框组件(checkbox.js)和下拉框组件(select.js), 使组件数据改变时在 index.html(即父组件中)点击获取数据按钮能够正常获取表单数据。

目标 2 完成后效果

图片描述

  1. index.html(即父组件) 中点击渲染数据,表单中的数据(即对应的子组件数据)需要正确显示。

目标 3 完成后效果(考生可以根据提供的 JSON 数据自行测试)

图片描述

规定

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

判分标准

  • 完成目标 1,得 10 分。
  • 目标 2 中每完成一个组件,得 5 分,共 10 分。
  • 完成目标 3,得 5 分。

总通过次数: 1 | 总提交次数: 1 | 通过率: 100%

难度: 中等 标签: 蓝桥杯, 2023, 国赛, Web 前端, Vue.js

题解

index.html

vue
<!DOCTYPE html>
<html lang="zn-CH">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="./lib/vue.min.js"></script>
    <script src="./lib/axios.min.js"></script>
    <link rel="stylesheet" href="./css/index.css" />
    <title>表单生成器</title>
  </head>

  <body>
    <div id="app">
      <form>
        <div class="left-box">
          <!-- 组件容器 -->
          <div v-for="(item, i) in formData" :key="`${i}item`" class="item">
            <!-- 此处 label 为表单的标题 -->
            <div class="label">{{item.title}}</div>
            <!-- TODO: 根据 formData 中的 type 不同完成组件渲染  -->
            <my-select v-if="item.type === 'select'" :options="item.options" v-model="item.value"></my-select>
            <my-checkbox v-if="item.type === 'checkbox'" v-for="radio in item.options" :key="radio" :label="radio.label" v-model="item.value">{{ radio.name }}</my-checkbox>
            <my-rate v-if="item.type === 'rate'" v-model="item.value"></my-rate>
          </div>
        </div>
        <!-- 表单设置 -->
        <div class="right-box">
          <div>
            <button class="btn" @click="fetchData">初始化</button>
            <button class="btn" id="result" @click="confirm">获得数据</button>
            <button class="btn" id="render" @click="setData">渲染数据</button
            ><br /><br />
          </div>
          <textarea v-model="testData" cols="45" rows="12"></textarea>
          <div class="error" v-if="hasError">JSON 格式输入有误,请仔细检查</div>
        </div>
      </form>
    </div>
    <script src="./components/checkbox.js"></script>
    <script src="./components/select.js"></script>
    <script src="./components/rate.js"></script>
    <script>
      var app = new Vue({
        el: "#app",
        data: {
          formData: [],
          testData: "",
          hasError: false,
        },
        created() {
          this.fetchData();
        },
        methods: {
          async fetchData() {
            // 请求 JSON 数据
            const { data } = await axios.get("./data.json");
            this.formData = data.result;
            this.confirm();
          },
          // 设置数据
          setData(e) {
            e && e.preventDefault();
            try {
              const newData = JSON.parse(this.testData);
              this.formData = this.formData.map((x) => {
                x.value = newData[x.field];
                return x;
              });
              this.hasError = false;
            } catch (err) {
              if (err) {
                this.hasError = true;
              }
            }
          },
          // 获得数据
          confirm(e) {
            e && e.preventDefault();
            const params = {};
            this.formData.forEach(({ field, value }) => {
              params[field] = value;
            });
            this.testData = JSON.stringify(params, null, 2);
          },
         
        },
      });
    </script>
  </body>
</html>

checkbox.js

js
/*
 * 多选框组件
 */
const checkboxTemplate = `
<div class="radio-box">
  <input type="checkbox" :checked="isCheck" @change="change" :id="label">
  <label :for="label" class="radio-stype check-box"></label><span> <slot /> </span>
</div>
`;
Vue.component("my-checkbox", {
  template: checkboxTemplate,
  props: ["label", "value"],
  // TODO 请在此继续完成组件代码的编写
  data() {
    return {
      isCheck: false,
      checkList: []
    }
  },
  mounted() {
    this.isCheck = this.value.includes(this.label);
  },
  watch: {
    value(newValue) {
      // 当 value 属性发生变化时,更新多选框的选中状态
      this.isCheck = newValue.includes(this.label);
    }
  },
  computed: {
    
  },
  methods: {
    change() {
      console.log(this.label, this.value);
      if (this.value.includes(this.label)) {
        this.value = this.value.filter(item => item !== this.label)
      } else {
        this.value.push(this.label)
      }
      console.log(this.label, this.value);
      this.$emit("input", this.value);

    }
  }
});

select.js

js
/*
 * 选择框组件
 */
const selectTemplate = `
<div class="select">
<div class="input" @click="openValue">
  <input id="selectVal" v-model="content" type="text" placeholder="请点击选择">
  <span class="tri" :class="{tri_up: show}"></span>
</div>
<div class="list" v-show="show">
  <ul>
    <li @click="getvalue(item)" v-for="(item,index) in listData" :key="item.index">{{ item.name }}</li>
  </ul>
</div>
</div>
`;
Vue.component("my-select", {
  template: selectTemplate,
  props: ["options", "value"],
  // TODO 请在此继续完成组件代码的编写
  data() {
    return {
      content: '',
      show: false,
      listData: [],
      label: ''
    }
  },
  watch: {
    value() {
      this.options.forEach(item => {
        if (item.label === this.value) {
          this.content = item.name
        }
      })
    },
    options() {
      this.listData = this.options
      this.listData.forEach((item, index) => {
        item.index = index
      })
    }
  },
  mounted() {
    this.options.forEach(item => {
      if (item.label === this.value) {
        this.content = item.name
      }
    })
    this.listData = this.options
    this.listData.forEach((item, index) => {
      item.index = index
    })
  },
  methods: {
    openValue() {
      this.show = !this.show
    },
    getvalue(item) {
      this.content = item.name
      this.show = !this.show
      this.$emit('input', item.label)
    }
  }
});

rate.js

js
/*
 * 评分组件
  星星 UI 文件路径说明:
      1. 点亮:`css/icon/star_full.svg`
      2. 未点亮:`css/icon/star.svg`
 */
const rateTemplate = `
      <div class="rate">
        <div @click="select(i)" v-for="i in 5" :key="i+'s'" class="item">
          <object class="icon" :data="getPath(i)" type="image/svg+xml"></object>
        </div>
      </div>
      `;
Vue.component("my-rate", {
  template: rateTemplate,
  props: ["value"],
  data() {
    return {
      curCount: 0,
    };
  },
  watch: {
    value() {
      this.curCount = this.value;
    },
  },
  mounted() {
    this.curCount = this.value;
  },
  methods: {
    select(i) {
      this.curCount === i ? (this.curCount = 0) : (this.curCount = i);
      this.$emit("input", this.curCount);
    },
    getPath(i) {
      return `css/icon/star${i <= this.curCount ? "_full" : ""}.svg`;
    },
  },
});