@ -1,21 +1,21 @@
'use client'
import { useState } from 'react'
import { useState , useMemo } from 'react'
import Link from 'next/link'
import { Download , Plus , Search , ChevronLeft , ChevronRight , Monitor , Cpu , Wifi , Power } from 'lucide-react'
import { Download , Plus , Search , ChevronLeft , ChevronRight , Monitor , Cpu , Wifi , Power , Tag } from 'lucide-react'
const devicesData = [
{ id : 1 , sn : 'GD30-2025-000001' , model : 'GD-30 Supreme' , type : '高密度电法仪' , status : '已激活' , firmware : 'v2.3.5' , productionDate : '2025-01-15 14:30' , customer : '北京地质研究院' } ,
{ id : 2 , sn : 'GD30-2025-000002' , model : 'GD-30 Supreme' , type : '高密度电法仪' , status : '已激活' , firmware : 'v2.3.5' , productionDate : '2025-01-18 09:15' , customer : '中国地质大学' } ,
{ id : 3 , sn : 'GD30-2024-000056' , model : 'GD-30 Supreme' , type : '高密度电法仪' , status : '已出厂' , firmware : 'v2.3.4' , productionDate : '2024-12-20 16:00' , customer : '成都理工大学' } ,
{ id : 4 , sn : 'GT20-2025-000045' , model : 'GD-20' , type : '二维电法仪' , status : '已激活' , firmware : 'v1.8.5' , productionDate : '2025-02-10 11:20' , customer : '武汉地质调查中心' } ,
{ id : 5 , sn : 'GT20-2025-000046' , model : 'GD-20' , type : '二维电法仪' , status : '装配中' , firmware : 'v1.8.5' , productionDate : '2025-03-01 08:45' , customer : '-' } ,
{ id : 6 , sn : 'GD30-2024-000078' , model : 'GD-30 Supreme' , type : '高密度电法仪' , status : '已出厂' , firmware : 'v2.3.4' , productionDate : '2024-11-05 13:30' , customer : '长安大学' } ,
{ id : 7 , sn : 'GD10-2024-000033' , model : 'GD-10 Supreme' , type : '入门级电法仪' , status : '已激活' , firmware : 'v1.5.2' , productionDate : '2024-09-12 10:00' , customer : '河海大学' } ,
{ id : 8 , sn : 'GD30-2024-000089' , model : 'GD-30 Supreme' , type : '高密度电法仪' , status : '装配中' , firmware : 'v2.3.5' , productionDate : '2025-03-05 15:10' , customer : '-' } ,
{ id : 9 , sn : 'GT20-2025-000012' , model : 'GD-20' , type : '二维电法仪' , status : '已激活' , firmware : 'v1.8.5' , productionDate : '2025-01-22 09:30' , customer : '中南大学' } ,
{ id : 10 , sn : 'GD30-2024-000102' , model : 'GD-30 Supreme' , type : '高密度电法仪' , status : '已出厂' , firmware : 'v2.3.4' , productionDate : '2024-10-18 14:00' , customer : '吉林大学' } ,
{ id : 11 , sn : 'GD10-2024-000034' , model : 'GD-10 Supreme' , type : '入门级电法仪' , status : '装配中' , firmware : 'v1.5.2' , productionDate : '2025-03-08 11:45' , customer : '-' } ,
{ id : 12 , sn : 'GD30-2024-000145' , model : 'GD-30 Supreme' , type : '高密度电法仪' , status : '已激活' , firmware : 'v2.3.5' , productionDate : '2024-08-25 16:20' , customer : '同济大学' } ,
{ id : 1 , sn : 'GD30-2025-000001' , model : 'GD-30 Supreme' , type : '高密度电法仪' , status : '已激活' , firmware : 'v2.3.5' , productionDate : '2025-01-15 14:30' , customer : '北京地质研究院' , batch : 'B2025-01' } ,
{ id : 2 , sn : 'GD30-2025-000002' , model : 'GD-30 Supreme' , type : '高密度电法仪' , status : '已激活' , firmware : 'v2.3.5' , productionDate : '2025-01-18 09:15' , customer : '中国地质大学' , batch : 'B2025-01' } ,
{ id : 3 , sn : 'GD30-2024-000056' , model : 'GD-30 Supreme' , type : '高密度电法仪' , status : '已出厂' , firmware : 'v2.3.4' , productionDate : '2024-12-20 16:00' , customer : '成都理工大学' , batch : 'B2024-12' } ,
{ id : 4 , sn : 'GT20-2025-000045' , model : 'GD-20' , type : '二维电法仪' , status : '已激活' , firmware : 'v1.8.5' , productionDate : '2025-02-10 11:20' , customer : '武汉地质调查中心' , batch : 'B2025-02' } ,
{ id : 5 , sn : 'GT20-2025-000046' , model : 'GD-20' , type : '二维电法仪' , status : '装配中' , firmware : 'v1.8.5' , productionDate : '2025-03-01 08:45' , customer : '-' , batch : 'B2025-03' } ,
{ id : 6 , sn : 'GD30-2024-000078' , model : 'GD-30 Supreme' , type : '高密度电法仪' , status : '已出厂' , firmware : 'v2.3.4' , productionDate : '2024-11-05 13:30' , customer : '长安大学' , batch : 'B2024-11' } ,
{ id : 7 , sn : 'GD10-2024-000033' , model : 'GD-10 Supreme' , type : '入门级电法仪' , status : '已激活' , firmware : 'v1.5.2' , productionDate : '2024-09-12 10:00' , customer : '河海大学' , batch : 'B2024-09' } ,
{ id : 8 , sn : 'GD30-2024-000089' , model : 'GD-30 Supreme' , type : '高密度电法仪' , status : '装配中' , firmware : 'v2.3.5' , productionDate : '2025-03-05 15:10' , customer : '-' , batch : 'B2025-03' } ,
{ id : 9 , sn : 'GT20-2025-000012' , model : 'GD-20' , type : '二维电法仪' , status : '已激活' , firmware : 'v1.8.5' , productionDate : '2025-01-22 09:30' , customer : '中南大学' , batch : 'B2025-01' } ,
{ id : 10 , sn : 'GD30-2024-000102' , model : 'GD-30 Supreme' , type : '高密度电法仪' , status : '已出厂' , firmware : 'v2.3.4' , productionDate : '2024-10-18 14:00' , customer : '吉林大学' , batch : 'B2024-10' } ,
{ id : 11 , sn : 'GD10-2024-000034' , model : 'GD-10 Supreme' , type : '入门级电法仪' , status : '装配中' , firmware : 'v1.5.2' , productionDate : '2025-03-08 11:45' , customer : '-' , batch : 'B2025-03' } ,
{ id : 12 , sn : 'GD30-2024-000145' , model : 'GD-30 Supreme' , type : '高密度电法仪' , status : '已激活' , firmware : 'v2.3.5' , productionDate : '2024-08-25 16:20' , customer : '同济大学' , batch : 'B2024-08' } ,
]
const modelOptions = [ '全部' , 'GD-30 Supreme' , 'GD-20' , 'GD-10 Supreme' ]
@ -44,10 +44,23 @@ export default function DevicesPage() {
const [ filterStatus , setFilterStatus ] = useState ( '全部' )
const [ filterDate , setFilterDate ] = useState ( '' )
const [ searchText , setSearchText ] = useState ( '' )
const [ selectedBatch , setSelectedBatch ] = useState ( '全部' )
const [ currentPage , setCurrentPage ] = useState ( 1 )
const pageSize = 8
// 从数据中提取所有批次并按时间倒序排列,统计每个批次的设备数量
const batchList = useMemo ( ( ) = > {
const batchMap = new Map < string , number > ( )
devicesData . forEach ( d = > {
batchMap . set ( d . batch , ( batchMap . get ( d . batch ) || 0 ) + 1 )
} )
return Array . from ( batchMap . entries ( ) )
. sort ( ( a , b ) = > b [ 0 ] . localeCompare ( a [ 0 ] ) )
. map ( ( [ batch , count ] ) = > ( { batch , count } ) )
} , [ ] )
const filtered = devicesData . filter ( d = > {
if ( selectedBatch !== '全部' && d . batch !== selectedBatch ) return false
if ( filterModel !== '全部' && d . model !== filterModel ) return false
if ( filterStatus !== '全部' && d . status !== filterStatus ) return false
if ( filterDate && ! d . productionDate . startsWith ( filterDate ) ) return false
@ -57,6 +70,11 @@ export default function DevicesPage() {
const totalPages = Math . ceil ( filtered . length / pageSize )
const paged = filtered . slice ( ( currentPage - 1 ) * pageSize , currentPage * pageSize )
const handleBatchSelect = ( batch : string ) = > {
setSelectedBatch ( batch )
setCurrentPage ( 1 )
}
return (
< div style = { { padding : 24 } } >
{ /* Header */ }
@ -104,52 +122,106 @@ export default function DevicesPage() {
< / div >
< / div >
{ /* Device Cards */ }
< div style = { { display : 'grid' , gridTemplateColumns : 'repeat(4, 1fr)' , gap : 16 , marginBottom : 24 } } >
{ paged . map ( device = > (
< div key = { device . id } style = { { backgroundColor : '#fff' , borderRadius : 8 , boxShadow : '0 1px 2px rgba(0,0,0,0.05)' , overflow : 'hidden' } } >
< div style = { { padding : 20 } } >
< div style = { { display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' , marginBottom : 12 } } >
< span style = { { fontSize : 15 , fontWeight : 600 , color : 'rgba(0,0,0,0.85)' } } > { device . sn } < / span >
{ /* Main content: Batch sidebar + Device cards */ }
< div style = { { display : 'flex' , gap : 20 } } >
{ /* Batch sidebar */ }
< div style = { { width : 200 , flexShrink : 0 , backgroundColor : '#fff' , borderRadius : 8 , boxShadow : '0 1px 2px rgba(0,0,0,0.05)' , padding : 16 , alignSelf : 'flex-start' } } >
< div style = { { display : 'flex' , alignItems : 'center' , gap : 6 , marginBottom : 16 } } >
< Tag size = { 15 } style = { { color : '#4a7c59' } } / >
< span style = { { fontSize : 14 , fontWeight : 600 , color : 'rgba(0,0,0,0.85)' } } > 生 产 批 次 < / span >
< / div >
< div style = { { display : 'flex' , flexDirection : 'column' , gap : 4 } } >
< button
onClick = { ( ) = > handleBatchSelect ( '全部' ) }
style = { {
display : 'flex' , justifyContent : 'space-between' , alignItems : 'center' ,
padding : '8px 12px' , borderRadius : 6 , border : 'none' , cursor : 'pointer' , fontSize : 13 , textAlign : 'left' ,
backgroundColor : selectedBatch === '全部' ? '#eef5f0' : 'transparent' ,
color : selectedBatch === '全部' ? '#4a7c59' : 'rgba(0,0,0,0.65)' ,
fontWeight : selectedBatch === '全部' ? 600 : 400 ,
} }
>
< span > 全 部 批 次 < / span >
< span style = { {
fontSize : 12 , padding : '1px 8px' , borderRadius : 10 ,
backgroundColor : selectedBatch === '全部' ? '#4a7c59' : '#f0f0f0' ,
color : selectedBatch === '全部' ? '#fff' : 'rgba(0,0,0,0.45)' ,
} } > { devicesData . length } < / span >
< / button >
{ batchList . map ( ( { batch , count } ) = > (
< button
key = { batch }
onClick = { ( ) = > handleBatchSelect ( batch ) }
style = { {
display : 'flex' , justifyContent : 'space-between' , alignItems : 'center' ,
padding : '8px 12px' , borderRadius : 6 , border : 'none' , cursor : 'pointer' , fontSize : 13 , textAlign : 'left' ,
backgroundColor : selectedBatch === batch ? '#eef5f0' : 'transparent' ,
color : selectedBatch === batch ? '#4a7c59' : 'rgba(0,0,0,0.65)' ,
fontWeight : selectedBatch === batch ? 600 : 400 ,
} }
>
< span > { batch } < / span >
< span style = { {
fontSize : 12 , padding : '1px 8px' , borderRadius : 10 ,
backgroundColor : selectedBatch === batch ? '#4a7c59' : '#f0f0f0' ,
color : selectedBatch === batch ? '#fff' : 'rgba(0,0,0,0.45)' ,
} } > { count } < / span >
< / button >
) ) }
< / div >
< / div >
{ /* Right content area */ }
< div style = { { flex : 1 , minWidth : 0 } } >
{ /* Device Cards */ }
< div style = { { display : 'grid' , gridTemplateColumns : 'repeat(3, 1fr)' , gap : 16 , marginBottom : 24 } } >
{ paged . map ( device = > (
< div key = { device . id } style = { { backgroundColor : '#fff' , borderRadius : 8 , boxShadow : '0 1px 2px rgba(0,0,0,0.05)' , overflow : 'hidden' } } >
< div style = { { padding : 20 } } >
< div style = { { display : 'flex' , alignItems : 'center' , justifyContent : 'space-between' , marginBottom : 12 } } >
< span style = { { fontSize : 15 , fontWeight : 600 , color : 'rgba(0,0,0,0.85)' } } > { device . sn } < / span >
< / div >
< div style = { { display : 'flex' , alignItems : 'center' , gap : 6 , marginBottom : 4 } } >
{ getStatusIcon ( device . status ) }
< span style = { { . . . getStatusStyle ( device . status ) , padding : '2px 8px' , borderRadius : 4 , fontSize : 12 } } > { device . status } < / span >
< span style = { { fontSize : 11 , padding : '2px 8px' , borderRadius : 4 , backgroundColor : '#f5f5f5' , color : 'rgba(0,0,0,0.45)' , border : '1px solid #e8e8e8' } } > { device . batch } < / span >
< / div >
< div style = { { fontSize : 13 , color : 'rgba(0,0,0,0.65)' , lineHeight : 2 } } >
< div > 型 号 : { device . model } { device . type } < / div >
< div > 主 机 版 本 : { device . firmware } < / div >
< div > 生 产 日 期 : { device . productionDate } < / div >
< / div >
< / div >
< div style = { { display : 'flex' , borderTop : '1px solid #F0F0F0' } } >
< Link href = { ` /devices/ ${ device . sn } ` } style = { { flex : 1 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , gap : 4 , padding : '10px 0' , fontSize : 13 , color : '#4a7c59' , textDecoration : 'none' , cursor : 'pointer' } } >
详 情
< / Link >
{ device . status === '已激活' && (
< >
< div style = { { width : 1 , backgroundColor : '#F0F0F0' } } / >
< button style = { { flex : 1 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , gap : 4 , padding : '10px 0' , fontSize : 13 , color : '#FF4D4F' , border : 'none' , background : 'none' , cursor : 'pointer' } } >
< Power size = { 13 } / > 下 线
< / button >
< / >
) }
< / div >
< / div >
< div style = { { display : 'flex' , alignItems : 'center' , gap : 6 , marginBottom : 10 } } >
{ getStatusIcon ( device . status ) }
< span style = { { . . . getStatusStyle ( device . status ) , padding : '2px 8px' , borderRadius : 4 , fontSize : 12 } } > { device . status } < / span >
< / div >
< div style = { { fontSize : 13 , color : 'rgba(0,0,0,0.65)' , lineHeight : 2 } } >
< div > 型 号 : { device . model } { device . type } < / div >
< div > 主 机 版 本 : { device . firmware } < / div >
< div > 生 产 日 期 : { device . productionDate } < / div >
< / div >
< / div >
< div style = { { display : 'flex' , borderTop : '1px solid #F0F0F0' } } >
< Link href = { ` /devices/ ${ device . sn } ` } style = { { flex : 1 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , gap : 4 , padding : '10px 0' , fontSize : 13 , color : '#4a7c59' , textDecoration : 'none' , cursor : 'pointer' } } >
详 情
< / Link >
{ device . status === '已激活' && (
< >
< div style = { { width : 1 , backgroundColor : '#F0F0F0' } } / >
< button style = { { flex : 1 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , gap : 4 , padding : '10px 0' , fontSize : 13 , color : '#FF4D4F' , border : 'none' , background : 'none' , cursor : 'pointer' } } >
< Power size = { 13 } / > 下 线
< / button >
< / >
) }
) ) }
< / div >
{ /* Pagination */ }
< div style = { { display : 'flex' , justifyContent : 'space-between' , alignItems : 'center' } } >
< span style = { { fontSize : 14 , color : 'rgba(0,0,0,0.45)' } } >
显 示 { filtered . length > 0 ? ( currentPage - 1 ) * pageSize + 1 : 0 } - { Math . min ( currentPage * pageSize , filtered . length ) } / 共 { filtered . length } 台
< / span >
< div style = { { display : 'flex' , alignItems : 'center' , gap : 4 } } >
< button onClick = { ( ) = > setCurrentPage ( p = > Math . max ( 1 , p - 1 ) ) } disabled = { currentPage === 1 } style = { { padding : '4px 8px' , border : '1px solid #D9D9D9' , borderRadius : 4 , backgroundColor : '#fff' , cursor : currentPage === 1 ? 'not-allowed' : 'pointer' , opacity : currentPage === 1 ? 0.4 : 1 } } > < ChevronLeft size = { 16 } / > < / button >
{ Array . from ( { length : totalPages } , ( _ , i ) = > (
< button key = { i } onClick = { ( ) = > setCurrentPage ( i + 1 ) } style = { { width : 32 , height : 32 , borderRadius : 4 , fontSize : 14 , border : currentPage === i + 1 ? '1px solid #4a7c59' : '1px solid #D9D9D9' , color : currentPage === i + 1 ? '#4a7c59' : 'rgba(0,0,0,0.65)' , backgroundColor : currentPage === i + 1 ? '#eef5f0' : '#fff' , cursor : 'pointer' } } > { i + 1 } < / button >
) ) }
< button onClick = { ( ) = > setCurrentPage ( p = > Math . min ( totalPages , p + 1 ) ) } disabled = { currentPage === totalPages || totalPages === 0 } style = { { padding : '4px 8px' , border : '1px solid #D9D9D9' , borderRadius : 4 , backgroundColor : '#fff' , cursor : currentPage === totalPages || totalPages === 0 ? 'not-allowed' : 'pointer' , opacity : currentPage === totalPages || totalPages === 0 ? 0.4 : 1 } } > < ChevronRight size = { 16 } / > < / button >
< / div >
< / div >
) ) }
< / div >
{ /* Pagination */ }
< div style = { { display : 'flex' , justifyContent : 'space-between' , alignItems : 'center' } } >
< span style = { { fontSize : 14 , color : 'rgba(0,0,0,0.45)' } } >
显 示 { ( currentPage - 1 ) * pageSize + 1 } - { Math . min ( currentPage * pageSize , filtered . length ) } / 共 { filtered . length } 台
< / span >
< div style = { { display : 'flex' , alignItems : 'center' , gap : 4 } } >
< button onClick = { ( ) = > setCurrentPage ( p = > Math . max ( 1 , p - 1 ) ) } disabled = { currentPage === 1 } style = { { padding : '4px 8px' , border : '1px solid #D9D9D9' , borderRadius : 4 , backgroundColor : '#fff' , cursor : currentPage === 1 ? 'not-allowed' : 'pointer' , opacity : currentPage === 1 ? 0.4 : 1 } } > < ChevronLeft size = { 16 } / > < / button >
{ Array . from ( { length : totalPages } , ( _ , i ) = > (
< button key = { i } onClick = { ( ) = > setCurrentPage ( i + 1 ) } style = { { width : 32 , height : 32 , borderRadius : 4 , fontSize : 14 , border : currentPage === i + 1 ? '1px solid #4a7c59' : '1px solid #D9D9D9' , color : currentPage === i + 1 ? '#4a7c59' : 'rgba(0,0,0,0.65)' , backgroundColor : currentPage === i + 1 ? '#eef5f0' : '#fff' , cursor : 'pointer' } } > { i + 1 } < / button >
) ) }
< button onClick = { ( ) = > setCurrentPage ( p = > Math . min ( totalPages , p + 1 ) ) } disabled = { currentPage === totalPages } style = { { padding : '4px 8px' , border : '1px solid #D9D9D9' , borderRadius : 4 , backgroundColor : '#fff' , cursor : currentPage === totalPages ? 'not-allowed' : 'pointer' , opacity : currentPage === totalPages ? 0.4 : 1 } } > < ChevronRight size = { 16 } / > < / button >
< / div >
< / div >
< / div >