动态添加时间范围,如何置灰已选择时间?

作者站长头像
站长
· 阅读数 15

先说下需求:开始时段和结束时段是24小时按照30分钟分割成48个点的数据,数据格式如下:["00:00","00:30","01:00","01:30",.....,"23:30"],每行数据后都有新增和删除操作。

1、开始时段选择后,结束时段小于开始时段的值置灰不能选择。

2、假如:开始时段选择了 "01:00",结束时段选择了 "03:30",点击后边新增时,前边已选的数据置灰不能选择,第二条的数据选择只能在这个范围内:"00:00"-"01:00","03:30"-"23:30", 当用户开始时间选择了"00:00",则结束时间只能选择"00:00"到"01:00"范围内的任何一个值,其他时间段都置灰。 当用户开始时间选择了"03:30",则结束时间只能在"03:30"到"23:30"范围内的任何一个值,而"00:00"到"03:30"置灰不能选择。

3、当第二条数据选择了 "05:00",结束时间选择了"05:30",点击后边新增时,前边已选的数据置灰不能选择,第三条的数据选择只能在这个范围内:"00:00"-"01:00","03:30"-"05:00","05:30"-"23:00", 当用户开始时间选择了"00:00",则结束时间只能选择"00:00"到"01:00"范围内的任何一个值,其他时间段都置灰。 当用户开始时间选择了"03:30",则结束时间只能选择"03:30"到"05:00"范围内的任何一个值,其他时间段都置灰。 当用户开始时间选择了"05:30",则结束时间只能在"05:30"到"23:30"范围内的任何一个值,而"00:00"到"05:30"置灰不能选择。 以此类推...

4、当删除某行数据时,已删除的数据要重新进行可选和置灰操作。

页面如下:初次进入页面显示:动态添加时间范围,如何置灰已选择时间?添加一行数据显示:动态添加时间范围,如何置灰已选择时间?

以下是我的代码,各位大佬后边的逻辑如何实现?

https://codepen.io/wdvkmqph-the-reactor/pen/abxVWrK

回复
1个回答
avatar
test
2024-06-20

父组件代码

<template>
  <div class="app-container">
    <el-table
      size="mini"
      class="mt15"
      border
      :data="tableData"
      :span-method="arraySpanMethod"
    >
      <el-table-column
        label="季节"
        prop="season"
        align="center"
        min-width="120"
      >
      </el-table-column>
      <el-table-column
        label="时段"
        prop="period"
        align="center"
        min-width="120"
      >
      </el-table-column>
      <el-table-column
        label="具体时间"
        prop="timeList"
        align="center"
        min-width="350"
      >
        <template slot-scope="scope">
          <span
            v-for="(item, index) in scope.row.timeList"
            :key="index"
            class="ml8"
          >
            <el-tag size="mini" v-if="item.startTime">{{
              item.startTime + "-" + item.endTime
            }}</el-tag>
          </span>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" min-width="120">
        <template slot-scope="scope">
          <el-button
            type="text"
            class="primary"
            size="mini"
            icon="el-icon-edit"
            @click="handleFormEdit(scope.row, scope.$index)"
            >编辑
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 添加时间弹窗 -->
    <add-time
      :show-add-dialog="showAddDialog"
      @get-add-result="getAddResult"
    ></add-time>
  </div>
</template>
<script>
// 组件
import AddTime from "./component/addTime.vue";
// utils
import { deepClone } from "js-fastcode";
export default {
  name: "VueTemplateIndex",
  components: { AddTime },
  data() {
    return {
      companyArr: [],
      companyPos: 0,
      showAddDialog: { visible: false },
      idx: "", // 编辑当前行行数
      tableData: [],
    };
  },
  mounted() {
    const season = ["春季", "夏季"];
    const period = ["尖峰", "高峰",  "低谷"];
    this.tableData = Array.from({ length: season.length }, (_, i) =>
      period.map((pj, j) => ({
        season: season[i],
        period: pj,
        timeList: [],
      }))
    ).flat();
    this.merge(this.tableData);
  },
  methods: {
    // 表格行合并方法
    merge(tableData) {
      // 要合并的数组的方法
      this.companyArr = [];
      this.companyPos = 0;
      for (let i = 0; i < tableData.length; i++) {
        if (i === 0) {
          // 第一行必须存在
          this.companyArr.push(1);
          this.companyPos = 0;
        } else {
          // 判断当前元素与上一个元素是否相同 this.companyPos是companyArr内容的序号
          if (tableData[i].season === tableData[i - 1].season) {
            this.companyArr[this.companyPos] += 1;
            this.companyArr.push(0);
          } else {
            this.companyArr.push(1);
            this.companyPos = i;
          }
        }
      }
    },
    // 合并行
    arraySpanMethod({ row, column, rowIndex, columnIndex }) {
      if (columnIndex === 0) {
        const _row_1 = this.companyArr[rowIndex];
        const _col_1 = _row_1 > 0 ? 1 : 0; // 如果被合并了_row=0则它这个列需要取消
        return {
          rowspan: _row_1,
          colspan: _col_1,
        };
      }
    },
    // 新增数据
    handleFormEdit(list, index) {
      let arr = deepClone(this.tableData),
        brr = [];
      brr = arr
        .filter((item) => item.season === list.season)
        .map((item) => item.timeList)
        .flat();
      this.idx = index;
      this.showAddDialog = {
        visible: true,
        title: "编辑",
        data: brr,
        list: list.timeList,
      };
    },
    // 新增回调
    getAddResult(list) {
      this.tableData[this.idx].timeList = list;
    },
  },
};
</script>

