完成:黄家明项目的开发并存档

This commit is contained in:
2025-06-20 19:55:23 +08:00
parent f83b856438
commit 1b143c0c1f
18 changed files with 1295 additions and 84 deletions

View File

@@ -52,6 +52,8 @@ export interface HjmCar {
address?: string, address?: string,
// 用户ID // 用户ID
userId?: number; userId?: number;
// 认领状态
claim?: number;
// 排序(数字越小越靠前) // 排序(数字越小越靠前)
sortNumber?: number; sortNumber?: number;
// 备注 // 备注

View File

@@ -0,0 +1,101 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api/index';
import type { HjmViolation, HjmViolationParam } from './model';
/**
* 分页查询黄家明_违章记录
*/
export async function pageHjmViolation(params: HjmViolationParam) {
const res = await request.get<ApiResult<PageResult<HjmViolation>>>(
'/hjm/hjm-violation/page',
params
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 查询黄家明_违章记录列表
*/
export async function listHjmViolation(params?: HjmViolationParam) {
const res = await request.get<ApiResult<HjmViolation[]>>(
'/hjm/hjm-violation',
params
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 添加黄家明_违章记录
*/
export async function addHjmViolation(data: HjmViolation) {
const res = await request.post<ApiResult<unknown>>(
'/hjm/hjm-violation',
data
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 修改黄家明_违章记录
*/
export async function updateHjmViolation(data: HjmViolation) {
const res = await request.put<ApiResult<unknown>>(
'/hjm/hjm-violation',
data
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 删除黄家明_违章记录
*/
export async function removeHjmViolation(id?: number) {
const res = await request.del<ApiResult<unknown>>(
'/hjm/hjm-violation/' + id
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 批量删除黄家明_违章记录
*/
export async function removeBatchHjmViolation(data: (number | undefined)[]) {
const res = await request.del<ApiResult<unknown>>(
'/hjm/hjm-violation/batch',
{
data
}
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* 根据id查询黄家明_违章记录
*/
export async function getHjmViolation(id: number) {
const res = await request.get<ApiResult<HjmViolation>>(
'/hjm/hjm-violation/' + id
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}

View File

@@ -0,0 +1,43 @@
import type { PageParam } from '@/api/index';
/**
* 黄家明_违章记录
*/
export interface HjmViolation {
// 自增ID
id?: number;
// 车辆编号
code?: string;
// 标题
title?: string;
// 文章分类ID
categoryId?: number;
// 处罚金额
money?: string;
// 扣分
score?: string;
// 录入员
adminId?: number;
// 用户ID
userId?: number;
// 排序(数字越小越靠前)
sortNumber?: number;
// 备注
comments?: string;
// 状态, 0未处理, 1已处理
status?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 黄家明_违章记录搜索条件
*/
export interface HjmViolationParam extends PageParam {
id?: number;
keywords?: string;
}

View File

@@ -15,7 +15,8 @@ export default defineAppConfig({
"register", "register",
"forget", "forget",
"setting", "setting",
"agreement" "agreement",
"sms-login"
] ]
}, },
{ {
@@ -34,7 +35,8 @@ export default defineAppConfig({
"company/company", "company/company",
"profile/profile", "profile/profile",
"setting/setting", "setting/setting",
"userVerify/index" "userVerify/index",
"userVerify/admin"
] ]
}, },
{ {
@@ -48,6 +50,8 @@ export default defineAppConfig({
"exam/exam", "exam/exam",
"bx/bx", "bx/bx",
"bx/bx-add", "bx/bx-add",
"violation/add",
"violation/list",
"trajectory/trajectory", "trajectory/trajectory",
"gps-log/gps-log" "gps-log/gps-log"
// "bx/bx-list", // "bx/bx-list",

View File

@@ -21,7 +21,7 @@ const List = () => {
const reload = () => { const reload = () => {
// 搜索条件 // 搜索条件
const where = {status: 1,deleted: 0, keywords} const where = {status: 1, deleted: 0, keywords}
// 判断身份 // 判断身份
const roleCode = Taro.getStorageSync('RoleCode'); const roleCode = Taro.getStorageSync('RoleCode');
if(roleCode == 'kuaidiyuan'){ if(roleCode == 'kuaidiyuan'){
@@ -33,8 +33,13 @@ const List = () => {
where.organizationId = Taro.getStorageSync('OrganizationId'); where.organizationId = Taro.getStorageSync('OrganizationId');
} }
if(roleCode == 'kuaidi'){ if(roleCode == 'kuaidi'){
// @ts-ignore if(Taro.getStorageSync('OrganizationId') == Taro.getStorageSync('OrganizationParentId')){
where.organizationParentId = Taro.getStorageSync('OrganizationParentId'); // @ts-ignore
where.organizationParentId = Taro.getStorageSync('OrganizationParentId');
}else {
// @ts-ignore
where.organizationId = Taro.getStorageSync('OrganizationId');
}
} }
// 获取车辆列表 // 获取车辆列表

View File

@@ -115,7 +115,7 @@ const Query = () => {
title: '请上传车辆图片', title: '请上传车辆图片',
icon: 'error' icon: 'error'
}); });
// return false return false
} }
if (!FormData.gpsNo) { if (!FormData.gpsNo) {
Taro.showToast({ Taro.showToast({
@@ -130,7 +130,7 @@ const Query = () => {
...FormData, ...FormData,
status: 1 status: 1
}).then(() => { }).then(() => {
Taro.showToast({title: `绑定成功`, icon: 'success'}) Taro.showToast({title: `安装成功`, icon: 'success'})
setTimeout(() => { setTimeout(() => {
return Taro.navigateBack() return Taro.navigateBack()
}, 1000) }, 1000)
@@ -311,7 +311,7 @@ const Query = () => {
const onClaimVehicle = () => { const onClaimVehicle = () => {
updateHjmCar({ updateHjmCar({
...FormData, ...FormData,
status: 2, claim: 1,
driverId: Taro.getStorageSync('UserId'), driverId: Taro.getStorageSync('UserId'),
driverName: Taro.getStorageSync('RealName') driverName: Taro.getStorageSync('RealName')
}).then(() => { }).then(() => {
@@ -361,6 +361,7 @@ const Query = () => {
return false return false
} }
// 4.查询角色 // 4.查询角色
const role = await listUserRole({userId: Taro.getStorageSync('UserId')}) const role = await listUserRole({userId: Taro.getStorageSync('UserId')})
const roleCode = role[0].roleCode; const roleCode = role[0].roleCode;
@@ -394,9 +395,15 @@ const Query = () => {
} }
} }
} }
// 1.已认领则展示车辆
// 1.符合条件则由安装人员安装车辆,否则提示无权限 if (carInfo.status == 2) {
console.log(roleCode,'roleCode..') setClaimVehicle(false)
Taro.setNavigationBarTitle({
title: '车辆信息'
})
return false
}
// 2.符合条件则由安装人员安装车辆,否则提示无权限
if (carInfo.status == 0 && roleCode != 'Installer') { if (carInfo.status == 0 && roleCode != 'Installer') {
Taro.setNavigationBarTitle({ Taro.setNavigationBarTitle({
title: '安装设备' title: '安装设备'
@@ -408,24 +415,29 @@ const Query = () => {
setDisabled(true) setDisabled(true)
return false return false
} }
// 2.如果已安装,则判断是否已认领车辆 // 3.如果已安装,则判断是否已认领车辆
if (carInfo.status == 1 && roleCode == 'kuaidiyuan') { if (carInfo.status == 1 && roleCode == 'kuaidiyuan') {
// 2.1先查询名下有多少辆车 // 2.1先查询名下有多少辆车
const carCount = await pageHjmCar({driverId: Taro.getStorageSync('UserId')}) const carCount = await pageHjmCar({driverId: Taro.getStorageSync('UserId')})
if (carCount?.count && carCount?.count == 0) { if (carCount?.count == 0) {
// 2.2无车辆则认领 // 2.2无车辆则认领
setClaimVehicle(true) setClaimVehicle(true)
Taro.setNavigationBarTitle({ Taro.setNavigationBarTitle({
title: '认领车辆' title: '认领车辆'
}) })
} else {
// 2.3存在车辆则展示车辆信息
setClaimVehicle(false)
Taro.setNavigationBarTitle({
title: '车辆信息'
})
} }
return false
} }
// if(carInfo.status == 1 && Taro.getStorageSync('UserId') != carInfo.driverId){
// Taro.showToast({
// title: '暂无权限',
// icon: 'error'
// })
// setTimeout(() => {
// Taro.navigateBack()
// },2000)
// return false
// }
} }
} }
@@ -448,6 +460,8 @@ const Query = () => {
}) })
}, []) }, [])
// @ts-ignore
// @ts-ignore
return ( return (
<> <>
{/* 未安装 */} {/* 未安装 */}
@@ -539,10 +553,11 @@ const Query = () => {
onChange={(value) => setFormData({...FormData, gpsNo: value})} onChange={(value) => setFormData({...FormData, gpsNo: value})}
/> />
<div <div
className="right" className="right p-1"
style={{display: 'flex', alignItems: 'center'}} style={{display: 'flex', alignItems: 'center'}}
onClick={saveGpsNo}
> >
<Scan onClick={saveGpsNo}/> <Scan />
</div> </div>
</div> </div>
</Form.Item> </Form.Item>
@@ -637,7 +652,6 @@ const Query = () => {
)} )}
</div> </div>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'操作员'} label={'操作员'}
name="driver" name="driver"
@@ -648,10 +662,7 @@ const Query = () => {
</Form> </Form>
</div> </div>
</div> </div>
) : ''} ) : (
{/* 已安装 */}
{FormData?.status != 0 ? (
<div className={'car-info w-full bg-white'}> <div className={'car-info w-full bg-white'}>
{/* 显示多张图片 */} {/* 显示多张图片 */}
<div style={{ <div style={{
@@ -705,6 +716,9 @@ const Query = () => {
<Cell className={'car-info-item-content'}> <Cell className={'car-info-item-content'}>
{FormData.fenceName} {FormData.fenceName}
</Cell> </Cell>
<Cell className={'car-info-item-content'}>
{FormData.status == 2 ? '已绑定' : '未绑定'}
</Cell>
<Cell className={'car-info-item-content'}> <Cell className={'car-info-item-content'}>
{FormData.status == 2 ? FormData.driver : '-'} {FormData.status == 2 ? FormData.driver : '-'}
</Cell> </Cell>
@@ -754,7 +768,7 @@ const Query = () => {
} }
</div> </div>
</div> </div>
) : ''} )}
</> </>
) )
} }

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '添加违章记录'
})

244
src/hjm/violation/add.tsx Normal file
View File

@@ -0,0 +1,244 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro'
import {
Button,
TextArea,
Cell,
Input,
} from '@nutui/nutui-react-taro'
import {addHjmViolation} from "@/api/hjm/hjmViolation";
import {HjmViolation} from "@/api/hjm/hjmViolation/model";
/**
* 添加违章记录页面
*/
function Add() {
const [loading, setLoading] = useState<boolean>(false)
const [lastSubmitTime, setLastSubmitTime] = useState<number>(0) // 最后提交时间
const [formData, setFormData] = useState<HjmViolation>({
code: '',
title: '',
money: '',
score: '',
comments: ''
})
// 初始化页面数据
const initPageData = async () => {
try {
setLoading(false)
} catch (error) {
console.error('初始化失败:', error)
Taro.showToast({
title: '初始化失败',
icon: 'none'
})
}
}
// 提交表单
const handleSubmit = async () => {
// 防止重复提交 - 检查loading状态
if (loading) {
Taro.showToast({
title: '正在提交中,请稍候...',
icon: 'loading'
})
return
}
// 防止快速连续点击 - 2秒内不允许重复提交
const now = Date.now()
if (now - lastSubmitTime < 2000) {
Taro.showToast({
title: '请勿频繁提交',
icon: 'none'
})
return
}
setLastSubmitTime(now)
// 表单验证
if (!formData.code?.trim()) {
Taro.showToast({
title: '请输入车辆编号',
icon: 'none'
})
return
}
if (!formData.title?.trim()) {
Taro.showToast({
title: '请输入违章标题',
icon: 'none'
})
return
}
// if (!formData.money?.trim()) {
// Taro.showToast({
// title: '请输入处罚金额',
// icon: 'none'
// })
// return
// }
//
// if (!formData.score?.trim()) {
// Taro.showToast({
// title: '请输入扣分',
// icon: 'none'
// })
// return
// }
setLoading(true)
// 构建提交数据
const submitData: HjmViolation = {
...formData,
status: 0 // 0未处理, 1已处理
}
addHjmViolation(submitData).then((res) => {
console.log(res)
Taro.showToast({
title: '提交成功',
icon: 'success'
})
// 清空表单
setFormData({
code: '',
title: '',
money: '',
score: '',
comments: ''
})
setTimeout(() => {
Taro.navigateBack()
}, 2000)
}).finally(() => {
setLoading(false)
})
}
useEffect(() => {
initPageData().then(r => {
console.log(r, 'rr')
})
}, [])
return (
<div style={{
backgroundColor: '#f5f5f5',
minHeight: '100vh',
paddingBottom: '80px'
}}>
{/* 页面标题 */}
{/*<div style={{*/}
{/* backgroundColor: '#fff',*/}
{/* margin: '16px',*/}
{/* borderRadius: '12px',*/}
{/* padding: '16px',*/}
{/* boxShadow: '0 2px 8px rgba(0,0,0,0.06)'*/}
{/*}}>*/}
{/* <div style={{*/}
{/* display: 'flex',*/}
{/* alignItems: 'center',*/}
{/* gap: '8px',*/}
{/* marginBottom: '12px'*/}
{/* }}>*/}
{/* <Truck size={18} color="#1890ff"/>*/}
{/* <span style={{fontSize: '16px', fontWeight: 'bold'}}>添加违章记录</span>*/}
{/* </div>*/}
{/*</div>*/}
{/* 违章信息表单 */}
<div style={{
backgroundColor: '#fff',
margin: '16px 16px 16px',
borderRadius: '12px',
overflow: 'hidden'
}}>
<div style={{
padding: '16px',
borderBottom: '1px solid #f0f0f0'
}}>
<span style={{fontSize: '16px', fontWeight: 'bold'}}></span>
</div>
<Cell.Group>
<Cell title="车辆编号" style={{padding: '12px 16px'}}>
<Input
placeholder="YT000001"
value={formData.code}
onChange={(value) => setFormData({...formData, code: value})}
style={{backgroundColor: '#ffffff', borderRadius: '8px'}}
/>
</Cell>
<Cell title="违章标题" style={{padding: '12px 16px'}}>
<Input
placeholder="不按规定停车"
value={formData.title}
onChange={(value) => setFormData({...formData, title: value})}
style={{backgroundColor: '#ffffff', borderRadius: '8px'}}
/>
</Cell>
<Cell title="违章描述" style={{padding: '12px 16px'}}>
<TextArea
placeholder={'XX路1号...'}
value={formData.comments}
onChange={(value) => setFormData({...formData, comments: value})}
style={{backgroundColor: '#ffffff', borderRadius: '8px'}}
/>
</Cell>
<Cell title="处罚金额" style={{padding: '12px 16px'}}>
<Input
type="number"
placeholder="200(元)"
maxLength={3}
value={formData.money}
onChange={(value) => setFormData({...formData, money: value})}
style={{backgroundColor: '#ffffff', borderRadius: '8px'}}
/>
</Cell>
<Cell title="扣分" style={{padding: '12px 16px'}}>
<Input
type="number"
maxLength={2}
placeholder="2(分)"
value={formData.score}
onChange={(value) => setFormData({...formData, score: value})}
style={{backgroundColor: '#ffffff', borderRadius: '8px'}}
/>
</Cell>
</Cell.Group>
</div>
{/* 提交按钮 */}
<div style={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
backgroundColor: '#fff',
padding: '16px',
borderTop: '1px solid #f0f0f0'
}}>
<Button
type="primary"
block
loading={loading}
disabled={loading}
onClick={handleSubmit}
>
{loading ? '提交中...' : '提交违章记录'}
</Button>
</div>
</div>
)
}
export default Add

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '违章记录'
})

260
src/hjm/violation/list.tsx Normal file
View File

@@ -0,0 +1,260 @@
import React, {useEffect, useState} from "react";
import {
Loading,
Empty,
Button,
Input,
Tag,
Space
} from '@nutui/nutui-react-taro'
import {Search, Calendar, Truck, File, AddCircle} from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro'
import {pageHjmViolation} from "@/api/hjm/hjmViolation";
import {HjmViolation} from "@/api/hjm/hjmViolation/model";
/**
* 报险记录列表页面
*/
const List: React.FC = () => {
const [list, setList] = useState<HjmViolation[]>([])
const [loading, setLoading] = useState<boolean>(false)
const [keywords, setKeywords] = useState<string>('')
const [refreshing, setRefreshing] = useState<boolean>(false)
console.log(refreshing)
// 获取状态显示
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) => {
try {
if (showLoading) setLoading(true)
setRefreshing(true)
const res = await pageHjmViolation({
keywords: keywords.trim(),
})
setList(res?.list || [])
} catch (error) {
console.error('获取报险记录失败:', error)
Taro.showToast({
title: '获取报险记录失败',
icon: 'error'
})
} finally {
setLoading(false)
setRefreshing(false)
}
}
const onSearch = () => {
reload()
}
const onKeywordsChange = (value: string) => {
setKeywords(value)
}
const onAddInsurance = () => {
Taro.navigateTo({
url: '/hjm/violation/add'
})
}
useEffect(() => {
reload().then()
}, [])
return (
<>
{/* 搜索栏 */}
<div style={{
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="暂无报险记录">
<Button type="primary" onClick={onAddInsurance}>
</Button>
</Empty>
) : (
<div style={{padding: '0 16px'}}>
{list.map((item, index) => {
const statusDisplay = getStatusDisplay(item.status)
return (
<div
key={index}
style={{
backgroundColor: '#fff',
borderRadius: '12px',
padding: '16px',
marginBottom: '12px',
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
border: '1px solid #f0f0f0'
}}
>
<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>
)}
</div>
)
})}
</div>
)}
</div>
<div style={{
position: 'fixed',
bottom: '20px',
right: '20px',
zIndex: 30,
padding: '8px',
borderRadius: '20px',
overflow: "hidden",
backgroundColor: '#ff0000',
}}>
<AddCircle size={28} color={'#ffffff'} onClick={onAddInsurance} />
</div>
</>
)
}
export default List

View File

@@ -39,6 +39,12 @@ const ExpirationTime = () => {
if(data.certification){ if(data.certification){
setIsAdmin( true) setIsAdmin( true)
} }
if(Taro.getStorageSync('certification') == 'jj'){
setIsAdmin(true)
}
if(Taro.getStorageSync('certification') == 'yz'){
setIsAdmin(true)
}
data.roles?.map((item, index) => { data.roles?.map((item, index) => {
if (index == 0) { if (index == 0) {
setRoleName(item.roleCode) setRoleName(item.roleCode)

View File

@@ -65,6 +65,7 @@ const Header = (props: any) => {
setRoleName('交警') setRoleName('交警')
Taro.setStorageSync('RoleName', '交警') Taro.setStorageSync('RoleName', '交警')
Taro.setStorageSync('RoleCode', 'jiaojing') Taro.setStorageSync('RoleCode', 'jiaojing')
Taro.setStorageSync('certification', 'jj')
return false; return false;
} }
// 邮政协会/管局 // 邮政协会/管局
@@ -73,6 +74,7 @@ const Header = (props: any) => {
setRoleName('邮政协会/管局') setRoleName('邮政协会/管局')
Taro.setStorageSync('RoleName', '邮政协会/管局') Taro.setStorageSync('RoleName', '邮政协会/管局')
Taro.setStorageSync('RoleCode', 'youzheng') Taro.setStorageSync('RoleCode', 'youzheng')
Taro.setStorageSync('certification', 'yz')
return false; return false;
} }
// 快递公司 // 快递公司

View File

@@ -116,16 +116,15 @@ function Home() {
setLongitude(res.longitude) setLongitude(res.longitude)
} }
console.log(Taro.getStorageSync('RoleName'))
// 已认证用户 // 已认证用户
if(Taro.getStorageSync('Certification')){ if (Taro.getStorageSync('certification')) {
setIsAdmin(true) setIsAdmin(true)
setScale(11) setScale(11)
pageHjmCarByMap(res.latitude,res.longitude) pageHjmCarByMap(res.latitude, res.longitude)
} }
// 游客 // 游客
if(!Taro.getStorageSync('access_token') || Taro.getStorageSync('RoleName') == '注册用户'){ if (!Taro.getStorageSync('access_token') || Taro.getStorageSync('RoleName') == '注册用户') {
setScale(15) setScale(15)
const arr = [] const arr = []
// @ts-ignore // @ts-ignore
@@ -145,7 +144,7 @@ function Home() {
} }
const onQuery = () => { const onQuery = () => {
if(!keywords){ if (!keywords) {
Taro.showToast({ Taro.showToast({
title: '请输入关键字', title: '请输入关键字',
icon: 'none' icon: 'none'
@@ -155,36 +154,36 @@ function Home() {
reload(); reload();
} }
const pageHjmCarByMap = (latitude?: any,longitude?: any) => { const pageHjmCarByMap = (latitude?: any, longitude?: any) => {
// 搜索条件 // 搜索条件
const where = {} const where = {}
if(latitude){ if (latitude) {
// @ts-ignore // @ts-ignore
where.latitude = latitude where.latitude = latitude
} }
if(longitude){ if (longitude) {
// @ts-ignore // @ts-ignore
where.longitude = longitude where.longitude = longitude
} }
// 判断身份 // 判断身份
const roleCode = Taro.getStorageSync('RoleCode'); const roleCode = Taro.getStorageSync('RoleCode');
if(roleCode == 'kuaidiyuan'){ if (roleCode == 'kuaidiyuan') {
// @ts-ignore // @ts-ignore
where.driverId = Taro.getStorageSync('UserId') where.driverId = Taro.getStorageSync('UserId')
} }
if(roleCode == 'zhandian'){ if (roleCode == 'zhandian') {
// @ts-ignore // @ts-ignore
where.organizationId = Taro.getStorageSync('OrganizationId'); where.organizationId = Taro.getStorageSync('OrganizationId');
} }
if(roleCode == 'kuaidi'){ if (roleCode == 'kuaidi') {
// @ts-ignore // @ts-ignore
where.organizationParentId = Taro.getStorageSync('OrganizationParentId'); where.organizationParentId = Taro.getStorageSync('OrganizationParentId');
} }
pageByQQMap(where).then(res => { pageByQQMap(where).then(res => {
console.log(res,'pageByQQMap') console.log(res, 'pageByQQMap')
if(res?.count == 0){ if (res?.count == 0) {
const arr = [] const arr = []
// @ts-ignore // @ts-ignore
arr.push({ arr.push({
@@ -226,16 +225,16 @@ function Home() {
} }
const reload = () => { const reload = () => {
if(!Taro.getStorageSync('access_token')){ if (!Taro.getStorageSync('access_token')) {
return false; return false;
} }
if(!isAdmin){ if (!isAdmin) {
return false; return false;
} }
setMarkers([]) setMarkers([])
setScale(12) setScale(12)
pageHjmCar({keywords,deleted: 0}).then(res => { pageHjmCar({keywords, deleted: 0}).then(res => {
if(res?.count == 0){ if (res?.count == 0) {
Taro.showToast({ Taro.showToast({
title: '没有搜索结果', title: '没有搜索结果',
icon: 'none' icon: 'none'
@@ -247,7 +246,7 @@ function Home() {
const data = res?.list[0]; const data = res?.list[0];
setLongitude(data?.longitude) setLongitude(data?.longitude)
setLatitude(data?.latitude) setLatitude(data?.latitude)
if(isAdmin){ if (isAdmin) {
setMarkers([{ setMarkers([{
id: data.id, id: data.id,
latitude: data.latitude, latitude: data.latitude,
@@ -268,7 +267,7 @@ function Home() {
}]) }])
} }
} }
console.log(list.length,'carList.length') console.log(list.length, 'carList.length')
}) })
}; };
@@ -308,8 +307,12 @@ function Home() {
getUserInfo().then((data) => { getUserInfo().then((data) => {
if (data) { if (data) {
// 是否管理员 // 是否管理员
console.log(data.certification, 'certification') if (data.certification) {
if(data.certification){ setIsAdmin(true)
}
// 是否交警
if (Taro.getStorageSync('certification') == 'jj') {
console.log('交警', '12312')
setIsAdmin(true) setIsAdmin(true)
} }
setUserInfo(data) setUserInfo(data)
@@ -319,8 +322,11 @@ function Home() {
if (!data.openid) { if (!data.openid) {
Taro.login({ Taro.login({
success: (res) => { success: (res) => {
getWxOpenId({code: res.code}).then(() => { // 排查交警和邮政角色不保存openid
}) if (Taro.getStorageSync('RoleCode') !== 'jiaojing' || Taro.getStorageSync('RoleCode') !== 'youzheng' || Taro.getStorageSync('RoleCode') !== 'Installer') {
getWxOpenId({code: res.code}).then(() => {
})
}
} }
}) })
} }

View File

@@ -3,9 +3,10 @@ import navTo from "@/utils/common";
import UserFooter from "./UserFooter"; import UserFooter from "./UserFooter";
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import {ArrowRight, ShieldCheck, Truck, LogisticsError} from '@nutui/icons-react-taro' import {ArrowRight, ShieldCheck, Truck, LogisticsError} from '@nutui/icons-react-taro'
import {CSSProperties} from "react"; import {CSSProperties, useEffect, useState} from "react";
const UserCell = () => { const UserCell = () => {
const [roleName, setRoleName] = useState<string>('')
const InfiniteUlStyle: CSSProperties = { const InfiniteUlStyle: CSSProperties = {
height: '88vh', height: '88vh',
padding: '16px', padding: '16px',
@@ -30,6 +31,10 @@ const UserCell = () => {
}) })
} }
useEffect(() => {
setRoleName(Taro.getStorageSync('RoleCode'))
}, []);
return ( return (
<> <>
<div style={InfiniteUlStyle} id="scroll"> <div style={InfiniteUlStyle} id="scroll">
@@ -50,38 +55,85 @@ const UserCell = () => {
}} }}
/> />
</Cell.Group> </Cell.Group>
<Cell.Group divider={true}> {
<Cell roleName === 'kuaidi' && (
className="nutui-cell-clickable" <Cell.Group divider={true}>
title={ <Cell
<div style={{display: 'inline-flex', alignItems: 'center'}}> className="nutui-cell-clickable"
<Truck size={16}/> title={
<span className={'pl-3 text-sm'}></span> <div style={{display: 'inline-flex', alignItems: 'center'}}>
</div> <ShieldCheck size={16}/>
} <span className={'pl-3 text-sm'}></span>
align="center" </div>
extra={<ArrowRight color="#cccccc" size={18}/>} }
onClick={() => { align="center"
navTo('/user/car/index', true) extra={<ArrowRight color="#cccccc" size={18}/>}
}} onClick={() => {
/> navTo('/user/userVerify/admin', true)
</Cell.Group> }}
<Cell.Group divider={true}> />
<Cell </Cell.Group>
className="nutui-cell-clickable" )
title={ }
<div style={{display: 'inline-flex', alignItems: 'center'}}> {
<LogisticsError size={16}/> roleName === 'kuaidiyuan' && (
<span className={'pl-3 text-sm'}></span> <>
</div> <Cell.Group divider={true}>
} <Cell
align="center" className="nutui-cell-clickable"
extra={<ArrowRight color="#cccccc" size={18}/>} title={
onClick={() => { <div style={{display: 'inline-flex', alignItems: 'center'}}>
navTo('/hjm/bx/bx', true) <Truck size={16}/>
}} <span className={'pl-3 text-sm'}></span>
/> </div>
</Cell.Group> }
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/user/car/index', true)
}}
/>
</Cell.Group>
<Cell.Group divider={true}>
<Cell
className="nutui-cell-clickable"
title={
<div style={{display: 'inline-flex', alignItems: 'center'}}>
<LogisticsError size={16}/>
<span className={'pl-3 text-sm'}></span>
</div>
}
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/hjm/bx/bx', true)
}}
/>
</Cell.Group>
</>
)
}
{
roleName === 'jiaojing' && (
<Cell.Group divider={true}>
<Cell
className="nutui-cell-clickable"
title={
<div style={{display: 'inline-flex', alignItems: 'center'}}>
<Truck size={16}/>
<span className={'pl-3 text-sm'}></span>
</div>
}
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/hjm/violation/list', true)
}}
/>
</Cell.Group>
)
}
{/*<Cell.Group divider={true} description={*/} {/*<Cell.Group divider={true} description={*/}
{/* <div style={{display: 'inline-flex', alignItems: 'center'}}>*/} {/* <div style={{display: 'inline-flex', alignItems: 'center'}}>*/}
{/* <span style={{marginTop: '12px'}}>管理</span>*/} {/* <span style={{marginTop: '12px'}}>管理</span>*/}
@@ -171,7 +223,14 @@ const UserCell = () => {
title="账号安全" title="账号安全"
align="center" align="center"
extra={<ArrowRight color="#cccccc" size={18}/>} extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => navTo('/user/profile/profile',true)} onClick={() => navTo('/user/profile/profile', true)}
/>
<Cell
className="nutui-cell-clickable"
title="短信登录"
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => navTo('/passport/sms-login', true)}
/> />
<Cell <Cell
className="nutui-cell-clickable" className="nutui-cell-clickable"

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '验证码登录',
navigationBarTextStyle: 'black'
})

204
src/passport/sms-login.tsx Normal file
View File

@@ -0,0 +1,204 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro'
import {Input, Button} from '@nutui/nutui-react-taro'
import {loginBySms, sendSmsCaptcha} from "@/api/passport/login";
import {LoginParam} from "@/api/passport/login/model";
const SmsLogin = () => {
const [loading, setLoading] = useState<boolean>(false)
const [sendingCode, setSendingCode] = useState<boolean>(false)
const [countdown, setCountdown] = useState<number>(0)
const [formData, setFormData] = useState<LoginParam>({
phone: '',
code: ''
})
const reload = () => {
Taro.hideTabBar()
}
useEffect(() => {
reload()
}, [])
// 倒计时效果
useEffect(() => {
let timer: NodeJS.Timeout
if (countdown > 0) {
timer = setTimeout(() => {
setCountdown(countdown - 1)
}, 1000)
}
return () => {
if (timer) clearTimeout(timer)
}
}, [countdown])
// 验证手机号格式
const validatePhone = (phone: string): boolean => {
const phoneRegex = /^1[3-9]\d{9}$/
return phoneRegex.test(phone)
}
// 发送短信验证码
const handleSendCode = async () => {
if (!formData.phone) {
Taro.showToast({
title: '请输入手机号码',
icon: 'none'
})
return
}
if (!validatePhone(formData.phone)) {
Taro.showToast({
title: '请输入正确的手机号码',
icon: 'none'
})
return
}
if (sendingCode || countdown > 0) {
return
}
try {
setSendingCode(true)
await sendSmsCaptcha({ phone: formData.phone })
Taro.showToast({
title: '验证码已发送',
icon: 'success'
})
// 开始60秒倒计时
setCountdown(60)
} catch (error: any) {
Taro.showToast({
title: error.message || '发送失败',
icon: 'error'
})
} finally {
setSendingCode(false)
}
}
// 处理登录
const handleLogin = async () => {
// 防止重复提交
if (loading) {
return
}
// 表单验证
if (!formData.phone) {
Taro.showToast({
title: '请输入手机号码',
icon: 'none'
})
return
}
if (!validatePhone(formData.phone)) {
Taro.showToast({
title: '请输入正确的手机号码',
icon: 'none'
})
return
}
if (!formData.code) {
Taro.showToast({
title: '请输入验证码',
icon: 'none'
})
return
}
if (formData.code.length !== 6) {
Taro.showToast({
title: '请输入6位验证码',
icon: 'none'
})
return
}
try {
setLoading(true)
await loginBySms({
phone: formData.phone,
code: formData.code
})
Taro.showToast({
title: '登录成功',
icon: 'success'
})
// 延迟跳转到首页
setTimeout(() => {
Taro.reLaunch({
url: '/pages/index/index'
})
}, 1500)
} catch (error: any) {
Taro.showToast({
title: error.message || '登录失败',
icon: 'error'
})
} finally {
setLoading(false)
}
}
return (
<>
<div className={'flex flex-col justify-center px-5 pt-3'}>
<div className={'flex flex-col justify-between items-center my-2'}>
<Input
type="number"
placeholder="请输入手机号码"
maxLength={11}
value={formData.phone}
onChange={(value) => setFormData({...formData, phone: value})}
style={{backgroundColor: '#ffffff', borderRadius: '8px'}}
/>
</div>
<div className={'flex justify-between items-center bg-white rounded-lg my-2 pr-2'}>
<Input
type="number"
placeholder="请输入6位验证码"
maxLength={6}
value={formData.code}
onChange={(value) => setFormData({...formData, code: value})}
style={{ backgroundColor: '#ffffff', borderRadius: '8px'}}
/>
<Button
size="small"
type={countdown > 0 ? "default" : "primary"}
loading={sendingCode}
disabled={sendingCode || countdown > 0}
onClick={handleSendCode}
>
{countdown > 0 ? `${countdown}s` : sendingCode ? '发送中...' : '获取验证码'}
</Button>
</div>
<div className={'flex justify-center my-5'}>
<Button
type="info"
size={'large'}
className={'w-full rounded-lg p-2'}
loading={loading}
disabled={loading}
onClick={handleLogin}
>
{loading ? '登录中...' : '登录'}
</Button>
</div>
</div>
</>
)
}
export default SmsLogin

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '实名认证管理'
})

View File

@@ -0,0 +1,248 @@
import React, {useEffect, useState} from "react";
import {
Loading,
Empty,
Button,
Input,
Tag,
Space
} from '@nutui/nutui-react-taro'
import {Search, Calendar, Truck, File, AddCircle} from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro'
import {pageHjmViolation} from "@/api/hjm/hjmViolation";
import {UserVerify} from "@/api/system/userVerify/model";
import {pageUserVerify} from "@/api/system/userVerify";
/**
* 报险记录列表页面
*/
const List: React.FC = () => {
const [list, setList] = useState<UserVerify[]>([])
const [loading, setLoading] = useState<boolean>(false)
const [keywords, setKeywords] = useState<string>('')
const [refreshing, setRefreshing] = useState<boolean>(false)
console.log(refreshing)
// 获取状态显示
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) => {
try {
if (showLoading) setLoading(true)
setRefreshing(true)
const res = await pageUserVerify({
keywords: keywords.trim(),
})
setList(res?.list || [])
} catch (error) {
console.error('获取报险记录失败:', error)
Taro.showToast({
title: '获取报险记录失败',
icon: 'error'
})
} finally {
setLoading(false)
setRefreshing(false)
}
}
const onSearch = () => {
reload()
}
const onKeywordsChange = (value: string) => {
setKeywords(value)
}
const onAddInsurance = () => {
Taro.navigateTo({
url: '/hjm/violation/add'
})
}
useEffect(() => {
reload().then()
}, [])
return (
<>
{/* 搜索栏 */}
<div style={{
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="暂无报险记录">
<Button type="primary" onClick={onAddInsurance}>
</Button>
</Empty>
) : (
<div style={{padding: '0 16px'}}>
{list.map((item, index) => {
const statusDisplay = getStatusDisplay(item.status)
return (
<div
key={index}
style={{
backgroundColor: '#fff',
borderRadius: '12px',
padding: '16px',
marginBottom: '12px',
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
border: '1px solid #f0f0f0'
}}
>
<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.realName}
</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.phone}
</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'
}}
>
{item.statusText}
</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>
)}
</div>
)
})}
</div>
)}
</div>
</>
)
}
export default List