From 0ff976b4e9406b9c88aef069a308355ea7083f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Mon, 27 Oct 2025 17:19:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(hjm):=20=E5=AE=9E=E7=8E=B0=E8=BD=A6?= =?UTF-8?q?=E8=BE=86=E4=BD=8D=E7=BD=AE=E5=9D=90=E6=A0=87=E8=BD=AC=E6=8D=A2?= =?UTF-8?q?=E4=B8=8E=E8=BF=9D=E7=AB=A0=E6=9F=A5=E8=AF=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 GPS 坐标转换工具类,支持 WGS84、GCJ02 和 BD09 坐标系互转 - 在车辆定位页面集成坐标转换逻辑,确保地图显示准确性 -优化车辆列表分页加载功能,提升大数据量下的用户体验- 修改车辆热销组件属性类型定义,增强代码可维护性 - 调整违章记录列表页面,新增通过车辆编号查询功能 - 更新最佳销售车辆组件键值生成逻辑,提高组件稳定性 - 完善地图组件 markers 和 polygons 数据校验,防止无效坐标渲染 - 在车辆详情页增加违章查询跳转按钮,方便用户操作 - 移除违章记录删除功能,避免误删重要数据 -优化空状态提示文案,使用户理解更清晰 --- src/hjm/BestSellers.tsx | 17 +-- src/hjm/list.tsx | 80 +++++++++++--- src/hjm/location.tsx | 216 +++++++++++++++++++++++++++++++------ src/hjm/query.tsx | 9 ++ src/hjm/violation/list.tsx | 41 +++---- src/utils/gpsUtil.js | 203 ++++++++++++++++++++++++++++++++++ 6 files changed, 497 insertions(+), 69 deletions(-) create mode 100644 src/utils/gpsUtil.js diff --git a/src/hjm/BestSellers.tsx b/src/hjm/BestSellers.tsx index e58b323..4647d3a 100644 --- a/src/hjm/BestSellers.tsx +++ b/src/hjm/BestSellers.tsx @@ -1,10 +1,15 @@ import {useEffect} from "react"; import {Image, Space} from '@nutui/nutui-react-taro' import Taro from '@tarojs/taro' +import {HjmCar} from "@/api/hjm/hjmCar/model"; -const BestSellers = (props: any) => { +interface BestSellersProps { + data: HjmCar[]; +} + +const BestSellers = (props: BestSellersProps) => { const reload = () => { - + // 可以在这里添加重新加载逻辑 } useEffect(() => { @@ -16,7 +21,7 @@ const BestSellers = (props: any) => {
{props.data?.map((item, index) => { return ( -
Taro.navigateTo({url: '/hjm/query?id=' + item.code})}> { item.image && ( {
) })} - {props.data.length === 0 && ( -
+ {(!props.data || props.data.length === 0) && ( +
暂无数据
)} @@ -50,4 +55,4 @@ const BestSellers = (props: any) => {
) } -export default BestSellers +export default BestSellers \ No newline at end of file diff --git a/src/hjm/list.tsx b/src/hjm/list.tsx index 7541ef8..903daef 100644 --- a/src/hjm/list.tsx +++ b/src/hjm/list.tsx @@ -1,4 +1,4 @@ -import {useEffect, useState} from "react"; +import {useEffect, useState, CSSProperties} from "react"; import {Search} from '@nutui/icons-react-taro' import {Button, Input, InfiniteLoading} from '@nutui/nutui-react-taro' import {pageHjmCar} from "@/api/hjm/hjmCar"; @@ -8,6 +8,14 @@ import './location.scss' import BestSellers from "./BestSellers"; import {getUserInfo} from "@/api/layout"; +const InfiniteUlStyle: CSSProperties = { + height: '80vh', + width: '100%', + padding: '0', + overflowY: 'auto', + overflowX: 'hidden', +} + /** * 文章终极列表 * @constructor @@ -15,14 +23,20 @@ import {getUserInfo} from "@/api/layout"; const List = () => { const [keywords, setKeywords] = useState('') const [list, setList] = useState([]) + const [page, setPage] = useState(1) + const [hasMore, setHasMore] = useState(true) + const [loading, setLoading] = useState(false) const onKeywords = (keywords: string) => { setKeywords(keywords) } - const reload = async () => { + const loadList = async (pageNum: number, isRefresh = false) => { + if (loading) return; + + setLoading(true) // 搜索条件 - const where = {status: 1, deleted: 0, keywords} + const where = {status: 1, deleted: 0, keywords, page: pageNum, limit: 10} // 读取用户信息 const user = await getUserInfo(); @@ -46,20 +60,53 @@ const List = () => { where.installerId = user.userId; } if(roleCode == 'user'){ + setLoading(false) return false; } // 获取车辆列表 - pageHjmCar(where).then(res => { - setList(res?.list || []) - }) + try { + const res = await pageHjmCar(where); + if (res?.list && res?.list.length > 0) { + if (isRefresh) { + setList(res.list); + } else { + setList(prevList => [...prevList, ...res.list]); + } + setHasMore(res.list.length >= 10); // 如果返回的数据少于10条,说明没有更多了 + } else { + if (isRefresh) { + setList([]); + } + setHasMore(false); + } + } catch (error) { + console.error('获取车辆列表失败:', error); + if (isRefresh) { + setList([]); + } + setHasMore(false); + } finally { + setLoading(false); + } + } + + const reload = async () => { + setPage(1); + await loadList(1, true); + } + + const loadMore = async () => { + if (!hasMore || loading) return; + const nextPage = page + 1; + setPage(nextPage); + await loadList(nextPage); } useEffect(() => { reload() }, []) - return ( <>
@@ -90,12 +137,19 @@ const List = () => {
- - - +
+ + + +
) } -export default List +export default List \ No newline at end of file diff --git a/src/hjm/location.tsx b/src/hjm/location.tsx index 926c5c0..62428be 100644 --- a/src/hjm/location.tsx +++ b/src/hjm/location.tsx @@ -5,6 +5,8 @@ import {Button, Input} from '@nutui/nutui-react-taro' import {useRouter} from '@tarojs/taro' import {getHjmCarByCode} from "@/api/hjm/hjmCar"; import {HjmCar} from "@/api/hjm/hjmCar/model"; +// 导入整个GPS对象,因为gpsUtil.js导出的是一个对象而不是命名导出 +import GPS from '@/utils/gpsUtil.js'; import './location.scss' /** @@ -36,9 +38,17 @@ const Location = () => { // 转为多边形点数组 const points = coordPairs.map(coord => { const [lat, lng] = coord.split(','); + const latitude = parseFloat(lat); + const longitude = parseFloat(lng); + + // 确保解析后的坐标是有效数字 + if (isNaN(latitude) || isNaN(longitude)) { + throw new Error(`无效坐标: ${lat}, ${lng}`); + } + return { - latitude: parseFloat(lat), - longitude: parseFloat(lng) + latitude: latitude, + longitude: longitude } }); @@ -57,10 +67,41 @@ const Location = () => { getHjmCarByCode(keywords).then(data => { console.log('执行搜索', data) setItem(data) - // setLatitude(data.latitude) - // setLongitude(data.longitude) - // wgs48坐标转gcj02坐标代 - + // 转换WGS84坐标到GCJ02坐标 + if (data.latitude && data.longitude && + !isNaN(data.latitude) && + !isNaN(data.longitude) && + isFinite(data.latitude) && + isFinite(data.longitude)) { + console.log('原始坐标:', data.latitude, data.longitude) + try { + // 确保坐标是数字类型 + const lat = Number(data.latitude); + const lng = Number(data.longitude); + const transformed = GPS.gcj_encrypt(lat, lng); + console.log(transformed, 'transformedtransformedtransformedtransformedtransformed') + // 确保转换结果有效 + if (transformed && !isNaN(transformed.lat) && !isNaN(transformed.lng)) { + setLatitude(transformed.lat); + setLongitude(transformed.lng); + console.log('转换结果:', transformed) + } else { + // 如果转换结果无效,使用原始坐标 + setLatitude(data.latitude); + setLongitude(data.longitude); + console.log('转换失败:', data.longitude) + } + } catch (error) { + console.error('坐标转换错误:', error); + // 如果转换出错,使用原始坐标 + setLatitude(data.latitude); + setLongitude(data.longitude); + } + } else { + // 如果坐标无效,使用原始坐标 + setLatitude(data.latitude); + setLongitude(data.longitude); + } setKeywords(data.code) if (data.fence) { @@ -69,8 +110,44 @@ const Location = () => { // 使用通用函数解析坐标字符串 const {points} = parseCoordinateString(coordStr); + + // 转换围栏坐标点 + if (points.length > 0) { + const transformedPoints = points.map(point => { + // 确保坐标有效 + if (point.latitude && point.longitude && + !isNaN(point.latitude) && + !isNaN(point.longitude) && + isFinite(point.latitude) && + isFinite(point.longitude)) { + try { + // 确保坐标是数字类型 + const lat = Number(point.latitude); + const lng = Number(point.longitude); + const transformed = GPS.gcj_encrypt(lat, lng); + // 确保转换结果有效 + if (transformed && !isNaN(transformed.lat) && !isNaN(transformed.lng)) { + return { + latitude: transformed.lat, + longitude: transformed.lng + }; + } else { + // 如果转换结果无效,返回原始坐标 + return point; + } + } catch (error) { + console.error('围栏坐标转换错误:', error); + // 如果转换出错,返回原始坐标 + return point; + } + } + // 如果坐标无效,返回原始坐标 + return point; + }); + setPoints(transformedPoints); + } + console.log('解析结果 - 多边形点:', points); - setPoints(points); setShowCircles(true) } }) @@ -80,8 +157,38 @@ const Location = () => { if (code) { getHjmCarByCode(code).then(data => { setItem(data) - setLatitude(data.latitude) - setLongitude(data.longitude) + // 转换WGS84坐标到GCJ02坐标 + if (data.latitude && data.longitude && + !isNaN(data.latitude) && + !isNaN(data.longitude) && + isFinite(data.latitude) && + isFinite(data.longitude)) { + try { + // 确保坐标是数字类型 + const lat = Number(data.latitude); + const lng = Number(data.longitude); + const transformed = GPS.gcj_encrypt(lat, lng); + // 确保转换结果有效 + if (transformed && !isNaN(transformed.lat) && !isNaN(transformed.lng)) { + setLatitude(transformed.lat); + setLongitude(transformed.lng); + } else { + // 如果转换结果无效,使用原始坐标 + setLatitude(data.latitude); + setLongitude(data.longitude); + } + } catch (error) { + console.error('坐标转换错误:', error); + // 如果转换出错,使用原始坐标 + setLatitude(data.latitude); + setLongitude(data.longitude); + } + } else { + // 如果坐标无效,使用原始坐标 + setLatitude(data.latitude); + setLongitude(data.longitude); + } + setKeywords(data.code) if (data.fence) { // 方法2:使用实际的 fence 数据(如果是字符串格式) @@ -89,8 +196,44 @@ const Location = () => { // 使用通用函数解析坐标字符串 const {points} = parseCoordinateString(coordStr); + + // 转换围栏坐标点 + if (points.length > 0) { + const transformedPoints = points.map(point => { + // 确保坐标有效 + if (point.latitude && point.longitude && + !isNaN(point.latitude) && + !isNaN(point.longitude) && + isFinite(point.latitude) && + isFinite(point.longitude)) { + try { + // 确保坐标是数字类型 + const lat = Number(point.latitude); + const lng = Number(point.longitude); + const transformed = GPS.gcj_encrypt(lat, lng); + // 确保转换结果有效 + if (transformed && !isNaN(transformed.lat) && !isNaN(transformed.lng)) { + return { + latitude: transformed.lat, + longitude: transformed.lng + }; + } else { + // 如果转换结果无效,返回原始坐标 + return point; + } + } catch (error) { + console.error('围栏坐标转换错误:', error); + // 如果转换出错,返回原始坐标 + return point; + } + } + // 如果坐标无效,返回原始坐标 + return point; + }); + setPoints(transformedPoints); + } + console.log('解析结果 - 多边形点:', points); - setPoints(points); setShowCircles(true) } @@ -194,31 +337,40 @@ const Location = () => { longitude={longitude} latitude={latitude} scale={scale} - markers={[{ - id: 1, - latitude: latitude, - longitude: longitude, - label: { - content: `${item?.code}`, - color: '#000000', - fontSize: 12, - borderRadius: 5, - bgColor: '#FFFFFF', - // @ts-ignore - padding: '5px 5px', - borderWidth: 1 - }, - }]} - polygons={points.length > 0 ? [ - { - points: points, - color: '#ff0000', - strokeWidth: 3 - } - ] : []} + markers={ + latitude && longitude && + !isNaN(latitude) && !isNaN(longitude) && + isFinite(latitude) && isFinite(longitude) + ? [{ + id: 1, + latitude: latitude, + longitude: longitude, + iconPath: '' + }] + : [] + } + polygons={ + points.length > 0 && + points.every(point => + point.latitude && point.longitude && + !isNaN(point.latitude) && !isNaN(point.longitude) && + isFinite(point.latitude) && isFinite(point.longitude) + ) + ? [ + { + points: points, + color: '#ff0000', + strokeWidth: 3 + } + ] + : [] + } onTap={() => { console.log('map tap') }} + onError={(e) => { + console.error('地图组件错误:', e) + }} style={{width: '100%', height: '100vh'}} /> diff --git a/src/hjm/query.tsx b/src/hjm/query.tsx index d73e320..938627b 100644 --- a/src/hjm/query.tsx +++ b/src/hjm/query.tsx @@ -839,6 +839,15 @@ const Query = () => { }> 位置信息 + ) } diff --git a/src/hjm/violation/list.tsx b/src/hjm/violation/list.tsx index f244ad7..3e246d0 100644 --- a/src/hjm/violation/list.tsx +++ b/src/hjm/violation/list.tsx @@ -8,9 +8,10 @@ import { Space, Pagination } from '@nutui/nutui-react-taro' +import {useRouter} from '@tarojs/taro' import {Search, Calendar, Truck, File, AddCircle} from '@nutui/icons-react-taro' import Taro, {useDidShow} from '@tarojs/taro' -import {pageHjmViolation, removeHjmViolation} from "@/api/hjm/hjmViolation"; +import {pageHjmViolation} from "@/api/hjm/hjmViolation"; import {HjmViolation} from "@/api/hjm/hjmViolation/model"; @@ -18,6 +19,7 @@ import {HjmViolation} from "@/api/hjm/hjmViolation/model"; * 报险记录列表页面 */ const List: React.FC = () => { + const {params} = useRouter(); const [list, setList] = useState([]) const [loading, setLoading] = useState(false) const [keywords, setKeywords] = useState('') @@ -64,6 +66,9 @@ const List: React.FC = () => { where.organizationId = Taro.getStorageSync('OrganizationId'); } } + if(params.id){ + where.code = params.id; + } const res = await pageHjmViolation(where) setList(res?.list || []) @@ -183,7 +188,7 @@ const List: React.FC = () => { 加载中... ) : list.length === 0 ? ( - + ) : (
@@ -292,22 +297,22 @@ const List: React.FC = () => { url: `/hjm/violation/add?id=${item.id}` }) }}>修改 - + {/**/}
)} diff --git a/src/utils/gpsUtil.js b/src/utils/gpsUtil.js new file mode 100644 index 0000000..bb9a7d1 --- /dev/null +++ b/src/utils/gpsUtil.js @@ -0,0 +1,203 @@ +var GPS = { + PI: 3.14159265358979324, + x_pi: 3.14159265358979324 * 3000.0 / 180.0, + delta: function(lat, lng) { + // Krasovsky 1940 + // + // a = 6378245.0, 1/f = 298.3 + // b = a * (1 - f) + // ee = (a^2 - b^2) / a^2; + var a = 6378245.0; // a: 卫星椭球坐标投影到平面地图坐标系的投影因子。 + var ee = 0.00669342162296594323; // ee: 椭球的偏心率。 + var dLat = this.transformLat(lng - 105.0, lat - 35.0); + var dLon = this.transformLon(lng - 105.0, lat - 35.0); + var radLat = lat / 180.0 * this.PI; + var magic = Math.sin(radLat); + magic = 1 - ee * magic * magic; + var sqrtMagic = Math.sqrt(magic); + dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * this.PI); + dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * this.PI); + return { + 'lat': dLat, + 'lng': dLon + }; + }, + + //WGS-84 to GCJ-02 ,传入的数据要是Number + gcj_encrypt: function(wgsLat, wgsLon) { + if (this.outOfChina(wgsLat, wgsLon)) + return { + 'lat': wgsLat, + 'lng': wgsLon + }; + + var d = this.delta(wgsLat, wgsLon); + return { + 'lat': Number( (wgsLat + d.lat).toFixed(6)), + 'lng': Number( (wgsLon + d.lng).toFixed(6)) + }; + }, + + //WGS-84 to GCJ-02 + gcj_encrypt222: function(wgsLat, wgsLon) { + if (this.outOfChina(wgsLat, wgsLon)) + return wgsLat + "," + wgsLon; + + var d = this.delta(wgsLat, wgsLon); + + var lat = (wgsLat + d.lat).toFixed(6); + var lng = (wgsLon + d.lng).toFixed(6); + return lat+ "," + lng + + }, + + + //GCJ-02 to WGS-84 + gcj_decrypt: function(gcjLat, gcjLon) { + if (this.outOfChina(gcjLat, gcjLon)) + return { + 'lat': Number((gcjLat).toFixed(6)), + 'lng': Number((gcjLon).toFixed(6)) + }; + + var d = this.delta(gcjLat, gcjLon); + return { + 'lat': Number( (gcjLat - d.lat).toFixed(6)), + 'lng': Number( (gcjLon - d.lng).toFixed(6)) + }; + }, + //GCJ-02 to WGS-84 exactly + gcj_decrypt_exact: function(gcjLat, gcjLon) { + var initDelta = 0.01; + var threshold = 0.000000001; + var dLat = initDelta, + dLon = initDelta; + var mLat = gcjLat - dLat, + mLon = gcjLon - dLon; + var pLat = gcjLat + dLat, + pLon = gcjLon + dLon; + var wgsLat, wgsLon, i = 0; + while (1) { + wgsLat = (mLat + pLat) / 2; + wgsLon = (mLon + pLon) / 2; + var tmp = this.gcj_encrypt(wgsLat, wgsLon) + dLat = tmp.lat - gcjLat; + dLon = tmp.lng - gcjLon; + if ((Math.abs(dLat) < threshold) && (Math.abs(dLon) < threshold)) + break; + + if (dLat > 0) pLat = wgsLat; + else mLat = wgsLat; + if (dLon > 0) pLon = wgsLon; + else mLon = wgsLon; + + if (++i > 10000) break; + } + //console.log(i); + return { + 'lat': (wgsLat).toFixed(6), + 'lng': (wgsLon).toFixed(6) + }; + }, + //GCJ-02 to BD-09 + bd_encrypt: function(gcjLat, gcjLon) { + var x = gcjLon, + y = gcjLat; + var z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * this.x_pi); + var theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * this.x_pi); + bdLon = z * Math.cos(theta) + 0.0065; + bdLat = z * Math.sin(theta) + 0.006; + return { + 'lat': (bdLat).toFixed(6), + 'lng': (bdLon).toFixed(6) + }; + }, + //BD-09 to GCJ-02 + bd_decrypt: function(bdLat, bdLon) { + var x = bdLon - 0.0065, + y = bdLat - 0.006; + var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * this.x_pi); + var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * this.x_pi); + var gcjLon = z * Math.cos(theta); + var gcjLat = z * Math.sin(theta); + return { + 'lat': (gcjLat).toFixed(6), + 'lng': (gcjLon).toFixed(6) + }; + }, + //WGS-84 to Web mercator + //mercatorLat -> y mercatorLon -> x + mercator_encrypt: function(wgsLat, wgsLon) { + var x = wgsLon * 20037508.34 / 180.; + var y = Math.log(Math.tan((90. + wgsLat) * this.PI / 360.)) / (this.PI / 180.); + y = y * 20037508.34 / 180.; + return { + 'lat': (y).toFixed(6), + 'lng': (x).toFixed(6) + }; + /* + if ((Math.abs(wgsLon) > 180 || Math.abs(wgsLat) > 90)) + return null; + var x = 6378137.0 * wgsLon * 0.017453292519943295; + var a = wgsLat * 0.017453292519943295; + var y = 3189068.5 * Math.log((1.0 + Math.sin(a)) / (1.0 - Math.sin(a))); + return {'lat' : y, 'lng' : x}; + //*/ + }, + // Web mercator to WGS-84 + // mercatorLat -> y mercatorLon -> x + mercator_decrypt: function(mercatorLat, mercatorLon) { + var x = mercatorLon / 20037508.34 * 180.; + var y = mercatorLat / 20037508.34 * 180.; + y = 180 / this.PI * (2 * Math.atan(Math.exp(y * this.PI / 180.)) - this.PI / 2); + return { + 'lat': (y).toFixed(6), + 'lng': (x).toFixed(6) + }; + /* + if (Math.abs(mercatorLon) < 180 && Math.abs(mercatorLat) < 90) + return null; + if ((Math.abs(mercatorLon) > 20037508.3427892) || (Math.abs(mercatorLat) > 20037508.3427892)) + return null; + var a = mercatorLon / 6378137.0 * 57.295779513082323; + var x = a - (Math.floor(((a + 180.0) / 360.0)) * 360.0); + var y = (1.5707963267948966 - (2.0 * Math.atan(Math.exp((-1.0 * mercatorLat) / 6378137.0)))) * 57.295779513082323; + return {'lat' : y, 'lng' : x}; + //*/ + }, + // two point's distance + distance: function(latA, lonA, latB, lonB) { + var earthR = 6371000.; + var x = Math.cos(latA * this.PI / 180.) * Math.cos(latB * this.PI / 180.) * Math.cos((lonA - lonB) * this.PI / 180); + var y = Math.sin(latA * this.PI / 180.) * Math.sin(latB * this.PI / 180.); + var s = x + y; + if (s > 1) s = 1; + if (s < -1) s = -1; + var alpha = Math.acos(s); + var distance = alpha * earthR; + return distance; + }, + outOfChina: function(lat, lng) { + if (lng < 72.004 || lng > 137.8347) + return true; + if (lat < 0.8293 || lat > 55.8271) + return true; + return false; + }, + transformLat: function(x, y) { + var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); + ret += (20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(y * this.PI) + 40.0 * Math.sin(y / 3.0 * this.PI)) * 2.0 / 3.0; + ret += (160.0 * Math.sin(y / 12.0 * this.PI) + 320 * Math.sin(y * this.PI / 30.0)) * 2.0 / 3.0; + return ret; + }, + transformLon: function(x, y) { + var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); + ret += (20.0 * Math.sin(6.0 * x * this.PI) + 20.0 * Math.sin(2.0 * x * this.PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(x * this.PI) + 40.0 * Math.sin(x / 3.0 * this.PI)) * 2.0 / 3.0; + ret += (150.0 * Math.sin(x / 12.0 * this.PI) + 300.0 * Math.sin(x / 30.0 * this.PI)) * 2.0 / 3.0; + return ret; + } +}; + +module.exports = GPS;