| | |
| | | const MARK_ICON_RED = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/36c926b7-1e6d-4310-86c2-a71b69dc49b7.png' |
| | | const MARK_ICON_GREEN = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/8f048a0f-3c36-4b6e-ae14-77d85bc919bf.png' |
| | | |
| | | const NEVER_EXPIRE = -1 // -1 永不失效 |
| | | const IMMEDIATE_EFFECT = -2 // -2 立即生效 |
| | | const MAX_VERTICES = 16 // 最大顶点数限制 |
| | | const MAX_AREA = 81 // 最大区域面积限制(单位:平方公里) |
| | | let centerPoint = [104.066301, 30.572961] |
| | |
| | | map.on('click', (e: any) => { |
| | | // layerOptions.isActive = false |
| | | console.log('点击事件', e) |
| | | if (isDrawing) |
| | | return |
| | | // 检查是否点击在运营区域(主围栏)内或者点击在禁行区内 |
| | | const isPointInFence = fence && (!isDrawing || modalType.value === 'detail') && isPointInPolygon(e.lnglat, fence.getPath()) |
| | | const isPointInDisallowedArea = fence && (!isDrawing || modalType.value === 'detail') && disallowedAreaList.value?.some((item: any) => isPointInPolygon(e.lnglat, item.fence?.getPath())) |
| | |
| | | handleDisallowedAreaEdit(disabledArea) |
| | | } |
| | | else { |
| | | console.log('点击在运营区域内') |
| | | // 关闭所有区域的编辑态 |
| | | fenceEditor?.close() |
| | | disallowedAreaList.value.forEach((item: any) => item.isActive = false) |
| | |
| | | /** 绘制禁行区域多边形围栏 */ |
| | | function drawDisallowedAreaPolygon(id: any) { |
| | | // 正在绘制或编辑时,禁止再次绘制 |
| | | if (isDrawing || isEditing) { |
| | | // if (isDrawing || isEditing) { |
| | | if (isEditing) { |
| | | console.log('正在绘制或编辑禁行区,禁止再次绘制') |
| | | return |
| | | } |
| | |
| | | message.warning('请绘制至少3个点的禁行区') |
| | | } |
| | | isEditing = false |
| | | isDrawing = true |
| | | fenceEditorClosed = true |
| | | setTimeout(() => { |
| | | disallowedArea.isActive = true |
| | | disallowedArea.fence = null |
| | | drawDisallowedAreaPolygon(id) |
| | | }, 100) |
| | | return false |
| | | } |
| | | const area = calcPolygonArea(path) |
| | | console.log('禁行区面积:', path) |
| | |
| | | required: true, |
| | | trigger: ['blur', 'change'], |
| | | validator: (_rule: any, value: any) => { |
| | | // 如果是立即生效,则跳过时间比较验证 |
| | | if (formModel.value.effectType === 1 && effectTimeText.value === '立即生效') { |
| | | return true |
| | | } |
| | | // 验证不能为空 |
| | | if (value == null) { |
| | | return new Error('请选择生效时间') |
| | | } |
| | | // 如果是立即生效,则跳过时间比较验证 |
| | | if (value === IMMEDIATE_EFFECT) { |
| | | return true |
| | | } |
| | | // 如果是具体时间值,则验证不能早于当前时间 |
| | | if (typeof value === 'number') { |
| | |
| | | required: true, |
| | | trigger: ['blur', 'change'], |
| | | validator: (_rule: any, value: any) => { |
| | | // 如果是永不失效,则跳过时间比较验证 |
| | | if (formModel.value.failureType === 1 && failureTimeText.value === '永不失效') { |
| | | return true |
| | | } |
| | | // 验证不能为空 |
| | | if (value == null) { |
| | | return new Error('请选择失效时间') |
| | | } |
| | | // 如果是永不失效,则跳过时间比较验证 |
| | | if (value === NEVER_EXPIRE) { |
| | | return true |
| | | } |
| | | // 如果生效时间是立即生效(IMMEDIATE_EFFECT),则失效时间需要大于当前时间 |
| | | if (formModel.value.effectTime === IMMEDIATE_EFFECT && typeof value === 'number') { |
| | | if (formModel.value.effectType === 1 && effectTimeText.value === '立即生效' && typeof value === 'number') { |
| | | const currentTime = Date.now() |
| | | if (value <= currentTime) { |
| | | return new Error('失效时间不能小于当前时间') |
| | |
| | | value: 7, |
| | | }, |
| | | ]) |
| | | const effectiveShortcuts = { |
| | | 立即生效: IMMEDIATE_EFFECT, |
| | | } |
| | | const expireShortcuts = { |
| | | 永不失效: NEVER_EXPIRE, |
| | | } |
| | | |
| | | // 处理星期选择变化 |
| | | function handleWeekSelectionChange(selectedValues: number[], item: any) { |
| | |
| | | |
| | | const effectTimeText = ref('') |
| | | const failureTimeText = ref('') |
| | | const effectTimePicker = ref<any>() |
| | | const failureTimePicker = ref<any>() |
| | | |
| | | /** 监听生效时间 */ |
| | | function handleEffectTime() { |
| | | effectTimeText.value = '立即生效' |
| | | formModel.value.effectType = 1 |
| | | effectTimePicker.value?.handlePanelClose() |
| | | } |
| | | watch(() => formModel.value.effectTime, (newVal) => { |
| | | if (newVal === IMMEDIATE_EFFECT) { |
| | | effectTimeText.value = '立即生效' |
| | | formModel.value.effectType = 1 |
| | | } |
| | | else if (typeof newVal === 'number') { |
| | | if (typeof newVal === 'number') { |
| | | effectTimeText.value = dayjs(newVal).format('YYYY-MM-DD HH:mm:ss') |
| | | formModel.value.effectType = 2 |
| | | } |
| | |
| | | }) |
| | | |
| | | /** 监听失效时间 */ |
| | | function handleFailureTime() { |
| | | failureTimeText.value = '永不失效' |
| | | formModel.value.failureType = 1 |
| | | failureTimePicker.value?.handlePanelClose() |
| | | } |
| | | watch(() => formModel.value.failureTime, (newVal) => { |
| | | if (newVal === NEVER_EXPIRE) { |
| | | failureTimeText.value = '永不失效' |
| | | formModel.value.failureType = 1 |
| | | } |
| | | else if (typeof newVal === 'number') { |
| | | if (typeof newVal === 'number') { |
| | | failureTimeText.value = dayjs(newVal).format('YYYY-MM-DD HH:mm:ss') |
| | | formModel.value.failureType = 2 |
| | | } |
| | |
| | | groupList.value.push({ |
| | | name: `策略组${groupList.value.length + 1}`, |
| | | type: 0, |
| | | time: [], |
| | | time: [{ |
| | | startTime: null, |
| | | endTime: null, |
| | | id: 1, |
| | | }], |
| | | edit: false, |
| | | typeStr: [], |
| | | id: groupList.value.length + 1, |
| | |
| | | }) |
| | | |
| | | // 处理禁行区自定义选择变化 |
| | | function handleSelectionChange(selectedValues: number[], item: any) { |
| | | function handleSelectionChange(selectedValues: any, item: any) { |
| | | if (groupList.value.length > 0) { |
| | | if (selectedValues && (item.typeStr || item.typeStr.length > 0) && item.time.length === 0) { |
| | | item.time.push({ |
| | | startTime: null, |
| | | endTime: null, |
| | | id: item.time.length + 1, |
| | | }) |
| | | } |
| | | return |
| | | } |
| | | // 如果选中了"每天"(值为0),则只保留"每天"的选项 |
| | | if (selectedValues.includes(0)) { |
| | | if (selectedValues.length > 0 && selectedValues.includes(0)) { |
| | | item.typeStr = [0] |
| | | } |
| | | else { |
| | |
| | | // 否则保留当前选择 |
| | | item.typeStr = selectedValues |
| | | } |
| | | } |
| | | if (selectedValues.length > 0 && (item.typeStr || item.typeStr.length > 0) && item.time.length === 0) { |
| | | item.time.push({ |
| | | startTime: null, |
| | | endTime: null, |
| | | id: item.time.length + 1, |
| | | }) |
| | | } |
| | | } |
| | | |
| | |
| | | disallowedFormModel.value.operationStrategy = [] |
| | | } |
| | | /** 确认禁行区编辑 */ |
| | | /** 判断禁行区时间是否被禁用 */ |
| | | function isDisallowedTimeDisabled(hour: number, minute: number | undefined, item: any, type: 'start' | 'end') { |
| | | if (groupList.value.length === 0 || !item.typeStr) { |
| | | return false |
| | | } |
| | | |
| | | const selectedGroup = groupList.value.find((g: any) => g.id === item.typeStr) |
| | | if (!selectedGroup) { |
| | | return false |
| | | } |
| | | |
| | | // 计算允许的时间段集合 |
| | | const allowedRanges = selectedGroup.time.map((sub: any) => { |
| | | if (!sub.startTime || !sub.endTime) |
| | | return null |
| | | const [startH, startM] = sub.startTime.split(':').map(Number) |
| | | const [endH, endM] = sub.endTime.split(':').map(Number) |
| | | return { |
| | | start: startH * 60 + startM, |
| | | end: endH * 60 + endM, |
| | | } |
| | | }).filter(Boolean) |
| | | |
| | | if (minute === undefined) { |
| | | // 小时禁用逻辑:如果该小时内没有任何一分钟在允许范围内,则禁用该小时 |
| | | // 即:该小时的时间段 [hour*60, (hour+1)*60) 与所有允许时间段都没有交集 |
| | | const hourStart = hour * 60 |
| | | const hourEnd = (hour + 1) * 60 // 不包含 |
| | | |
| | | const hasIntersection = allowedRanges.some((range: any) => { |
| | | // 判断区间 [hourStart, hourEnd) 与 [range.start, range.end] 是否有交集 |
| | | // max(start1, start2) < min(end1, end2) |
| | | // 注意:这里 range.end 是包含的,为了方便比较,视作闭区间 |
| | | // 但小时结束是不包含的。 |
| | | // 实际上只要 hourStart <= range.end 且 hourEnd > range.start |
| | | return Math.max(hourStart, range.start) < Math.min(hourEnd, range.end + 1) |
| | | }) |
| | | return !hasIntersection |
| | | } |
| | | else { |
| | | // 分钟禁用逻辑 |
| | | const currentMinute = hour * 60 + minute |
| | | const isAllowed = allowedRanges.some((range: any) => { |
| | | return currentMinute >= range.start && currentMinute <= range.end |
| | | }) |
| | | return !isAllowed |
| | | } |
| | | } |
| | | |
| | | function handleConfirm() { |
| | | // 校验禁行区的运营策略时段必须在所选运营区策略的时段覆盖内 |
| | | if (groupList.value.length > 0) { |
| | |
| | | /** 添加禁行区 */ |
| | | function addDisallowedArea() { |
| | | if (disallowedAreaList.value.some((item: any) => item.fence === null)) { |
| | | message.error('请先绘制禁行区') |
| | | message.error('请先绘制或删除当前选中禁行区') |
| | | return |
| | | } |
| | | disallowedAreaList.value.forEach((sub: any) => { |
| | |
| | | disallowedAreaList.value.push({ |
| | | areaName: `禁行区${disallowedAreaList.value.length + 1}`, |
| | | areaDescription: '', |
| | | operationStrategy: '', |
| | | operationStrategy: [{ |
| | | name: `策略组1`, |
| | | type: 0, |
| | | time: [], |
| | | edit: false, |
| | | typeStr: [], |
| | | id: 1, |
| | | }], |
| | | areaStatus: 1, |
| | | isActive: true, |
| | | edit: false, |
| | |
| | | |
| | | /** 开启禁行区编辑能力 */ |
| | | function handleDisallowedAreaEdit(item: any) { |
| | | if (disallowedAreaList.value.some((item: any) => item.fence === null)) { |
| | | message.error('请先绘制或删除当前选中禁行区') |
| | | return |
| | | } |
| | | closeFenceEditor() |
| | | toggleDisallowedArea(item) |
| | | enableDisallowedAreaFenceEdit(item.id) |
| | |
| | | |
| | | /** 删除禁行区 */ |
| | | function removeDisallowedArea(item: any) { |
| | | if (mouseTool?.polygonTemp) { |
| | | mouseTool.polygonTemp.obj.setPath([]) |
| | | mouseTool?.close() |
| | | mouseTool = null |
| | | isDrawing = false |
| | | } |
| | | if (item.fence) { |
| | | map.remove(item.fence) |
| | | item.fence.destroy() |
| | | fenceEditor.close() |
| | | fenceEditor?.close() |
| | | } |
| | | disallowedAreaList.value = disallowedAreaList.value.filter((sub: any) => sub.id !== item.id) |
| | | } |
| | |
| | | /** 保存草稿/发布围栏 */ |
| | | function handleAddFence(type: number) { |
| | | fencePoints = fence.getPath().map((p: any) => [p.lng, p.lat]) |
| | | console.log(disallowedAreaList.value) |
| | | if (disallowedAreaList.value.some((item: any) => !item.fence || item.fence === null || item.fence.getPath().map((p: any) => (`${p.lng}_${p.lat}`)).length < 3)) { |
| | | message.error('请先绘制禁行区') |
| | | return |
| | | } |
| | | if (isPolygonSelfIntersect(fencePoints)) { |
| | | message.error('围栏存在自相交,请调整形状') |
| | | message.error('运营区域不允许自相交') |
| | | return |
| | | } |
| | | const fenceArea = renderedPolygons.value.filter((item: any) => item.areaType === 1).find((item: any) => isPolygonIntersectOthers(fencePoints, item.points)) |
| | |
| | | } |
| | | const disallowedArea = disallowedAreaList.value.find((item: any) => isPolygonSelfIntersect(item.fence.getPath().map((p: any) => [p.lng, p.lat]))) |
| | | if (disallowedArea) { |
| | | message.error(`禁行区 ${disallowedArea.areaName} 存在自相交,请调整形状`) |
| | | message.error(`禁行区 ${disallowedArea.areaName} 存在自相交`) |
| | | return |
| | | } |
| | | const disallowedAreaIntersect = disallowedAreaList.value.find((item: any) => isPolygonIntersectOthers(fencePoints, item.fence.getPath().map((p: any) => [p.lng, p.lat]))) |
| | | if (disallowedAreaIntersect) { |
| | | message.error(`${disallowedAreaIntersect.areaName} 与运营区相交,请调整形状`) |
| | | message.error(`${disallowedAreaIntersect.areaName} 与运营区相交`) |
| | | return |
| | | } |
| | | // 禁行区不能在运营区外 |
| | |
| | | return |
| | | } |
| | | formRef.value.validate().then(() => { |
| | | // 验证运营策略重复 |
| | | const strategyTypes = new Set<string>() |
| | | for (const item of groupList.value) { |
| | | let typeKey = '' |
| | | if (item.type === 0) { // 工作日 |
| | | typeKey = 'workday' |
| | | } |
| | | else if (item.type === 2) { // 非工作日 |
| | | typeKey = 'holiday' |
| | | } |
| | | else if (item.type === 1) { // 法定节假日 |
| | | typeKey = 'statutoryHoliday' |
| | | } |
| | | else if (item.type === 3) { // 自定义 |
| | | if (item.typeStr && item.typeStr.length > 0) { |
| | | const sortedDays = [...item.typeStr].sort((a, b) => a - b).join(',') |
| | | typeKey = `custom-${sortedDays}` |
| | | } |
| | | } |
| | | if (typeKey) { |
| | | if (strategyTypes.has(typeKey)) { |
| | | message.error(`运营策略存在重复:${item.name} 的类型已存在`) |
| | | mainScrollRef.value?.scrollTo({ top: 9999, behavior: 'smooth' }) |
| | | return |
| | | } |
| | | strategyTypes.add(typeKey) |
| | | } |
| | | // 验证每天与其他策略互斥 |
| | | if (typeKey.includes('custom-0') && groupList.value.length > 1) { |
| | | message.error('选择“每天”时,不能添加其他运营策略') |
| | | mainScrollRef.value?.scrollTo({ top: 9999, behavior: 'smooth' }) |
| | | return |
| | | } |
| | | } |
| | | // 验证运营策略时间段 |
| | | for (const item of groupList.value) { |
| | | const timeIntervals: { start: string, end: string }[] = [] |
| | |
| | | // 比较开始时间和结束时间 |
| | | if (sub.startTime >= sub.endTime) { |
| | | message.error('运营策略时间段中,结束时间不能小于或等于开始时间') |
| | | mainScrollRef.value?.scrollTo({ top: 9999, behavior: 'smooth' }) |
| | | return |
| | | } |
| | | timeIntervals.push({ start: sub.startTime, end: sub.endTime }) |
| | | } |
| | | else if (!sub.startTime || !sub.endTime) { |
| | | message.error('运营策略时间段中,开始时间和结束时间不能为空') |
| | | mainScrollRef.value?.scrollTo({ top: 9999, behavior: 'smooth' }) |
| | | return |
| | | } |
| | | } |
| | |
| | | for (let i = 0; i < timeIntervals.length - 1; i++) { |
| | | if (timeIntervals[i].end > timeIntervals[i + 1].start) { |
| | | message.error('同一个策略的时段不能重叠') |
| | | mainScrollRef.value?.scrollTo({ top: 9999, behavior: 'smooth' }) |
| | | return |
| | | } |
| | | } |
| | |
| | | id: fenceDetail.value.electronicFenceAreas?.find((item: any) => item.areaType === 1)?.id ?? null, |
| | | }] |
| | | disallowedAreaList.value.forEach((item: any) => { |
| | | const operationStrategy = item.operationStrategy.map((sub: any) => ({ |
| | | console.log(item.operationStrategy) |
| | | const operationStrategy = item.operationStrategy?.map((sub: any) => ({ |
| | | name: sub.name, |
| | | type: sub.type, |
| | | time: sub.time.map((key: any) => (`${key.startTime}-${key.endTime}`)), |
| | |
| | | }).finally(() => endLoading()) |
| | | } |
| | | } |
| | | }).catch(() => { |
| | | mainScrollRef.value?.scrollTo({ top: 0, behavior: 'smooth' }) |
| | | }) |
| | | } |
| | | |
| | |
| | | status: fenceDetail.value.status || 1, |
| | | areaType: 1, |
| | | area: fenceDetail.value.area || '', |
| | | effectTime: fenceDetail.value.effectType === 1 ? IMMEDIATE_EFFECT : dayjs(fenceDetail.value.effectTime).valueOf(), |
| | | effectTime: fenceDetail.value.effectType === 1 ? null : dayjs(fenceDetail.value.effectTime).valueOf(), |
| | | effectType: fenceDetail.value.effectType || 2, |
| | | failureTime: fenceDetail.value.failureType === 1 ? NEVER_EXPIRE : dayjs(fenceDetail.value.failureTime).valueOf(), |
| | | failureTime: fenceDetail.value.failureType === 1 ? null : dayjs(fenceDetail.value.failureTime).valueOf(), |
| | | failureType: fenceDetail.value.failureType || 2, |
| | | operationStrategy: fenceDetail.value.operationStrategy || null, |
| | | expireTimeTimestamp: fenceDetail.value.expireTimeTimestamp || null, |
| | | areaStatus: fenceDetail.value.electronicFenceAreas.filter((item: any) => item.areaType === 1)[0].areaStatus || 1, |
| | | } |
| | | if (fenceDetail.value.effectType === 1) { |
| | | effectTimeText.value = '立即生效' |
| | | } |
| | | if (fenceDetail.value.failureType === 1) { |
| | | failureTimeText.value = '永不失效' |
| | | } |
| | | /** 运营区域的运营策略 */ |
| | | groupList.value = groupList.value.map((group: any, index: number) => { |
| | |
| | | <!-- 地图容器 --> |
| | | <div ref="domRef" class="w-full h-full" :style="drawerVisible ? 'width: calc(100% - 280px);' : ''" /> |
| | | <!-- 提示 --> |
| | | <div v-if="modalType === 'add'" class="tips"> |
| | | <div v-if="modalType === 'add' && (isDrawing || isEditing) " class="tips"> |
| | | {{ !isActiceDisallowedArea ? '请在地图上【非运营区域】绘制多边形区域,双击闭合' : '请在地图上【蓝色运营区域】绘制多边形区域,双击闭合' }} |
| | | </div> |
| | | <div v-if="modalType === 'edit' && fenceDetail?.status !== 2" class="tips"> |
| | | <div v-if="modalType === 'edit' && fenceDetail?.status !== 2 && (isDrawing || isEditing)" class="tips"> |
| | | {{ !isActiceDisallowedArea ? '【蓝色运营区】可调整区域点位,请勿与其他运营区重叠' : '请在地图上【蓝色运营区域】绘制多边形区域,双击闭合' }} |
| | | </div> |
| | | <!-- 抽屉 --> |
| | |
| | | <img v-else src="../../../assets/geofence/foldUp.png" class="icon" @click="toggleDrawer"> |
| | | </div> |
| | | <div class="drawer-content"> |
| | | <!-- <div class="drawer-head" v-if="(modalType === 'edit' || modalType === 'detail') && showDrawerHead"> |
| | | <div class="title" v-if="fenceDetail">{{ fenceDetail?.electronicName || '' }} |
| | | <NTag style="font-weight: 500;" |
| | | :type="fenceDetail?.status === 0 || fenceDetail?.status === 3 ? 'default' : fenceDetail?.status === 2 ? 'success' : 'warning'" |
| | | :bordered="false" class="h-24px ml-4px"> |
| | | {{ fenceDetail?.status === 0 ? '草稿' : fenceDetail?.status === 1 ? '待生效' : fenceDetail?.status === 2 ? '启用中' : '已停用' }} |
| | | </NTag> |
| | | </div> |
| | | <img src="../../../assets/geofence/close2.png" class="w-24px h-24px ml-auto cursor-pointer" @click="onClose" |
| | | v-if="modalType === 'edit'"> |
| | | </div> |
| | | <div class="drawer-head" v-if="modalType === 'add' && showDrawerHead"> |
| | | <div class="title">围栏详情</div> |
| | | <img src="../../../assets/geofence/close2.png" class="w-24px h-24px ml-auto cursor-pointer" @click="onClose"> |
| | | </div> --> |
| | | <div ref="mainScrollRef" class="drawer-main"> |
| | | <div class="main-head" :class="{ 'sticky-top': showDrawerHead }"> |
| | | <div v-if="modalType === 'add'" class="flex items-center gap-4px mb-16px"> |
| | |
| | | </n-input> |
| | | <div class="position"> |
| | | <n-date-picker |
| | | v-model:value="formModel.effectTime" type="datetime" clearable |
| | | class="w-full opacity-0" :shortcuts="effectiveShortcuts" :disabled="isEdit" |
| | | ref="effectTimePicker" v-model:value="formModel.effectTime" type="datetime" |
| | | clearable |
| | | class="w-full opacity-0" :disabled="isEdit" |
| | | @clear="effectTimeText = ''" |
| | | /> |
| | | > |
| | | <template #footer> |
| | | <div> |
| | | <NButton text type="primary" @click="handleEffectTime"> |
| | | 立即生效 |
| | | </NButton> |
| | | </div> |
| | | </template> |
| | | </n-date-picker> |
| | | </div> |
| | | <!-- <n-date-picker panel type="datetime" :shortcuts="effectiveShortcuts"/> --> |
| | | </n-form-item> |
| | |
| | | </n-input> |
| | | <div class="position"> |
| | | <n-date-picker |
| | | v-model:value="formModel.failureTime" type="datetime" clearable |
| | | class="w-full opacity-0" :shortcuts="expireShortcuts" :disabled="isEdit" |
| | | ref="failureTimePicker" v-model:value="formModel.failureTime" type="datetime" |
| | | clearable |
| | | class="w-full opacity-0" :disabled="isEdit" |
| | | @clear="failureTimeText = ''" |
| | | /> |
| | | > |
| | | <template #footer> |
| | | <div> |
| | | <NButton text type="primary" @click="handleFailureTime"> |
| | | 永不失效 |
| | | </NButton> |
| | | </div> |
| | | </template> |
| | | </n-date-picker> |
| | | </div> |
| | | </n-form-item> |
| | | </div> |
| | |
| | | <div v-for="sub in item.time" :key="sub.id" class="gap-8px flex items-center time-range"> |
| | | <n-time-picker |
| | | v-model:formatted-value="sub.startTime" format="HH:mm" value-format="HH:mm" |
| | | placeholder="请选择" clearable :disabled="isEdit" |
| | | placeholder="请选择" clearable :disabled="isEdit" :status="sub.startTime ? 'success' : 'error'" |
| | | /> |
| | | 至 |
| | | <n-time-picker |
| | | v-model:formatted-value="sub.endTime" format="HH:mm" value-format="HH:mm" |
| | | placeholder="请选择" clearable :disabled="isEdit" |
| | | placeholder="请选择" clearable :disabled="isEdit" :status="sub.endTime ? 'success' : 'error'" |
| | | /> |
| | | <img |
| | | v-if="!isEdit" src="../../../assets/geofence/del.png" |
| | |
| | | </div> |
| | | <div> |
| | | <n-select |
| | | v-if="item.type === 1" v-model:value="item.typeStr" class="flex-1" |
| | | v-if="item.type === 1 && groupList.length === 0" v-model:value="item.typeStr" class="flex-1" |
| | | :options="disallowedAreaOptions" |
| | | :disabled="item.type !== 1" :multiple="groupList.length === 0" :max-tag-count="1" clearable |
| | | :status="item.typeStr && item.typeStr.length > 0 ? 'success' : 'error'" |
| | | :disabled="item.type !== 1" multiple :max-tag-count="1" clearable |
| | | @update:value="(value) => handleSelectionChange(value, item)" |
| | | /> |
| | | <n-select |
| | | v-if="item.type === 1 && groupList.length > 0" v-model:value="item.typeStr" class="flex-1" |
| | | :options="disallowedAreaOptions" |
| | | :status="item.typeStr ? 'success' : 'error'" |
| | | :disabled="item.type !== 1" clearable |
| | | @update:value="(value) => handleSelectionChange(value, item)" |
| | | /> |
| | | </div> |
| | |
| | | <div v-for="sub in item.time" :key="sub.id" class="gap-8px flex items-center time-range"> |
| | | <n-time-picker |
| | | v-model:formatted-value="sub.startTime" format="HH:mm" value-format="HH:mm" |
| | | placeholder="请选择" clearable |
| | | placeholder="请选择" clearable :status="sub.startTime ? 'success' : 'error'" |
| | | :is-hour-disabled="(hour: number) => isDisallowedTimeDisabled(hour, undefined, item, 'start')" |
| | | :is-minute-disabled="(minute: number, hour: number) => isDisallowedTimeDisabled(hour, minute, item, 'start')" |
| | | /> |
| | | 至 |
| | | <n-time-picker |
| | | v-model:formatted-value="sub.endTime" format="HH:mm" value-format="HH:mm" |
| | | placeholder="请选择" clearable |
| | | placeholder="请选择" clearable :status="sub.endTime ? 'success' : 'error'" |
| | | :is-hour-disabled="(hour: number) => isDisallowedTimeDisabled(hour, undefined, item, 'end')" |
| | | :is-minute-disabled="(minute: number, hour: number) => isDisallowedTimeDisabled(hour, minute, item, 'end')" |
| | | /> |
| | | <img |
| | | src="../../../assets/geofence/del.png" |