修复已知问题

This commit is contained in:
2025-06-06 18:23:07 +08:00
parent 3ac3fd8cb2
commit 7ff3e3f064
51 changed files with 2503 additions and 1257 deletions

202
src/hjm/bx/BestSellers.tsx Normal file
View File

@@ -0,0 +1,202 @@
import React from "react";
import {Image, Space, Tag, Button} from '@nutui/nutui-react-taro'
import {Truck, User, Shield, Location} from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro'
import {HjmCar} from "@/api/hjm/hjmCar/model";
interface BestSellersProps {
data: HjmCar[]
onRefresh?: () => void
}
/**
* 车辆列表组件
*/
const BestSellers: React.FC<BestSellersProps> = ({data, onRefresh}) => {
// 获取保险状态显示
const getInsuranceStatusDisplay = (status?: number) => {
switch (status) {
case 0:
return {text: '未投保', color: '#ff4d4f', bgColor: '#fff2f0'}
case 1:
return {text: '已投保', color: '#52c41a', bgColor: '#f6ffed'}
case 2:
return {text: '即将到期', color: '#faad14', bgColor: '#fffbe6'}
default:
return {text: '未知', color: '#8c8c8c', bgColor: '#f5f5f5'}
}
}
// 跳转到车辆详情
const navigateToDetail = (item: HjmCar) => {
Taro.navigateTo({
url: `/hjm/query?id=${item.id}`
})
}
// 快速报险
const quickInsurance = (item: HjmCar, event: any) => {
event.stopPropagation()
Taro.navigateTo({
url: `/hjm/bx/bx-add?carId=${item.id}&carCode=${item.code}`
})
}
if (!data || data.length === 0) {
return null
}
return (
<div style={{padding: '0 16px', marginBottom: '16px'}}>
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '12px'
}}>
{data.map((item, index) => {
const insuranceStatus = getInsuranceStatusDisplay(item.insuranceStatus)
return (
<div
key={index}
style={{
backgroundColor: '#fff',
borderRadius: '12px',
padding: '16px',
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
border: '1px solid #f0f0f0'
}}
onClick={() => navigateToDetail(item)}
>
<div style={{display: 'flex', gap: '12px'}}>
{/* 车辆图片 */}
<div style={{flexShrink: 0}}>
<Image
src={item.image || 'https://via.placeholder.com/80x80?text=车辆'}
mode="aspectFill"
radius="8px"
width="80"
height="80"
style={{
border: '1px solid #f0f0f0'
}}
/>
</div>
{/* 车辆信息 */}
<div style={{flex: 1, minWidth: 0}}>
<Space direction="vertical" size={8}>
{/* 车辆编号 */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px'
}}>
<Truck size={16} color="#1890ff"/>
<span style={{
fontSize: '16px',
fontWeight: 'bold',
color: '#262626'
}}>
{item.code || '未知编号'}
</span>
</div>
{/* 快递公司 */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px'
}}>
<Location size={14} color="#8c8c8c"/>
<span style={{fontSize: '13px', color: '#8c8c8c'}}>
</span>
<span style={{fontSize: '13px', color: '#595959'}}>
{item.parentOrganization || '未知'}
</span>
</div>
{/* 保险状态 */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px'
}}>
<Shield size={14} color="#8c8c8c"/>
<span style={{fontSize: '13px', color: '#8c8c8c'}}>
</span>
<Tag
color={insuranceStatus.color}
style={{
backgroundColor: insuranceStatus.bgColor,
border: `1px solid ${insuranceStatus.color}`,
fontSize: '12px'
}}
>
{insuranceStatus.text}
</Tag>
</div>
{/* 操作员 */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px'
}}>
<User size={14} color="#8c8c8c"/>
<span style={{fontSize: '13px', color: '#8c8c8c'}}>
</span>
<span style={{fontSize: '13px', color: '#595959'}}>
{item.driver || '未绑定'}
</span>
</div>
</Space>
</div>
</div>
{/* 操作按钮 */}
<div style={{
marginTop: '12px',
paddingTop: '12px',
borderTop: '1px solid #f0f0f0',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}>
<Button
type="primary"
size="small"
onClick={(e) => quickInsurance(item, e)}
style={{
borderRadius: '16px',
fontSize: '12px'
}}
>
</Button>
<Button
type="default"
size="small"
onClick={() => navigateToDetail(item)}
style={{
borderRadius: '16px',
fontSize: '12px'
}}
>
</Button>
</div>
</div>
)
})}
</div>
</div>
)
}
export default BestSellers

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '一键报险'
})

