修复已知问题

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

View File

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

View File

@@ -1,116 +0,0 @@
import {useEffect, useState} from "react";
import {useRouter} from '@tarojs/taro'
import {getHjmCar, pageHjmCar} from "@/api/hjm/hjmCar";
import {HjmCar} from "@/api/hjm/hjmCar/model";
import {Image,Cell} from '@nutui/nutui-react-taro'
import './location.scss'
/**
* 电子围栏
* @constructor
*/
const Fence = () => {
const {params} = useRouter();
const [keywords, setKeywords] = useState<string>()
const [item, setItem] = useState<HjmCar>()
// 打开地图选择位置
// const chooseLocation = async () => {
// try {
// const res = await Taro.chooseLocation({
// latitude, // 默认纬度
// longitude // 默认经度
// })
// console.log('选择的位置:', res);
// } catch (err) {
// console.error('选择位置失败:', err);
// }
// }
const reload = () => {
const id = Number(params.id);
// 执行搜索
if (keywords) {
pageHjmCar({keywords}).then(res => {
if (res?.list && res?.list?.length > 0) {
const data = res?.list[0];
setItem(data)
setKeywords(data.code)
}
})
return false;
}
// 获取车辆信息
if (id) {
getHjmCar(id).then(data => {
setItem(data)
setKeywords(data.code)
})
}
}
useEffect(() => {
reload()
}, [])
return (
<>
{/*<div className={'fixed z-20 top-5 left-0 w-full'}>*/}
{/* <div className={'px-4'}>*/}
{/* <div*/}
{/* style={{*/}
{/* display: 'flex',*/}
{/* alignItems: 'center',*/}
{/* background: '#fff',*/}
{/* padding: '0 10px',*/}
{/* borderRadius: '20px'*/}
{/* }}*/}
{/* >*/}
{/* <Search/>*/}
{/* <Input*/}
{/* placeholder="车辆编号"*/}
{/* value={keywords}*/}
{/* onChange={onKeywords}*/}
{/* />*/}
{/* <div*/}
{/* className={'flex items-center'}*/}
{/* >*/}
{/* <Button type="warning" onClick={reload}>*/}
{/* 查询*/}
{/* </Button>*/}
{/* </div>*/}
{/* </div>*/}
{/* </div>*/}
{/*</div>*/}
{item ? (
<div className={'car-info w-full bg-white'}>
<Image src={item?.image} mode={'widthFix'} width={'100%'} className={'bg-gray-50'}/>
<div className={'px-2'}>
<Cell className={'car-info-item-title'}>
{item?.code}
</Cell>
<Cell className={'car-info-item-title'}>
{item?.kuaidi}
</Cell>
<Cell className={'car-info-item-title'}>
{item?.kuaidiAdmin}
</Cell>
<Cell className={'car-info-item-content'}>
{item?.driver}
</Cell>
<Cell className={'car-info-item-content'}>
{item?.insuranceStatus}
</Cell>
<Cell className={'car-info-item-content'}>
GPS编号{item?.gpsNo}
</Cell>
<Cell className={'car-info-item-content'}>
{item?.fenceName}
</Cell>
</div>
</div>
) : ''}
</>
)
}
export default Fence

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

View File

