peng.peng
1 year ago
19 changed files with 743 additions and 9 deletions
After Width: | Height: | Size: 577 B |
@ -0,0 +1,56 @@ |
|||
'use strict'; |
|||
|
|||
import { basicAction } from '@peace/utils' |
|||
import { ApiTable } from '$utils' |
|||
|
|||
export function getDeviceList(query) { |
|||
return dispatch => basicAction({ |
|||
type: 'get', |
|||
dispatch: dispatch, |
|||
query: query || {}, |
|||
actionType: 'GET_Device_REPORT', |
|||
url: `${ApiTable.getDeviceList}`, |
|||
msg: { error: '获取设备列表失败' }, |
|||
reducer: { name: 'device' } |
|||
}); |
|||
} |
|||
|
|||
|
|||
export function addDevice(params) { |
|||
return (dispatch) => basicAction({ |
|||
type: 'post', |
|||
data: params, |
|||
dispatch, |
|||
actionType: 'ADD_Device_REPORT', |
|||
url: ApiTable.addDevice, |
|||
msg: { |
|||
option: '设备新增', |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
export function deleteDevice(id) { |
|||
return (dispatch) => basicAction({ |
|||
type: 'del', |
|||
dispatch, |
|||
actionType: 'DELETE_Device_REPORT', |
|||
url: ApiTable.modifyDevice.replace('{id}', id), |
|||
msg: { |
|||
option: '设备删除', |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
export function modifyDevice(id, params, msg) { |
|||
return (dispatch) => basicAction({ |
|||
type: 'put', |
|||
data: params, |
|||
dispatch, |
|||
actionType: 'MODIFY_Device_REPORT', |
|||
url: ApiTable.modifyDevice.replace('{id}', id), |
|||
msg: { |
|||
option: msg || '设备编辑', |
|||
}, |
|||
}); |
|||
} |
|||
|
@ -0,0 +1,7 @@ |
|||
'use strict'; |
|||
|
|||
import * as device from './device' |
|||
|
|||
export default { |
|||
...device |
|||
} |
@ -0,0 +1,213 @@ |
|||
'use strict'; |
|||
import React, { useState, useEffect } from 'react'; |
|||
import { connect } from 'react-redux'; |
|||
import moment from 'moment'; |
|||
import { Modal, Form, Button, Upload, message } from 'antd'; |
|||
import { DownloadOutlined } from '@ant-design/icons'; |
|||
import XLSX from 'xlsx'; |
|||
import { DEVICE_TYPES } from './modelModal'; |
|||
// import porvince from './province.json'
|
|||
const workerKeys = { |
|||
name: '设备名称', |
|||
type: '设备类型', |
|||
specification: '规格型号', |
|||
dateProduced: '生产日期', |
|||
dateInstall: '安装时间', |
|||
dateGuarantee: '质保期', |
|||
dateMainten: '维保期', |
|||
} |
|||
//下载模板和上传文件读取
|
|||
const ImportDeviceModal = props => { |
|||
const { dispatch, actions, onCancel, onOk, devices } = props; |
|||
const { deviceManage } = actions; |
|||
const [msg, setMsg] = useState(''); |
|||
const [loading, setLoading] = useState(false); |
|||
const [postData, setPostData] = useState([]); |
|||
|
|||
//初始化
|
|||
useEffect(() => { |
|||
|
|||
}, []); |
|||
|
|||
const confirm = () => { |
|||
if (postData.length) { |
|||
setLoading(true) |
|||
//导入明细接口通用
|
|||
dispatch(deviceManage?.addDevice(postData)).then(res => { |
|||
if (res.success) { |
|||
onOk() |
|||
} |
|||
setLoading(false) |
|||
}) |
|||
} else { |
|||
message.warning({ content: '没有数据可以提交,请上传数据文件', duration: 2 }) |
|||
} |
|||
} |
|||
|
|||
const dldCsvMb = () => { |
|||
//表头
|
|||
let head = []; |
|||
Object.keys(workerKeys).map(key => { |
|||
head.push(workerKeys[key]); |
|||
}) |
|||
head = head.join(',') + "\n"; |
|||
//数据
|
|||
//let data = 1 + ',' + 2 + ',' + 3 + ',' + 4 + ',' + 5
|
|||
let templateCsv = "data:text/xls;charset=utf-8,\ufeff" + head; |
|||
//创建一个a标签
|
|||
let link = document.createElement("a"); |
|||
//为a标签设置属性
|
|||
link.setAttribute("href", templateCsv); |
|||
link.setAttribute("download", `设备导入模板.xls`); |
|||
//点击a标签
|
|||
link.click(); |
|||
} |
|||
const download = () => { |
|||
dldCsvMb(); |
|||
} |
|||
|
|||
const judgeTimeValid = (v) => { |
|||
let valid = true; |
|||
if (v.split('/').length !== 3) { |
|||
valid = false; |
|||
} else { |
|||
let time = new Date(v); |
|||
// if (!time) {
|
|||
// return valid;//可以不填
|
|||
// }
|
|||
const ymd = /^((19|20)[0-9]{2})[\/\-]((0[1-9])|(1[0-2]))[\/\-]((0[1-9])|((1|2)[0-9])|(3[0-1]))$/;//年月日
|
|||
if (time instanceof Date) { |
|||
let timeStr = moment(time).format('YYYY/MM/DD'); |
|||
if (!ymd.test(timeStr)) { |
|||
valid = false; |
|||
} |
|||
} else { |
|||
valid = false; |
|||
} |
|||
} |
|||
return valid; |
|||
} |
|||
|
|||
return (<Modal |
|||
title={"导入设备信息"} visible={true} |
|||
onOk={confirm} width={520} |
|||
confirmLoading={loading} |
|||
onCancel={() => { |
|||
setMsg('') |
|||
setLoading(false) |
|||
setPostData([]) |
|||
onCancel() |
|||
}} |
|||
> |
|||
<Form> |
|||
<Upload |
|||
action={'/'} accept={'.xls,.xlsx'} |
|||
maxCount={1} |
|||
onRemove={(currentFile, fileList, fileItem) => { |
|||
setMsg(''); |
|||
setPostData([]); |
|||
}} |
|||
customRequest={async (data) => { |
|||
const { file, onSuccess, onError } = data |
|||
const error = (msg) => { |
|||
setMsg(msg) |
|||
onError({ message: msg }) |
|||
} |
|||
let isLt = file.size / 1024 / 1024 < 200 |
|||
if (!isLt) { |
|||
error(`文件最大不超过200M`); |
|||
return; |
|||
} |
|||
const reader = new FileReader(); |
|||
reader.onload = function (e) { |
|||
const data = e.target.result; |
|||
const workbook = XLSX.read(data, { type: 'binary', codepage: 936 }); |
|||
const firstSheetName = workbook.SheetNames[0]; |
|||
const worksheet = workbook.Sheets[firstSheetName]; |
|||
|
|||
//XLSX.utils.sheet_to_json解析 ------把身份证号和手机号变成 字符串------
|
|||
Object.keys(worksheet).forEach(k => { |
|||
worksheet[k].w ? worksheet[k].v = worksheet[k].w : '' |
|||
}) |
|||
//------------------end------------------
|
|||
|
|||
const res = XLSX.utils.sheet_to_json(worksheet); |
|||
|
|||
if (res.length > 1000) { |
|||
error('一次性上传数据行数应小于1000行,请分批上传') |
|||
return |
|||
} |
|||
if (!res.length) { |
|||
error('请填写至少一行数据') |
|||
return |
|||
} |
|||
let postData = [] |
|||
|
|||
for (let i = 0; i < res.length; i++) { |
|||
let d = res[i] |
|||
let obj = {}; |
|||
Object.keys(workerKeys).map(key => { |
|||
obj[key] = d[workerKeys[key]] || null; |
|||
}) |
|||
//必填项
|
|||
let notNullKeys = ['name', 'type', 'specification', 'dateProduced', 'dateGuarantee', 'dateMainten', 'dateInstall'] |
|||
for (let k = 0; k < notNullKeys.length; k++) { |
|||
let key = notNullKeys[k]; |
|||
if (!obj[key]) { |
|||
error(`第${i + 2}行【${workerKeys[key]}】不能为空`) |
|||
return |
|||
} |
|||
} |
|||
|
|||
//判断设备类型
|
|||
let ext = DEVICE_TYPES.find(m => m == obj.type); |
|||
if (!ext) { |
|||
error(`第${i + 2}行的【设备类型】错误,请填写【${DEVICE_TYPES.toString()}】中的一种`) |
|||
return |
|||
} |
|||
|
|||
let tValid = judgeTimeValid(obj.dateProduced) && judgeTimeValid(obj.dateGuarantee) |
|||
&& judgeTimeValid(obj.dateMainten) && judgeTimeValid(obj.dateInstall); |
|||
if (!tValid) { |
|||
error(`第${i + 2}行【日期格式】错误,请填写yyyy/mm/dd格式`) |
|||
return; |
|||
} |
|||
|
|||
let dateInstallValid = moment(obj.dateInstall).valueOf() > moment().startOf('d').add(1, 'd').valueOf(); |
|||
if (dateInstallValid) { |
|||
error(`第${i + 2}行【安装日期】不能填写今天之后的时间`) |
|||
return; |
|||
} |
|||
|
|||
|
|||
|
|||
postData.push(obj) |
|||
} |
|||
setPostData(postData) |
|||
let msg = '文件解析完成,点击确定按钮上传保存!' |
|||
setMsg(msg) |
|||
onSuccess({ message: msg }) |
|||
} |
|||
reader.readAsBinaryString(file); |
|||
}}> |
|||
<Button icon={<DownloadOutlined />} theme="light"> |
|||
请选择文件 |
|||
</Button> |
|||
</Upload> |
|||
<span>{msg}</span> |
|||
<div style={{ color: '#ccc', marginTop: 20 }}>最大不超过200M,导入文件需与 |
|||
<span onClick={() => download()} style={{ cursor: 'pointer', color: '#0066FF' }}>导入模板</span> |
|||
一致</div> |
|||
</Form> |
|||
</Modal>) |
|||
} |
|||
|
|||
function mapStateToProps(state) { |
|||
const { auth, global } = state; |
|||
return { |
|||
user: auth.user, |
|||
actions: global.actions, |
|||
} |
|||
} |
|||
|
|||
export default connect(mapStateToProps)(ImportDeviceModal); |
@ -0,0 +1,111 @@ |
|||
import React, { useRef } from 'react'; |
|||
import { Button, Form } from 'antd'; |
|||
import { InfoCircleOutlined } from '@ant-design/icons'; |
|||
import { |
|||
ModalForm, |
|||
ProFormSelect, |
|||
ProFormText, |
|||
ProFormDatePicker |
|||
} from '@ant-design/pro-form'; |
|||
import moment from 'moment'; |
|||
export default (props) => { |
|||
const { title, triggerRender, editData = null, onFinish, devices } = props; |
|||
const formItemLayout = { labelCol: { span: 6 }, wrapperCol: { span: 16 } }; |
|||
const initialValues = editData ? { |
|||
...editData, |
|||
} : {}; |
|||
const [form] = Form.useForm(); |
|||
const formRef = useRef(); |
|||
const disabledDate = (value) => { |
|||
return value.valueOf() > moment().startOf('d').add(1, 'd').valueOf(); |
|||
} |
|||
return ( |
|||
<ModalForm |
|||
formRef={formRef} |
|||
title={title || ''} |
|||
initialValues={initialValues} |
|||
trigger={ |
|||
triggerRender ? triggerRender : <Button type="primary" > |
|||
{title || ''} |
|||
</Button> |
|||
} |
|||
layout="horizontal" |
|||
grid={true} |
|||
{...formItemLayout} |
|||
modalProps={{ |
|||
destroyOnClose: true, |
|||
onCancel: () => { }, |
|||
}} |
|||
onFinish={async (values) => { |
|||
return onFinish && await onFinish(values, editData, form) |
|||
// return true;
|
|||
}} |
|||
width={500} |
|||
> |
|||
|
|||
|
|||
<ProFormText |
|||
rules={[ |
|||
{ required: true, message: '请输入设备名称' }, |
|||
{ max: 255, message: '设备名称长度不能大于255个字符' }, |
|||
]} |
|||
name="name" |
|||
label="设备名称" |
|||
/> |
|||
|
|||
<ProFormSelect |
|||
rules={[{ required: true, message: '请选择设备类型' }]} |
|||
options={ |
|||
DEVICE_TYPES |
|||
.map(s => { |
|||
return { label: s, value: s } |
|||
}) |
|||
} |
|||
name="type" |
|||
label="设备类型" |
|||
/> |
|||
|
|||
<ProFormText |
|||
rules={[ |
|||
{ required: true, message: '请输入规格型号' }, |
|||
{ max: 20, message: '规格型号长度不能大于20个字符' }, |
|||
]} |
|||
name="specification" |
|||
label="规格型号" |
|||
/> |
|||
|
|||
<ProFormDatePicker |
|||
rules={[{ required: true, message: '请输入生产日期' }]} |
|||
name="dateProduced" |
|||
label="生产日期" |
|||
fieldProps={ |
|||
{ disabledDate: disabledDate } |
|||
} |
|||
|
|||
/> |
|||
<ProFormDatePicker |
|||
rules={[{ required: true, message: '请输入安装日期' }]} |
|||
name="dateInstall" |
|||
label="安装日期" |
|||
fieldProps={ |
|||
{ disabledDate: disabledDate } |
|||
} |
|||
|
|||
/> |
|||
<ProFormDatePicker |
|||
rules={[{ required: true, message: '请输入质保期' }]} |
|||
name="dateGuarantee" |
|||
label="质保期" |
|||
/> |
|||
<ProFormDatePicker |
|||
rules={[{ required: true, message: '请输入维保期' }]} |
|||
name="dateMainten" |
|||
label="维保期" |
|||
/> |
|||
|
|||
</ModalForm> |
|||
); |
|||
}; |
|||
|
|||
export const DEVICE_TYPES = ['安防系统', '厨房系统', '电梯', '供电系统', '空调', '排水系统', '水系统', '通道门禁', |
|||
'通风系统', '通信系统', '显示视频', '消防系统', '照明系统'] |
@ -0,0 +1,254 @@ |
|||
import React, { useEffect, useState } from 'react' |
|||
import { Spin, Popconfirm, message, Button, Input } from 'antd'; |
|||
import { connect } from 'react-redux'; |
|||
import ProTable from '@ant-design/pro-table'; |
|||
import DeviceModal from '../components/modelModal' |
|||
import moment from 'moment'; |
|||
import ImportDeviceModal from '../components/importDevicesModal' |
|||
function DeviceManagement(props) { |
|||
const { loading, clientHeight, actions, dispatch, devices } = props; |
|||
const [pageSize, setPageSize] = useState(10); |
|||
const [currentPage, setCurrentPage] = useState(1); |
|||
const [rowSelected, setRowSelected] = useState([]) |
|||
const [showImportModal, setShowImportModal] = useState(false); |
|||
const [name, setName] = useState(); |
|||
|
|||
const queryData = (search) => { |
|||
const query = { |
|||
limit: search ? 10 : pageSize || 10, |
|||
page: search ? 1 : currentPage || 1, |
|||
name: name |
|||
} |
|||
dispatch(actions.deviceManage.getDeviceList(query)); |
|||
} |
|||
|
|||
useEffect(() => { |
|||
queryData(); |
|||
}, [pageSize, currentPage]); |
|||
|
|||
const handleDelete = (id) => { |
|||
dispatch(actions.deviceManage.deleteDevice(id)).then(() => { |
|||
queryData(); |
|||
setRowSelected([]) |
|||
}); |
|||
}; |
|||
|
|||
const onFinish = async (values, editData) => { |
|||
if (editData) { |
|||
const dataToSave = { ...values } |
|||
return dispatch( |
|||
actions.deviceManage.modifyDevice(editData.id, dataToSave, values?.msg || ''), |
|||
).then((res) => { |
|||
if (res.success) { |
|||
queryData(); |
|||
return true; |
|||
} else { |
|||
return false; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
return dispatch(actions.deviceManage.addDevice({ |
|||
...values, |
|||
})).then(res => { |
|||
if (res.success) { |
|||
queryData(); |
|||
return true; |
|||
} else { |
|||
return false; |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
const columns = [ |
|||
{ |
|||
title: '设备名称', |
|||
dataIndex: 'name', |
|||
ellipsis: true, |
|||
}, |
|||
{ |
|||
title: '设备类型', |
|||
dataIndex: 'type', |
|||
ellipsis: true, |
|||
}, |
|||
{ |
|||
title: '规格型号', |
|||
dataIndex: 'specification', |
|||
ellipsis: true, |
|||
}, |
|||
{ |
|||
title: '生产日期', |
|||
dataIndex: 'dateProduced', |
|||
ellipsis: true, |
|||
}, |
|||
{ |
|||
title: '安装时间', |
|||
dataIndex: 'dateInstall', |
|||
ellipsis: true, |
|||
}, |
|||
{ |
|||
title: '质保期', |
|||
dataIndex: 'dateGuarantee', |
|||
ellipsis: true, |
|||
search: false, |
|||
}, |
|||
{ |
|||
title: '维保期', |
|||
dataIndex: 'dateMainten', |
|||
ellipsis: true, |
|||
}, |
|||
{ |
|||
title: '设备投入使用时长', |
|||
dataIndex: 'length', |
|||
ellipsis: true, |
|||
render: (text, record) => { |
|||
const start = moment(record?.dateInstall); |
|||
const end = moment(); |
|||
const days = end.diff(start, 'days'); |
|||
|
|||
return days + '天' |
|||
} |
|||
|
|||
}, |
|||
{ |
|||
title: '操作', |
|||
width: 160, |
|||
key: 'option', |
|||
valueType: 'option', |
|||
render: (text, record) => { |
|||
const options = []; |
|||
options.push(<DeviceModal |
|||
triggerRender={<a>编辑</a>} |
|||
editData={record} |
|||
title="编辑设备" |
|||
onFinish={onFinish} |
|||
key="editModel" |
|||
/>) |
|||
|
|||
options.push( |
|||
<Popconfirm |
|||
key="del" |
|||
placement="top" |
|||
title="是否确认删除该设备?" |
|||
onConfirm={() => handleDelete(record.id)} |
|||
okText="是" |
|||
cancelText="否" |
|||
> |
|||
<a>删除</a> |
|||
</Popconfirm>) |
|||
|
|||
return options; |
|||
|
|||
}, |
|||
}, |
|||
]; |
|||
|
|||
return <div id='patrol-record' className='global-main'> |
|||
<Spin spinning={loading}> |
|||
<div style={{ marginBottom: 19 }}> |
|||
<div className='top' style={{ marginBottom: 19 }}> |
|||
<div className='title'> |
|||
<span className='line'></span> |
|||
<span className='cn'>设备管理</span> |
|||
<span className='en'> DEVICE</span> |
|||
</div> |
|||
<div> |
|||
<DeviceModal |
|||
triggerRender={<Button type='primary'>新建</Button>} |
|||
title="新建设备" |
|||
onFinish={onFinish} |
|||
key="addModel" |
|||
/> |
|||
<Button type="primary" style={{ marginRight: 10, marginLeft: 10 }} onClick={() => { setShowImportModal(true) }}>批量新增</Button> |
|||
<Popconfirm title="确认删除?" onConfirm={() => { |
|||
rowSelected?.length > 0 ? handleDelete(rowSelected?.toString()) : message.warning('请先选择要删除的设备') |
|||
}}> |
|||
<Button>批量删除</Button> |
|||
</Popconfirm> |
|||
|
|||
<Input onChange={e => setName(e?.target?.value)} style={{ width: '13vw', marginLeft: 20, marginRight: 10 }} /> |
|||
<Button type="primary" onClick={() => { |
|||
setPageSize(10) |
|||
setCurrentPage(1) |
|||
queryData(true) |
|||
}}>查询</Button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<ProTable |
|||
columns={columns} |
|||
rowKey="id" |
|||
|
|||
dateFormatter="string" |
|||
scroll={ |
|||
{ |
|||
scrollToFirstRowOnChange: true, |
|||
y: clientHeight - 260 |
|||
} |
|||
} |
|||
pagination={{ |
|||
size: 'large', |
|||
total: devices?.count, |
|||
showSizeChanger: true, |
|||
// showQuickJumper: true,
|
|||
current: currentPage, |
|||
pageSize: pageSize || 10, |
|||
pageSizeOptions: [10, 20, 50], |
|||
showTotal: (total) => { |
|||
return <span style={{ fontSize: 15 }}>{`共${Math.ceil(total / pageSize)}页,${total}项`}</span> |
|||
}, |
|||
onShowSizeChange: (currentPage, pageSize) => { |
|||
setCurrentPage(currentPage); |
|||
setPageSize(pageSize); |
|||
}, |
|||
onChange: (page, pageSize) => { |
|||
setCurrentPage(page); |
|||
setPageSize(pageSize); |
|||
} |
|||
}} |
|||
dataSource={devices?.rows || []} |
|||
rowSelection={{ |
|||
selectedRowKeys: rowSelected, |
|||
onChange: (selectedRowKeys) => { |
|||
setRowSelected(selectedRowKeys); |
|||
}, |
|||
getCheckboxProps: (record) => { |
|||
return { |
|||
disabled: record.username === 'SuperAdmin', |
|||
} |
|||
} |
|||
}} |
|||
options={false} |
|||
search={false} |
|||
/> |
|||
{showImportModal && <ImportDeviceModal |
|||
devices={devices?.rows || []} |
|||
onCancel={() => { |
|||
setShowImportModal(false) |
|||
}} |
|||
onOk={() => { |
|||
setShowImportModal(false) |
|||
queryData() |
|||
}} |
|||
/>} |
|||
</Spin> |
|||
</div> |
|||
|
|||
|
|||
} |
|||
|
|||
function mapStateToProps(state) { |
|||
const { |
|||
auth, global, device |
|||
} = state; |
|||
return { |
|||
loading: device.isRequesting, |
|||
clientHeight: global.clientHeight, |
|||
actions: global.actions, |
|||
devices: device?.data || {} |
|||
}; |
|||
} |
|||
|
|||
export default connect(mapStateToProps)(DeviceManagement); |
|||
|
|||
|
@ -0,0 +1,5 @@ |
|||
'use strict'; |
|||
|
|||
import DeviceManage from './deviceManage' |
|||
|
|||
export { DeviceManage }; |
@ -0,0 +1,15 @@ |
|||
'use strict'; |
|||
|
|||
import reducers from './reducers'; |
|||
import routes from './routes'; |
|||
import actions from './actions'; |
|||
import { getNavItem } from './nav-item'; |
|||
|
|||
export default { |
|||
key: 'deviceManage', |
|||
name: '设备管理', |
|||
reducers: reducers, |
|||
routes: routes, |
|||
actions: actions, |
|||
getNavItem: getNavItem |
|||
}; |
@ -0,0 +1,13 @@ |
|||
import React from 'react'; |
|||
import { Link } from 'react-router-dom'; |
|||
import { Menu } from 'antd'; |
|||
import { Func } from '$utils'; |
|||
export function getNavItem(user, dispatch) { |
|||
return ( |
|||
<Menu.Item icon={<img src='/assets/images/menu/device.png' style={{ width: 24, height: 24 }} />} |
|||
key="deviceManage"> |
|||
<Link to="/deviceManage">设备管理</Link> |
|||
</Menu.Item> |
|||
|
|||
); |
|||
} |
@ -0,0 +1,5 @@ |
|||
'use strict'; |
|||
|
|||
export default { |
|||
|
|||
} |
@ -0,0 +1,13 @@ |
|||
'use strict'; |
|||
import { DeviceManage } from './containers'; |
|||
|
|||
export default [{ |
|||
type: 'inner', |
|||
route: { |
|||
path: '/deviceManage', |
|||
key: 'deviceManage', |
|||
breadcrumb: '设备管理', |
|||
component: DeviceManage, |
|||
|
|||
} |
|||
}]; |
Loading…
Reference in new issue