动态添加时间范围,如何置灰已选择时间?
先说下需求:开始时段和结束时段是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、当删除某行数据时,已删除的数据要重新进行可选和置灰操作。
页面如下:初次进入页面显示:添加一行数据显示:
以下是我的代码,各位大佬后边的逻辑如何实现?

父组件代码
<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>

- 经过验证的有效解决办法
- 自己的经验指引,对解决问题有帮助
- 遵循 Markdown 语法排版,代码语义正确
- 询问内容细节或回复楼层
- 与题目无关的内容
- “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容