Files
nl-acs3.0/nl-vue/src/views/nl_agv/map/form.vue
2026-01-21 20:43:15 +08:00

403 lines
9.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<xn-form-container
:title="formData.id ? '编辑地图信息' : '新增地图信息'"
:width="900"
:visible="visible"
:destroy-on-close="true"
@close="onClose"
>
<div class="form-content">
<!-- 左侧地图预览区域 -->
<div class="image-preview-section" v-if="formData.id && imagePreviewUrl">
<div class="preview-title">地图预览</div>
<div class="preview-container">
<a-spin :spinning="imageLoading" tip="加载中...">
<a-image v-if="imagePreviewUrl" :src="imagePreviewUrl" :preview="true" class="map-image" />
</a-spin>
</div>
</div>
<!-- 右侧表单区域 -->
<div class="form-section" :class="{ 'full-width': !formData.id || !imagePreviewUrl }">
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<a-form-item label="地图ID" name="mapId">
<a-input-number
v-model:value="formData.mapId"
placeholder="请输入地图ID"
:disabled="!!formData.id"
style="width: 100%"
:min="1"
/>
</a-form-item>
<a-form-item label="地图名称:" name="mapName">
<a-input
v-model:value="formData.mapName"
placeholder="请输入地图名称"
allow-clear
/>
</a-form-item>
<a-form-item label="版本号:" name="version">
<a-input-number
v-model:value="formData.version"
placeholder="请输入版本号"
style="width: 100%"
:min="1"
/>
</a-form-item>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="地图宽度:" name="width">
<a-input-number
v-model:value="formData.width"
placeholder="宽度"
style="width: 100%"
:min="1"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="地图高度:" name="height">
<a-input-number
v-model:value="formData.height"
placeholder="高度"
style="width: 100%"
:min="1"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="原点X坐标" name="x">
<a-input-number
v-model:value="formData.x"
placeholder="X坐标"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="原点Y坐标" name="y">
<a-input-number
v-model:value="formData.y"
placeholder="Y坐标"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="地图文件:" name="url">
<a-upload
v-model:file-list="fileList"
name="file"
list-type="picture-card"
class="map-uploader"
:show-upload-list="true"
:before-upload="beforeUpload"
:custom-request="handleUpload"
@remove="handleRemove"
@preview="handlePreview"
:max-count="1"
>
<div v-if="fileList.length < 1">
<plus-outlined />
<div style="margin-top: 8px">上传地图</div>
</div>
</a-upload>
<div class="upload-tip">支持jpgpng格式建议上传清晰的地图图片</div>
</a-form-item>
<a-form-item label="启用状态:" name="enable">
<a-switch
v-model:checked="formData.enable"
checked-children="启用"
un-checked-children="禁用"
/>
</a-form-item>
</a-form>
</div>
</div>
<template #footer>
<a-button class="xn-mr8" @click="onClose">关闭</a-button>
<a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
</template>
</xn-form-container>
</template>
<script setup name="nlAgvMapForm">
import { required } from '@/utils/formRules'
import mapApi from '@/api/agv/mapApi'
import fileApi from '@/api/dev/fileApi'
import { message } from 'ant-design-vue'
// 文件服务器地址
const FILE_SERVER = 'http://localhost:8082'
// 默认是关闭状态
const visible = ref(false)
const emit = defineEmits({ successful: null })
const formRef = ref()
// 表单数据
const formData = ref({
enable: false,
version: 1,
x: 0,
y: 0
})
const submitLoading = ref(false)
const fileList = ref([])
const imagePreviewUrl = ref('') // 图片预览URL
const imageLoading = ref(false) // 图片加载状态
// 加载地图图片
const loadMapImage = (url, updateFileList = false) => {
if (!url) return
imageLoading.value = true
// 构建完整的图片URL
let imageUrl = url
if (url.startsWith('/Users/') || url.startsWith('C:\\') || url.startsWith('/')) {
// 本地文件路径,通过文件服务器访问
imageUrl = FILE_SERVER + url
}
const img = new Image()
img.onload = () => {
imagePreviewUrl.value = imageUrl
// 如果需要更新文件列表(编辑时)
if (updateFileList) {
fileList.value = [
{
uid: '-1',
name: 'map-image.png',
status: 'done',
url: imageUrl,
thumbUrl: imageUrl
}
]
}
imageLoading.value = false
}
img.onerror = () => {
message.error('地图图片加载失败')
imageLoading.value = false
}
img.src = imageUrl
}
// 打开抽屉
const onOpen = (record) => {
visible.value = true
formData.value = {
enable: false,
version: 1,
x: 0,
y: 0
}
fileList.value = []
imagePreviewUrl.value = ''
if (record) {
submitLoading.value = true
const param = {
id: record.id
}
mapApi
.detail(param)
.then((data) => {
formData.value = Object.assign({}, data)
// 如果有地图URL加载图片
if (data.url) {
loadMapImage(data.url, true)
}
})
.catch((error) => {
message.error('获取详情失败: ' + (error.message || error))
})
.finally(() => {
submitLoading.value = false
})
}
}
// 关闭抽屉
const onClose = () => {
formRef.value.resetFields()
fileList.value = []
imagePreviewUrl.value = ''
visible.value = false
}
// 默认要校验的
const formRules = {
mapId: [required('请输入地图ID')],
mapName: [required('请输入地图名称')],
version: [required('请输入版本号')],
width: [required('请输入地图宽度')],
height: [required('请输入地图高度')],
url: [required('请上传地图文件')]
}
// 上传前校验
const beforeUpload = (file) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'
if (!isJpgOrPng) {
message.error('只能上传 JPG/PNG 格式的图片!')
return false
}
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
message.error('图片大小不能超过 10MB!')
return false
}
return true
}
// 自定义上传
const handleUpload = ({ file, onSuccess, onError }) => {
const uploadFormData = new FormData()
uploadFormData.append('file', file)
fileApi
.fileUploadDynamicReturnId(uploadFormData)
.then((res) => {
if (res) {
// 保存文件路径到表单数据
formData.value.url = res.filePath || res.url || res
message.success('上传成功')
// 加载新上传的图片预览
if (formData.value.url) {
loadMapImage(formData.value.url)
}
onSuccess(res)
} else {
message.error('上传失败')
onError(new Error('上传失败'))
}
})
.catch((error) => {
message.error('上传失败: ' + error.message)
onError(error)
})
}
// 删除图片
const handleRemove = () => {
formData.value.url = ''
imagePreviewUrl.value = ''
}
// 预览图片
const handlePreview = async (file) => {
// 如果已经有预览URL直接使用
if (imagePreviewUrl.value) {
return
}
// 如果是新上传的文件使用file对象创建预览
if (file.originFileObj) {
imagePreviewUrl.value = URL.createObjectURL(file.originFileObj)
}
}
// 验证并提交数据
const onSubmit = () => {
formRef.value
.validate()
.then(() => {
submitLoading.value = true
const apiMethod = formData.value.id ? mapApi.edit : mapApi.save
apiMethod(formData.value)
.then(() => {
message.success('保存成功')
onClose()
emit('successful')
})
.catch((error) => {
message.error('保存失败: ' + (error.message || error))
})
.finally(() => {
submitLoading.value = false
})
})
.catch(() => {})
}
// 调用这个函数将子组件的一些数据和方法暴露出去
defineExpose({
onOpen
})
</script>
<style scoped lang="less">
.form-content {
display: flex;
gap: 24px;
min-height: 400px;
}
.image-preview-section {
flex: 0 0 350px;
display: flex;
flex-direction: column;
.preview-title {
font-size: 16px;
font-weight: 500;
margin-bottom: 16px;
color: #333;
}
.preview-container {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 8px;
padding: 20px;
.map-image {
max-width: 100%;
max-height: 400px;
object-fit: contain;
}
}
}
.form-section {
flex: 1;
min-width: 0;
&.full-width {
flex: 1 1 100%;
}
}
.map-uploader {
:deep(.ant-upload-select-picture-card) {
width: 120px;
height: 120px;
}
:deep(.ant-upload-list-picture-card-container) {
width: 120px;
height: 120px;
}
}
.upload-tip {
color: #999;
font-size: 12px;
margin-top: 8px;
}
.xn-mr8 {
margin-right: 8px;
}
</style>