@@ -63,8 +63,6 @@ const tabs = [
{ key : 'smart' , label : '智能装备' , filter : ( item ) = > [ 'CLEANING_DEVICE' , 'ROBOT' , 'DRONE' ] . includes ( item . type ) } ,
] ;
export default function DeviceManagementPage() {
const [ activeTab , setActiveTab ] = useState ( 'all' ) ;
const { modal } = App . useApp ( ) ;
@@ -80,6 +78,15 @@ export default function DeviceManagementPage() {
const [ bindForm ] = Form . useForm ( ) ;
const [ selectedDroneKeys , setSelectedDroneKeys ] = useState ( [ ] ) ;
// 无人机分页 + 筛选
const [ droneParams , setDroneParams ] = useState ( {
pageNum : 1 ,
pageSize : 10 ,
gateway_sn : '' ,
status : "" ,
} ) ;
const [ droneTotal , setDroneTotal ] = useState ( 0 ) ;
// 智能装备相关状态
const [ smartList , setSmartList ] = useState ( [ ] ) ;
const [ selectedSmartKeys , setSelectedSmartKeys ] = useState ( [ ] ) ;
@@ -91,23 +98,24 @@ export default function DeviceManagementPage() {
} ) ;
const [ smartTotal , setSmartTotal ] = useState ( 0 ) ;
// 下拉数据状态
const [ orgOptions , setOrgOptions ] = useState ( [ ] ) ;
const [ siteOptions , setSiteOptions ] = useState ( [ ] ) ;
const [ userOptions , setUserOptions ] = useState ( [ ] ) ;
// ✅ 新增: 全局匹配用的全量数据状态
// 全局匹配用的全量数据状态
const [ allOrgOptions , setAllOrgOptions ] = useState ( [ ] ) ;
const [ allSiteOptions , setAllSiteOptions ] = useState ( [ ] ) ;
const [ allUserOptions , setAllUserOptions ] = useState ( [ ] ) ;
// 获取无人机列表(带分页 + 筛选)
const fetchDroneList = async ( ) = > {
setLoading ( true ) ;
try {
const res : any = await getPlaneDeviceList ( ) ;
const res = await getPlaneDeviceList ( droneParams ) ;
if ( res . code === 200 ) {
setDroneList ( res . data ? . detail || res . data || [ ] ) ;
setDroneList ( res . rows || [ ] ) ;
setDroneTotal ( res . total || res . rows ? . length || 0 ) ;
}
} catch ( err ) {
utils . message . error ( '获取无人机列表失败' ) ;
@@ -119,11 +127,10 @@ export default function DeviceManagementPage() {
const fetchSmartList = async ( ) = > {
setLoading ( true ) ;
try {
const res : any = await getSmartDeviceList ( smartParams ) ;
const res = await getSmartDeviceList ( smartParams ) ;
if ( res . code === 200 ) {
setSmartList ( res . rows || [ ] ) ;
setSmartTotal ( res . total || res . rows . length ) ; // 👈 关键:把总数存起来
setSmartTotal ( res . total || res . rows . length ) ;
}
} catch ( err ) {
utils . message . error ( '获取智能装备列表失败' ) ;
@@ -135,7 +142,7 @@ export default function DeviceManagementPage() {
// 获取组织列表
const fetchOrgs = async ( ) = > {
try {
const res : any = await getOrgList ( { } ) ;
const res = await getOrgList ( { } ) ;
if ( res . code === 200 ) {
setOrgOptions ( res . rows || [ ] ) ;
}
@@ -148,7 +155,7 @@ export default function DeviceManagementPage() {
const fetchSites = async ( orgId ) = > {
setSiteOptions ( [ ] ) ;
try {
const res : any = await getSiteByOrgId ( { id : orgId } ) ;
const res = await getSiteByOrgId ( { id : orgId } ) ;
if ( res . code === 200 ) {
setSiteOptions ( res . data || [ ] ) ;
}
@@ -160,7 +167,7 @@ export default function DeviceManagementPage() {
// 获取用户列表
const fetchUsers = async ( siteId ) = > {
try {
const res : any = await getUser ( { siteId , pageNum : 1 , pageSize : 1000 } ) ;
const res = await getUser ( { siteId , pageNum : 1 , pageSize : 1000 } ) ;
if ( res . code === 200 ) {
setUserOptions ( res . rows || [ ] ) ;
}
@@ -169,15 +176,14 @@ export default function DeviceManagementPage() {
}
} ;
// ✅ 新增: 初始化时获取全量数据用于列表匹配
// 初始化时获取全量数据用于列表匹配
const fetchAllDataForMapping = async ( ) = > {
try {
const [ orgRes , siteRes , userRes ] : any = await Promise . all ( [
const [ orgRes , siteRes , userRes ] = await Promise . all ( [
getOrgList ( { pageNum : 1 , pageSize : 1000 } ) ,
getStationList ( { pageNum : 1 , pageSize : 1000 } ) ,
getUser ( { pageNum : 1 , pageSize : 10000 } )
] ) ;
if ( orgRes . code === 200 ) setAllOrgOptions ( orgRes . rows || [ ] ) ;
if ( siteRes . code === 200 ) setAllSiteOptions ( siteRes . rows || [ ] ) ;
if ( userRes . code === 200 ) setAllUserOptions ( userRes . rows || [ ] ) ;
@@ -189,20 +195,24 @@ export default function DeviceManagementPage() {
useEffect ( ( ) = > {
if ( activeTab === 'drone' || activeTab === 'smart' ) {
fetchAllDataForMapping ( ) ;
if ( activeTab === 'drone' ) fetchDroneList ( ) ;
if ( activeTab === 'smart' ) fetchSmartList ( ) ;
}
} , [ activeTab ] ) ;
useEffect ( ( ) = > {
if ( activeTab === 'drone' ) fetchDroneList ( ) ;
} , [ activeTab , droneParams ] ) ;
useEffect ( ( ) = > {
if ( activeTab === 'smart' ) fetchSmartList ( ) ;
} , [ activeTab , smartParams ] ) ;
// 打开绑定弹窗
const openBindModal = async ( ) = > {
if ( selectedDroneKeys . length === 0 ) {
return utils . message . warning ( '请先在列表中选中需要绑定的无人机' ) ;
}
setBindModalVisible ( true ) ;
await fetchOrgs ( ) ;
// ✅ 修改:不再回显,仅填入已选 SN
bindForm . setFieldsValue ( {
sns : selectedDroneKeys.join ( ', ' ) ,
orgId : undefined ,
@@ -218,10 +228,8 @@ export default function DeviceManagementPage() {
if ( selectedSmartKeys . length === 0 ) {
return utils . message . warning ( '请先在列表中选中需要绑定的智能装备' ) ;
}
setBindSmartModalVisible ( true ) ;
await fetchOrgs ( ) ;
bindSmartForm . setFieldsValue ( {
deviceIds : selectedSmartKeys.join ( ', ' ) ,
orgId : undefined ,
@@ -240,7 +248,7 @@ export default function DeviceManagementPage() {
userId : values.userId ,
sns : values.sns.split ( ',' ) . map ( s = > s . trim ( ) ) ,
} ;
const res : any = await bindDrone ( payload ) ;
const res = await bindDrone ( payload ) ;
if ( res . code === 200 && res . data == true ) {
utils . message . success ( '绑定成功' ) ;
setBindModalVisible ( false ) ;
@@ -263,7 +271,7 @@ export default function DeviceManagementPage() {
userId : values.userId ,
deviceIds : values.deviceIds.split ( ',' ) . map ( s = > s . trim ( ) ) ,
} ;
const res : any = await bindSmartDevice ( payload ) ;
const res = await bindSmartDevice ( payload ) ;
if ( res . code === 200 ) {
utils . message . success ( '绑定成功' ) ;
setBindSmartModalVisible ( false ) ;
@@ -282,9 +290,7 @@ export default function DeviceManagementPage() {
if ( validIds . length === 0 ) {
return utils . message . warning ( '未选择有效设备,无法解绑' ) ;
}
const typeText = { 1 : '人员' , 2 : '场站' , 3 : '组织' } [ type ] ;
modal . confirm ( {
title : '确认解绑' ,
content : ` 确定要为选中的智能装备清除 ${ typeText } 绑定吗? ` ,
@@ -292,7 +298,7 @@ export default function DeviceManagementPage() {
cancelText : '取消' ,
onOk : async ( ) = > {
try {
const res : any = await unbindSmartDevice ( { deviceIds : validIds , type } ) ;
const res = await unbindSmartDevice ( { deviceIds : validIds , type } ) ;
if ( res . code === 200 ) {
utils . message . success ( '解绑成功' ) ;
fetchSmartList ( ) ;
@@ -311,27 +317,21 @@ export default function DeviceManagementPage() {
{ title : '设备名称' , width : 150 , dataIndex : 'deviceAlias' , key : 'name' } ,
{ title : '设备码' , width : 280 , dataIndex : 'serialNumber' , key : 'sn' } ,
{
title : '所属组织' ,
width : 150 ,
key : 'orgName' ,
title : '所属组织' , width : 150 , key : 'orgName' ,
render : ( _ , record ) = > {
const org = allOrgOptions . find ( o = > ( o . orgId || o . id ) == record . orgId ) ;
return org ? ( org . orgName || org . name ) : < Text type = "secondary" > 未 绑 定 < / Text > ;
} ,
} ,
{
title : '所属场站' ,
width : 150 ,
key : 'siteName' ,
title : '所属场站' , width : 150 , key : 'siteName' ,
render : ( _ , record ) = > {
const site = allSiteOptions . find ( s = > ( s . siteId || s . id ) == record . siteId ) ;
return site ? ( site . siteName || site . name ) : < Text type = "secondary" > 未 绑 定 < / Text > ;
} ,
} ,
{
title : '负责人' ,
width : 150 ,
key : 'userName' ,
title : '负责人' , width : 150 , key : 'userName' ,
render : ( _ , record ) = > {
const user = allUserOptions . find ( u = > u . userId == record . userId ) ;
return user ? ( user . nickName || user . userName ) : < Text type = "secondary" > 未 绑 定 < / Text > ;
@@ -339,10 +339,7 @@ export default function DeviceManagementPage() {
} ,
{ title : '在线状态' , width : 120 , dataIndex : 'onlineStatus' , key : 'online' , render : s = > < Tag color = { s === 'ONLINE' || s === 1 ? 'green' : 'red' } > { s === 'ONLINE' || s === 1 ? '在线' : '离线' } < / Tag > } ,
{
title : '操作' ,
width : 280 ,
key : 'action' ,
fixed : 'right' ,
title : '操作' , width : 280 , key : 'action' , fixed : 'right' ,
render : ( _ , record ) = > (
< Space >
< Tooltip title = "清除人员绑定" >
@@ -360,22 +357,13 @@ export default function DeviceManagementPage() {
] ;
const handleUnbind = ( sns , type ) = > {
// ✅ 关键: 过滤掉空、null、undefined、纯空格的项
const validSns = ( sns || [ ] ) . filter ( item = >
item !== null &&
item !== undefined &&
item !== '' &&
item . trim ( ) !== ''
item !== null && item !== undefined && item !== '' && item . trim ( ) !== ''
) ;
// 如果过滤完没有有效SN, 直接提示
if ( validSns . length === 0 ) {
return utils . message . warning ( '未选择有效设备,无法解绑' ) ;
}
const typeText = { 1 : '人员' , 2 : '场站' , 3 : '组织' } [ type ] ;
modal . confirm ( {
title : '确认解绑' ,
content : ` 确定要为选中的无人机清除 ${ typeText } 绑定吗? ` ,
@@ -383,8 +371,7 @@ export default function DeviceManagementPage() {
cancelText : '取消' ,
onOk : async ( ) = > {
try {
// ✅ 传给接口的是过滤后的有效数组
const res : any = await unbindDrone ( { sns : validSns , type } ) ;
const res = await unbindDrone ( { sns : validSns , type } ) ;
if ( res . code === 200 ) {
utils . message . success ( '解绑成功' ) ;
fetchDroneList ( ) ;
@@ -404,40 +391,32 @@ export default function DeviceManagementPage() {
{ title : 'SN码' , width : 150 , dataIndex : 'device_sn' , key : 'sn' } ,
{ title : '机场SN' , width : 150 , dataIndex : 'gateway_sn' , key : 'gsn' } ,
{
title : '所属组织' ,
key : 'orgName' ,
width : 150 ,
title : '所属组织' , width : 150 , key : 'orgName' ,
render : ( _ , record ) = > {
const org = allOrgOptions . find ( o = > ( o . orgId || o . id ) == record . orgId ) ;
return org ? ( org . orgName || org . name ) : < Text type = "secondary" > 未 绑 定 < / Text > ;
} ,
} ,
{
title : '所属场站' ,
key : 'siteName' ,
width : 150 ,
title : '所属场站' , width : 150 , key : 'siteName' ,
render : ( _ , record ) = > {
const site = allSiteOptions . find ( s = > ( s . siteId || s . id ) == record . siteId ) ;
return site ? ( site . siteName || site . name ) : < Text type = "secondary" > 未 绑 定 < / Text > ;
} ,
} ,
{
title : '负责人' ,
key : 'userName' ,
width : 150 ,
title : '负责人' , width : 150 , key : 'userName' ,
render : ( _ , record ) = > {
const user = allUserOptions . find ( u = > u . userId == record . userId ) ;
return user ? ( user . nickName || user . userName ) : < Text type = "secondary" > 未 绑 定 < / Text > ;
} ,
} ,
{
title : '状态' , width : 150 ,
dataIndex : 'onlineStatus' , key : 'status' , render : s = > < Tag color = { s === 1 ? 'green' : 'default' } > { s === 1 ? '在线' : '离线' } < / Tag >
title : '状态' , width : 150 , dataIndex : 'onlineStatus' , key : 'status' ,
render : s = > < Tag color = { s === 1 ? 'green' : 'default' } > { s === 1 ? '在线' : '离线' } < / Tag >
} ,
{
title : '操作' ,
key : 'action' ,
fixed : 'right' ,
title : '操作' , key : 'action' , fixed : 'right' ,
render : ( _ , record ) = > (
< Space >
< Tooltip title = "人员解绑" >
@@ -463,17 +442,13 @@ export default function DeviceManagementPage() {
{ title : '设备名称' , dataIndex : 'name' , key : 'name' } ,
{ title : '类型' , dataIndex : 'type' , key : 'type' } ,
{
title : '在线' ,
dataIndex : 'online' ,
key : 'online' ,
title : '在线' , dataIndex : 'online' , key : 'online' ,
render : ( text ) = > (
< Tag color = { text === 'ONLINE' ? 'green' : 'red' } > { text } < / Tag >
) ,
} ,
{
title : '运行' ,
dataIndex : 'status' ,
key : 'status' ,
title : '运行' , dataIndex : 'status' , key : 'status' ,
render : ( text ) = > {
const colorMap = {
NORMAL : 'green' ,
@@ -485,8 +460,7 @@ export default function DeviceManagementPage() {
} ,
} ,
{
title : '操作' ,
key : 'action' ,
title : '操作' , key : 'action' ,
render : ( _ , record ) = > (
< Space >
< Button type = "link" icon = { < EditOutlined / > } onClick = { ( ) = > handleEdit ( record ) } > 编 辑 < / Button >
@@ -549,13 +523,10 @@ export default function DeviceManagementPage() {
< Tabs.TabPane key = "all" tab = "设备管理" / >
< Tabs.TabPane key = "drone" tab = "无人机管理" / >
< Tabs.TabPane key = "smart" tab = "智能装备" / >
< Tabs.TabPane key = "camera" tab = "摄像头管理" / >
< Tabs.TabPane key = "serialGenerate" tab = "机器人生成" / >
< / Tabs >
{ /* ====================== 判断显示内容 ====================== */ }
{ activeTab === 'serialGenerate' ? (
< SerialNumGenerate / >
) : activeTab === 'smart' ? (
@@ -569,17 +540,10 @@ export default function DeviceManagementPage() {
< / Space >
< / div >
< div style = { {
height : 'calc(100vh - 220px)' ,
overflowY : 'auto' ,
paddingRight : '4px' ,
} } >
< div style = { { height : 'calc(100vh - 220px)' , overflowY : 'auto' , paddingRight : '4px' } } >
< Table
rowSelection = { {
selectedRowKeys : selectedSmartKeys ,
onChange : setSelectedSmartKeys ,
} }
rowKey = { ( record ) = > record . deviceId || record . id }
rowSelection = { { selectedRowKeys : selectedSmartKeys , onChange : setSelectedSmartKeys } }
rowKey = { ( record ) = > record . serialNumber }
columns = { smartColumns }
dataSource = { smartList }
loading = { loading }
@@ -587,17 +551,13 @@ export default function DeviceManagementPage() {
current : smartParams.pageNum ,
pageSize : smartParams.pageSize ,
total : smartTotal ,
showSizeChanger : true ,
showTotal : ( t ) = > ` 共 ${ t } 条 ` ,
onChange : ( page , pageSize ) = > {
setSmartParams ( { pageNum : page , pageSize } ) ;
} ,
} }
scroll = { {
y : 'auto' ,
x : '700px'
} }
scroll = { { y : 'auto' , x : '700px' } }
/ >
< / div >
@@ -617,7 +577,6 @@ export default function DeviceManagementPage() {
< Form.Item label = "已选装备 ID" name = "deviceIds" rules = { [ { required : true , message : '请选择装备' } ] } >
< Input.TextArea placeholder = "例如: ID001, ID002" rows = { 3 } disabled / >
< / Form.Item >
< Form.Item label = "所属组织" name = "orgId" rules = { [ { required : true , message : '请选择组织' } ] } >
< Select
placeholder = "请选择组织"
@@ -633,7 +592,6 @@ export default function DeviceManagementPage() {
) ) }
< / Select >
< / Form.Item >
< Form.Item label = "所属场站" name = "siteId" >
< Select
placeholder = "请先选择组织"
@@ -649,7 +607,6 @@ export default function DeviceManagementPage() {
) ) }
< / Select >
< / Form.Item >
< Form.Item label = "负责人" name = "userId" >
< Select
placeholder = "请先选择场站"
@@ -665,37 +622,50 @@ export default function DeviceManagementPage() {
< / >
) : activeTab === 'drone' ? (
< >
< div style = { { marginBottom : 16 } } >
< Space >
< Button type = "primary" icon = { < LinkOutlined / > } onClick = { openBindModal } > 绑 定 无 人 机 < / Button >
< Button disabled = { selectedDroneKeys . length === 0 } onClick = { ( ) = > handleUnbind ( selectedDroneKeys , 1 ) } > 批 量 解 绑 ( 用 户 ) < / Button >
< Button disabled = { selectedDroneKeys . length === 0 } onClick = { ( ) = > handleUnbind ( selectedDroneKeys , 2 ) } > 批 量 解 绑 ( 场 站 ) < / Button >
< Button disabled = { selectedDroneKeys . length === 0 } onClick = { ( ) = > handleUnbind ( selectedDroneKeys , 3 ) } > 批 量 解 绑 ( 组 ) < / Button >
< / Space >
{ /* 无人机搜索栏 */ }
< div style = { { marginBottom : 16 , display : 'flex' , gap : 10 , flexWrap : 'wrap' } } >
< Input
placeholder = "机场SN"
style = { { width : 200 } }
value = { droneParams . gateway_sn }
onChange = { ( e ) = > setDroneParams ( { . . . droneParams , gateway_sn : e.target.value } ) }
/ >
< Select
placeholder = "状态"
style = { { width : 120 } }
value = { droneParams . status }
allowClear
onChange = { ( val ) = > setDroneParams ( { . . . droneParams , status : val } ) }
>
< Select.Option value = "1" > 在 线 < / Select.Option >
< Select.Option value = "2" > 离 线 < / Select.Option >
< / Select >
< Button icon = { < ReloadOutlined / > } onClick = { ( ) = > setDroneParams ( { pageNum : 1 , pageSize : 10 , gateway_sn : '' , status : '' } ) } > 重 置 < / Button >
< Button type = "primary" icon = { < LinkOutlined / > } onClick = { openBindModal } > 绑 定 无 人 机 < / Button >
< Button disabled = { selectedDroneKeys . length === 0 } onClick = { ( ) = > handleUnbind ( selectedDroneKeys , 1 ) } > 批 量 解 绑 ( 用 户 ) < / Button >
< Button disabled = { selectedDroneKeys . length === 0 } onClick = { ( ) = > handleUnbind ( selectedDroneKeys , 2 ) } > 批 量 解 绑 ( 场 站 ) < / Button >
< Button disabled = { selectedDroneKeys . length === 0 } onClick = { ( ) = > handleUnbind ( selectedDroneKeys , 3 ) } > 批 量 解 绑 ( 组 ) < / Button >
< / div >
< div style = { {
height : 'calc(100vh - 220px)' ,
overflowY : 'auto' ,
paddingRight : '4px' ,
} } >
< div style = { { height : 'calc(100vh - 220px)' , overflowY : 'auto' , paddingRight : '4px' } } >
< Table
rowSelection = { {
selectedRowKeys : selectedDroneKeys ,
onChange : setSelectedDroneKeys ,
} }
rowSelection = { { selectedRowKeys : selectedDroneKeys , onChange : setSelectedDroneKeys } }
rowKey = "gateway_sn"
columns = { droneColumns }
dataSource = { droneList }
loading = { loading }
pagination = { {
current : droneParams.pageNum ,
pageSize : droneParams.pageSize ,
total : droneTotal ,
showSizeChanger : true ,
showTotal : t = > ` 共 ${ t } 条 `
} }
scroll = { {
y : 'auto ' , // 最大高度,超过才滚动
x : '700px' /* 横向最小宽度,不够自动出滚动条 */
showTotal : t = > ` 共 ${ t } 条 ` ,
onChange : ( page , pageSize ) = > {
setDroneParams ( { . . . droneParams , pageNum : page , pageSize } ) ;
} ,
} }
scroll = { { y : 'auto' , x : '700px' } }
/ >
< / div >
@@ -706,7 +676,7 @@ export default function DeviceManagementPage() {
setBindModalVisible ( false ) ;
setSiteOptions ( [ ] ) ;
setUserOptions ( [ ] ) ;
bindForm . resetFields ( ) ; // 加一句重置,避免缓存
bindForm . resetFields ( ) ;
} }
onOk = { ( ) = > bindForm . submit ( ) }
width = { 500 }
@@ -715,8 +685,6 @@ export default function DeviceManagementPage() {
< Form.Item label = "已选无人机 SN" name = "sns" rules = { [ { required : true , message : '请输入 SN' } ] } >
< Input.TextArea placeholder = "例如: SN001, SN002" rows = { 3 } disabled / >
< / Form.Item >
{ /* ========== 修复:组织 ========== */ }
< Form.Item label = "所属组织" name = "orgId" rules = { [ { required : true , message : '请选择组织' } ] } >
< Select
allowClear
@@ -729,17 +697,12 @@ export default function DeviceManagementPage() {
} }
>
{ orgOptions . map ( org = > (
< Select.Option
key = { ` org- ${ org . id } ` }
value = { org . id }
>
< Select.Option key = { ` org- ${ org . id } ` } value = { org . id } >
{ org . name || org . orgName || '未命名组织' }
< / Select.Option >
) ) }
< / Select >
< / Form.Item >
{ /* ========== 修复:场站 ========== */ }
< Form.Item label = "所属场站" name = "siteId" >
< Select
placeholder = "请先选择组织"
@@ -751,39 +714,29 @@ export default function DeviceManagementPage() {
} }
>
{ siteOptions . map ( site = > (
< Select.Option
key = { ` site- ${ site . id } ` }
value = { site . id }
>
< Select.Option key = { ` site- ${ site . id } ` } value = { site . id } >
{ site . name || site . siteName || '未命名场站' }
< / Select.Option >
) ) }
< / Select >
< / Form.Item >
{ /* ========== 修复:用户 ========== */ }
< Form.Item label = "负责人" name = "userId" >
< Select
placeholder = "请先选择场站"
disabled = { ! userOptions . length }
>
{ userOptions . map ( user = > (
< Select.Option
key = { ` user- ${ user . userId } ` }
value = { user . userId }
>
< Select.Option key = { ` user- ${ user . userId } ` } value = { user . userId } >
{ user . nickName || user . userName || user . realName || '未知用户' }
< / Select.Option >
) ) }
< / Select >
< / Form.Item >
< / Form >
< /M o d a l >
< /Modal >
< / >
) : (
// 显示原来的设备列表
< >
{ /* 筛选栏 */ }
< div style = { { marginBottom : 16 } } >
< Form form = { form } layout = "inline" >
< Form.Item name = "keyword" >
@@ -800,8 +753,6 @@ export default function DeviceManagementPage() {
< / Form.Item >
< / Form >
< / div >
{ /* 表格 */ }
< Table
rowKey = "id"
columns = { columns }
@@ -816,8 +767,7 @@ export default function DeviceManagementPage() {
} }
/ >
< / >
)
}
< / d i v >
) }
< / div >
) ;
}