366
src/hjm/bx/bx-add.tsx Normal file
View File

@@ -0,0 +1,366 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro'
import {
Image,
Button,
TextArea,
Cell,
Loading,
Space
} from '@nutui/nutui-react-taro'
import {Camera, Truck} from '@nutui/icons-react-taro'
import {addHjmBxLog} from "@/api/hjm/hjmBxLog";
import {pageHjmCar} from "@/api/hjm/hjmCar";
import {uploadFile} from "@/api/system/file";
import {HjmBxLog} from "@/api/hjm/hjmBxLog/model";
import {HjmCar} from "@/api/hjm/hjmCar/model";
/**
* 一键报险 - 添加报险记录页面
*/
function BxAdd() {
const [loading, setLoading] = useState<boolean>(false)
const [uploading, setUploading] = useState<boolean>(false)
const [carInfo, setCarInfo] = useState<HjmCar | null>(null)
const [formData, setFormData] = useState<HjmBxLog>({
carId: undefined,
image: '',
comments: '',
status: 0 // 0: 待审核, 1: 已通过, 2: 已驳回
})
// 事故类型选项
const accidentTypes = [
{text: '轻微刮擦', value: '轻微刮擦'},
{text: '碰撞事故', value: '碰撞事故'},
{text: '追尾事故', value: '追尾事故'},
{text: '侧翻事故', value: '侧翻事故'},
{text: '其他事故', value: '其他事故'}
]
const [accidentType, setAccidentType] = useState<string>('')
const [accidentDescription, setAccidentDescription] = useState<string>('')
// 初始化页面数据
const initPageData = async () => {
try {
pageHjmCar({driverId: Taro.getStorageSync('UserId')}).then(res => {
const car = res?.list[0];
setLoading(true)
if (car) {
setCarInfo(car)
setFormData(prev => ({
...prev,
carId: car.id
}))
} else {
Taro.showToast({
title: '获取车辆信息失败',
icon: 'none'
})
setTimeout(() => {
Taro.navigateBack()
}, 1000)
}
})
} catch (error) {
console.error('获取车辆信息失败:', error)
Taro.showToast({
title: '获取车辆信息失败',
icon: 'none'
})
} finally {
setLoading(false)
}
}
// 拍照上传
const takePhoto = () => {
Taro.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['camera'],
success: async () => {
try {
setUploading(true)
// 这里应该调用实际的上传接口
uploadFile().then(data => {
setFormData({
...formData,
image: data.url
})
});
} catch (error) {
} finally {
setUploading(false)
}
}
})
}
// 提交表单
const handleSubmit = async () => {
// 表单验证
if (!formData.carId) {
Taro.showToast({
title: '请选择车辆',
icon: 'none'
})
return
}
if (!accidentType) {
Taro.showToast({
title: '请选择事故类型',
icon: 'none'
})
return
}
if (!formData.image) {
Taro.showToast({
title: '请上传事故现场照片',
icon: 'none'
})
return
}
try {
setLoading(true)
// 构建提交数据
const submitData: HjmBxLog = {
...formData,
comments: `事故类型:${accidentType}\n事故描述${accidentDescription || '无'}`
}
await addHjmBxLog(submitData)
Taro.showToast({
title: '报险提交成功',
icon: 'success'
})
setTimeout(() => {
Taro.navigateBack()
}, 1500)
} catch (error) {
console.error('提交失败:', error)
Taro.showToast({
title: '提交失败',
icon: 'error'
})
} finally {
setLoading(false)
}
}
useEffect(() => {
initPageData()
}, [])
if (loading && !carInfo) {
return (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh'
}}>
<Loading type="spinner">...</Loading>
</div>
)
}
return (
<div style={{
backgroundColor: '#f5f5f5',
minHeight: '100vh',
paddingBottom: '80px'
}}>
{/* 车辆信息卡片 */}
{carInfo && (
<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>
<Space direction="vertical" size={8}>
<div style={{display: 'flex', justifyContent: 'space-between'}}>
<span style={{color: '#8c8c8c'}}></span>
<span style={{fontWeight: 'bold'}}>{carInfo.code}</span>
</div>
<div style={{display: 'flex', justifyContent: 'space-between'}}>
<span style={{color: '#8c8c8c'}}></span>
<span>{carInfo.parentOrganization}</span>
</div>
<div style={{display: 'flex', justifyContent: 'space-between'}}>
<span style={{color: '#8c8c8c'}}></span>
<span>{carInfo.driver || '未绑定'}</span>
</div>
</Space>
</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="事故类型"
description={accidentType || '请选择事故类型'}
onClick={() => {
Taro.showActionSheet({
itemList: accidentTypes.map(item => item.text),
success: (res) => {
setAccidentType(accidentTypes[res.tapIndex].value)
}
})
}}
/>
{/* 事故时间 */}
{/*<Cell*/}
{/* title="事故时间"*/}
{/* description={accidentTime ? new Date(accidentTime).toLocaleString() : '请选择事故时间'}*/}
{/* onClick={() => {*/}
{/* const now = new Date()*/}
{/* setAccidentTime(now.toISOString().slice(0, 16))*/}
{/* }}*/}
{/*/>*/}
</Cell.Group>
</div>
{/* 事故描述 */}
<div style={{
backgroundColor: '#fff',
margin: '0 16px 16px',
borderRadius: '12px',
padding: '16px'
}}>
<div style={{marginBottom: '12px'}}>
<span style={{fontSize: '16px', fontWeight: 'bold'}}></span>
<span style={{color: '#8c8c8c', fontSize: '12px', marginLeft: '8px'}}></span>
</div>
<TextArea
placeholder={'请详细描述事故经过、损失情况等...'}
value={accidentDescription}
onChange={setAccidentDescription}
/>
</div>
{/* 现场照片 */}
<div style={{
backgroundColor: '#fff',
margin: '0 16px 16px',
borderRadius: '12px',
padding: '16px'
}}>
<div style={{marginBottom: '12px'}}>
<span style={{fontSize: '16px', fontWeight: 'bold'}}></span>
<span style={{color: '#ff4d4f', fontSize: '12px', marginLeft: '8px'}}>*</span>
</div>
<div style={{
display: 'flex',
flexWrap: 'wrap',
gap: '12px'
}}>
{formData.image && (
<div style={{position: 'relative'}}>
<Image
src={formData.image}
width="100"
height="100"
radius="8px"
mode="aspectFill"
/>
<Button
size="small"
type="default"
style={{
position: 'absolute',
top: '-8px',
right: '-8px',
width: '24px',
height: '24px',
borderRadius: '12px',
fontSize: '12px'
}}
onClick={() => setFormData(prev => ({...prev, image: ''}))}
>
×
</Button>
</div>
)}
{!formData.image && (
<Button
size="small"
loading={uploading}
onClick={takePhoto}
style={{
width: '100px',
height: '100px',
borderRadius: '8px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
border: '2px dashed #d9d9d9'
}}
>
<Camera size={24}/>
<span style={{fontSize: '12px', marginTop: '4px'}}></span>
</Button>
)}
</div>
</div>
{/* 提交按钮 */}
<div style={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
backgroundColor: '#fff',
padding: '16px',
borderTop: '1px solid #f0f0f0'
}}>
<Button
type="primary"
block
onClick={handleSubmit}
>
</Button>
</div>
</div>
)
}
export default BxAdd