子组件代码

<template>
  <el-dialog
    :visible.sync="showAddDialog.visible"
    v-dialogDrag
    width="620px"
    :title="showAddDialog.title"
    :close-on-click-modal="false"
    :append-to-body="true"
    @close="handleDialogClose"
  >
    <el-table size="mini" border :data="tableData">
      <el-table-column prop="startTime" align="center" label="开始时段">
        <template slot-scope="scope">
          <el-select
            v-model="scope.row.startTime"
            style="width: 138px"
            placeholder="开始时段"
            size="mini"
            :disabled="scope.row.disabled"
            @change="handleStartChange(scope.row)"
          >
            <el-option
              v-for="item in startTimeList"
              :key="item.value"
              :label="item.label"
              :value="item.value"
              :disabled="item.disabled"
            >
            </el-option>
          </el-select>
        </template>
      </el-table-column>
      <el-table-column prop="endTime" align="center" label="结束时段">
        <template slot-scope="scope">
          <el-select
            v-model="scope.row.endTime"
            style="width: 138px"
            placeholder="结束时段"
            size="mini"
            :disabled="!scope.row.startTime || scope.row.disabled"
          >
            <el-option
              v-for="item in endTimeList"
              :key="item.value"
              :label="item.label"
              :value="item.value"
              :disabled="item.disabled"
            >
            </el-option>
          </el-select>
        </template>
      </el-table-column>
      <el-table-column align="center" label="操作">
        <template slot-scope="scope">
          <span>
            <el-button
              v-if="scope.$index === tableData.length - 1"
              type="primary"
              icon="el-icon-plus"
              circle
              size="mini"
              @click="handleRowAdd(scope.row, scope.$index)"
            >
            </el-button>
            <el-button
              type="warning"
              icon="el-icon-minus"
              circle
              size="mini"
              @click="handleRowDelete(scope.row, scope.$index)"
            ></el-button>
          </span>
        </template>
      </el-table-column>
    </el-table>
    <div slot="footer" class="dialog-footer">
      <el-button size="small" @click="handleDialogClose">取 消</el-button>
      <el-button size="small" type="primary" @click="handleDialogSave">
        确 定
      </el-button>
    </div>
  </el-dialog>
</template>

