李红攀:小程序新增农事记录
This commit is contained in:
4
env/.env
vendored
4
env/.env
vendored
@@ -10,8 +10,8 @@ VITE_WX_APPID = 'wx1a832d51073d3a35'
|
|||||||
VITE_APP_PUBLIC_BASE=/
|
VITE_APP_PUBLIC_BASE=/
|
||||||
|
|
||||||
# 后台请求地址
|
# 后台请求地址
|
||||||
VITE_SERVER_BASEURL = 'http://localhost:48080/admin-api'
|
VITE_SERVER_BASEURL = 'http://192.168.1.107:48080/admin-api'
|
||||||
VITE_UPLOAD_BASEURL = 'http://localhost:48080/upload'
|
VITE_UPLOAD_BASEURL = 'http://192.168.1.107:48080/upload'
|
||||||
# 备注:如果后台带统一前缀,则也要加到后面,eg: https://ukw0y1.laf.run/api
|
# 备注:如果后台带统一前缀,则也要加到后面,eg: https://ukw0y1.laf.run/api
|
||||||
|
|
||||||
# 注意,如果是微信小程序,还有一套请求地址的配置,根据 develop、trial、release 分别设置上传地址,见 `src/utils/index.ts`。
|
# 注意,如果是微信小程序,还有一套请求地址的配置,根据 develop、trial、release 分别设置上传地址,见 `src/utils/index.ts`。
|
||||||
|
|||||||
2
env/.env.development
vendored
2
env/.env.development
vendored
@@ -6,4 +6,4 @@ VITE_DELETE_CONSOLE = false
|
|||||||
VITE_SHOW_SOURCEMAP = false
|
VITE_SHOW_SOURCEMAP = false
|
||||||
|
|
||||||
# 后台请求地址
|
# 后台请求地址
|
||||||
# VITE_SERVER_BASEURL = 'http://localhost:48080'
|
# VITE_SERVER_BASEURL = 'http://1921.168.1.107:48080'
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ export interface AgriOperationFormReqVO {
|
|||||||
workContent?: string
|
workContent?: string
|
||||||
imageUrls?: string
|
imageUrls?: string
|
||||||
remark?: string
|
remark?: string
|
||||||
|
longitude?: number
|
||||||
|
latitude?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取农事操作分页列表 */
|
/** 获取农事操作分页列表 */
|
||||||
|
|||||||
18
src/api/agri/plot.ts
Normal file
18
src/api/agri/plot.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { http } from '@/http/http'
|
||||||
|
|
||||||
|
export interface AgriPlotVO {
|
||||||
|
id: number
|
||||||
|
plotCode?: string
|
||||||
|
plotName?: string
|
||||||
|
area?: number
|
||||||
|
boundaryGeojson?: string
|
||||||
|
centerLongitude?: number
|
||||||
|
centerLatitude?: number
|
||||||
|
managerName?: string
|
||||||
|
status?: number
|
||||||
|
remark?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPlot(id: number) {
|
||||||
|
return http.get<AgriPlotVO>(`/agri/plot/get?id=${id}`)
|
||||||
|
}
|
||||||
@@ -85,6 +85,26 @@
|
|||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
</wd-cell-group>
|
</wd-cell-group>
|
||||||
|
|
||||||
|
<wd-cell-group title="地块地图" border>
|
||||||
|
<view class="map-section">
|
||||||
|
<view class="map-status-row">
|
||||||
|
<text class="map-status-text" :class="mapStatusClass">{{ mapStatusText }}</text>
|
||||||
|
<wd-button size="small" plain @click="handleRelocate">定位当前位置</wd-button>
|
||||||
|
</view>
|
||||||
|
<map
|
||||||
|
class="plot-map"
|
||||||
|
:longitude="mapCenter.longitude"
|
||||||
|
:latitude="mapCenter.latitude"
|
||||||
|
:scale="mapScale"
|
||||||
|
:markers="mapMarkers"
|
||||||
|
:polygons="mapPolygons"
|
||||||
|
:show-location="true"
|
||||||
|
:enable-zoom="true"
|
||||||
|
:enable-scroll="true"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</wd-cell-group>
|
||||||
</wd-form>
|
</wd-form>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -107,9 +127,12 @@ import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
|
|||||||
import type { UploadFile, UploadMethod } from 'wot-design-uni/components/wd-upload/types'
|
import type { UploadFile, UploadMethod } from 'wot-design-uni/components/wd-upload/types'
|
||||||
import { OPERATION_TYPE_MAP } from '@/api/agri/operation'
|
import { OPERATION_TYPE_MAP } from '@/api/agri/operation'
|
||||||
import type { AgriBatchVO } from '@/api/agri/batch'
|
import type { AgriBatchVO } from '@/api/agri/batch'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
import { useToast } from 'wot-design-uni'
|
import { useToast } from 'wot-design-uni'
|
||||||
import { getEnvBaseUrl, navigateBackPlus } from '@/utils'
|
import { getEnvBaseUrl, navigateBackPlus } from '@/utils'
|
||||||
|
import { getGeoJsonPolygonPaths, isPointInGeoJson } from '@/utils/geo'
|
||||||
|
import { getPlot } from '@/api/agri/plot'
|
||||||
import {
|
import {
|
||||||
createOperation,
|
createOperation,
|
||||||
getOperation,
|
getOperation,
|
||||||
@@ -161,12 +184,66 @@ const formData = ref(createDefaultFormData())
|
|||||||
|
|
||||||
/** 图片列表 */
|
/** 图片列表 */
|
||||||
const fileList = ref<UploadFile[]>([])
|
const fileList = ref<UploadFile[]>([])
|
||||||
|
const locationPoint = ref<{ longitude: number, latitude: number } | null>(null)
|
||||||
|
const mapScale = ref(16)
|
||||||
|
const mapCenter = ref({ longitude: 116.397428, latitude: 39.90923 })
|
||||||
|
const mapPolygons = ref<any[]>([])
|
||||||
|
const currentPlotGeoJson = ref<any | null>(null)
|
||||||
|
const mapReady = ref(false)
|
||||||
|
const locationLoading = ref(false)
|
||||||
|
const locationWatchActive = ref(false)
|
||||||
|
const locationDenied = ref(false)
|
||||||
|
let locationWatchTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
|
||||||
const formRules = {
|
const formRules = {
|
||||||
batchId: [{ required: true, message: '请选择批次' }],
|
batchId: [{ required: true, message: '请选择批次' }],
|
||||||
operationType: [{ required: true, message: '请选择操作类型' }],
|
operationType: [{ required: true, message: '请选择操作类型' }],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapMarkers = computed(() => {
|
||||||
|
if (!locationPoint.value) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return [{
|
||||||
|
id: 1,
|
||||||
|
longitude: locationPoint.value.longitude,
|
||||||
|
latitude: locationPoint.value.latitude,
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
title: '当前位置',
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
|
const inPlotRange = computed(() => {
|
||||||
|
if (!locationPoint.value || !currentPlotGeoJson.value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return isPointInGeoJson(locationPoint.value, currentPlotGeoJson.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStatusText = computed(() => {
|
||||||
|
if (locationLoading.value) {
|
||||||
|
return '正在获取当前位置...'
|
||||||
|
}
|
||||||
|
if (!formData.value.batchId) {
|
||||||
|
return '请选择批次后查看地块范围'
|
||||||
|
}
|
||||||
|
if (!currentPlotGeoJson.value) {
|
||||||
|
return '当前批次暂无可展示的地块范围'
|
||||||
|
}
|
||||||
|
if (!locationPoint.value || locationDenied.value) {
|
||||||
|
return '需要开启定位权限,用于显示当前位置和判断是否在地块范围内'
|
||||||
|
}
|
||||||
|
return inPlotRange.value ? '当前位于地块范围内' : '当前位置不在地块范围内'
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStatusClass = computed(() => {
|
||||||
|
if (!locationPoint.value || !currentPlotGeoJson.value || locationLoading.value) {
|
||||||
|
return 'is-neutral'
|
||||||
|
}
|
||||||
|
return inPlotRange.value ? 'is-success' : 'is-warning'
|
||||||
|
})
|
||||||
|
|
||||||
/** 批次下拉列 */
|
/** 批次下拉列 */
|
||||||
const batchColumns = computed(() =>
|
const batchColumns = computed(() =>
|
||||||
batchList.value
|
batchList.value
|
||||||
@@ -213,11 +290,206 @@ function buildUploadFileList(imageUrls?: string) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSelectedPlotId() {
|
||||||
|
const selectedBatch = batchList.value.find(batch => batch.id === formData.value.batchId)
|
||||||
|
return selectedBatch?.plotId
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseBoundaryGeoJson(boundaryGeojson?: string) {
|
||||||
|
if (!boundaryGeojson) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(boundaryGeojson)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMapCenter(longitude: number, latitude: number) {
|
||||||
|
mapCenter.value = { longitude, latitude }
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPlotGeoJsonToMap(geoJson: any) {
|
||||||
|
currentPlotGeoJson.value = geoJson
|
||||||
|
const polygonPaths = getGeoJsonPolygonPaths(geoJson)
|
||||||
|
mapPolygons.value = polygonPaths.map((points, index) => ({
|
||||||
|
id: index + 1,
|
||||||
|
points,
|
||||||
|
strokeWidth: 2,
|
||||||
|
strokeColor: '#1E80FF',
|
||||||
|
fillColor: 'rgba(30,128,255,0.20)',
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (!locationPoint.value && polygonPaths[0]?.[0]) {
|
||||||
|
updateMapCenter(polygonPaths[0][0].longitude, polygonPaths[0][0].latitude)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearPlotMap() {
|
||||||
|
currentPlotGeoJson.value = null
|
||||||
|
mapPolygons.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSelectedPlotMap() {
|
||||||
|
const plotId = getSelectedPlotId()
|
||||||
|
if (!plotId) {
|
||||||
|
clearPlotMap()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const plot = await getPlot(plotId)
|
||||||
|
const geoJson = parseBoundaryGeoJson(plot?.boundaryGeojson)
|
||||||
|
if (!geoJson) {
|
||||||
|
clearPlotMap()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
applyPlotGeoJsonToMap(geoJson)
|
||||||
|
} catch {
|
||||||
|
clearPlotMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentLocation() {
|
||||||
|
return new Promise<{ longitude: number, latitude: number }>((resolve, reject) => {
|
||||||
|
uni.getLocation({
|
||||||
|
type: 'gcj02',
|
||||||
|
success: (res) => {
|
||||||
|
resolve({
|
||||||
|
longitude: Number(res.longitude),
|
||||||
|
latitude: Number(res.latitude),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fail: reject,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function openLocationPermissionSetting() {
|
||||||
|
uni.showModal({
|
||||||
|
title: '定位权限提醒',
|
||||||
|
content: '需要开启定位权限,用于显示当前位置和判断是否在地块范围内',
|
||||||
|
confirmText: '去开启',
|
||||||
|
success: (res) => {
|
||||||
|
if (!res.confirm) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (typeof uni.openSetting === 'function') {
|
||||||
|
uni.openSetting({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLocationFailure(showPermissionToast: boolean) {
|
||||||
|
locationPoint.value = null
|
||||||
|
locationDenied.value = true
|
||||||
|
if (!showPermissionToast) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
openLocationPermissionSetting()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshCurrentLocation(showPermissionToast = true) {
|
||||||
|
locationLoading.value = true
|
||||||
|
try {
|
||||||
|
const currentLocation = await getCurrentLocation()
|
||||||
|
locationPoint.value = currentLocation
|
||||||
|
locationDenied.value = false
|
||||||
|
updateMapCenter(currentLocation.longitude, currentLocation.latitude)
|
||||||
|
mapReady.value = true
|
||||||
|
return currentLocation
|
||||||
|
} catch {
|
||||||
|
handleLocationFailure(showPermissionToast)
|
||||||
|
return null
|
||||||
|
} finally {
|
||||||
|
locationLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRelocate() {
|
||||||
|
await refreshCurrentLocation(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopLocationWatch() {
|
||||||
|
if (locationWatchTimer) {
|
||||||
|
clearInterval(locationWatchTimer)
|
||||||
|
locationWatchTimer = null
|
||||||
|
}
|
||||||
|
locationWatchActive.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function startLocationWatch() {
|
||||||
|
stopLocationWatch()
|
||||||
|
if (currentId.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
locationWatchActive.value = true
|
||||||
|
locationWatchTimer = setInterval(() => {
|
||||||
|
refreshCurrentLocation(false)
|
||||||
|
}, 10000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前使用 gcj02 定位坐标参与圈地判断,默认要求地块 boundaryGeojson 采用相同坐标系。
|
||||||
|
// 若圈地数据来源坐标系不是 gcj02,需要在后端或数据入库环节统一,而不是在这里做隐式转换。
|
||||||
|
async function checkLocationInLand() {
|
||||||
|
const plotId = getSelectedPlotId()
|
||||||
|
if (!plotId) {
|
||||||
|
toast.show('请选择批次')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentLocation = await refreshCurrentLocation(false)
|
||||||
|
if (!currentLocation) {
|
||||||
|
toast.show('定位获取失败,请开启定位权限后重试')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let geoJson = currentPlotGeoJson.value
|
||||||
|
if (!geoJson) {
|
||||||
|
try {
|
||||||
|
const plot = await getPlot(plotId)
|
||||||
|
geoJson = parseBoundaryGeoJson(plot?.boundaryGeojson)
|
||||||
|
if (geoJson) {
|
||||||
|
applyPlotGeoJsonToMap(geoJson)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
toast.show('地块范围获取失败,请稍后重试')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!geoJson) {
|
||||||
|
toast.show('该地块范围数据格式异常,暂无法提交')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const inRange = isPointInGeoJson(currentLocation, geoJson)
|
||||||
|
if (!inRange) {
|
||||||
|
toast.show('当前位置不在该地块范围内,请移动到地块范围内后再提交')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
/** 批次选择回调 */
|
/** 批次选择回调 */
|
||||||
function onBatchConfirm({ value }: { value: unknown }) {
|
function onBatchConfirm({ value }: { value: unknown }) {
|
||||||
formData.value.batchId = normalizePickerValue(value)
|
formData.value.batchId = normalizePickerValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(() => formData.value.batchId, async (batchId) => {
|
||||||
|
if (!batchId || currentId.value) {
|
||||||
|
clearPlotMap()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await loadSelectedPlotMap()
|
||||||
|
if (!mapReady.value) {
|
||||||
|
await refreshCurrentLocation(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/** 操作类型选择回调 */
|
/** 操作类型选择回调 */
|
||||||
function onTypeConfirm({ value }: { value: unknown }) {
|
function onTypeConfirm({ value }: { value: unknown }) {
|
||||||
formData.value.operationType = normalizePickerValue(value)
|
formData.value.operationType = normalizePickerValue(value)
|
||||||
@@ -305,6 +577,8 @@ function buildSubmitData() {
|
|||||||
operatorName: formData.value.operatorName || '',
|
operatorName: formData.value.operatorName || '',
|
||||||
workContent: formData.value.workContent || '',
|
workContent: formData.value.workContent || '',
|
||||||
remark: formData.value.remark || '',
|
remark: formData.value.remark || '',
|
||||||
|
longitude: locationPoint.value?.longitude,
|
||||||
|
latitude: locationPoint.value?.latitude,
|
||||||
}
|
}
|
||||||
if (operationDateTimestamp.value) {
|
if (operationDateTimestamp.value) {
|
||||||
const date = new Date(operationDateTimestamp.value)
|
const date = new Date(operationDateTimestamp.value)
|
||||||
@@ -335,6 +609,11 @@ async function handleSubmit() {
|
|||||||
const { valid } = await formRef.value!.validate()
|
const { valid } = await formRef.value!.validate()
|
||||||
if (!valid) return
|
if (!valid) return
|
||||||
|
|
||||||
|
if (!currentId.value) {
|
||||||
|
const checked = await checkLocationInLand()
|
||||||
|
if (!checked) return
|
||||||
|
}
|
||||||
|
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
const submitData = buildSubmitData()
|
const submitData = buildSubmitData()
|
||||||
@@ -360,8 +639,59 @@ onMounted(async () => {
|
|||||||
operationDateTimestamp.value = Date.now()
|
operationDateTimestamp.value = Date.now()
|
||||||
await loadBatchList()
|
await loadBatchList()
|
||||||
await getDetail()
|
await getDetail()
|
||||||
|
if (!currentId.value) {
|
||||||
|
await refreshCurrentLocation(true)
|
||||||
|
await loadSelectedPlotMap()
|
||||||
|
startLocationWatch()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
if (!currentId.value) {
|
||||||
|
refreshCurrentLocation(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
stopLocationWatch()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.map-section {
|
||||||
|
padding: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-status-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-status-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-status-text.is-success {
|
||||||
|
color: #18a058;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-status-text.is-warning {
|
||||||
|
color: #d46b08;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-status-text.is-neutral {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plot-map {
|
||||||
|
width: 100%;
|
||||||
|
height: 420rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
156
src/utils/geo.js
Normal file
156
src/utils/geo.js
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
export function isPointInGeoJson(point, geoJson) {
|
||||||
|
if (!point || !isFiniteNumber(point.longitude) || !isFiniteNumber(point.latitude) || !geoJson) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const geometries = collectGeometries(geoJson)
|
||||||
|
if (!geometries.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return geometries.some((geometry) => {
|
||||||
|
if (!geometry || !geometry.type || !geometry.coordinates) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (geometry.type === 'Polygon') {
|
||||||
|
return isPointInPolygonCoordinates(point, geometry.coordinates)
|
||||||
|
}
|
||||||
|
if (geometry.type === 'MultiPolygon') {
|
||||||
|
return geometry.coordinates.some(polygon => isPointInPolygonCoordinates(point, polygon))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGeoJsonPolygonPaths(geoJson) {
|
||||||
|
const geometries = collectGeometries(geoJson)
|
||||||
|
if (!geometries.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return geometries.flatMap((geometry) => {
|
||||||
|
if (!geometry || !geometry.type || !geometry.coordinates) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (geometry.type === 'Polygon') {
|
||||||
|
return buildPolygonPaths(geometry.coordinates)
|
||||||
|
}
|
||||||
|
if (geometry.type === 'MultiPolygon') {
|
||||||
|
return geometry.coordinates.flatMap(polygon => buildPolygonPaths(polygon))
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectGeometries(geoJson) {
|
||||||
|
if (!geoJson || typeof geoJson !== 'object') {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (geoJson.type === 'Feature') {
|
||||||
|
return geoJson.geometry ? [geoJson.geometry] : []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (geoJson.type === 'FeatureCollection') {
|
||||||
|
return (geoJson.features || []).flatMap(feature => collectGeometries(feature))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (geoJson.type === 'Polygon' || geoJson.type === 'MultiPolygon') {
|
||||||
|
return [geoJson]
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPointInPolygonCoordinates(point, polygonCoordinates) {
|
||||||
|
if (!Array.isArray(polygonCoordinates) || polygonCoordinates.length === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const [outerRing, ...holes] = polygonCoordinates
|
||||||
|
if (!isPointInRing(point, outerRing)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return !holes.some(hole => isPointInRing(point, hole))
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPolygonPaths(polygonCoordinates) {
|
||||||
|
if (!Array.isArray(polygonCoordinates) || polygonCoordinates.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const [outerRing] = polygonCoordinates
|
||||||
|
const points = normalizeRingPoints(outerRing)
|
||||||
|
return points.length >= 3 ? [points] : []
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeRingPoints(ring) {
|
||||||
|
if (!Array.isArray(ring)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return ring
|
||||||
|
.filter(isValidCoordinate)
|
||||||
|
.map(coordinate => ({
|
||||||
|
longitude: Number(coordinate[0]),
|
||||||
|
latitude: Number(coordinate[1]),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPointInRing(point, ring) {
|
||||||
|
if (!Array.isArray(ring) || ring.length < 3) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = Number(point.longitude)
|
||||||
|
const y = Number(point.latitude)
|
||||||
|
let inside = false
|
||||||
|
|
||||||
|
for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) {
|
||||||
|
const current = ring[i]
|
||||||
|
const previous = ring[j]
|
||||||
|
if (!isValidCoordinate(current) || !isValidCoordinate(previous)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const xi = Number(current[0])
|
||||||
|
const yi = Number(current[1])
|
||||||
|
const xj = Number(previous[0])
|
||||||
|
const yj = Number(previous[1])
|
||||||
|
|
||||||
|
if (isPointOnSegment(x, y, xi, yi, xj, yj)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const intersects = ((yi > y) !== (yj > y))
|
||||||
|
&& (x <= ((xj - xi) * (y - yi)) / (yj - yi) + xi)
|
||||||
|
|
||||||
|
if (intersects) {
|
||||||
|
inside = !inside
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inside
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPointOnSegment(px, py, x1, y1, x2, y2) {
|
||||||
|
const cross = (px - x1) * (y2 - y1) - (py - y1) * (x2 - x1)
|
||||||
|
if (Math.abs(cross) > 1e-10) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const dot = (px - x1) * (px - x2) + (py - y1) * (py - y2)
|
||||||
|
return dot <= 1e-10
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidCoordinate(coordinate) {
|
||||||
|
return Array.isArray(coordinate)
|
||||||
|
&& coordinate.length >= 2
|
||||||
|
&& isFiniteNumber(coordinate[0])
|
||||||
|
&& isFiniteNumber(coordinate[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFiniteNumber(value) {
|
||||||
|
return Number.isFinite(Number(value))
|
||||||
|
}
|
||||||
@@ -123,7 +123,7 @@ export function getEnvBaseUrl() {
|
|||||||
|
|
||||||
// # 有些同学可能需要在微信小程序里面根据 develop、trial、release 分别设置上传地址,参考代码如下。
|
// # 有些同学可能需要在微信小程序里面根据 develop、trial、release 分别设置上传地址,参考代码如下。
|
||||||
// TODO @芋艿:这个后续也要调整。
|
// TODO @芋艿:这个后续也要调整。
|
||||||
const VITE_SERVER_BASEURL__WEIXIN_DEVELOP = 'http://localhost:48080/admin-api'
|
const VITE_SERVER_BASEURL__WEIXIN_DEVELOP = 'http://192.168.1.107:48080/admin-api'
|
||||||
const VITE_SERVER_BASEURL__WEIXIN_TRIAL = 'http://119.96.62.56:7004/admin-api'
|
const VITE_SERVER_BASEURL__WEIXIN_TRIAL = 'http://119.96.62.56:7004/admin-api'
|
||||||
const VITE_SERVER_BASEURL__WEIXIN_RELEASE = 'http://localhost:48080/admin-api'
|
const VITE_SERVER_BASEURL__WEIXIN_RELEASE = 'http://localhost:48080/admin-api'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user