291
src/hjm/bx/bx-list.tsx Normal file
View File

@@ -0,0 +1,291 @@
import React, {useEffect, useState} from "react";
import {
InfiniteLoading,
Loading,
Empty,
Button,
Input,
Tag,
Image,
Space,
Cell
} from '@nutui/nutui-react-taro'
import {Search, Calendar, Truck, File} from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro'
import {pageHjmBxLog} from "@/api/hjm/hjmBxLog";
import {HjmBxLog} from "@/api/hjm/hjmBxLog/model";
/**
* 报险记录列表页面
*/
const BxList: React.FC = () => {
const [list, setList] = useState<HjmBxLog[]>([])
const [loading, setLoading] = useState<boolean>(false)
const [keywords, setKeywords] = useState<string>('')
const [refreshing, setRefreshing] = useState<boolean>(false)
// 获取状态显示
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 pageHjmBxLog({
keywords: keywords.trim() || undefined
})
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/bx/bx-add'
})
}
const viewDetail = (item: HjmBxLog) => {
Taro.navigateTo({
url: `/hjm/bx/bx-detail?id=${item.id}`
})
}
useEffect(() => {
reload()
}, [])
return (
<>
{/* 搜索栏 */}
<div style={{
position: 'fixed',
top: '20px',
left: 0,
right: 0,
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: '80px',
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'
}}
onClick={() => viewDetail(item)}
>
<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.id}
</span>
</div>
<Space direction="vertical" size={4}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px'
}}>
<Truck size={14} color="#8c8c8c"/>
<span style={{fontSize: '13px', color: '#8c8c8c'}}>
ID{item.carId}
</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.image && (
<div style={{marginBottom: '12px'}}>
<Image
src={item.image}
width="60"
height="60"
radius="6px"
mode="aspectFill"
/>
</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
}}>
<Button
type="primary"
shape="round"
size="large"
onClick={onAddInsurance}
style={{
width: '56px',
height: '56px',
borderRadius: '28px',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)'
}}
>
+
</Button>
</div>
</>
)
}
export default BxList