@@ -0,0 +1,131 @@
import {useEffect, useState} from "react";
import {Button} from '@nutui/nutui-react-taro'
import Taro from '@tarojs/taro'
import {pageHjmQuestions} from "@/api/hjm/hjmQuestions";
import {HjmQuestions} from "@/api/hjm/hjmQuestions/model";
/**
* 简化版考试系统 - 用于测试
* @constructor
*/
const ExamSimple = () => {
const [questions, setQuestions] = useState<HjmQuestions[]>([])
const [loading, setLoading] = useState<boolean>(false)
// 加载题目
const loadQuestions = () => {
setLoading(true)
console.log('开始加载题目...')
pageHjmQuestions({}).then(data => {
console.log('API返回数据:', data)
const questionList = data?.list || []
setQuestions(questionList)
console.log('加载题目成功:', questionList)
Taro.showToast({
title: `加载成功,共${questionList.length}道题`,
icon: 'success'
})
}).catch(error => {
console.error('加载题目失败:', error)
Taro.showToast({
title: '加载题目失败',
icon: 'error'
})
}).finally(() => {
setLoading(false)
})
}
useEffect(() => {
loadQuestions()
}, [])
return (
<div style={{ padding: '16px' }}>
<h1 style={{ fontSize: '20px', fontWeight: 'bold', marginBottom: '16px' }}>
</h1>
<div style={{ marginBottom: '16px' }}>
<p>{questions.length} </p>
<p>{loading ? '加载中...' : '加载完成'}</p>
</div>
<div style={{ marginBottom: '16px' }}>
<Button
type="primary"
onClick={loadQuestions}
loading={loading}
>
</Button>
</div>
{questions.length > 0 && (
<div style={{ backgroundColor: 'white', padding: '16px', borderRadius: '8px' }}>
<h3 style={{ marginBottom: '12px' }}></h3>
{questions.slice(0, 5).map((question, index) => (
<div key={index} style={{ marginBottom: '12px', padding: '8px', backgroundColor: '#f5f5f5', borderRadius: '4px' }}>
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>
{index + 1}{question.question}
</div>
<div style={{ fontSize: '12px', color: '#666' }}>
{question.type === 0 ? '选择题' : question.type === 1 ? '填空题' : '问答题'} |
{question.difficulty === 0 ? '简单' : question.difficulty === 1 ? '中等' : '困难'}
</div>
{question.type === 0 && (
<div style={{ fontSize: '12px', marginTop: '4px' }}>
{question.choicesList && question.choicesList.length > 0 ? (
// 使用 choicesList 显示选项
<>
{question.choicesList.map((choice, index) => {
const optionLabel = String.fromCharCode(65 + index); // A, B, C, D
return (
<div key={index} style={{ color: choice.isCorrect ? '#52c41a' : '#333' }}>
{optionLabel}: {choice.content} {choice.isCorrect && '✓'}
</div>
);
})}
</>
) : (
// 备用方案:使用传统字段
<>
A: {question.choicesA}<br/>
B: {question.choicesB}<br/>
C: {question.choicesC}<br/>
D: {question.choicesD}<br/>
: {question.correctAnswer}
</>
)}
</div>
)}
</div>
))}
{questions.length > 5 && (
<div style={{ textAlign: 'center', color: '#666', fontSize: '14px' }}>
{questions.length - 5} ...
</div>
)}
</div>
)}
<div style={{ marginTop: '16px' }}>
<Button
type="success"
onClick={() => {
Taro.navigateTo({
url: '/hjm/exam/exam'
})
}}
disabled={questions.length === 0}
>
</Button>
</div>
</div>
)
}
export default ExamSimple

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '开始考试'
})

608
src/hjm/exam/exam.tsx Normal file
View File

