src/assets/point/active-coverage.png
src/assets/point/add.png
src/assets/point/car.png
src/assets/point/checked.png
src/assets/point/coverage.png
src/assets/point/delete.png
src/assets/point/location.png
src/assets/point/more.png
src/assets/point/notice.png
src/assets/point/operation.png
src/assets/point/point.png
src/assets/point/reduce.png
src/assets/point/test.png
src/assets/point/uncheck.png
src/assets/point/upload.png
src/views/operationsManagement/pointManagement/index.scss
New file @@ -0,0 +1,967 @@ // 点位管理页面样式文件 .point-management-container { display: flex; width: 100%; height: 100%; position: relative; background: #f5f7fa; } .flex { display: flex; } .a-center { align-items: center; } .j-between { justify-content: space-between; } .relative { position: relative; } // ==================== 左侧面板 ==================== .left-panel { width: 280px; height: 100%; background: #ffffff; position: absolute; top: 0; left: 0; display: flex; flex-direction: column; z-index: 999; } // 面板头部 .panel-header { padding: 16px 12px 4px 12px; } .panel-title { /* 16/subtitle-Medium */ font-family: PingFang SC; font-size: 16px; font-weight: 600; line-height: 24px; letter-spacing: normal; color: #3D3D3D; } // ==================== 搜索框 ==================== .search-box { padding: 0 12px 12px 12px; border-bottom: 1px solid rgba(23, 31, 38, 0.15); } .search-input-wrapper { position: relative; width: 100%; } .search-icon { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; color: #999; pointer-events: none; } .search-input { width: 100%; height: 32px; padding: 0 8px 0 8px; border: 1px solid #dcdfe6; border-radius: 4px; font-size: 14px; color: #333; background: #ffffff; transition: all 0.3s; outline: none; &::placeholder { color: rgba(23, 31, 38, 0.3); } &:focus { border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } &:hover { border-color: #b3b8c4; } } .clear-btn { position: absolute; right: 30px; top: 50%; transform: translateY(-50%); width: 12px; height: 12px; background: #c0c4cc; border: none; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 12px; transition: all 0.2s; &:hover { background: #909399; } } // ==================== 点位列表 ==================== .point-list { flex: 1; overflow-y: auto; &::-webkit-scrollbar { width: 0px; } &::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 0px; } &::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 0px; transition: background 0.3s; &:hover { background: #a8a8a8; } } } .point-item { padding: 7px 12px; background: #ffffff; border-bottom: 1px solid rgba(23, 31, 38, 0.15); cursor: pointer; position: relative; overflow: hidden; &:hover { background: #F4F6F8; box-sizing: border-box; border-width: 0px 0px 1px 0px; border-style: solid; border-color: rgba(23, 31, 38, 0.1); &::before { opacity: 1; } } } .point-id { font-family: PingFang SC; font-size: 13px; font-weight: normal; line-height: 18px; letter-spacing: normal; color: #171F26; } .point-type { padding: 2px 8px; border-radius: 4px; /* blue/1-浅色背景 */ background: #E6F4FF; font-family: PingFang SC; font-size: 12px; font-weight: normal; line-height: 16px; text-align: center; letter-spacing: normal; /* blue/6-主题色 */ color: #0068FF; } .type-dark { background: #E8E9E9; color: #171F26; } .point-status { padding: 2px 8px; font-family: PingFang SC; font-size: 12px; font-weight: normal; line-height: 16px; text-align: center; /* blue/6-主题色 */ color: #0068FF; margin: 0 4px; } // 待测试 .wait-test { background: #FFF6EA; color: #FFAA2A; } // 测试中 .testing { background: #E6F4FF; color: #0068FF; } // 测试失败---运营失效 .test-fail { background: #FFF3F1; color: #E02716; } // 可运营 .test-pass { color: #0068FF; background: #E6F4FF; } // 待生效--待启用 .test-take-effect { background: #FFF6EA; color: #FFAA2A; } //启用中 .test-starting { background: #E9F9ED; color: #19B144; } // 已停用 .test-stop { background: #E8E9E9; color: #171F26; } // 失效 .test-invalid { background: #E8E9E9; color: #171F26; } .point-address { margin: 4px 0; font-family: PingFang SC; font-size: 13px; font-weight: normal; line-height: 18px; letter-spacing: normal; color: #171F26; } .point-time { font-family: PingFang SC; font-size: 13px; font-weight: normal; line-height: 18px; letter-spacing: normal; /* Text color/FONT Gy4-置灰信息 */ color: rgba(23, 31, 38, 0.3); } // ==================== 底部按钮 ==================== .panel-footer { display: flex; align-items: center; justify-content: center; border-top: 1px solid #e8eaed; background: #fff; padding: 12px; } .add-btn { /* 自动布局子元素 */ width: 124px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: 4px; background: #0068FF; color: #ffffff; cursor: pointer; font-family: PingFang SC; font-size: 13px; font-weight: normal; text-align: center; letter-spacing: normal; color: #FFFFFF; &:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); } &:active { transform: translateY(0); box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); } .icon { font-size: 18px; } } // ==================== 右侧地图区域 ==================== .map-container { flex: 1; height: 100%; position: relative; overflow: hidden; } .map-content { width: 100%; height: 100%; } // 地图加载提示 .map-loading { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; color: #666; font-size: 14px; } .map-error { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; color: #f56c6c; font-size: 14px; padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); } // ==================== 响应式设计 ==================== @media screen and (max-width: 1200px) { .left-panel { width: 320px; } } @media screen and (max-width: 768px) { .point-management-container { flex-direction: column; } .left-panel { width: 100%; height: 50%; } .map-container { height: 50%; } } // ==================== 空状态 ==================== .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; color: #999; .empty-icon { font-size: 64px; margin-bottom: 16px; opacity: 0.5; } .empty-text { font-size: 14px; color: #999; } } //右侧信息 .right-panel { position: absolute; right: 0; top: 0; display: flex; height: 100%; } // 地图操作 .map-operate { display: flex; flex-direction: column; justify-content: space-between; padding: 8px; height: 100%; } .operate-item { display: flex; align-items: center; padding: 8px; gap: 2px; z-index: 0; border-radius: 4px; background: #FFFFFF; box-sizing: border-box; /* Gray color/Gray3-按钮-输入框描边 */ border: 1px solid rgba(23, 31, 38, 0.15); cursor: pointer; position: relative; &:hover { border-radius: 4px; /* blue/1-浅色背景 */ background: #E6F4FF; box-sizing: border-box; /* Gray color/Gray3-按钮-输入框描边 */ border: 1px solid rgba(23, 31, 38, 0.15); .operate-text { color: #0068FF !important; } } &:active { border-radius: 4px; background: #A3D4FF; box-sizing: border-box; border: 1px solid rgba(23, 31, 38, 0.15); .operate-text { color: #0068FF !important; } } .operate-icon { /* 自动布局子元素 */ width: 16px; height: 16px; z-index: 0; } .operate-text { font-family: PingFang SC; font-size: 13px; font-weight: normal; line-height: 15.21px; text-align: center; letter-spacing: normal; /* blue/6-主题色 */ color: #171F26; } } .coverage { gap: 4px; margin-left: 8px; } .dot-detail { position: absolute; top: 34px; left: 0; display: flex; flex-direction: column; padding: 8px; gap: 4px; border-radius: 4px; /* white */ background: #FFFFFF; /* 二级投影-悬浮弹框 */ box-shadow: 0px 8px 20px 0px rgba(0, 0, 0, 0.1); .dot-item { display: flex; align-items: center; padding: 2px 4px; gap: 4px; border-radius: 4px; box-sizing: border-box; &:hover { background: #A3D4FF; border: 1px solid rgba(23, 31, 38, 0.15); } .dot-icon { /* 自动布局子元素 */ width: 16px; height: 16px; z-index: 0; } .dot-tit { font-family: PingFang SC; font-size: 13px; font-weight: normal; line-height: 18px; letter-spacing: normal; color: #171F26; } } .active-dot { background: #A3D4FF; border: 1px solid rgba(23, 31, 38, 0.15); } } // 图层下拉 .coverage-detail { position: absolute; top: 34px; right: 0; /* 自动布局 */ display: flex; flex-direction: column; padding: 0px 28px 10px 12px; gap: 8px; z-index: 1; border-radius: 4px; background: #FFFFFF; box-shadow: 0px 8px 20px 0px rgba(0, 0, 0, 0.1); .coverage-tit { /* 13/段落-Regular */ font-family: PingFang SC; font-size: 13px; font-weight: normal; line-height: 20px; letter-spacing: normal; color: rgba(23, 31, 38, 0.6); } .coverage-icon { width: 16px; height: 16px; z-index: 0; margin-right: 8px; } .coverage-txt { font-family: PingFang SC; font-size: 13px; font-weight: normal; line-height: 18px; letter-spacing: normal; color: #171F26; } } .operate-bottom { display: flex; justify-content: flex-end; .operate-card { display: flex; flex-direction: column; } .location { margin-bottom: 8px; } .add { margin-bottom: -1px; } .location-icon { width: 32px; height: 32px; display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 0; } } // 点位详情 .point-detail { width: 280px; height: 100%; display: flex; flex-direction: column; background: #fff; .point-info { padding: 16px 0px; margin-left: 12px; flex: 1; display: flex; flex-direction: column; overflow-y: auto; } .point-title { padding-bottom: 16px; border-bottom: 1px solid rgba(23, 31, 38, 0.15); } .point-detail-content { flex: 1; overflow-y: auto; &::-webkit-scrollbar { width: 0px; } &::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 0px; } &::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 0px; transition: background 0.3s; &:hover { background: #a8a8a8; } } .info-card { margin-top: 16px; display: flex; align-items: center; .info-icon { width: 3px; height: 14px; border-radius: 3px; background: #0068FF; margin-right: 8px; } .card-title { font-family: PingFang SC; font-size: 16px; font-weight: 600; line-height: 24px; letter-spacing: normal; color: #171F26; } } .info-form { margin-top: 8px; .label-require { font-family: PingFang SC; font-size: 13px; font-weight: normal; line-height: 18px; letter-spacing: normal; color: #E02716; margin-right: 4px; } .info-form-label { font-family: PingFang SC; font-size: 13px; font-weight: normal; line-height: 18px; letter-spacing: normal; color: #171F26; } .info-form-value { margin-top: 8px; padding-right: 12px; } .form-input { width: 100%; height: 32px; align-items: center; padding: 6px 8px; border-radius: 4px; background: #FFFFFF; box-sizing: border-box; border: 1px solid rgba(23, 31, 38, 0.15); font-family: PingFang SC; font-size: 13px; font-weight: normal; line-height: 20px; letter-spacing: normal; color: #171F26; background: #ffffff; transition: all 0.3s; outline: none; &::placeholder { color: rgba(23, 31, 38, 0.3); } &:focus { border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } &:hover { border-color: #b3b8c4; } } .point-img { display: flex; flex-wrap: wrap; .img-item { position: relative; width: 83.33px; height: 56px; z-index: 1; border-radius: 4px; margin-right: 3px; margin-bottom: 3px; &:nth-child(3n+3) { margin-right: 0; } .point-img-item { width: 100%; height: 100%; object-fit: cover; border-radius: 4px; cursor: pointer; transition: transform 0.3s; } &:hover { z-index: 10; } &:hover .point-img-item { transform: scale(1.05); } .img-delete { position: absolute; top: -9px; right: -9px; width: 18px; height: 18px; cursor: pointer; z-index: 2; opacity: 0; transition: opacity 0.3s; } &:hover .img-delete { opacity: 1; } } .upload-item { cursor: pointer; border: 1px dashed rgba(23, 31, 38, 0.15); background: rgba(23, 31, 38, 0.02); transition: all 0.3s; display: flex; align-items: center; justify-content: center; overflow: visible; &:hover { border-color: #0068FF; background: rgba(0, 104, 255, 0.05); } .upload-icon { width: 83.33px; height: 56px; border-radius: 4px; object-fit: contain; opacity: 0.6; transition: opacity 0.3s; } &:hover .upload-icon { opacity: 1; } } } .notice-card { margin-top: 8px; display: flex; align-items: center; .notice-txt { font-family: PingFang SC; font-size: 12px; font-weight: normal; line-height: 16px; letter-spacing: normal; color: rgba(23, 31, 38, 0.6); } .img-notice { width: 12px; height: 12px; margin-right: 6px; } } } } .panel-footer { width: 280px; height: 48px; display: flex; align-items: center; padding: 8px 12px; gap: 8px; background: #FFFFFF; box-sizing: border-box; border-width: 1px 0px 0px 0px; border-style: solid; border-color: rgba(23, 31, 38, 0.1); .cancel-btn { flex: 1; height: 32px; border-radius: 4px; box-sizing: border-box; border: 1px solid rgba(23, 31, 38, 0.15); font-family: PingFang SC; font-size: 13px; font-weight: normal; line-height: 32px; text-align: center; letter-spacing: normal; color: #171F26; } .submit-btn { flex: 1; height: 32px; border-radius: 4px; background: #0068FF; font-family: PingFang SC; font-size: 13px; font-weight: normal; text-align: center; line-height: 32px; letter-spacing: normal; color: #FFFFFF; } } } // ==================== 图片预览弹窗 ==================== .image-preview-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); display: flex; align-items: center; justify-content: center; z-index: 9999; animation: fadeIn 0.3s ease; .preview-content { position: relative; max-width: 90vw; max-height: 90vh; display: flex; align-items: center; justify-content: center; } .preview-image { max-width: 100%; max-height: 90vh; object-fit: contain; border-radius: 8px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); animation: zoomIn 0.3s ease; } .preview-close { position: absolute; top: -40px; right: -40px; width: 32px; height: 32px; background: rgba(255, 255, 255, 0.9); border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s; svg { width: 16px; height: 16px; color: #333; } &:hover { background: #fff; transform: rotate(90deg); } } } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes zoomIn { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } } src/views/operationsManagement/pointManagement/index.vue
@@ -1,5 +1,5 @@ <script setup lang="tsx"> import { ref, onMounted } from 'vue' import { computed, onMounted, ref } from 'vue' import { useScriptTag } from '@vueuse/core' // 添加更好的错误处理和调试 @@ -8,6 +8,49 @@ const domRef = ref<HTMLDivElement>() const map = ref(null) const mapError = ref('') const searchKeyword = ref('') const activePointId = ref<number | null>(null) const uploadInput = ref<HTMLInputElement>() const previewVisible = ref(false) const previewImage = ref('') // 模拟已上传的图片列表 const uploadedImages = ref([ { id: 1, url: 'https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg' }, { id: 2, url: 'https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg' }, { id: 3, url: 'https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg' }, { id: 4, url: 'https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg' }, { id: 5, url: 'https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg' }, { id: 6, url: 'https://naive-ui.oss-cn-beijing.aliyuncs.com/carousel-img/carousel1.jpeg' }, ]) // 模拟点位数据 const pointList = ref([ { id: 1, name: '天河体育中心站点', address: '广州市天河区天河路299号', status: '待启用', type: '运营', isOpen: false }, { id: 2, name: '珠江新城站点', address: '广州市天河区珠江新城花城广场', status: '待测试', type: '测试', isOpen: true }, { id: 3, name: '广州塔站点', address: '广州市海珠区阅江西路222号', status: '启用中', type: '运营', isOpen: true }, { id: 4, name: '北京路站点', address: '广州市越秀区北京路', status: '已停用', type: '运营', isOpen: false }, { id: 5, name: '白云机场站点', address: '广州市白云区机场路', status: '失效', type: '运营', isOpen: false }, { id: 6, name: '珠江新城站点', address: '广州市天河区珠江新城花城广场', status: '测试中', type: '测试', isOpen: true }, { id: 7, name: '珠江新城站点', address: '广州市天河区珠江新城花城广场', status: '测试不通过', type: '测试', isOpen: false }, { id: 8, name: '珠江新城站点', address: '广州市天河区珠江新城花城广场', status: '待生效', type: '测试', isOpen: false }, { id: 9, name: '珠江新城站点', address: '广州市天河区珠江新城花城广场', status: '可运营', type: '测试', isOpen: true }, { id: 10, name: '珠江新城站点', address: '广州市天河区珠江新城花城广场', status: '启用中', type: '测试', isOpen: true }, { id: 11, name: '珠江新城站点', address: '广州市天河区珠江新城花城广场', status: '已停用', type: '测试', isOpen: false }, { id: 12, name: '珠江新城站点', address: '广州市天河区珠江新城花城广场', status: '失效', type: '测试', isOpen: false }, ]) // 过滤后的点位列表 const filteredPointList = computed(() => { if (!searchKeyword.value.trim()) { return pointList.value } const keyword = searchKeyword.value.toLowerCase() return pointList.value.filter(point => point.name.toLowerCase().includes(keyword) || point.address.toLowerCase().includes(keyword), ) }) async function initMap() { try { @@ -20,15 +63,193 @@ return } map.value = new AMap.Map(domRef.value, { zoom: 10, zoom: 11, center: [113.264499, 23.130061], mapStyle: 'amap://styles/whitesmoke', // 设置地图的显示样式 }) console.log('地图初始化成功:', map.value) } catch (err) { mapError.value = '地图初始化失败: ' + (err as Error).message } catch (err) { mapError.value = `地图初始化失败: ${(err as Error).message}` console.error('地图初始化错误:', err) } } // switch颜色 function railStyle({ focused, checked, }: { focused: boolean checked: boolean }) { const style: CSSProperties = {} if (checked) { style.background = '#E5E7EB' } else { style.background = '#2080f0' } return style } // 搜索点位 function handleSearch() { console.log('搜索关键词:', searchKeyword.value) } // 清空搜索 function clearSearch() { searchKeyword.value = '' } // 新增点位 function handleAddPoint() { console.log('新增点位') } // 点击点位 function handlePointClick(point: any) { activePointId.value = point.id console.log('点击点位:', point) } // 切换点位开关状态 function handleSwitchChange(point: any, value: boolean) { console.log('切换点位开关:', point, value) point.isOpen = value // 这里可以调用 API 更新点位状态 } // 获取状态类名 function getStatusClass(type: string, status: string) { if (type === '运营') { switch (status) { case '待启用': return 'test-take-effect' case '启用中': return 'test-starting' case '已停用': return 'test-stop' case '失效': return 'test-fail' } } else { switch (status) { case '待测试': return 'wait-test' case '测试中': return 'testing' case '测试不通过': return 'test-fail' case '可运营': return 'test-pass' case '待生效': return 'test-take-effect' case '启用中': return 'test-starting' case '已停用': return 'test-stop' case '失效': return 'test-invalid' } } } function getTypeClass(type: string) { if (type === '运营') { return 'point-type' } else { return 'type-dark' } } // 触发文件上传 function triggerUpload() { uploadInput.value?.click() } // 处理文件选择 function handleFileChange(event: Event) { const target = event.target as HTMLInputElement const files = target.files if (!files || files.length === 0) { return } // 检查图片数量限制 const remainingSlots = 6 - uploadedImages.value.length if (remainingSlots <= 0) { console.error('最多只能上传6张图片') target.value = '' return } // 限制本次上传的文件数量 const filesToUpload = Math.min(files.length, remainingSlots) // 验证文件 for (let i = 0; i < filesToUpload; i++) { const file = files[i] // 验证文件类型 if (!['image/jpeg', 'image/png'].includes(file.type)) { console.error(`文件 ${file.name} 格式不支持,仅支持 jpg/png`) continue } // 验证文件大小(2MB) if (file.size > 2 * 1024 * 1024) { console.error(`文件 ${file.name} 超过2MB限制`) continue } // 预览图片 const reader = new FileReader() reader.onload = (e) => { const url = e.target?.result as string // 再次检查数量限制 if (uploadedImages.value.length < 6) { uploadedImages.value.push({ id: Date.now() + i, url, }) } } reader.readAsDataURL(file) // TODO: 调用上传 API console.log('准备上传文件:', file.name) } // 提示用户如果有文件被忽略 if (files.length > filesToUpload) { console.warn(`只能上传 ${filesToUpload} 张图片,已忽略其余 ${files.length - filesToUpload} 张`) } // 清空 input,允许重复选择同一文件 target.value = '' } // 删除图片 function handleDeleteImage(id: number) { const index = uploadedImages.value.findIndex(img => img.id === id) if (index > -1) { uploadedImages.value.splice(index, 1) } } // 预览图片 function handlePreviewImage(url: string) { previewImage.value = url previewVisible.value = true } // 关闭预览 function closePreview() { previewVisible.value = false previewImage.value = '' } onMounted(() => { @@ -37,12 +258,312 @@ </script> <template> <!-- 地图区域 --> <div class="w-full h-full" id="mapBox"> <div id="map" ref="domRef" class="w-full h-full" style="position: relative;" /> <div class="point-management-container"> <!-- 左侧点位管理面板 --> <div class="left-panel"> <!-- 标题 --> <div class="panel-header"> <h2 class="panel-title"> 点位管理 </h2> </div> <!-- 搜索框 --> <div class="search-box"> <div class="search-input-wrapper"> <input v-model="searchKeyword" type="text" class="search-input" placeholder="搜索点位名称或地址" @keyup.enter="handleSearch" > <button v-if="searchKeyword" class="clear-btn" @click="clearSearch"> × </button> <svg class="search-icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"> <path fill="currentColor" d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0 0 11.6 0l43.6-43.5a8.2 8.2 0 0 0 0-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z" /> </svg> </div> </div> <!-- 点位列表 --> <div class="point-list"> <div v-if="filteredPointList.length === 0" class="empty-state"> <div class="empty-text"> 暂无点位数据 </div> </div> <div v-for="point in filteredPointList" v-else :key="point.id" class="point-item" :class="{ active: activePointId === point.id }" @click="handlePointClick(point)" > <div class="flex a-center j-between"> <div class="flex a-center"> <div class="point-id"> ID:{{ point.id }} </div> <div class="point-status" :class="getStatusClass(point.type, point.status)"> {{ point.status }} </div> <div class="point-type" :class="getTypeClass(point.type)"> {{ point.type }} </div> </div> <!-- switch 开关 --> <n-switch v-model:value="point.isOpen" size="small" :rail-style="railStyle" @update:value="(value) => handleSwitchChange(point, value)" @click.stop /> </div> <div class="point-address"> 国贸大厦北门停车点 </div> <div class="point-time"> 限停时长:60分钟 </div> </div> </div> <!-- 底部新增按钮 --> <div class="panel-footer"> <div class="add-btn" @click="handleAddPoint"> <span class="icon">+</span> 新增点位 </div> </div> </div> <!-- 地图区域 --> <div class="map-container"> <div v-if="mapError" class="map-error"> {{ mapError }} </div> <div id="map" ref="domRef" class="map-content" /> </div> <!-- 右侧运营详情和地图操作 --> <div class="right-panel"> <div class="map-operate"> <div class="flex a-center relative"> <!-- 打点 --> <div class="operate-item"> <img src="../../../assets//point/point.png" class="operate-icon"> <div class="operate-text"> 打点 </div> </div> <!-- 图层 --> <div class="operate-item coverage"> <img src="../../../assets//point/coverage.png" class="operate-icon"> <!-- <img src="../../../assets//point/active-coverage.png" class="operate-icon"> --> <div class="operate-text"> 图层 </div> <img src="../../../assets//point/more.png" class="operate-icon"> </div> <!-- 打点下拉 --> <div class="dot-detail"> <div class="dot-item active-dot"> <img src="../../../assets//point/test.png" class="dot-icon"> <div class="dot-tit"> 测试点 </div> </div> <div class="dot-item"> <img src="../../../assets//point/operation.png" class="dot-icon"> <div class="dot-tit"> 运营点 </div> </div> </div> <!-- 图层下拉 --> <div class="coverage-detail"> <div class="coverage-tit"> 区域 </div> <div class="flex a-center"> <img src="../../../assets//point/uncheck.png" class="coverage-icon"> <div class="coverage-txt"> 运营区 </div> </div> <div class="flex a-center"> <img src="../../../assets//point/checked.png" class="coverage-icon"> <div class="coverage-txt"> 禁行区 </div> </div> <div class="coverage-tit"> 点位 </div> <div class="flex a-center"> <img src="../../../assets//point/checked.png" class="coverage-icon"> <div class="coverage-txt"> 运营点 </div> </div> <div class="flex a-center"> <img src="../../../assets//point/checked.png" class="coverage-icon"> <div class="coverage-txt"> 测试点 </div> </div> </div> </div> <div class="operate-bottom"> <div class="operate-card"> <img src="../../../assets//point/location.png" class="location-icon location"> <img src="../../../assets//point/add.png" class="location-icon add"> <img src="../../../assets//point/reduce.png" class="location-icon"> </div> </div> </div> <div class="point-detail"> <div class="point-info"> <div class="point-title"> <h2 class="panel-title"> 点位详情 </h2> </div> <!-- 详情内容 待完善 --> <div class="point-detail-content"> <div class="info-card"> <span class="info-icon" /> <div class="card-title"> 基本信息 </div> </div> <!-- 点位名称 --> <div class="info-form"> <div class="info-form-label"> <span class="label-require">*</span>点位名称 </div> <div class="info-form-value"> <input class="form-input" type="text" placeholder="请输入点位名称"> </div> </div> <!-- POI坐标 --> <div class="info-form"> <div class="info-form-label"> POI </div> <div class="info-form-value"> <input class="form-input" type="text" placeholder="请输入点位名称"> </div> </div> <!-- 点位ID --> <div class="info-form"> <div class="info-form-label"> 点位ID </div> <div class="info-form-value"> <input class="form-input" type="text" placeholder="请输入点位名称"> </div> </div> <!-- 备注 --> <div class="info-form"> <div class="info-form-label"> 备注 </div> <div class="info-form-value"> <n-input type="textarea" maxlength="200" show-count /> </div> </div> <!-- 点位图片 --> <div class="info-card"> <span class="info-icon" /> <div class="card-title"> 点位图片 </div> </div> <div class="info-form"> <div class="point-img"> <div v-for="item in uploadedImages" :key="item.id" class="img-item"> <img :src="item.url" class="point-img-item" @click="handlePreviewImage(item.url)"> <img class="img-delete" src="../../../assets/point/delete.png" @click.stop="handleDeleteImage(item.id)" > </div> <!-- 自定义上传区域 - 图片少于6张时显示 --> <div v-if="uploadedImages.length < 6" class="img-item upload-item" @click="triggerUpload"> <img src="../../../assets/point/upload.png" class="upload-icon"> <input ref="uploadInput" type="file" accept="image/jpeg,image/png" multiple style="display: none" @change="handleFileChange" > </div> </div> <div class="notice-card"> <img src="../../../assets/point/notice.png" class="img-notice"> <div class="notice-txt"> Supported: jpg/png, 单个图片最大2M </div> </div> </div> <!-- 运营信息 --> <div class="info-card"> <span class="info-icon" /> <div class="card-title"> 运营信息 </div> </div> <!-- 限停时长(分钟) --> <div class="info-form"> <div class="info-form-label"> 限停时长(分钟) </div> <div class="info-form-value"> <input class="form-input" type="text" placeholder="请输入点位名称"> </div> </div> <!-- 运营时间段 --> <div class="info-form"> <div class="info-form-label"> <span class="label-require">*</span>运营时间段 </div> <div class="info-form-value"> <input class="form-input" type="text" placeholder="请输入点位名称"> </div> </div> </div> </div> <!-- 底部保存 取消 --> <div class="panel-footer"> <div class="cancel-btn"> 取消 </div> <div class="submit-btn"> 保存 </div> </div> </div> </div> <!-- 图片预览弹窗 --> <div v-if="previewVisible" class="image-preview-modal" @click="closePreview"> <div class="preview-content"> <img :src="previewImage" class="preview-image" @click.stop> <div class="preview-close" @click="closePreview"> <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"> <path fill="currentColor" d="M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z" /> </svg> </div> </div> </div> </div> </template> <style lang="scss" scoped> @import './index.scss'; </style> <style lang="scss"> .robotaxi-content { padding: 0 !important;