457 lines
15 KiB
Vue
457 lines
15 KiB
Vue
<template>
|
||
<div class="current1">
|
||
<div class="upload-div w-1/2 xl:w-1/2 m-4 mr-0" v-if="!isEdit">
|
||
<a-upload-dragger
|
||
v-model:fileList="shpFileList"
|
||
name="shpFileName"
|
||
style="height: 274px; width: 300px"
|
||
class="upload-dragger"
|
||
:multiple="false"
|
||
:maxCount="1"
|
||
:before-upload="shpBeforeUpload"
|
||
:customRequest="shpCustomRequest"
|
||
accept=".zip,.xls,.xlsx"
|
||
>
|
||
<p class="ant-upload-drag-icon">
|
||
<PlusOutlined />
|
||
</p>
|
||
<div style="opacity: 0.7"> 文件上传 </div>
|
||
<div class="upload-span">
|
||
<div class="upload-span-content">
|
||
<div style="opacity: 0.7">将文件拖拽到这里或点击上传按钮</div>
|
||
<div style="color: #1e5eff">支持扩展名:zip、xls、xlsx</div>
|
||
<div style="opacity: 0.7"> zip中需要包含 .shp .shx .dbf 文件 </div>
|
||
<div style="opacity: 0.7"> 且.shp文件大小小于3GB </div>
|
||
</div>
|
||
</div>
|
||
</a-upload-dragger>
|
||
</div>
|
||
<div class="upload-form w-1/2 xl:w-1/2 m-4 mr-0">
|
||
<a-form
|
||
ref="formRef"
|
||
:model="uploadFrom"
|
||
:labelCol="{ width: '75px' }"
|
||
labelAlign="right"
|
||
:rules="uploadFormRules"
|
||
>
|
||
<a-form-item label="服务名称" name="serverName">
|
||
<a-input v-model:value="uploadFrom.serverName" placeholder="请输入服务名称" />
|
||
</a-form-item>
|
||
<a-form-item label="空间参考" name="spatialRef">
|
||
<a-select
|
||
v-model:value="uploadFrom.spatialRef"
|
||
placeholder="请选择空间参考"
|
||
:disabled="isEdit"
|
||
>
|
||
<a-select-option value="EPSG:4326">EPSG:4326</a-select-option>
|
||
<a-select-option value="EPSG:3857">EPSG:3857</a-select-option>
|
||
<a-select-option value="EPSG:900913">EPSG:900913</a-select-option>
|
||
</a-select>
|
||
</a-form-item>
|
||
<a-form-item label="数据表名" name="dataTable">
|
||
<a-input
|
||
v-model:value="uploadFrom.dataTable"
|
||
placeholder="请输入表名"
|
||
:disabled="isEdit"
|
||
/>
|
||
</a-form-item>
|
||
<a-form-item label="图层样式" name="appendPath">
|
||
<a-upload
|
||
v-model:fileList="tucengFileList"
|
||
name="tucengFileName"
|
||
list-type="picture-card"
|
||
class="upload-dragger"
|
||
:multiple="false"
|
||
:maxCount="1"
|
||
:show-upload-list="false"
|
||
:before-upload="tucengBeforeUpload"
|
||
:custom-request="tucengCustomRequest"
|
||
accept=".sld"
|
||
>
|
||
<div>
|
||
<PlusOutlined />
|
||
<div class="ant-upload-text">上传图层样式</div>
|
||
</div>
|
||
</a-upload>
|
||
<div style="position: relative; top: 5px" v-if="tucengFileName">
|
||
{{ tucengFileName }}
|
||
<DeleteOutlined @click="deleteTuceng" />
|
||
</div>
|
||
</a-form-item>
|
||
</a-form>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script lang="ts" setup>
|
||
import { ref, reactive, watch } from 'vue';
|
||
// vben
|
||
import { PermissionBtn } from '@/components/PermissionBtn/index';
|
||
import { useMessage } from '@/hooks/web/useMessage';
|
||
import type { UploadProps } from 'ant-design-vue';
|
||
import { message, Upload } from 'ant-design-vue';
|
||
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
||
import { ShpGeoLayerParseShpInfo } from '@/api/demo/system';
|
||
import { CheckTableExist } from '@/api/database/index';
|
||
import axios from 'axios';
|
||
import { cloneDeep } from 'lodash-es';
|
||
import { getAppEnvConfig } from '@/utils/env';
|
||
|
||
const { VITE_GLOB_API_URL } = getAppEnvConfig();
|
||
|
||
const { createMessage } = useMessage();
|
||
|
||
const formRef = ref();
|
||
|
||
const props = defineProps(['uploadForm', 'isEdit']);
|
||
const uploadFrom = ref(props.uploadForm);
|
||
const isEdit = ref(props.isEdit);
|
||
const emit = defineEmits([
|
||
'uploadFormSubmit',
|
||
'setDataSourceType',
|
||
'setShpUploadType',
|
||
'setSldUploadType',
|
||
]);
|
||
|
||
watch(
|
||
() => props.uploadForm,
|
||
() => {
|
||
if (props.isEdit) {
|
||
uploadFrom.value = props.uploadForm;
|
||
}
|
||
},
|
||
);
|
||
|
||
const uploadFormRules = reactive({
|
||
serverName: [{ required: true, message: '请输入服务名称', trigger: 'blur' }],
|
||
spatialRef: [{ required: true, message: '请选择空间参考', trigger: 'blur' }],
|
||
// dataType: [{ required: true, message: '请选择数据类型', trigger: 'blur' }],
|
||
appendPath: [{ required: true, message: '请上传图层样式', trigger: 'blur' }],
|
||
dataTable: [
|
||
{ required: true, message: '请输入数据表名', trigger: 'blur' },
|
||
{
|
||
validator: (rule, value, callback) => {
|
||
let reg = /^[a-z]\w{0,28}$/;
|
||
if (value !== '' && !reg.test(value)) {
|
||
callback(
|
||
new Error('小写字母开头,可包含下划线,但不能有汉字和大写字母,不能超过30字符'),
|
||
);
|
||
}
|
||
callback();
|
||
},
|
||
trigger: 'blur',
|
||
},
|
||
{
|
||
validator: (rule, value, callback) => {
|
||
let query: any = { tableName: value };
|
||
CheckTableExist(query)
|
||
.then((res) => {
|
||
if (res) {
|
||
callback(new Error('数据库已有此数据表名,请更换表名'));
|
||
} else {
|
||
callback();
|
||
}
|
||
})
|
||
.catch(() => {
|
||
callback(new Error('检查数据表名时发生错误'));
|
||
});
|
||
},
|
||
trigger: 'blur',
|
||
},
|
||
],
|
||
});
|
||
|
||
// 步骤1-提交
|
||
const submit1 = () => {
|
||
formRef.value
|
||
.validate()
|
||
.then(() => {
|
||
let params = {
|
||
zipFilePath: uploadFrom.value.shpPath,
|
||
tableName: uploadFrom.value.dataTable,
|
||
srid: uploadFrom.value.spatialRef,
|
||
};
|
||
ShpGeoLayerParseShpInfo(params).then((res) => {
|
||
uploadFrom.value.dataType = res.dataType;
|
||
uploadFrom.value.headers = res.headers;
|
||
emit('uploadFormSubmit', uploadFrom.value);
|
||
});
|
||
})
|
||
.catch((error) => {
|
||
console.log(error);
|
||
});
|
||
};
|
||
|
||
// 步骤1-shp上传
|
||
const shpFileList = ref<UploadProps['fileList']>([]);
|
||
const shpFileName = ref<string>('');
|
||
let isShpUpload = false;
|
||
// 步骤1-shp上传之前
|
||
const shpBeforeUpload: UploadProps['beforeUpload'] = (file) => {
|
||
let extension = file.name.substring(file.name.lastIndexOf('.') + 1);
|
||
let size = file.size / 1024 / 1024;
|
||
const isZipOrXls = extension === 'zip' || extension === 'xls' || extension === 'xlsx';
|
||
const suitableSize = size < 1024 * 3;
|
||
if (!isZipOrXls) {
|
||
createMessage.error('只能上传后缀是.zip .xls或者.xlsx的文件');
|
||
}
|
||
if (extension == 'zip') {
|
||
emit('setDataSourceType', 'shp');
|
||
} else if (['xls', 'xlsx'].includes(extension)) {
|
||
emit('setDataSourceType', 'excel');
|
||
}
|
||
if (!suitableSize) {
|
||
createMessage.error('文件大小不得超过3GB');
|
||
}
|
||
isShpUpload = isZipOrXls && suitableSize;
|
||
if (isShpUpload) {
|
||
emit('setShpUploadType', true);
|
||
}
|
||
return isShpUpload;
|
||
};
|
||
// 步骤1-shp上传文件接口
|
||
const shpCustomRequest = (options) => {
|
||
if (isShpUpload) {
|
||
const formData = new FormData();
|
||
formData.append('files', options.file);
|
||
// 假设 Token 存储在 localStorage 中
|
||
const token = localStorage.getItem('X-Token');
|
||
// 设置请求头部,带上 Token
|
||
const headers = {
|
||
'X-Token': token,
|
||
};
|
||
axios
|
||
.post(VITE_GLOB_API_URL + '/api/Files/Upload', formData, { headers, timeout: 0 })
|
||
.then((response) => {
|
||
options.onSuccess(response.data, options.file);
|
||
shpFileName.value = response.data.result[0].fileName;
|
||
uploadFrom.value.shpPath = response.data.result[0].filePath;
|
||
emit('setShpUploadType', false);
|
||
// uploadFrom.value.shpFileName = response.data.result[0].fileName;
|
||
})
|
||
.catch((error) => {
|
||
options.onError(error);
|
||
});
|
||
}
|
||
};
|
||
|
||
// 步骤1-图层样式上传
|
||
const tucengFileList = ref<UploadProps['fileList']>([]);
|
||
const tucengFileName = ref<string>('');
|
||
let isSldUpload = false;
|
||
// 步骤1-图层上传之前
|
||
const tucengBeforeUpload: UploadProps['beforeUpload'] = (file) => {
|
||
let extension = file.name.substring(file.name.lastIndexOf('.') + 1);
|
||
let size = file.size / 1024 / 1024;
|
||
const isSld = extension === 'sld';
|
||
const suitableSize = size < 1;
|
||
if (!isSld) {
|
||
createMessage.error('只能上传后缀是.sld的文件');
|
||
}
|
||
if (!suitableSize) {
|
||
createMessage.error('文件大小不得超过1M');
|
||
}
|
||
isSldUpload = isSld && suitableSize;
|
||
if (isSldUpload) {
|
||
emit('setSldUploadType', true);
|
||
}
|
||
return isSldUpload;
|
||
};
|
||
// 步骤1-图层上传文件接口
|
||
const tucengCustomRequest = (options) => {
|
||
if (isSldUpload) {
|
||
const formData = new FormData();
|
||
readAndParseSLDFile(options.file);
|
||
formData.append('files', options.file);
|
||
// 假设 Token 存储在 localStorage 中
|
||
const token = localStorage.getItem('X-Token');
|
||
// 设置请求头部,带上 Token
|
||
const headers = {
|
||
'X-Token': token,
|
||
};
|
||
|
||
axios
|
||
.post(VITE_GLOB_API_URL + '/api/Files/Upload', formData, { headers })
|
||
.then((response) => {
|
||
options.onSuccess(response.data, options.file);
|
||
tucengFileName.value = response.data.result[0].fileName;
|
||
uploadFrom.value.appendPath = response.data.result[0].filePath;
|
||
emit('setSldUploadType', false);
|
||
// uploadFrom.value.appendFileName = response.data.result[0].fileName;
|
||
})
|
||
.catch((error) => {
|
||
options.onError(error);
|
||
});
|
||
console.log(uploadFrom.value);
|
||
}
|
||
};
|
||
// 步骤1-删除图层样式
|
||
const deleteTuceng = () => {
|
||
tucengFileName.value = '';
|
||
uploadFrom.value.appendPath = '';
|
||
// uploadFrom.value.appendFileName = '';
|
||
};
|
||
// 清除验证
|
||
const clearValidation = () => {
|
||
formRef.value.clearValidate();
|
||
shpFileName.value = '';
|
||
tucengFileName.value = '';
|
||
};
|
||
|
||
// 读取文件内容
|
||
const readAndParseSLDFile = async (file) => {
|
||
const reader = new FileReader();
|
||
reader.onload = (e) => {
|
||
const sldContent = e.target.result;
|
||
const styles = parseSLD(sldContent);
|
||
assemblyPaint(styles);
|
||
// uploadFrom.value.style = styles;
|
||
};
|
||
reader.onerror = (error) => {
|
||
console.error('Error reading the file:', error);
|
||
};
|
||
reader.readAsText(file);
|
||
};
|
||
// 解析sld文件
|
||
const parseSLD = (sldFileContent) => {
|
||
const parser = new DOMParser();
|
||
const xmlDoc = parser.parseFromString(sldFileContent, 'application/xml');
|
||
const namedLayers = xmlDoc.querySelectorAll('NamedLayer');
|
||
const styles: any = [];
|
||
|
||
namedLayers.forEach((namedLayer) => {
|
||
const userStyles = namedLayer.querySelectorAll('UserStyle');
|
||
userStyles.forEach((userStyle) => {
|
||
userStyle.children?.forEach((element) => {
|
||
if (element && element.nodeName == 'se:FeatureTypeStyle') {
|
||
element.children.forEach((featureTypeStyle) => {
|
||
let style: any = {};
|
||
const rules = featureTypeStyle.children;
|
||
rules.forEach((rule) => {
|
||
// se:Name
|
||
if (rule.nodeName == 'se:Name') {
|
||
style.ruleName = rule.textContent;
|
||
}
|
||
|
||
// ogc:Filter
|
||
if (rule.nodeName == 'ogc:Filter') {
|
||
const ogcFilter = rule.children;
|
||
if (ogcFilter[0].nodeName == 'ogc:PropertyIsEqualTo') {
|
||
ogcFilter[0].children.forEach((item) => {
|
||
if (item.nodeName == 'ogc:PropertyName') {
|
||
style.ogcPropertyName = item.textContent.toLowerCase();
|
||
}
|
||
if (item.nodeName == 'ogc:Literal') {
|
||
style.ogcLiteral = item.textContent;
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// se:PolygonSymbolizer
|
||
if (rule.nodeName == 'se:PolygonSymbolizer') {
|
||
const sePolygonSymbolizers = rule.children;
|
||
const fillParams: any = [];
|
||
const strokeParams: any = [];
|
||
sePolygonSymbolizers.forEach((sePolygonSymbolizer) => {
|
||
if (sePolygonSymbolizer.nodeName == 'se:Fill') {
|
||
const fills = sePolygonSymbolizer.children;
|
||
fills.forEach((fill) => {
|
||
if (fill.nodeName == 'se:SvgParameter') {
|
||
fillParams[fill.getAttribute('name')] = fill.textContent;
|
||
}
|
||
});
|
||
}
|
||
if (sePolygonSymbolizer.nodeName == 'se:Stroke') {
|
||
const fills = sePolygonSymbolizer.children;
|
||
fills.forEach((fill) => {
|
||
if (fill.nodeName == 'se:SvgParameter') {
|
||
strokeParams[fill.getAttribute('name')] = fill.textContent;
|
||
}
|
||
});
|
||
}
|
||
});
|
||
style.seFill = fillParams;
|
||
style.seStroke = strokeParams;
|
||
styles.push(style);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
});
|
||
});
|
||
});
|
||
return styles;
|
||
};
|
||
// 生成paint
|
||
const assemblyPaint = (styles) => {
|
||
let result: any = {};
|
||
// fill-opacity
|
||
result['fill-opacity'] = styles[0].seFill['fill-opacity']
|
||
? parseFloat(styles[0].seFill['fill-opacity'])
|
||
: 1;
|
||
|
||
// fill-color
|
||
let onlyFillColor: any = [];
|
||
const fillColor = styles.map((style) => {
|
||
const { ruleName, ogcPropertyName, ogcLiteral, seFill, seStroke } = style;
|
||
onlyFillColor.push(seFill['fill']);
|
||
if (ogcLiteral) {
|
||
return [['==', ['get', ogcPropertyName], ogcLiteral], seFill['fill']];
|
||
} else {
|
||
return seFill['fill'];
|
||
}
|
||
});
|
||
result['fill-color'] = ['case', ...fillColor.flat()];
|
||
|
||
onlyFillColor = [...new Set(onlyFillColor)];
|
||
if (onlyFillColor.length == 1) {
|
||
result['fill-color'] = onlyFillColor[0];
|
||
}
|
||
|
||
// fill-outline-color
|
||
let onlyFillOutlineColor: any = [];
|
||
const fillOutlineColor = styles.map((style) => {
|
||
const { ruleName, ogcPropertyName, ogcLiteral, seFill, seStroke } = style;
|
||
onlyFillOutlineColor.push(seStroke['stroke']);
|
||
if (ogcLiteral) {
|
||
return [['==', ['get', ogcPropertyName], ogcLiteral], seStroke['stroke']];
|
||
} else {
|
||
return seStroke['stroke'];
|
||
}
|
||
});
|
||
result['fill-outline-color'] = ['case', ...fillOutlineColor.flat()];
|
||
|
||
onlyFillOutlineColor = [...new Set(onlyFillOutlineColor)];
|
||
if (onlyFillOutlineColor.length == 1) {
|
||
result['fill-outline-color'] = onlyFillOutlineColor[0];
|
||
}
|
||
|
||
// style
|
||
uploadFrom.value.style = JSON.stringify(result);
|
||
console.log(uploadFrom.value.style);
|
||
};
|
||
|
||
defineExpose({
|
||
submit1,
|
||
clearValidation,
|
||
});
|
||
</script>
|
||
<style lang="less" scoped>
|
||
.current1 {
|
||
display: flex;
|
||
}
|
||
.upload-div {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
top: -30px;
|
||
}
|
||
.upload-span {
|
||
margin: 30px;
|
||
}
|
||
.upload-form {
|
||
margin-left: 100px;
|
||
}
|
||
</style>
|