@@ -0,0 +1,608 @@
import {useEffect, useState} from "react";
import {ArrowLeft, ArrowRight} from '@nutui/icons-react-taro'
import {Button, Radio, Input, Progress} from '@nutui/nutui-react-taro'
import Taro from '@tarojs/taro'
import { TextArea } from '@nutui/nutui-react-taro'
import {pageHjmQuestions} from "@/api/hjm/hjmQuestions";
import {HjmQuestions} from "@/api/hjm/hjmQuestions/model";
import {addHjmExamLog} from "@/api/hjm/hjmExamLog";
// 用户答案接口
interface UserAnswer {
questionId: number;
answer: string;
isCorrect: boolean;
score: number;
}
// 考试状态枚举
enum ExamStatus {
NOT_STARTED = 'not_started',
IN_PROGRESS = 'in_progress',
COMPLETED = 'completed'
}
/**
* 考试系统
* @constructor
*/
const Exam = () => {
const [questions, setQuestions] = useState<HjmQuestions[]>([])
const [currentQuestionIndex, setCurrentQuestionIndex] = useState<number>(0)
const [userAnswers, setUserAnswers] = useState<UserAnswer[]>([])
const [currentAnswer, setCurrentAnswer] = useState<any>('')
const [examStatus, setExamStatus] = useState<ExamStatus>(ExamStatus.NOT_STARTED)
const [totalScore, setTotalScore] = useState<number>(0)
const [timeRemaining, setTimeRemaining] = useState<number>(600) // 10分钟考试时间
const [loading, setLoading] = useState<boolean>(false)
const [error, setError] = useState<string>('')
// 加载题目
const loadQuestions = () => {
setLoading(true)
setError('')
console.log('开始加载题目...')
pageHjmQuestions({}).then(data => {
console.log('API返回数据:', data)
const questionList = data?.list || []
// 限制为10道题目
const limitedQuestions = questionList.slice(0, 10)
setQuestions(limitedQuestions)
console.log('加载题目成功:', limitedQuestions)
// 调试:检查选择题的 choicesList 数据
limitedQuestions.forEach((question, index) => {
if (question.type === 0) {
console.log(`${index + 1}题 (选择题):`, {
question: question.question,
choicesList: question.choicesList,
correctAnswer: question.correctAnswer
});
}
});
if (limitedQuestions.length === 0) {
setError('没有找到题目数据')
}
}).catch(error => {
console.error('加载题目失败:', error)
setError('加载题目失败: ' + (error.message || '未知错误'))
Taro.showToast({
title: '加载题目失败',
icon: 'error'
})
}).finally(() => {
setLoading(false)
})
}
// 开始考试
const startExam = () => {
if (questions.length === 0) {
Taro.showToast({
title: '没有题目数据',
icon: 'error'
})
return
}
console.log('开始考试,题目数量:', questions.length)
setExamStatus(ExamStatus.IN_PROGRESS)
setCurrentQuestionIndex(0)
setUserAnswers([])
setCurrentAnswer('')
setTotalScore(0)
setTimeRemaining(600)
}
// 获取当前题目
const getCurrentQuestion = (): HjmQuestions | null => {
try {
if (questions && questions.length > 0 &&
currentQuestionIndex >= 0 &&
currentQuestionIndex < questions.length) {
return questions[currentQuestionIndex]
}
return null
} catch (error) {
console.error('获取当前题目时出错:', error)
return null
}
}
// 提交当前题目答案
const submitCurrentAnswer = () => {
const currentQuestion = getCurrentQuestion()
if (!currentQuestion || !currentAnswer.trim()) {
Taro.showToast({
title: '请选择或填写答案',
icon: 'error'
})
return
}
// 计算得分
const isCorrect = checkAnswer(currentQuestion, currentAnswer)
const score = isCorrect ? 10 : 0 // 每题10分
const userAnswer: UserAnswer = {
questionId: currentQuestion.id!,
answer: currentAnswer,
isCorrect,
score
}
const newUserAnswers = [...userAnswers, userAnswer]
setUserAnswers(newUserAnswers)
// 显示答题反馈
const currentScore = userAnswers.reduce((sum, answer) => sum + answer.score, 0) + score
Taro.showToast({
title: isCorrect ? `回答正确!+10分 (总分:${currentScore})` : `回答错误!(总分:${currentScore})`,
icon: isCorrect ? 'success' : 'none',
duration: 1500
})
// 清空当前答案
setCurrentAnswer('')
// 延迟跳转,让用户看到反馈
setTimeout(() => {
// 检查是否是最后一题
if (currentQuestionIndex === questions.length - 1) {
// 考试结束
finishExam(newUserAnswers)
} else {
// 下一题
setCurrentQuestionIndex(currentQuestionIndex + 1)
}
}, 1500)
}
// 检查答案是否正确
const checkAnswer = (question: HjmQuestions, answer: string): boolean => {
try {
if (question.type === 0) { // 选择题
// 使用 choicesList 来检查答案
if (question.choicesList && question.choicesList.length > 0) {
// 找到用户选择的选项索引
let selectedIndex = -1;
// 如果答案是字母格式A, B, C, D
if (answer.length === 1 && answer >= 'A' && answer <= 'D') {
selectedIndex = answer.charCodeAt(0) - 65; // A=0, B=1, C=2, D=3
}
// 如果答案是数字格式0, 1, 2, 3
else if (!isNaN(Number(answer))) {
selectedIndex = Number(answer);
}
// 检查索引是否有效
if (selectedIndex >= 0 && selectedIndex < question.choicesList.length) {
return question.choicesList[selectedIndex]?.isCorrect || false;
}
}
// 备用方案:使用 correctAnswer 字段
return answer === question.correctAnswer
} else if (question.type === 1) { // 填空题
// 简单的字符串匹配,可以根据需要改进
return answer.trim().toLowerCase() === question.correctAnswer?.trim().toLowerCase()
}
return false
} catch (error) {
console.error('检查答案时出错:', error)
return false
}
}
// 完成考试
const finishExam = (answers: UserAnswer[]) => {
const total = answers.reduce((sum, answer) => sum + answer.score, 0)
setTotalScore(total)
setExamStatus(ExamStatus.COMPLETED)
Taro.showToast({
title: `考试完成!得分:${total}`,
icon: 'success',
duration: 3000
})
// 考试得满分完成本月学习任务
addHjmExamLog({total: total.toString(), status: total == 100 ? 1 : 0, useTime: formatTime(600 - timeRemaining)}).then(() => {})
}
// 重新开始考试
const restartExam = () => {
setExamStatus(ExamStatus.NOT_STARTED)
setCurrentQuestionIndex(0)
setUserAnswers([])
setCurrentAnswer('')
setTotalScore(0)
setTimeRemaining(600)
}
// 计算进度百分比
const getProgress = (): number => {
if (questions.length === 0) return 0
return Math.round(((currentQuestionIndex + 1) / questions.length) * 100)
}
// 格式化时间显示
const formatTime = (seconds: number): string => {
const minutes = Math.floor(seconds / 60)
const remainingSeconds = seconds % 60
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`
}
// 获取成绩等级
const getGradeLevel = (score: number, totalScore: number): { level: string, color: string, description: string } => {
const percentage = (score / totalScore) * 100
if (percentage >= 90) {
return {level: '优秀', color: '#52c41a', description: '恭喜您!成绩优异!'}
} else if (percentage >= 80) {
return {level: '良好', color: '#1890ff', description: '成绩良好,继续努力!'}
} else if (percentage >= 70) {
return {level: '中等', color: '#faad14', description: '成绩中等,还有提升空间!'}
} else if (percentage >= 60) {
return {level: '及格', color: '#fa8c16', description: '刚好及格,需要加强学习!'}
} else {
return {level: '不及格', color: '#f5222d', description: '成绩不理想,建议重新学习!'}
}
}
// 获取答题统计
const getAnswerStats = () => {
const correctCount = userAnswers.filter(answer => answer.isCorrect).length
const wrongCount = userAnswers.length - correctCount
const totalTime = 600 - timeRemaining
return {
correctCount,
wrongCount,
totalTime: formatTime(totalTime),
accuracy: userAnswers.length > 0 ? Math.round((correctCount / userAnswers.length) * 100) : 0
}
}
useEffect(() => {
loadQuestions()
}, [])
// 倒计时效果
useEffect(() => {
let timer: NodeJS.Timeout
if (examStatus === ExamStatus.IN_PROGRESS && timeRemaining > 0) {
timer = setTimeout(() => {
setTimeRemaining(prev => prev - 1)
}, 1000)
} else if (timeRemaining === 0 && examStatus === ExamStatus.IN_PROGRESS) {
// 时间到,自动提交
finishExam(userAnswers)
}
return () => {
if (timer) {
clearTimeout(timer)
}
}
}, [examStatus, timeRemaining]) // 移除 userAnswers 依赖,避免无限循环
const currentQuestion = getCurrentQuestion()
return (
<div style={{padding: '16px', minHeight: '100vh', backgroundColor: '#f5f5f5'}}>
{/* 加载状态 */}
{loading && (
<div style={{backgroundColor: 'white', borderRadius: '8px', padding: '24px', textAlign: 'center'}}>
<h1 style={{fontSize: '20px', fontWeight: 'bold', marginBottom: '16px'}}>...</h1>
<div style={{color: '#666'}}></div>
</div>
)}
{/* 错误状态 */}
{error && !loading && (
<div style={{backgroundColor: 'white', borderRadius: '8px', padding: '24px', textAlign: 'center'}}>
<h1 style={{fontSize: '20px', fontWeight: 'bold', marginBottom: '16px', color: '#f5222d'}}></h1>
<div style={{marginBottom: '16px', color: '#666'}}>{error}</div>
<Button type="primary" onClick={loadQuestions}>
</Button>
</div>
)}
{/* 考试未开始 */}
{!loading && !error && examStatus === ExamStatus.NOT_STARTED && (
<div style={{backgroundColor: 'white', borderRadius: '8px', padding: '24px', textAlign: 'center'}}>
<h1 style={{fontSize: '20px', fontWeight: 'bold', marginBottom: '16px'}}>线</h1>
<div style={{marginBottom: '16px', color: '#666'}}>
<p>{questions.length} </p>
<p>10 </p>
<p>10 </p>
<p>{questions.length * 10} </p>
</div>
<Button
type="primary"
size="large"
onClick={startExam}
disabled={questions.length === 0}
>
</Button>
{questions.length === 0 && (
<div style={{marginTop: '8px', fontSize: '14px', color: '#f5222d'}}>
</div>
)}
</div>
)}
{/* 考试进行中 */}
{examStatus === ExamStatus.IN_PROGRESS && currentQuestion && (
<div>
{/* 顶部信息栏 */}
<div style={{backgroundColor: 'white', borderRadius: '8px', padding: '16px', marginBottom: '16px'}}>
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px'}}>
<span style={{fontSize: '14px', color: '#666'}}>
{currentQuestionIndex + 1} / {questions.length}
</span>
<span style={{fontSize: '14px', fontWeight: 'bold', color: '#f5222d'}}>
{formatTime(timeRemaining)}
</span>
</div>
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px'}}>
<span style={{fontSize: '14px', color: '#1890ff'}}>
{userAnswers.reduce((sum, answer) => sum + answer.score, 0)}
</span>
<span style={{fontSize: '14px', color: '#666'}}>
{userAnswers.length}
</span>
</div>
<Progress percent={getProgress()} color="#1890ff"/>
</div>
{/* 题目内容 */}
<div style={{backgroundColor: 'white', borderRadius: '8px', padding: '16px', marginBottom: '16px'}}>
<div style={{marginBottom: '16px'}}>
<h2 style={{fontSize: '18px', fontWeight: 'bold', marginBottom: '8px'}}>
{currentQuestionIndex + 1}. {currentQuestion.question}
</h2>
<div style={{fontSize: '14px', color: '#999'}}>
{currentQuestion.type === 0 ? '选择题' : currentQuestion.type === 1 ? '填空题' : '问答题'}
{currentQuestion.difficulty === 0 ? '简单' : currentQuestion.difficulty === 1 ? '中等' : '困难'}
</div>
</div>
{/* 选择题选项 */}
{currentQuestion.type === 0 && (
<div>
<Radio.Group
value={currentAnswer}
onChange={(value) => setCurrentAnswer(value)}
>
{currentQuestion.choicesList && currentQuestion.choicesList.length > 0 ? (
// 使用 choicesList 显示选项
currentQuestion.choicesList.map((choice, index) => {
const optionLabel = String.fromCharCode(65 + index); // A, B, C, D
return (
<div key={index} style={{marginBottom: '8px'}}>
<Radio value={optionLabel}>
{optionLabel}. {choice.content}
</Radio>
</div>
);
})
) : (
// 备用方案:使用传统的 choicesA, choicesB 等字段
<>
{currentQuestion.choicesA && (
<div style={{marginBottom: '8px'}}>
<Radio value="A">A. {currentQuestion.choicesA}</Radio>
</div>
)}
{currentQuestion.choicesB && (
<div style={{marginBottom: '8px'}}>
<Radio value="B">B. {currentQuestion.choicesB}</Radio>
</div>
)}
{currentQuestion.choicesC && (
<div style={{marginBottom: '8px'}}>
<Radio value="C">C. {currentQuestion.choicesC}</Radio>
</div>
)}
{currentQuestion.choicesD && (
<div style={{marginBottom: '8px'}}>
<Radio value="D">D. {currentQuestion.choicesD}</Radio>
</div>
)}
</>
)}
</Radio.Group>
</div>
)}
{/* 填空题输入 */}
{currentQuestion.type === 1 && (
<div style={{marginBottom: '16px'}}>
<Input
placeholder="请输入答案"
value={currentAnswer}
onChange={(value) => setCurrentAnswer(value)}
/>
</div>
)}
{/* 问答题输入 */}
{currentQuestion.type === 2 && (
<div style={{marginBottom: '16px'}}>
<TextArea
placeholder={'个性签名'}
value={currentAnswer}
onChange={(value) => setCurrentAnswer(value)}
/>
</div>
)}
</div>
{/* 操作按钮 */}
<div style={{backgroundColor: 'white', borderRadius: '8px', padding: '16px'}}>
<div style={{display: 'flex', justifyContent: 'space-between'}}>
<Button
type="default"
disabled={currentQuestionIndex === 0}
onClick={() => setCurrentQuestionIndex(currentQuestionIndex - 1)}
>
<ArrowLeft size={16}/>
</Button>
<Button
type="primary"
onClick={submitCurrentAnswer}
disabled={!currentAnswer.trim()}
>
{currentQuestionIndex === questions.length - 1 ? '提交试卷' : '下一题'}
<ArrowRight size={16}/>
</Button>
</div>
</div>
</div>
)}
{/* 考试完成 */}
{examStatus === ExamStatus.COMPLETED && (
<div style={{backgroundColor: 'white', borderRadius: '8px', padding: '24px', textAlign: 'center'}}>
<h1 style={{fontSize: '24px', fontWeight: 'bold', marginBottom: '16px', color: '#52c41a'}}>🎉
</h1>
{/* 成绩展示 */}
<div style={{marginBottom: '24px'}}>
<div style={{fontSize: '36px', fontWeight: 'bold', color: '#1890ff', marginBottom: '8px'}}>{totalScore}
</div>
<div style={{color: '#666', marginBottom: '8px'}}>{questions.length * 10} </div>
{/* 成绩等级 */}
{(() => {
const gradeInfo = getGradeLevel(totalScore, questions.length * 10)
return (
<div style={{marginBottom: '16px'}}>
<div
style={{
display: 'inline-block',
padding: '8px 16px',
borderRadius: '20px',
color: 'white',
fontWeight: 'bold',
marginBottom: '8px',
backgroundColor: gradeInfo.color
}}
>
{gradeInfo.level}
</div>
<div style={{fontSize: '14px', color: '#666'}}>{gradeInfo.description}</div>
</div>
)
})()}
</div>
{/* 答题统计 */}
<div style={{marginBottom: '24px', backgroundColor: '#f5f5f5', borderRadius: '8px', padding: '16px'}}>
<h3 style={{fontWeight: 'bold', marginBottom: '12px', textAlign: 'left'}}>📊 </h3>
{(() => {
const stats = getAnswerStats()
return (
<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', fontSize: '14px'}}>
<div style={{textAlign: 'center'}}>
<div style={{color: '#52c41a', fontWeight: 'bold', fontSize: '18px'}}>{stats.correctCount}</div>
<div style={{color: '#666'}}></div>
</div>
<div style={{textAlign: 'center'}}>
<div style={{color: '#f5222d', fontWeight: 'bold', fontSize: '18px'}}>{stats.wrongCount}</div>
<div style={{color: '#666'}}></div>
</div>
<div style={{textAlign: 'center'}}>
<div style={{color: '#1890ff', fontWeight: 'bold', fontSize: '18px'}}>{stats.accuracy}%</div>
<div style={{color: '#666'}}></div>
</div>
<div style={{textAlign: 'center'}}>
<div style={{color: '#722ed1', fontWeight: 'bold', fontSize: '18px'}}>{stats.totalTime}</div>
<div style={{color: '#666'}}></div>
</div>
</div>
)
})()}
</div>
{/* 答题详情 */}
<div style={{marginBottom: '24px', textAlign: 'left'}}>
<h3 style={{fontWeight: 'bold', marginBottom: '12px'}}>📝 </h3>
<div style={{maxHeight: '320px', overflowY: 'auto'}}>
{userAnswers.map((answer, index) => {
const question = questions.find(q => q.id === answer.questionId)
return (
<div
key={index}
style={{
padding: '12px',
borderRadius: '8px',
borderLeft: `4px solid ${answer.isCorrect ? '#52c41a' : '#f5222d'}`,
backgroundColor: answer.isCorrect ? '#f6ffed' : '#fff2f0',
marginBottom: '12px'
}}
>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: '8px'
}}>
<span style={{fontWeight: '500', fontSize: '14px'}}>{index + 1}</span>
<span style={{
fontSize: '14px',
fontWeight: 'bold',
color: answer.isCorrect ? '#52c41a' : '#f5222d'
}}>
{answer.isCorrect ? '✓ 正确' : '✗ 错误'} ({answer.score})
</span>
</div>
<div style={{fontSize: '14px', color: '#333', marginBottom: '8px'}}>
<strong></strong>{question?.question}
</div>
<div style={{fontSize: '14px'}}>
<span style={{color: '#666'}}></span>
<span style={{color: answer.isCorrect ? '#52c41a' : '#f5222d'}}>{answer.answer}</span>
</div>
{!answer.isCorrect && (
<div style={{fontSize: '14px', marginTop: '4px'}}>
<span style={{color: '#666'}}></span>
<span style={{color: '#52c41a'}}>
{(() => {
// 如果是选择题,显示正确选项的内容
if (question?.type === 0 && question.choicesList) {
const correctChoice = question.choicesList.find(choice => choice.isCorrect);
if (correctChoice) {
const correctIndex = question.choicesList.indexOf(correctChoice);
const correctLabel = String.fromCharCode(65 + correctIndex);
return `${correctLabel}. ${correctChoice.content}`;
}
}
// 备用方案:使用 correctAnswer 字段
return question?.correctAnswer || '未知';
})()}
</span>
</div>
)}
</div>
)
})}
</div>
</div>
<div style={{display: 'flex', flexDirection: 'column', gap: '8px'}}>
<Button type="primary" size="large" onClick={restartExam}>
</Button>
<Button type="default" size="large" onClick={() => Taro.navigateBack()}>
</Button>
</div>
</div>
)}
</div>
)
}
export default Exam

View File

@@ -163,8 +163,7 @@ const Location = () => {
{
points: points,
color: '#ff0000',
fillColor: '#ffcccc',
strokeWidth: 2
strokeWidth: 3
}
] : []}
onTap={() => {

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '观看视频'
})

69
src/hjm/video/video.tsx Normal file
View File

@@ -0,0 +1,69 @@
import {useEffect, useState} from "react";
import {Video} from '@nutui/nutui-react-taro'
import Taro from '@tarojs/taro'
import {useRouter} from '@tarojs/taro'
import {getCmsArticle} from "@/api/cms/cmsArticle";
import {CmsArticle} from "@/api/cms/cmsArticle/model";
import {View} from '@tarojs/components'
/**
* 文章终极列表
* @constructor
*/
const VideoForm = () => {
const {params} = useRouter();
const [item, setItem] = useState<CmsArticle>()
const [source, setSource] = useState({
src: '',
type: 'video/mp4',
})
const options = {
autoplay: true,
muted: true,
controls: true,
}
const play = (elm: any) => console.log('play', elm)
const pause = (elm: any) => console.log('pause', elm)
const playend = () => {
Taro.navigateTo({
url: '/hjm/exam/exam',
})
}
const reload = () => {
getCmsArticle(Number(params.id)).then(data => {
setItem(data)
Taro.setNavigationBarTitle({
title: `${data.title}`
})
console.log(item)
setSource({
src: `${data.pdfUrl || 'https://oss.wsdns.cn/20250605/9e88d2100425471288d4115cc48660ed.mp4'}`,
type: 'video/mp4',
})
})
}
useEffect(() => {
reload()
}, [])
return (
<div className={'px-3 mt-4 mb-10'}>
<div className={'flex flex-col justify-between items-center bg-white rounded-lg p-2'}>
<Video
source={source}
options={options}
onPlay={play}
onPause={pause}
onPlayEnd={playend}
style={{ height: '163px' }}
/>
</div>
<View className={'content text-gray-700 text-sm py-4 text-center'}>
</View>
</div>
)
}
export default VideoForm