Compare commits

...

10 Commits

Author SHA1 Message Date
96b75c6cab fix(user): 优化实名认证审核流程并修复权限控制问题
- 重构 onPass 和 onReject 方法,添加异常处理和 Promise 确保操作顺序
- 修复实名认证状态显示问题,当 statusText 为空时显示默认状态文本
- 更新权限检查逻辑,将固定存储检查改为角色权限动态验证
- 修复组织机构筛选逻辑,区分站点角色和商户角色的数据权限范围
- 添加商户角色的多组织机构查询支持,完善数据隔离机制
2026-01-13 15:29:12 +08:00
c250f2ae5e feat(config): 更新API基础URL配置
- 将src/utils/config.ts中的BaseUrl从yd-api.websoft.top更改为cms-api.websoft.top
- 更新config/env.ts中development、production和test环境的API_BASE_URL配置
- 修改src/bszx/pay/pay.tsx中支付接口请求地址为新的cms-api域名
2025-12-19 00:41:34 +08:00
7df71448c9 feat(passport): 更新登录页面交互逻辑
- 修改手机号输入框提示文案为“请输入账号”- 移除手机号格式校验逻辑
- 将短信验证码输入框改为密码类型,并修改提示文案为“请输入密码”- 移除获取验证码按钮及相关倒计时逻辑
- 调整用户协议勾选框文案,去除“勾选表示您”前缀-优化登录接口调用参数格式
2025-12-09 11:42:53 +08:00
6bf4799789 feat(passport): 更新登录页面交互逻辑
- 修改手机号输入框提示文案为“请输入账号”- 移除手机号格式校验逻辑
- 将短信验证码输入框改为密码类型,并修改提示文案为“请输入密码”- 移除获取验证码按钮及相关倒计时逻辑
- 调整用户协议勾选框文案,去除“勾选表示您”前缀-优化登录接口调用参数格式
2025-11-04 17:03:55 +08:00
7b78a39b52 feat(map): 添加GPS坐标转换功能以支持WGS84到GCJ02转换
- 在多个组件中导入GPS坐标转换工具
- 实现WGS84坐标到GCJ02坐标的转换逻辑
- 更新地图初始化坐标和标记点坐标处理方式
- 增加对无效坐标和转换异常的错误处理
- 修改车辆轨迹点坐标转换流程
- 调整地图显示逻辑以使用转换后坐标
2025-10-30 20:30:36 +08:00
7c0c367041 refactor(cms): 更新表单模型导入路径
- 将 PageParam 类型导入路径从 '@/api' 更改为 '@/api/index'
- 统一了类型导入的完整路径引用方式
2025-10-29 16:11:12 +08:00
3c61f2db3b refactor(cms): 更新表单模型导入路径
- 将 PageParam 类型导入路径从 '@/api' 更改为 '@/api/index'
- 统一了类型导入的完整路径引用方式
2025-10-29 16:10:20 +08:00
4157f9804c fix(violation):修复违规项图片显示和导航跳转问题
-修复图片地址解析逻辑,避免数组为空时报错- 统一使用 navTo 方法处理页面跳转
- 优化图片组件的健壮性判断条件
2025-10-29 15:34:18 +08:00
52d2d7c773 feat(violation):重构违章列表页面并新增组件- 移除原有列表展示逻辑,使用Items组件渲染列表项
- 新增FixedButton组件用于底部固定按钮- 实现无限滚动加载功能替换原有分页
- 调整搜索栏样式和位置,优化用户体验- 根据用户角色过滤数据展示内容- 添加车辆图片字段支持
- 注释掉处理状态选择功能,待后续完善
2025-10-29 14:41:47 +08:00
164cb594fa fix(violation):修复违规列表重复加载和分页问题
- 将 isLoading 状态改为使用 useRef 避免重复请求
-修复初始加载时的重复请求问题
-优化分页逻辑,避免不必要的重新加载
- 完善生命周期和事件监听处理- 改进列表项 key 的唯一性判断
2025-10-29 13:37:30 +08:00
13 changed files with 614 additions and 496 deletions

View File

@@ -1,4 +1,4 @@
import type { PageParam } from '@/api'; import type { PageParam } from '@/api/index';
/** /**
* 表单设计表 * 表单设计表

View File

@@ -8,6 +8,8 @@ export interface HjmViolation {
id?: number; id?: number;
// 车辆编号 // 车辆编号
code?: string; code?: string;
// 车辆图片
image?: string;
// 标题 // 标题
title?: string; title?: string;
// 文章分类ID // 文章分类ID

View File

@@ -0,0 +1,38 @@
import React from 'react';
import {View} from '@tarojs/components';
import {Button} from '@nutui/nutui-react-taro'
interface FixedButtonProps {
text?: string;
onClick?: () => void;
icon?: React.ReactNode;
disabled?: boolean;
background?: string;
}
function FixedButton({text, onClick, icon, disabled, background}: FixedButtonProps) {
return (
<>
{/* 底部安全区域占位 */}
<View className="h-20 w-full"></View>
<View
className="fixed z-50 bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-4 py-3 safe-area-bottom">
<Button
type="primary"
style={{
background
}}
size="large"
block
icon={icon}
disabled={disabled}
className="px-6"
onClick={onClick}>
{text || '新增'}
</Button>
</View>
</>
)
}
export default FixedButton;

View File