3
src/hjm/bx/bx.config.ts Normal file
View File

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

162
src/hjm/bx/bx.tsx Normal file
View File

@@ -0,0 +1,162 @@
import {useEffect, useState} from "react";
import {InfiniteLoading, Loading, Empty, Button, Input} from '@nutui/nutui-react-taro'
import {Search, Plus} from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro'
import {pageHjmCar} from "@/api/hjm/hjmCar";
import {HjmCar} from "@/api/hjm/hjmCar/model";
import BestSellers from "./BestSellers";
/**
* 一键报险 - 车辆列表页面
* @constructor
*/
const InsuranceList = () => {
const [list, setList] = useState<HjmCar[]>([])
const [loading, setLoading] = useState<boolean>(false)
const [keywords, setKeywords] = useState<string>('')
const [refreshing, setRefreshing] = useState<boolean>(false)
const reload = async (showLoading = true) => {
try {
if (showLoading) setLoading(true)
setRefreshing(true)
// 获取车辆列表 - 只获取正常状态的车辆
const res = await pageHjmCar({
status: 1,
keywords: keywords.trim() || undefined
})
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/bx/bx-add'
})
}
useEffect(() => {
reload()
}, [])
return (
<>
{/* 搜索栏 */}
<div style={{
position: 'fixed',
top: '20px',
left: 0,
right: 0,
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: '80px',
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={() => reload()}>
</Button>
</Empty>
) : (
<InfiniteLoading
style={{width: '100%'}}
hasMore={false}
onLoadMore={() => {}}
>
<BestSellers data={list} onRefresh={() => reload(false)}/>
</InfiniteLoading>
)}
</div>
{/* 浮动添加按钮 */}
<div style={{
position: 'fixed',
bottom: '20px',
right: '20px',
zIndex: 30
}}>
<Button
type="primary"
shape="round"
size="large"
onClick={onAddInsurance}
style={{
width: '56px',
height: '56px',
borderRadius: '28px',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)'
}}
>
<Plus size={24}/>
</Button>
</div>
</>
)
}
export default InsuranceList