<script>
import { devideTimes, deepClone } from "js-fastcode"; // 引入自定义js库
export default {
  name: "",
  props: {
    showAddDialog: {
      type: Object,
      default: () => ({}),
    },
  },
  data() {
    return {
      tableData: [],
      propsData: [], // 存储父组件传递过来的时间段
      allAddData: [], // 存储所有已选时间段
    };
  },
  watch: {
    showAddDialog: {
      handler(newVal, oldVal) {
        this.tableData = [];
        // 如果当前行有数据,则显示当前行数据
        if (newVal.list.length) {
          this.tableData = newVal.list.map((item) => ({
            ...item,
            disabled: true,
          }));
        } else {
          // 如果当前行没有数据,则默认显示开始时间和结束时间
          this.tableData = [{ startTime: "", endTime: "" }];
        }
        for (let i in this.endTimeList) {
          this.endTimeList[i].disabled = false;
        }
        for (let i in this.startTimeList) {
          this.startTimeList[i].disabled = false;
        }
        this.propsData = deepClone(newVal.data);
        // 将当前季节下的所有时间段都放在数组中
        this.allAddData = deepClone(newVal.data);
        this.handleDisable();
      },
      deep: true,
    },
  },
  computed: {
    // 获取默认的1-24小时数据
    timeOptions() {
      return devideTimes(30, 2);
    },
    startTimeList() {
      return this.timeOptions.map((item) => ({
        value: item,
        label: item,
        disabled: false,
      }));
    },
    endTimeList() {
      return this.timeOptions.map((item) => ({
        value: item,
        label: item,
        disabled: false,
      }));
    },
  },
  mounted() {},
  methods: {
    // 新增行
    handleRowAdd(row, idx) {
      this.allAddData = this.handleUnique([
        ...this.tableData,
        ...this.propsData,
      ]);
      this.handleDisable();
      // 判断开始时间所有项全部是不是禁用状态,如果是则说明所有时段已选择,否则未全部选择
      let flag = this.startTimeList.every((item) => item.disabled);

      if (flag) {
        this.$message.warning("所有时段都已选择");
        return false;
      }
      if (!row.startTime && !row.endTime) {
        this.$message.warning("开始时段和结束时段必填");
        return false;
      }
      this.tableData[idx].disabled = true;
      this.tableData.push({
        startTime: "",
        endTime: "",
      });
    },
    // 删除行
    handleRowDelete(row, index) {
      // 当只有一条数据时,初始化表格数据
      if (this.tableData.length === 1) {
        this.tableData = [{ startTime: "", endTime: "" }];
      } else {
        this.tableData.splice(index, 1);
      }

      this.propsData = this.propsData.filter(
        (item) =>
          !(item.startTime === row.startTime && item.endTime === row.endTime)
      );
      this.allAddData = this.handleUnique([
        ...this.tableData,
        ...this.propsData,
      ]);
      this.handleDisable();
    },
    // 数组对象去重
    handleUnique(arr) {
      let obj = {};
      return arr.reduce((cur, next) => {
        obj[next.startTime + next.endTime]
          ? ""
          : (obj[next.startTime + next.endTime] = true && cur.push(next));
        return cur;
      }, []);
    },
    // 切换起始时间
    handleStartChange(time) {
      let times = this.timeOptions;
      // 找到所选开始时间的下标
      let start_index = times.findIndex((value) => value === time.startTime);
      // 将结束时间小于开始时间的选项禁用
      for (let i = 0; i < start_index; i++) {
        this.endTimeList[i].disabled = true;
      }
      // 根据开始时间和结束时间的可用性设置结束时间的禁用状态
      for (let i = start_index + 1; i < this.startTimeList.length; i++) {
        if (this.startTimeList[i].disabled) {
          for (const [index, endTime] of this.endTimeList.entries()) {
            if (start_index <= index && index < i) {
              endTime.disabled = false;
            } else {
              endTime.disabled = true;
            }
          }
          break;
        } else {
          this.endTimeList[i].disabled = false;
        }
      }
    },
    // 禁止时间交集
    handleDisable() {
      let times = this.timeOptions;
      // 将开始时段所有选项置为可用
      this.startTimeList.forEach((start) => {
        start.disabled = false;
      });

      // 遍历所有已选时间段
      this.allAddData.forEach((item) => {
        let start_index = times.findIndex((value) => value === item.startTime);
        let end_index = times.findIndex((value) => value === item.endTime);
        this.startTimeList.forEach((start, i) => {
          if (i >= start_index && i <= end_index) {
            start.disabled = true;
          }
        });
      });
    },
    handleDialogClose() {
      this.showAddDialog.visible = false;
    },
    // 保存
    handleDialogSave() {
      // 当前数据大于两条,但是有一条数据未填写进行提示判断
      let flag =
        this.tableData.length >= 2 &&
        this.tableData.some(
          (item) => item.startTime === "" || item.endTime === ""
        );
      if (flag) {
        this.$message.warning("请将所有时段填写完整");
        return false;
      }
      // 提交时将未填写时段的数据去除,主要是针对只有一条数据未填写情况
      this.tableData = this.tableData.filter(
        (item) => item.startTime && item.endTime
      );
      this.showAddDialog.visible = false;
      this.$emit("get-add-result", this.tableData);
    },
  },
};
</script>

<style scoped lang="scss">
.remove-icon,
.plus-icon {
  font-size: $fs22;
  vertical-align: middle;
  margin-left: 10px;
}
</style>
回复
likes
适合作为回答的
  • 经过验证的有效解决办法
  • 自己的经验指引,对解决问题有帮助
  • 遵循 Markdown 语法排版,代码语义正确
不该作为回答的
  • 询问内容细节或回复楼层
  • 与题目无关的内容
  • “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容