<template>
<div :class="`${prefixCls}`">
<h5>Driving Time(second)</h5>
<div :class="`${prefixCls}__wrap`">
<table :class="`${prefixCls}__table`">
<thead>
<th class="col-fix col-fix-1" style="width: 130px"><span>Time</span></th>
<th
style="width: 180px"
v-for="(stop, stopIndex) in props.planLineRouteStopList"
:key="'column_' + stopIndex"
>{{ stop.stopDesc }}
</th>
</thead>
<tbody>
<tr
v-for="(group, groupIndex) in scheduleTimingGroupInfoList"
:key="'group' + groupIndex"
:class="`${prefixCls}__time-row`"
>
<td class="col-fix col-fix-1" style="width: 130px"
><span :style="selectedDrivingRow?.index == groupIndex ? 'color:#5757ff' : ''"
>{{ group.newTime }}
<el-icon class="add-icon ml-8px" @click="insertTimeGroup(group, groupIndex)">
<Plus />
</el-icon>
<el-icon
class="add-icon ml-2px"
v-show="groupIndex != 0"
@click="deleteTimeGroup(group, groupIndex)"
>
<Delete />
</el-icon>
</span>
</td>
<td
class="col-fix col-fix-2"
v-for="(travel, travelIndex) in group.stopTravelTimeList"
:key="'travel_' + groupIndex + travelIndex"
><span>
<el-input
class="no-padding-input"
v-model.number="travel.travelTime"
:ref="'travel_' + groupIndex + travelIndex"
/>
</span>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 插入时间段 -->
<el-dialog
v-model="drivingTimeDialog"
title="New Driving Time"
width="25%"
:before-close="colseDriveTimeDialog"
>
<span>Enter the time </span>
<el-time-picker
prefix-icon="el-icon-date"
v-model="newDrivingTime"
format="HH:mm:ss"
value-format="HH:mm:ss"
/>
<template #footer>
<div class="dialog-footer">
<el-button @click="colseDriveTimeDialog">Cancel</el-button>
<el-button type="primary" @click="handleAddTime"> Confirm </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import type { TabsInstance } from 'element-plus'
import type { PropType } from 'vue'
import { ScheduleInfoParams, ScheduleInfoObj } from '@/api/planning/schedule/type'
import { Plus, Delete } from '@element-plus/icons-vue'
defineOptions({ name: 'OperationDrivingTime' })
// Define types for props
interface StopTravelTime {
travelTime: number
}
interface ScheduleTimingGroup {
startTimeStr?: string
endTimeStr?: string
stopTravelTimeList?: StopTravelTime[]
}
const props = defineProps({
planLineRouteStopList: {
type: [Array, Object] as PropType<Array<any> | Record<string, any>>,
required: true
},
scheduleTimingGroupInfoList: {
type: [Array, Object] as PropType<Array<ScheduleTimingGroup> | Record<string, any>>,
required: true
}
})
const emit = defineEmits(['update:scheduleTimingGroup'])
const { getPrefixCls } = useDesign()
const { t } = useI18n()
const scrollPosition = ref(0)
const isScrollable = ref(false) // 控制滚动条显示状态
const maxScroll = ref(0)
const scrollRef = ref(null)
const message = useMessage()
const prefixCls = getPrefixCls('operation-driving-time')
const drivingTimeDialog = ref(false) // 时间段弹窗
const newDrivingTime = ref('')
const selectedDrivingRow = ref({})
const oldHeadwayInfoList = ref([]) // 时间段列表
const scheduleTimingGroupInfoList = computed(() => {
// props.scheduleTimingGroupInfoLis中第一个新增一个newTime值为Start Time ,最后一个值为End Time,其他值都是原始的group.startTimeStr
const modifiedList = (props.scheduleTimingGroupInfoList || []).map((group, index) => {
const isLast = index === (props.scheduleTimingGroupInfoList || []).length - 1
const isFirst = index === 0
if (isFirst) {
return { ...group, newTime: 'Start Time' }
} else if (isLast) {
return { ...group, newTime: 'End Time' }
} else {
return { ...group, newTime: group.startTimeStr }
}
})
return modifiedList
}, {
deep: true
})
const scheduleInfoObj = ref<ScheduleInfoObj>({
baseVersion: 0,
lineAbbr: '',
lineId: '',
lineNo: 0,
routeScheduleInfoList: []
})
// 新增时间
const handleAddTime = () => {
// 类型安全校验与空值保护
if (!newDrivingTime.value || !selectedDrivingRow.value) {
message.warning(t('pleaseSelectOnTime'))
return
}
// console.log('newDrivingTime.value', selectedDrivingRow.value)
const { startTimeStr, endTimeStr } = selectedDrivingRow.value.row
if (newDrivingTime.value >= endTimeStr || newDrivingTime.value <= startTimeStr) {
const waring = 'please select ' + startTimeStr + ' until ' + endTimeStr + ' Timing '
message.warning(waring)
console.warn('Time range validation failed:', {
inputTime: newDrivingTime.value,
validRange: `${startTimeStr}-${endTimeStr}`
})
return
}
try {
const newGroup = {
...selectedDrivingRow.value.row,
startTimeStr: newDrivingTime.value,
stopTravelTimeList: (selectedDrivingRow.value.row?.stopTravelTimeList || []).map((travel) => ({
...travel,
travelTime: ''
}))
}
const updatedList = [...scheduleTimingGroupInfoList.value]
updatedList.splice(Number(selectedDrivingRow.value.index ?? 0 + 1), 0, newGroup)
emit('update:scheduleTimingGroup', updatedList)
colseDriveTimeDialog()
return true
} catch (error) {
console.error('Time insertion error:', error)
message.error(t('scheduleUpdateFailed'))
return false
}
}
// 添加行
const insertTimeGroup = (group, groupIndex) => {
console.log('insertTimeGroup', group, groupIndex)
if (group.startTimeStr === '24:00:00') {
message.warning('peakSegmentNotAfterMidnight')
return
}
selectedDrivingRow.value = {
index: groupIndex,
row: JSON.parse(JSON.stringify(group))
}
drivingTimeDialog.value = true
}
// 删除当前行
const deleteTimeGroup = (group, groupIndex) => {
const updatedList = scheduleTimingGroupInfoList.value.filter((_, index) => index !== groupIndex)
emit('update:scheduleTimingGroup', updatedList)
}
// 关闭对话框
const colseDriveTimeDialog = () => {
newDrivingTime.value = ''
selectedDrivingRow.value = {}
drivingTimeDialog.value = false
}
watch(
() => props.scheduleTimingGroupInfoList,
(newVal) => {
if (newVal) {
emit('update:scheduleTimingGroup', JSON.parse(JSON.stringify(newVal)))
}
},
{ deep: true }
)
// 添加输入变更处理
const handleTimeInput = (value, groupIndex) => {
const updatedList = scheduleTimingGroupInfoList.value.map((group, index) => {
if (index === groupIndex) {
return { ...group, startTimeStr: value }
}
return group
})
emit('update:scheduleTimingGroup', updatedList)
}
onMounted(() => {})
// 组件卸载清理
onUnmounted(() => {})
</script>
<style scoped lang="scss">
$prefix-cls: #{$namespace}-operation-driving-time;
.#{$prefix-cls} {
width: 100%;
font-size: 14px;
h5 {
color: rgba(17, 43, 92, 1);
font-size: 18px;
line-height: 40px;
}
&__wrap {
max-height: calc(100vh - 550px);
overflow: auto;
// Webkit浏览器滚动条样式
&::-webkit-scrollbar {
width: 8px;
height: 8px;
background-color: transparent;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(73, 86, 197, 0.3);
border-radius: 4px;
background-clip: content-box;
border: 2px solid transparent;
}
&::-webkit-scrollbar-thumb:hover {
background-color: rgba(73, 86, 197, 0.5);
}
// IE/Edge滚动条样式
-ms-overflow-style: auto;
scrollbar-width: thin;
scrollbar-color: rgba(73, 86, 197, 0.3) transparent;
}
&__table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
td,
th {
height: 35px !important;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border: 1px solid #ddd;
box-sizing: border-box;
padding: 0;
span {
display: flex;
height: 100%;
width: 100%;
align-items: center;
justify-content: center;
}
}
th {
position: sticky;
top: -1px; // 调整表头上面不显示滚动内容
background: rgba(73, 86, 197, 0.2) !important;
font-weight: 500;
z-index: 3;
}
th.col-fix {
z-index: 4;
}
.col-fix {
position: sticky;
z-index: 2;
border: none;
background: #f4f4f4;
}
.col-fix-1 {
left: 0;
}
.col-fix-2 {
left: 120px;
}
td.col-fix,
th.col-fix {
span {
padding: 0 4px;
border-bottom: 1px solid #ddd;
border-left: 1px solid #ddd;
box-sizing: border-box;
}
}
}
&__time-row {
&:hover .add-icon {
opacity: 1;
font-weight: 600;
cursor: pointer;
}
.add-icon {
opacity: 0;
transition: opacity 0.2s ease;
}
}
}
</style>
<style lang="scss">
$prefix-cls: #{$namespace}-operation-driving-time;
.#{$prefix-cls} {
.no-padding-input .el-input__inner {
text-align: center !important;
padding: 0px !important;
border: none !important;
font-size: 14px !important;
}
} 当前scheduleTimingGroupInfoList 的数据通过新增、修改input、删除来触发update:scheduleTimingGroup 使其父组件保存时获取最新数据