| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728 |
- <template>
- <div class="element-config-panel" :class="{ 'is-collapsed': collapsed, 'is-visible': visible }">
- <!-- 折叠/展开提示条 -->
- <div class="panel-toggle-bar" @click="togglePanel">
- <div class="toggle-content">
- <i :class="collapsed ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
- <span v-if="selectedElement.id">
- {{ getElementTypeLabel(selectedElement.typeEle) }} - {{ selectedElement.id }}
- <span v-if="hasUnsavedChanges" class="unsaved-indicator">*</span>
- </span>
- <span v-else>选择元素以编辑参数</span>
- </div>
- <div v-if="!collapsed && selectedElement.id" class="toggle-actions">
- <el-button size="mini" @click.stop="resetForm">重置</el-button>
- <el-button size="mini" @click.stop="cancelEdit">取消</el-button>
- <el-button type="primary" size="mini" @click.stop="saveElement">保存</el-button>
- </div>
- </div>
- <!-- 参数配置表单 -->
- <div v-if="!collapsed && selectedElement.id" class="panel-content">
- <el-form
- ref="elementForm"
- :model="formData"
- :rules="formRules"
- label-width="120px"
- size="small"
- class="element-form"
- @input="handleFormChange"
- >
- <!-- 点类型参数 -->
- <template v-if="selectedElement.typeEle === 'Point'">
- <div class="form-section">
- <div class="section-title">基础参数</div>
- <el-form-item label="元素ID" prop="id">
- <el-input v-model="formData.id" disabled />
- </el-form-item>
- <el-form-item label="名称" prop="name">
- <el-input v-model="formData.name" placeholder="请输入名称" />
- </el-form-item>
- <el-form-item label="X坐标(m)" prop="x">
- <el-input-number
- v-model="formData.x"
- :precision="3"
- :step="0.1"
- controls-position="right"
- style="width: 100%;"
- />
- </el-form-item>
- <el-form-item label="Y坐标(m)" prop="y">
- <el-input-number
- v-model="formData.y"
- :precision="3"
- :step="0.1"
- controls-position="right"
- style="width: 100%;"
- />
- </el-form-item>
- <el-form-item label="Z坐标(m)" prop="z">
- <el-input-number
- v-model="formData.z"
- :precision="3"
- :step="0.1"
- controls-position="right"
- style="width: 100%;"
- />
- </el-form-item>
- </div>
- <div class="form-section">
- <div class="section-title">高级参数</div>
- <el-form-item label="航偏角使能" prop="isyawfix">
- <el-switch v-model="formData.isyawfix" />
- </el-form-item>
- <el-form-item label="航偏角(rad)" prop="yaw">
- <el-input-number
- v-model="formData.yaw"
- :precision="3"
- :step="0.1"
- controls-position="right"
- style="width: 100%;"
- />
- </el-form-item>
- </div>
- </template>
- <!-- 线/曲线类型参数 -->
- <template v-if="selectedElement.typeEle === 'LineString'">
- <div class="form-section">
- <div class="section-title">基础参数</div>
- <el-form-item label="元素ID" prop="id">
- <el-input v-model="formData.id" disabled />
- </el-form-item>
- <el-form-item label="名称" prop="name">
- <el-input v-model="formData.name" placeholder="请输入名称" />
- </el-form-item>
- <el-form-item label="起点ID" prop="startid">
- <el-input v-model="formData.startid" disabled />
- </el-form-item>
- <el-form-item label="终点ID" prop="endid">
- <el-input v-model="formData.endid" disabled />
- </el-form-item>
- </div>
- <div class="form-section">
- <div class="section-title">运动方向</div>
- <el-form-item label="起点→终点">
- <div class="direction-checkboxes">
- <el-checkbox v-model="formData.directList[0]">前行</el-checkbox>
- <el-checkbox v-model="formData.directList[1]">倒行</el-checkbox>
- </div>
- </el-form-item>
- <el-form-item label="终点→起点">
- <div class="direction-checkboxes">
- <el-checkbox v-model="formData.directList[2]">前行</el-checkbox>
- <el-checkbox v-model="formData.directList[3]">倒行</el-checkbox>
- </div>
- </el-form-item>
- </div>
- <div class="form-section">
- <div class="section-title">速度参数</div>
- <el-form-item label="最大限速(m/s)" prop="maxspeed">
- <el-input-number
- v-model="formData.maxspeed"
- :min="0"
- :precision="2"
- :step="0.1"
- controls-position="right"
- style="width: 100%;"
- />
- </el-form-item>
- <el-form-item label="最小限速(m/s)" prop="minspeed">
- <el-input-number
- v-model="formData.minspeed"
- :min="0"
- :precision="2"
- :step="0.1"
- controls-position="right"
- style="width: 100%;"
- />
- </el-form-item>
- </div>
- <div class="form-section">
- <div class="section-title">车道参数</div>
- <el-form-item label="车道宽度(m)" prop="lanewidth">
- <el-input-number
- v-model="formData.lanewidth"
- :min="0"
- :precision="2"
- :step="0.1"
- controls-position="right"
- style="width: 100%;"
- />
- </el-form-item>
- <el-form-item label="左车道数" prop="leftlanenum">
- <el-input-number
- v-model="formData.leftlanenum"
- :min="0"
- :step="1"
- controls-position="right"
- style="width: 100%;"
- />
- </el-form-item>
- <el-form-item label="右车道数" prop="rightlanenum">
- <el-input-number
- v-model="formData.rightlanenum"
- :min="0"
- :step="1"
- controls-position="right"
- style="width: 100%;"
- />
- </el-form-item>
- </div>
- <div class="form-section">
- <div class="section-title">高级参数</div>
- <el-form-item label="避障方式" prop="obstype">
- <el-select v-model="formData.obstype" style="width: 100%;">
- <el-option label="停车等待" :value="0" />
- <el-option label="车道绕障" :value="1" />
- <el-option label="路网绕障" :value="2" />
- </el-select>
- </el-form-item>
- <el-form-item label="正向前移(m)" prop="s2eforward">
- <el-input-number
- v-model="formData.s2eforward"
- :precision="2"
- :step="0.1"
- controls-position="right"
- style="width: 100%;"
- />
- </el-form-item>
- <el-form-item label="逆向前移(m)" prop="e2sforward">
- <el-input-number
- v-model="formData.e2sforward"
- :precision="2"
- :step="0.1"
- controls-position="right"
- style="width: 100%;"
- />
- </el-form-item>
- </div>
- </template>
- <!-- 面类型参数 -->
- <template v-if="selectedElement.typeEle === 'Polygon'">
- <div class="form-section">
- <div class="section-title">基础参数</div>
- <el-form-item label="元素ID" prop="id">
- <el-input v-model="formData.id" disabled />
- </el-form-item>
- <el-form-item label="名称" prop="name">
- <el-input v-model="formData.name" placeholder="请输入名称" />
- </el-form-item>
- <el-form-item label="区域类型" prop="type">
- <el-select v-model="formData.type" style="width: 100%;">
- <el-option label="隔离区域" :value="0" />
- <el-option label="装饰区域" :value="1" />
- <el-option label="禁行区域" :value="2" />
- <el-option label="会车管制区" :value="3" />
- <el-option label="道闸管控区" :value="4" />
- <el-option label="GPS定位区" :value="11" />
- <el-option label="动态禁行区" :value="22" />
- </el-select>
- </el-form-item>
- <el-form-item label="颜色" prop="color">
- <el-color-picker v-model="formData.color" />
- </el-form-item>
- <el-form-item label="透明度" prop="transparent">
- <el-slider
- v-model="formData.transparent"
- :min="0"
- :max="255"
- :step="5"
- style="width: 100%;"
- />
- </el-form-item>
- </div>
- </template>
- </el-form>
- </div>
- </div>
- </template>
- <script>
- export default {
- name: 'XtElementConfigPanel',
- props: {
- // 选中的元素
- selectedElement: {
- type: Object,
- default: () => ({})
- },
- // 面板是否可见
- visible: {
- type: Boolean,
- default: false
- },
- // 初始折叠状态
- initialCollapsed: {
- type: Boolean,
- default: true
- }
- },
- data() {
- return {
- collapsed: this.initialCollapsed,
- formData: {},
- originalFormData: {},
- hasUnsavedChanges: false,
- formRules: {
- name: [
- { max: 50, message: '名称长度不能超过50个字符', trigger: 'blur' }
- ],
- x: [
- { type: 'number', message: 'X坐标必须是数字', trigger: 'blur' }
- ],
- y: [
- { type: 'number', message: 'Y坐标必须是数字', trigger: 'blur' }
- ],
- z: [
- { type: 'number', message: 'Z坐标必须是数字', trigger: 'blur' }
- ],
- maxspeed: [
- { type: 'number', min: 0, message: '最大限速必须大于等于0', trigger: 'blur' }
- ],
- minspeed: [
- { type: 'number', min: 0, message: '最小限速必须大于等于0', trigger: 'blur' }
- ],
- lanewidth: [
- { type: 'number', min: 0, message: '车道宽度必须大于0', trigger: 'blur' }
- ]
- }
- }
- },
- computed: {
- // 计算面板是否应该显示
- shouldShow() {
- return this.visible && this.selectedElement && this.selectedElement.id
- }
- },
- watch: {
- selectedElement: {
- handler(newElement) {
- if (newElement && newElement.id) {
- this.initFormData(newElement)
- this.collapsed = false
- } else {
- this.collapsed = true
- this.formData = {}
- this.hasUnsavedChanges = false
- }
- },
- deep: true,
- immediate: true
- },
- visible(newVal) {
- if (!newVal) {
- this.collapsed = true
- }
- }
- },
- mounted() {
- // 监听 ESC 键
- document.addEventListener('keydown', this.handleKeydown)
- },
- beforeDestroy() {
- document.removeEventListener('keydown', this.handleKeydown)
- },
- methods: {
- // 初始化表单数据
- initFormData(element) {
- this.formData = { ...element }
-
- // 处理点类型的坐标
- if (element.typeEle === 'Point' && element.position) {
- this.formData.x = element.position[0] || 0
- this.formData.y = element.position[1] || 0
- this.formData.z = element.position[2] || 0
- }
-
- // 处理线类型的方向参数
- if (element.typeEle === 'LineString' && !element.directList) {
- // 如果没有directList,根据direct值生成
- const value = (element.direct || 100) - 100
- const binary = value.toString(2).padStart(4, '0')
- this.formData.directList = [
- binary[0] === '1',
- binary[1] === '1',
- binary[2] === '1',
- binary[3] === '1'
- ]
- }
-
- // 保存原始数据用于重置
- this.originalFormData = JSON.parse(JSON.stringify(this.formData))
- this.hasUnsavedChanges = false
- },
- // 切换面板展开/折叠
- togglePanel() {
- if (this.selectedElement.id) {
- this.collapsed = !this.collapsed
- this.$emit('panel-toggle', !this.collapsed)
- }
- },
- // 处理表单变化
- handleFormChange() {
- this.hasUnsavedChanges = true
- },
- // 保存元素
- saveElement() {
- this.$refs.elementForm.validate((valid) => {
- if (valid) {
- // 处理特殊字段
- const saveData = { ...this.formData }
-
- // 点类型:合并坐标
- if (this.selectedElement.typeEle === 'Point') {
- saveData.position = [
- this.formData.x || 0,
- this.formData.y || 0,
- this.formData.z || 0
- ]
- }
-
- // 线类型:处理方向参数
- if (this.selectedElement.typeEle === 'LineString' && this.formData.directList) {
- const binaryString = this.formData.directList.map(bit => (bit ? '1' : '0')).join('')
- const value = parseInt(binaryString, 2)
- saveData.direct = value + 100
- }
-
- this.$emit('save', saveData)
- this.hasUnsavedChanges = false
- this.originalFormData = JSON.parse(JSON.stringify(this.formData))
- }
- })
- },
- // 取消编辑
- cancelEdit() {
- if (this.hasUnsavedChanges) {
- this.$confirm('有未保存的更改,确定要取消吗?', '确认', {
- confirmButtonText: '确定',
- cancelButtonText: '继续编辑',
- type: 'warning'
- }).then(() => {
- this.resetForm()
- this.$emit('cancel')
- })
- } else {
- this.$emit('cancel')
- }
- },
- // 重置表单
- resetForm() {
- this.formData = JSON.parse(JSON.stringify(this.originalFormData))
- this.hasUnsavedChanges = false
- this.$refs.elementForm && this.$refs.elementForm.clearValidate()
- },
- // 获取元素类型标签
- getElementTypeLabel(type) {
- const typeMap = {
- 'Point': '点',
- 'LineString': '线',
- 'Polygon': '面'
- }
- return typeMap[type] || type
- },
- // 处理键盘事件
- handleKeydown(event) {
- if (!this.visible || this.collapsed) return
-
- if (event.key === 'Escape') {
- event.preventDefault()
- this.cancelEdit()
- } else if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
- event.preventDefault()
- this.saveElement()
- }
- },
- // 检查是否有未保存的更改
- checkUnsavedChanges() {
- return this.hasUnsavedChanges
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .element-config-panel {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- z-index: 1000;
- background: #fff;
- box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.1);
- border-top: 1px solid #e4e7ed;
- transform: translateY(100%);
- transition: all 0.3s ease;
- &.is-visible {
- transform: translateY(0);
- }
- &.is-collapsed {
- .panel-content {
- height: 0;
- overflow: hidden;
- }
- }
- .panel-toggle-bar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 12px 24px;
- background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
- border-bottom: 1px solid #e2e8f0;
- cursor: pointer;
- user-select: none;
- min-height: 48px;
- &:hover {
- background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
- }
- .toggle-content {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 14px;
- font-weight: 500;
- color: #475569;
- i {
- font-size: 16px;
- transition: transform 0.2s ease;
- }
- .unsaved-indicator {
- color: #f56c6c;
- font-weight: bold;
- margin-left: 4px;
- }
- }
- .toggle-actions {
- display: flex;
- gap: 8px;
-
- .el-button {
- padding: 6px 12px;
- font-size: 12px;
- }
- }
- }
- .panel-content {
- max-height: 60vh;
- overflow-y: auto;
- padding: 24px;
- background: #fff;
- // 自定义滚动条
- &::-webkit-scrollbar {
- width: 6px;
- }
- &::-webkit-scrollbar-track {
- background: #f1f1f1;
- border-radius: 3px;
- }
- &::-webkit-scrollbar-thumb {
- background: #c1c1c1;
- border-radius: 3px;
- &:hover {
- background: #a8a8a8;
- }
- }
- }
- .element-form {
- max-width: 1200px;
- margin: 0 auto;
- .form-section {
- margin-bottom: 32px;
- padding: 20px;
- background: #f8fafc;
- border-radius: 8px;
- border: 1px solid #e2e8f0;
- &:last-child {
- margin-bottom: 0;
- }
- .section-title {
- font-size: 16px;
- font-weight: 600;
- color: #334155;
- margin-bottom: 16px;
- padding-bottom: 8px;
- border-bottom: 2px solid #3b82f6;
- position: relative;
- &::before {
- content: '';
- position: absolute;
- left: 0;
- bottom: -2px;
- width: 40px;
- height: 2px;
- background: #3b82f6;
- }
- }
- }
- .direction-checkboxes {
- display: flex;
- gap: 16px;
- .el-checkbox {
- margin-right: 0;
- }
- }
- // 表单项样式优化
- :deep(.el-form-item) {
- margin-bottom: 20px;
- .el-form-item__label {
- font-weight: 500;
- color: #374151;
- line-height: 1.6;
- }
- .el-form-item__content {
- line-height: 1.6;
- }
- }
- // 输入框样式
- :deep(.el-input) {
- .el-input__inner {
- border-radius: 6px;
- border: 1px solid #d1d5db;
- transition: all 0.2s ease;
- &:focus {
- border-color: #3b82f6;
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
- }
- }
- }
- // 数字输入框样式
- :deep(.el-input-number) {
- width: 100%;
- .el-input__inner {
- border-radius: 6px;
- border: 1px solid #d1d5db;
- text-align: left;
- &:focus {
- border-color: #3b82f6;
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
- }
- }
- }
- // 选择器样式
- :deep(.el-select) {
- .el-input__inner {
- border-radius: 6px;
- border: 1px solid #d1d5db;
- &:focus {
- border-color: #3b82f6;
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
- }
- }
- }
- // 开关样式
- :deep(.el-switch) {
- .el-switch__core {
- border-radius: 12px;
- }
- }
- // 滑块样式
- :deep(.el-slider) {
- .el-slider__runway {
- background: #e2e8f0;
- border-radius: 3px;
- }
- .el-slider__bar {
- background: #3b82f6;
- border-radius: 3px;
- }
- .el-slider__button {
- border: 2px solid #3b82f6;
- background: #fff;
- }
- }
- // 复选框样式
- :deep(.el-checkbox) {
- .el-checkbox__input.is-checked .el-checkbox__inner {
- background-color: #3b82f6;
- border-color: #3b82f6;
- }
- .el-checkbox__inner {
- border-radius: 4px;
- }
- }
- }
- }
- // 响应式适配
- @media (max-width: 768px) {
- .element-config-panel {
- .panel-toggle-bar {
- padding: 8px 16px;
- flex-direction: column;
- gap: 8px;
- .toggle-actions {
- width: 100%;
- justify-content: flex-end;
- }
- }
- .panel-content {
- padding: 16px;
- }
- .element-form {
- .form-section {
- padding: 16px;
- margin-bottom: 24px;
- }
- .direction-checkboxes {
- flex-direction: column;
- gap: 8px;
- }
- }
- }
- }
- </style>
|