@@ -8,6 +8,8 @@ import './location.scss'
import {HjmFence} from "@/api/hjm/hjmFence/model"; import {HjmFence} from "@/api/hjm/hjmFence/model";
import {getHjmFence} from "@/api/hjm/hjmFence"; import {getHjmFence} from "@/api/hjm/hjmFence";
import {Market} from "../pages/index"; import {Market} from "../pages/index";
// 导入GPS坐标转换工具
import GPS from '@/utils/gpsUtil.js';
interface Where { interface Where {
latitude?: number; latitude?: number;
longitude?: number; longitude?: number;
@@ -42,9 +44,30 @@ const Location = () => {
// 转为多边形点数组 // 转为多边形点数组
const points = coordPairs.map(coord => { const points = coordPairs.map(coord => {
const [lat, lng] = coord.split(','); const [lat, lng] = coord.split(',');
const latitude = parseFloat(lat);
const longitude = parseFloat(lng);
// 确保解析后的坐标是有效数字
if (isNaN(latitude) || isNaN(longitude)) {
throw new Error(`无效坐标: ${lat}, ${lng}`);
}
// 转换WGS84坐标到GCJ02坐标
try {
const transformed = GPS.gcj_encrypt(latitude, longitude);
if (transformed && !isNaN(transformed.lat) && !isNaN(transformed.lng)) {
return {
latitude: transformed.lat,
longitude: transformed.lng
};
}
} catch (error) {
console.error('围栏坐标转换错误:', error);
}
return { return {
latitude: parseFloat(lat), latitude: latitude,
longitude: parseFloat(lng) longitude: longitude
} }
}); });
console.log(points,'pointspointspoints') console.log(points,'pointspointspoints')
@@ -57,30 +80,66 @@ const Location = () => {
const reload = () => { const reload = () => {
setScale(11) setScale(11)
setLongitude(108.355702)
setLatitude(22.857968) // 设置初始坐标(需要转换)
const initialLat = 22.857968;
const initialLng = 108.355702;
try {
const transformed = GPS.gcj_encrypt(initialLat, initialLng);
if (transformed && !isNaN(transformed.lat) && !isNaN(transformed.lng)) {
setLongitude(transformed.lng);
setLatitude(transformed.lat);
} else {
setLongitude(initialLng);
setLatitude(initialLat);
}
} catch (error) {
console.error('初始坐标转换错误:', error);
setLongitude(initialLng);
setLatitude(initialLat);
}
getHjmFence(4).then(data => { getHjmFence(4).then(data => {
setFence(data) setFence(data)
const coordStr = data.points || ''; const coordStr = data.points || '';
// 使用通用函数解析坐标字符串 // 使用通用函数解析坐标字符串(已包含坐标转换)
const {points} = parseCoordinateString(coordStr); const {points} = parseCoordinateString(coordStr);
console.log('解析结果 - 多边形点:', points); console.log('解析结果 - 多边形点:', points);
setPoints(points); setPoints(points);
setShowCircles(true) setShowCircles(true)
console.log(fence,'fencefencefence') console.log(fence,'fencefencefence')
}) })
const where: Where = {} const where: Where = {}
// 使用转换后的坐标
where.latitude = latitude where.latitude = latitude
where.longitude = longitude where.longitude = longitude
pageByQQMap(where).then(res => { pageByQQMap(where).then(res => {
if (res?.count == 0) { if (res?.count == 0) {
const arr = [] const arr = []
// 转换默认位置坐标
const defaultLat = 22.813371;
const defaultLng = 108.323885;
let markerLat = defaultLat;
let markerLng = defaultLng;
try {
const transformed = GPS.gcj_encrypt(defaultLat, defaultLng);
if (transformed && !isNaN(transformed.lat) && !isNaN(transformed.lng)) {
markerLat = transformed.lat;
markerLng = transformed.lng;
}
} catch (error) {
console.error('默认位置坐标转换错误:', error);
}
// @ts-ignore // @ts-ignore
arr.push({ arr.push({
id: 10001, id: 10001,
latitude: 22.813371, latitude: markerLat,
longitude: 108.323885, longitude: markerLng,
title: '当前位置', title: '当前位置',
name: '当前位置' name: '当前位置'
}) })
@@ -91,11 +150,31 @@ const Location = () => {
const data = res?.list; const data = res?.list;
const arr = [] const arr = []
data?.map((item: HjmCar) => { data?.map((item: HjmCar) => {
// 转换WGS84坐标到GCJ02坐标
let markerLat = item.latitude;
let markerLng = item.longitude;
if (item.latitude && item.longitude) {
try {
const lat = Number(item.latitude);
const lng = Number(item.longitude);
if (!isNaN(lat) && !isNaN(lng) && isFinite(lat) && isFinite(lng)) {
const transformed = GPS.gcj_encrypt(lat, lng);
if (transformed && !isNaN(transformed.lat) && !isNaN(transformed.lng)) {
markerLat = transformed.lat;
markerLng = transformed.lng;
}
}
} catch (error) {
console.error('车辆坐标转换错误:', error);
}
}
// @ts-ignore // @ts-ignore
arr.push({ arr.push({
id: item.id, id: item.id,
latitude: item.latitude, latitude: markerLat,
longitude: item.longitude, longitude: markerLng,
label: { label: {
content: `${item?.code}`, content: `${item?.code}`,
color: '#000000', color: '#000000',

View File

@@ -47,7 +47,7 @@ const List = () => {
// @ts-ignore // @ts-ignore
where.driverId = user.userId; where.driverId = user.userId;
} }
if(roleCode == 'zhandian'){ if(roleCode == 'zhandian' && user.merchants == null){
// @ts-ignore // @ts-ignore
where.organizationId = user.organizationId; where.organizationId = user.organizationId;
} }
@@ -59,6 +59,10 @@ const List = () => {
// @ts-ignore // @ts-ignore
where.installerId = user.userId; where.installerId = user.userId;
} }
if(user.merchants != null){
// @ts-ignore
where.organizationIds = user.merchants;
}
if(roleCode == 'user'){ if(roleCode == 'user'){
setLoading(false) setLoading(false)
return false; return false;

View File

@@ -7,6 +7,8 @@ import {HjmCar} from "@/api/hjm/hjmCar/model";
import './trajectory.scss' import './trajectory.scss'
import {pageHjmGpsLog} from "@/api/hjm/hjmGpsLog"; import {pageHjmGpsLog} from "@/api/hjm/hjmGpsLog";
import {formatCurrentDate, getCurrentHour} from "@/utils/time"; import {formatCurrentDate, getCurrentHour} from "@/utils/time";
// 导入GPS坐标转换工具
import GPS from '@/utils/gpsUtil.js';
/** /**
* 文章终极列表 * 文章终极列表
@@ -102,14 +104,33 @@ const Location = () => {
Taro.showLoading({title: '加载中...'}); Taro.showLoading({title: '加载中...'});
pageHjmGpsLog(where).then(res => { pageHjmGpsLog(where).then(res => {
console.log(res?.list, 'list') console.log(res?.list, 'list')
// setPoints(res?.list.map(item => { // 转换轨迹点坐标
// console.log(item, 'item.') const transformedPoints = (res?.list || []).map(item => {
// return { // 转换WGS84坐标到GCJ02坐标
// latitude: item.latitude, const lat = Number(item.latitude);
// longitude: item.longitude const lng = Number(item.longitude);
// }
// }) || []) if (item.latitude && item.longitude &&
setHjmGpsLog(res?.list || []); !isNaN(lat) &&
!isNaN(lng) &&
isFinite(lat) &&
isFinite(lng)) {
try {
const transformed = GPS.gcj_encrypt(lat, lng);
if (transformed && !isNaN(transformed.lat) && !isNaN(transformed.lng)) {
return {
...item,
latitude: transformed.lat,
longitude: transformed.lng
};
}
} catch (error) {
console.error('轨迹点坐标转换错误:', error);
}
}
return item;
});
setHjmGpsLog(transformedPoints);
}).finally(() => { }).finally(() => {
Taro.hideLoading(); Taro.hideLoading();
}) })
@@ -123,8 +144,34 @@ const Location = () => {
getHjmCarByCode(keywords).then(data => { getHjmCarByCode(keywords).then(data => {
console.log('执行搜索', data) console.log('执行搜索', data)
setItem(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) setKeywords(data.code)
// 获取车辆轨迹信息 // 获取车辆轨迹信息
getLocationRecord(data); getLocationRecord(data);
@@ -144,8 +191,34 @@ const Location = () => {
if (code) { if (code) {
getHjmCarByCode(code).then(data => { getHjmCarByCode(code).then(data => {
setItem(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) setKeywords(data.code)
// 获取车辆轨迹信息 // 获取车辆轨迹信息
getLocationRecord(data); getLocationRecord(data);
@@ -220,7 +293,6 @@ const Location = () => {
</div> </div>
</div> </div>
</div> </div>
{/*<Map polyline={{hjmGpsLog}}></Map>*/}
<Map <Map
id="map" id="map"
longitude={longitude} longitude={longitude}

View File

@@ -0,0 +1,59 @@
import {useEffect} from "react";
import {Image, Space,Button} from '@nutui/nutui-react-taro'
import Taro from '@tarojs/taro'
import {View} from '@tarojs/components'
import {HjmViolation} from "@/api/hjm/hjmViolation/model";
import navTo from "@/utils/common";
interface BestSellersProps {
data: HjmViolation[];
}
const BestSellers = (props: BestSellersProps) => {
const reload = () => {
// 可以在这里添加重新加载逻辑
}
useEffect(() => {
reload()
}, [])
return (
<View className={'px-2 mb-4'}>
<View className={'flex flex-col justify-between items-center rounded-lg px-3'}>
{props.data?.map((item, index) => {
return (
<View key={item.id || index} className={'flex bg-white rounded-lg w-full p-3 mb-3'}
onClick={() => navTo(`/hjm/violation/detail?id=${item.code}`)}>
<View className={'flex flex-col'}>
<Image src={item.image && JSON.parse(item.image).length > 0 && JSON.parse(item.image)[0].url} mode={'scaleToFill'}
radius="10%" width="70" height="70" className={'mb-1'}/>
{item.userId == Taro.getStorageSync('UserId') && (
<Button type={'default'} size="small" onClick={() => navTo(`/hjm/violation/add?id=${item.id}`)}></Button>
)}
</View>
<View className={'mx-3 flex flex-col'}>
<Space direction={'vertical'}>
<View className={'car-no text-lg font-bold'}>{item.title}</View>
<View className={'flex text-xs text-gray-500'}><span
className={'text-gray-700'}>{item.code}</span></View>
<View className={'flex text-xs text-gray-500'}><span
className={'text-gray-700'}>{item.comments}</span></View>
<View className={'flex text-xs text-gray-500'}><span
className={'text-gray-700'}>{item.createTime}</span></View>
</Space>
</View>
</View>
)
})}
{(!props.data || props.data.length === 0) && (
<View className={'flex justify-center items-center py-10'}>
<View className={'text-gray-500 text-sm'}></View>
</View>
)}
</View>
<View style={{height: '170px'}}></View>
</View>
)
}
export default BestSellers

View File

@@ -274,19 +274,19 @@ function Add() {
} style={{padding: '12px 16px'}}> } style={{padding: '12px 16px'}}>
</Cell> </Cell>
<Cell title="处理状态" description={'请选择'} extra={ {/*<Cell title="处理状态" description={'请选择'} extra={*/}
<div style={{ {/* <div style={{*/}
display: 'flex', {/* display: 'flex',*/}
alignItems: 'center', {/* alignItems: 'center',*/}
justifyContent: 'flex-end', {/* justifyContent: 'flex-end',*/}
height: '24px' {/* height: '24px'*/}
}}> {/* }}>*/}
<span style={{color: '#333', fontSize: '14px'}}> {/* <span style={{color: '#333', fontSize: '14px'}}>*/}
{statusOptions.find(option => option.value === formData.status)?.text || '请选择'} {/* {statusOptions.find(option => option.value === formData.status)?.text || '请选择'}*/}
</span> {/* </span>*/}
</div> {/* </div>*/}
} style={{padding: '12px 16px'}} onClick={() => setIsPickerVisible(true)}> {/*} style={{padding: '12px 16px'}} onClick={() => setIsPickerVisible(true)}>*/}
</Cell> {/*</Cell>*/}
</Cell.Group> </Cell.Group>
</div> </div>

View File

@@ -1,390 +1,178 @@
import React, {useEffect, useState} from "react"; import {useEffect, useState, CSSProperties} from "react";
import { import {Search} from '@nutui/icons-react-taro'
Loading, import {Button, Input, InfiniteLoading} from '@nutui/nutui-react-taro'
Empty,
Button,
Input,
Tag,
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} from "@/api/hjm/hjmViolation"; import {pageHjmViolation} from "@/api/hjm/hjmViolation";
import {HjmViolation} from "@/api/hjm/hjmViolation/model"; import {HjmViolation} from "@/api/hjm/hjmViolation/model";
import Taro from '@tarojs/taro'
import Items from "./Items";
import {useRouter} from '@tarojs/taro'
import {getUserInfo} from "@/api/layout";
import navTo from "@/utils/common";
import FixedButton from "@/components/FixedButton";
const InfiniteUlStyle: CSSProperties = {
height: '80vh',
width: '100%',
padding: '0',
overflowY: 'auto',
overflowX: 'hidden',
}
/** /**
* 报险记录列表页面 * 文章终极列表
* @constructor
*/ */
const List: React.FC = () => { const ViolationList = () => {
const {params} = useRouter(); const {params} = useRouter();
const [list, setList] = useState<HjmViolation[]>([])
const [loading, setLoading] = useState<boolean>(false)
const [keywords, setKeywords] = useState<string>('') const [keywords, setKeywords] = useState<string>('')
const [refreshing, setRefreshing] = useState<boolean>(false) const [list, setList] = useState<HjmViolation[]>([])
const [page, setPage] = useState<number>(1) const [page, setPage] = useState(1)
const [limit, setLimit] = useState<number>(10) const [hasMore, setHasMore] = useState(true)
const [total, setTotal] = useState<number>(0) const [loading, setLoading] = useState(false)
const [needRefresh, setNeedRefresh] = useState<boolean>(false) // 修改默认值为false避免初始加载时的重复
const [isLoadingRef, setIsLoadingRef] = useState<boolean>(false) // 防止并发加载
console.log(refreshing) const onKeywords = (keywords: string) => {
// 获取状态显示 setKeywords(keywords)
const getStatusDisplay = (status?: number) => {
switch (status) {
case 0:
return {text: '未处理', color: '#faad14', bgColor: '#fffbe6'}
case 1:
return {text: '已处理', color: '#52c41a', bgColor: '#f6ffed'}
case 2:
return {text: '已驳回', color: '#ff4d4f', bgColor: '#fff2f0'}
default:
return {text: '未知', color: '#8c8c8c', bgColor: '#f5f5f5'}
}
} }
const reload = async (showLoading = true) => { const loadList = async (isRefresh = false) => {
// 防止并发请求,如果正在加载则直接返回 if (loading) return;
if (isLoadingRef) {
console.log('正在加载中,跳过本次请求') setLoading(true)
return
// 搜索条件
const where: any = {
keywords: keywords.trim(),
page,
limit: 10, // 直接使用10不通过state
} }
setLimit(10) // 读取用户信息
try { const user = await getUserInfo();
setIsLoadingRef(true) // 设置加载标记
if (showLoading) setLoading(true)
setRefreshing(true)
const where: any = { // 判断身份
keywords: keywords.trim(), const roleCode = Taro.getStorageSync('RoleCode');
page, if(roleCode == 'kuaidiyuan'){
limit, // @ts-ignore
} where.driverId = user.userId;
}
const roleCode = Taro.getStorageSync('RoleCode'); if(roleCode == 'zhandian'){
if (roleCode == 'kuaidi') { // @ts-ignore
if (Taro.getStorageSync('OrganizationParentId') == 0) { where.organizationId = user.organizationId;
// @ts-ignore }
where.organizationParentId = Taro.getStorageSync('OrganizationId'); if(roleCode == 'kuaidi'){
} else { // @ts-ignore
// @ts-ignore where.organizationParentId = user.organizationId;
where.organizationId = Taro.getStorageSync('OrganizationId'); }
} if(roleCode == 'Installer'){
} // @ts-ignore
if(params.id){ where.installerId = user.userId;
where.code = params.id; }
} if(roleCode == 'user'){
console.log('开始请求数据, where:', where)
const res = await pageHjmViolation(where)
console.log('请求成功,获取到', res?.list?.length, '条数据')
// 确保数据被替换而不是追加
setList(res?.list || [])
setTotal(res?.count || 0)
} catch (error) {
console.error('获取报险记录失败:', error)
Taro.showToast({
title: '获取报险记录失败',
icon: 'error'
})
} finally {
setLoading(false) setLoading(false)
setRefreshing(false) return false;
setIsLoadingRef(false) // 清除加载标记 }
if(params.id){
where.code = params.id;
}
// 获取车辆列表
try {
const res = await pageHjmViolation(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 onSearch = () => { const reload = async () => {
reload() setPage(1);
await loadList(true);
} }
const onKeywordsChange = (value: string) => { const loadMore = async () => {
setKeywords(value) if (!hasMore || loading) return;
const nextPage = page + 1;
setPage(nextPage);
await loadList();
} }
const onAddInsurance = () => {
Taro.navigateTo({
url: '/hjm/violation/add'
})
}
// 页面显示时触发,包括从其他页面返回
useDidShow(() => {
console.log('useDidShow 触发, needRefresh:', needRefresh)
if (needRefresh) {
console.log('执行 reload 由于 needRefresh=true')
reload(false)
setNeedRefresh(false)
}
})
//
// useEffect(() => {
// // 监听刷新事件
// const handleRefresh = () => {
// console.log('接收到 violationListRefresh 事件')
// // 只设置标记不直接调用reload由useDidShow处理
// setNeedRefresh(true)
// }
//
// Taro.eventCenter.on('violationListRefresh', handleRefresh)
//
// // 初始加载数据 - 只在组件首次挂载时执行
// console.log('组件初始化,执行初始 reload')
// reload().then()
//
// // 清理事件监听器
// return () => {
// Taro.eventCenter.off('violationListRefresh', handleRefresh)
// }
// }, []) // 移除page和limit依赖避免不必要的重新加载
// 单独处理分页变化
useEffect(() => { useEffect(() => {
if (page > 1 || limit !== 10) { // 只有当分页参数真正改变时才重新加载 reload().then()
console.log('分页参数变化,执行 reload, page:', page, 'limit:', limit) }, [])
reload().then()
}
}, [page, limit])
const onPageChange = (current: number) => { // 页面显示时刷新列表(从添加页返回时触发)
setPage(current) Taro.useDidShow(() => {
} reload()
})
return ( return (
<> <>
{/* 搜索栏 */} <div className={'fixed z-20 top-5 left-0 w-full'}>
<div style={{ <div className={'px-4'}>
position: 'fixed',
top: '20px',
left: 0,
right: 0,
display: "none",
zIndex: 20,
padding: '0 16px',
backgroundColor: '#f5f5f5'
}}>
<div style={{
display: 'flex',
alignItems: 'center',
backgroundColor: '#fff',
padding: '8px 12px',
borderRadius: '20px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
}}>
<Search size={16} color="#999"/>
<Input
placeholder="搜索报险记录"
value={keywords}
onChange={onKeywordsChange}
onConfirm={onSearch}
style={{
border: 'none',
backgroundColor: 'transparent',
flex: 1,
marginLeft: '8px'
}}
/>
<Button
type="primary"
size="small"
onClick={onSearch}
loading={loading}
>
</Button>
</div>
</div>
{/* 报险记录列表 */}
<div style={{
marginTop: '10px',
paddingBottom: '80px'
}}>
{loading && list.length === 0 ? (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '200px'
}}>
<Loading type="spinner">...</Loading>
</div>
) : list.length === 0 ? (
<Empty description="暂无违章记录">
</Empty>
) : (
<div style={{padding: '0 16px'}}>
{list.map((item) => {
const statusDisplay = getStatusDisplay(item.status)
return (
<div
key={item.id || item.code} // 使用唯一ID而不是index
style={{
backgroundColor: '#fff',
borderRadius: '12px',
padding: '16px',
marginBottom: '12px',
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
border: '1px solid #f0f0f0'
}}
onClick={() => {
Taro.navigateTo({
url: `/hjm/violation/detail?id=${item.code}`
})
}}
>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: '12px'
}}>
<div style={{flex: 1}}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
marginBottom: '8px'
}}>
<File size={16} color="#1890ff"/>
<span style={{
fontSize: '16px',
fontWeight: 'bold',
color: '#262626'
}}>
{item.title}
</span>
</div>
<Space direction="vertical">
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px'
}}>
<Truck size={14} color="#8c8c8c"/>
<span style={{fontSize: '13px', color: '#8c8c8c'}}>
{item.code}
</span>
</div>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px'
}}>
<Calendar size={14} color="#8c8c8c"/>
<span style={{fontSize: '13px', color: '#8c8c8c'}}>
{item.createTime}
</span>
</div>
</Space>
</div>
<Tag
color={statusDisplay.color}
style={{
backgroundColor: statusDisplay.bgColor,
border: `1px solid ${statusDisplay.color}`,
fontSize: '12px'
}}
>
{statusDisplay.text}
</Tag>
</div>
{/* 备注信息 */}
{item.comments && (
<div style={{
backgroundColor: '#f8f9fa',
padding: '8px 12px',
borderRadius: '6px',
fontSize: '13px',
color: '#595959',
lineHeight: '1.4'
}}>
{item.comments.length > 50
? `${item.comments.substring(0, 50)}...`
: item.comments
}
</div>
)}
{item.userId == Taro.getStorageSync('UserId') && (
<div className={'flex justify-end mt-4'}>
<Space>
<Button type="default" onClick={(event: any) => {
event.stopPropagation()
Taro.navigateTo({
url: `/hjm/violation/add?id=${item.id}`
})
}}></Button>
{/*<Button type="primary" onClick={(event: any) => {*/}
{/* event.stopPropagation()*/}
{/* removeHjmViolation(item.id).then(() => {*/}
{/* Taro.showToast({*/}
{/* title: '删除成功',*/}
{/* icon: 'success'*/}
{/* })*/}
{/* // 删除成功后重新加载列表*/}
{/* reload(false)*/}
{/* }).catch((error) => {*/}
{/* Taro.showToast({*/}
{/* title: error.message || '删除失败',*/}
{/* icon: 'error'*/}
{/* })*/}
{/* })*/}
{/*}}>删除</Button>*/}
</Space>
</div>
)}
</div>
)
})}
</div>
)}
</div>
{
Taro.getStorageSync('RoleCode') == 'jiaojing' && (
<div <div
style={{ style={{
position: 'fixed', display: 'flex',
bottom: '20px', alignItems: 'center',
right: '20px', background: '#fff',
zIndex: 30, padding: '0 10px',
padding: '8px', borderRadius: '20px'
borderRadius: '20px', }}
overflow: "hidden", >
backgroundColor: '#ff0000', <Search/>
}}> <Input
<AddCircle size={28} color={'#ffffff'} onClick={onAddInsurance}/> placeholder="车辆编号"
value={keywords}
onChange={onKeywords}
onConfirm={reload}
/>
<div
className={'flex items-center'}
>
<Button type="warning" onClick={reload}>
</Button>
</div>
</div> </div>
</div>
</div>
<div style={InfiniteUlStyle} id="scroll">
<InfiniteLoading
target="scroll"
className={'w-full fixed left-0 top-20'}
hasMore={hasMore}
onLoadMore={loadMore}
loadingText="加载中..."
loadMoreText="没有更多了"
>
<Items data={list}/>
</InfiniteLoading>
</div>
{
Taro.getStorageSync('RoleCode') == 'jiaojing' && (
<>
<FixedButton onClick={() => navTo(`/hjm/violation/add`)} />
</>
) )
} }
{/* 分页 */}
{list.length > 0 && (
<div style={{
display: 'flex',
justifyContent: 'center',
padding: '20px 0',
backgroundColor: '#f5f5f5'
}}>
<Pagination
value={page}
total={total}
pageSize={limit}
onChange={onPageChange}
/>
</div>
)}
</> </>
) )
} }
export default ViolationList
export default List

View File

@@ -12,6 +12,8 @@ import {getSiteInfo, getUserInfo, getWxOpenId} from "@/api/layout";
import Login from "./Login"; import Login from "./Login";
import {pageByQQMap, pageHjmCar} from "@/api/hjm/hjmCar"; import {pageByQQMap, pageHjmCar} from "@/api/hjm/hjmCar";
import {HjmCar} from "@/api/hjm/hjmCar/model"; import {HjmCar} from "@/api/hjm/hjmCar/model";
// 导入GPS坐标转换工具
import GPS from '@/utils/gpsUtil.js';
export interface Market { export interface Market {
// 自增ID // 自增ID
@@ -108,14 +110,36 @@ function Home() {
const getLocation = async () => { const getLocation = async () => {
try { try {
const res = await Taro.getLocation({ const res = await Taro.getLocation({
type: 'gcj02' //返回可以用于wx.openLocation的经纬度 type: 'wgs84' // 使用WGS84坐标系然后手动转换为GCJ02
}) })
if (res.latitude) { // 转换WGS84坐标到GCJ02坐标
setLatitude(res.latitude) if (res.latitude && res.longitude &&
} !isNaN(res.latitude) &&
if (res.longitude) { !isNaN(res.longitude) &&
setLongitude(res.longitude) isFinite(res.latitude) &&
isFinite(res.longitude)) {
try {
const lat = Number(res.latitude);
const lng = Number(res.longitude);
const transformed = GPS.gcj_encrypt(lat, lng);
// 确保转换结果有效
if (transformed && !isNaN(transformed.lat) && !isNaN(transformed.lng)) {
setLatitude(transformed.lat);
setLongitude(transformed.lng);
} else {
// 如果转换结果无效,使用原始坐标
setLatitude(res.latitude);
setLongitude(res.longitude);
}
} catch (error) {
console.error('坐标转换错误:', error);
setLatitude(res.latitude);
setLongitude(res.longitude);
}
} else {
setLatitude(res.latitude);
setLongitude(res.longitude);
} }
// 已认证用户 // 已认证用户
@@ -185,11 +209,33 @@ function Home() {
const data = res?.list; const data = res?.list;
const arr = [] const arr = []
data?.map((item: HjmCar) => { data?.map((item: HjmCar) => {
// 转换WGS84坐标到GCJ02坐标
let markerLat = item.latitude;
let markerLng = item.longitude;
if (item.latitude && item.longitude &&
!isNaN(item.latitude) &&
!isNaN(item.longitude) &&
isFinite(item.latitude) &&
isFinite(item.longitude)) {
try {
const lat = Number(item.latitude);
const lng = Number(item.longitude);
const transformed = GPS.gcj_encrypt(lat, lng);
if (transformed && !isNaN(transformed.lat) && !isNaN(transformed.lng)) {
markerLat = transformed.lat;
markerLng = transformed.lng;
}
} catch (error) {
console.error('标记点坐标转换错误:', error);
}
}
// @ts-ignore // @ts-ignore
arr.push({ arr.push({
id: item.id, id: item.id,
latitude: item.latitude, latitude: markerLat,
longitude: item.longitude, longitude: markerLng,
label: { label: {
content: `${item?.code}`, content: `${item?.code}`,
color: '#000000', color: '#000000',
@@ -232,13 +278,36 @@ function Home() {
setList(res?.list || []) setList(res?.list || [])
if (res?.list && res?.list.length > 0) { if (res?.list && res?.list.length > 0) {
const data = res?.list[0]; const data = res?.list[0];
setLongitude(data?.longitude)
setLatitude(data?.latitude) // 转换WGS84坐标到GCJ02坐标
let displayLat = data.latitude;
let displayLng = data.longitude;
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)) {
displayLat = transformed.lat;
displayLng = transformed.lng;
}
} catch (error) {
console.error('搜索结果坐标转换错误:', error);
}
}
setLongitude(displayLng)
setLatitude(displayLat)
if (isAdmin) { if (isAdmin) {
setMarkers([{ setMarkers([{
id: data.id, id: data.id,
latitude: data.latitude, latitude: displayLat,
longitude: data.longitude, longitude: displayLng,
// @ts-ignore // @ts-ignore
label: { label: {
content: `${data?.code}`, content: `${data?.code}`,

View File

@@ -35,29 +35,29 @@ const Login = () => {
} }
// 发送短信验证码 // 发送短信验证码
const handleSendSmsCode = async () => { // const handleSendSmsCode = async () => {
if (!phone) { // if (!phone) {
Taro.showToast({ // Taro.showToast({
title: '请输入手机号', // title: '请输入手机号',
icon: 'error' // icon: 'error'
}) // })
return // return
} // }
//
// 验证手机号格式 // // 验证手机号格式
const phoneReg = /^1[3-9]\d{9}$/ // const phoneReg = /^1[3-9]\d{9}$/
if (!phoneReg.test(phone)) { // if (!phoneReg.test(phone)) {
Taro.showToast({ // Taro.showToast({
title: '手机号格式不正确', // title: '手机号格式不正确',
icon: 'error' // icon: 'error'
}) // })
return // return
} // }
//
// 显示图形验证码弹窗 // // 显示图形验证码弹窗
fetchCaptcha() // fetchCaptcha()
setShowCaptchaModal(true) // setShowCaptchaModal(true)
} // }
// 确认发送短信验证码 // 确认发送短信验证码
const confirmSendSmsCode = async () => { const confirmSendSmsCode = async () => {
@@ -96,21 +96,21 @@ const Login = () => {
const handleSmsLogin = async () => { const handleSmsLogin = async () => {
if (!phone) { if (!phone) {
Taro.showToast({ Taro.showToast({
title: '请输入手机号', title: '请输入号',
icon: 'error' icon: 'error'
}) })
return return
} }
// 验证手机号格式 // 验证手机号格式
const phoneReg = /^1[3-9]\d{9}$/ // const phoneReg = /^1[3-9]\d{9}$/
if (!phoneReg.test(phone)) { // if (!phoneReg.test(phone)) {
Taro.showToast({ // Taro.showToast({
title: '手机号格式不正确', // title: '手机号格式不正确',
icon: 'error' // icon: 'error'
}) // })
return // return
} // }
if (!smsCode) { if (!smsCode) {
Taro.showToast({ Taro.showToast({
@@ -123,7 +123,7 @@ const Login = () => {
try { try {
setLoading(true) setLoading(true)
// 短信登录时传入手机号和短信验证码 // 短信登录时传入手机号和短信验证码
const res = await loginBySms({ phone, code: smsCode }) const res = await loginBySms({ phone, code:smsCode })
console.log(res,'.......') console.log(res,'.......')
Taro.showToast({ Taro.showToast({
@@ -232,8 +232,7 @@ const Login = () => {
}}> }}>
<Input <Input
type="text" type="text"
placeholder="手机号" placeholder="请输入账号"
maxLength={11}
value={phone} value={phone}
onChange={(val) => setPhone(val)} onChange={(val) => setPhone(val)}
style={{ style={{
@@ -258,9 +257,8 @@ const Login = () => {
borderRadius: '8px' borderRadius: '8px'
}}> }}>
<Input <Input
type="text" type="password"
placeholder="短信验证码" placeholder="请输入密码"
maxLength={6}
value={smsCode} value={smsCode}
onChange={(val) => setSmsCode(val)} onChange={(val) => setSmsCode(val)}
style={{ style={{
@@ -269,18 +267,6 @@ const Login = () => {
padding: '10px' padding: '10px'
}} }}
/> />
<Button
type="info"
size="small"
disabled={countdown > 0}
onClick={handleSendSmsCode}
style={{
borderRadius: '0 8px 8px 0',
height: '40px'
}}
>
{countdown > 0 ? `${countdown}秒后重发` : '获取验证码'}
</Button>
</div> </div>
</div> </div>
</div> </div>
@@ -317,7 +303,7 @@ const Login = () => {
checked={isAgree} checked={isAgree}
onClick={() => setIsAgree(!isAgree)} onClick={() => setIsAgree(!isAgree)}
/> />
<span style={{color: '#999', marginLeft: '5px'}} onClick={() => setIsAgree(!isAgree)}></span> <span style={{color: '#999', marginLeft: '5px'}} onClick={() => setIsAgree(!isAgree)}></span>
<a <a
onClick={() => Taro.navigateTo({url: '/passport/agreement'})} onClick={() => Taro.navigateTo({url: '/passport/agreement'})}
style={{color: '#1890ff'}} style={{color: '#1890ff'}}

View File

@@ -98,50 +98,71 @@ const UserVerifyAdmin: React.FC = () => {
// 检查是否有特定角色 // 检查是否有特定角色
const hasRole = (roleCode: string) => { const hasRole = (roleCode: string) => {
if (!user || !user.roles) { if (!user || !user.roles) {
return false; return false
} }
return user.roles.some(role => role.roleCode === roleCode); return user.roles.some(role => role.roleCode === roleCode)
}; }
const onPass = async (item: UserVerify) => { const onPass = async (item: UserVerify) => {
const role = await listUserRole({roleId: 1701,userId: item.userId}) try {
const userRole = role[0]; const role = await listUserRole({roleId: 1701, userId: item.userId})
// 审核通过 const userRole = role[0]
updateUserVerify({
...item, // 审核通过 - 先更新实名认证状态
status: 1 await updateUserVerify({
}).then(() => { ...item,
if(userRole){ status: 1
updateUserRole({ })
...userRole,
roleId: 1738 // 再更新用户角色(如果存在对应角色)
}) if (userRole) {
} await updateUserRole({
Taro.showToast({ ...userRole,
title: '操作成功', roleId: 1738
icon: 'success'
})
reload().then()
}) })
}
Taro.showToast({
title: '操作成功',
icon: 'success'
})
await reload(false)
} catch (error: any) {
console.error('审核通过失败:', error)
Taro.showToast({
title: error?.message || '操作失败',
icon: 'error'
})
}
} }
const onReject = async (item: UserVerify) => { const onReject = async (item: UserVerify) => {
const role = await listUserRole({roleId: 1738,userId: item.userId}) try {
const userRole = role[0]; const role = await listUserRole({roleId: 1738, userId: item.userId})
if(userRole){ const userRole = role[0]
userRole.roleId = 1701;
updateUserRole(userRole).then(() => { // 审核驳回 - 先恢复用户角色(如果存在)
updateUserVerify({ if (userRole) {
...item, userRole.roleId = 1701
status: 2 await updateUserRole(userRole)
}).then(() => { }
Taro.showToast({
title: '操作成功', await updateUserVerify({
icon: 'success' ...item,
}) status: 2
reload().then() })
})
Taro.showToast({
title: '操作成功',
icon: 'success'
})
await reload(false)
} catch (error: any) {
console.error('审核驳回失败:', error)
Taro.showToast({
title: error?.message || '操作失败',
icon: 'error'
}) })
} }
} }
@@ -297,7 +318,7 @@ const UserVerifyAdmin: React.FC = () => {
fontSize: '12px' fontSize: '12px'
}} }}
> >
{item.statusText} {item.statusText || statusDisplay.text}
</Tag> </Tag>
</div> </div>
@@ -317,7 +338,7 @@ const UserVerifyAdmin: React.FC = () => {
} }
</div> </div>
)} )}
{Taro.getStorageSync('kuaidi') && ( {(hasRole('admin') || hasRole('kuaidi') || hasRole('zhandian')) && (
<Space className={'pt-4 flex justify-end'}> <Space className={'pt-4 flex justify-end'}>
<Button type="success" onClick={() => onPass(item)}></Button> <Button type="success" onClick={() => onPass(item)}></Button>
<Button type="warning" onClick={() => onReject(item)}></Button> <Button type="warning" onClick={() => onReject(item)}></Button>

View File

@@ -4,7 +4,7 @@ import {BaseUrl, TenantId} from "@/utils/config";
let baseUrl = BaseUrl let baseUrl = BaseUrl
if(process.env.NODE_ENV === 'development'){ if(process.env.NODE_ENV === 'development'){
baseUrl = 'http://localhost:9200/api' // baseUrl = 'http://localhost:9200/api'
} }
export function request<T>(options:any) { export function request<T>(options:any) {
const token = Taro.getStorageSync('access_token'); const token = Taro.getStorageSync('access_token');