index.tsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. import { useCallback, useEffect, useState } from 'react';
  2. import { Button, Card, Col, Form, Image, Input, Radio, Row, Select, Tooltip } from 'antd';
  3. import { useIntl, useLocation } from 'umi';
  4. import { RadioCard, UploadImage } from '@/components';
  5. import { PlusOutlined } from '@ant-design/icons';
  6. import { service } from '../index';
  7. import SaveProductModal from './SaveProduct';
  8. import { getButtonPermission } from '@/utils/menu';
  9. import { onlyMessage } from '@/utils/util';
  10. import { PageContainer } from '@ant-design/pro-layout';
  11. import styles from '../../Cascade/Save/index.less';
  12. import { useDomFullHeight } from '@/hooks';
  13. const DefaultAccessType = 'gb28181-2016';
  14. const defaultImage = '/images/device-media.png';
  15. const Save = () => {
  16. const location: any = useLocation();
  17. const id = location?.query?.id;
  18. const intl = useIntl();
  19. const [form] = Form.useForm();
  20. const { minHeight } = useDomFullHeight(`.mediaDevice`);
  21. const [productVisible, setProductVisible] = useState(false);
  22. const [accessType, setAccessType] = useState(DefaultAccessType);
  23. const [productList, setProductList] = useState<any[]>([]);
  24. const [oldPassword, setOldPassword] = useState('');
  25. const [loading, setLoading] = useState(false);
  26. const img1 = require('/public/images/media/doc1.png');
  27. const img2 = require('/public/images/media/doc2.png');
  28. const img3 = require('/public/images/media/doc3.png');
  29. const img4 = require('/public/images/media/doc4.png');
  30. const getProductList = async (productParams: any) => {
  31. const resp = await service.queryProductList(productParams);
  32. if (resp.status === 200) {
  33. setProductList(resp.result);
  34. }
  35. };
  36. const queryProduct = async (value: string) => {
  37. getProductList({
  38. terms: [
  39. { column: 'accessProvider', value: value },
  40. { column: 'state', value: 1 },
  41. ],
  42. sorts: [{ name: 'createTime', order: 'desc' }],
  43. });
  44. };
  45. useEffect(() => {
  46. if (id) {
  47. service.getDetail(id).then((res) => {
  48. if (res.status === 200) {
  49. form.setFieldsValue({
  50. ...res.result,
  51. photoUrl: res.result?.photoUrl || defaultImage,
  52. channel: res.result?.provider,
  53. });
  54. const _accessType = res.result?.provider || DefaultAccessType;
  55. setAccessType(_accessType);
  56. queryProduct(_accessType);
  57. setOldPassword(res.result.password);
  58. }
  59. });
  60. } else {
  61. form.setFieldsValue({
  62. channel: DefaultAccessType,
  63. photoUrl: defaultImage,
  64. });
  65. queryProduct(DefaultAccessType);
  66. setAccessType(DefaultAccessType);
  67. }
  68. }, []);
  69. const handleSave = useCallback(async () => {
  70. const formData = await form.validateFields();
  71. if (formData) {
  72. const { channel, ...extraFormData } = formData;
  73. if (formData?.others?.access_pwd === oldPassword && !id) {
  74. delete extraFormData.others?.access_pwd;
  75. }
  76. if (formData.id === '') {
  77. delete extraFormData.id;
  78. }
  79. setLoading(true);
  80. const resp = id
  81. ? await service.updateData(channel, id, { ...extraFormData, channel })
  82. : await service.saveData(channel, { ...extraFormData, channel });
  83. setLoading(false);
  84. if (resp.status === 200) {
  85. form.resetFields();
  86. onlyMessage('操作成功');
  87. history.back();
  88. } else {
  89. onlyMessage('操作失败', 'error');
  90. }
  91. }
  92. }, [oldPassword]);
  93. // const intlFormat = (
  94. // id: string,
  95. // defaultMessage: string,
  96. // paramsID?: string,
  97. // paramsMessage?: string,
  98. // ) => {
  99. // const paramsObj: Record<string, string> = {};
  100. // if (paramsID) {
  101. // const paramsMsg = intl.formatMessage({
  102. // id: paramsID,
  103. // defaultMessage: paramsMessage,
  104. // });
  105. // paramsObj.name = paramsMsg;
  106. // }
  107. // return intl.formatMessage(
  108. // {
  109. // id,
  110. // defaultMessage,
  111. // },
  112. // paramsObj,
  113. // );
  114. // };
  115. return (
  116. <>
  117. <PageContainer>
  118. <Card className="mediaDevice" style={{ minHeight }}>
  119. <Row gutter={24}>
  120. <Col span={12}>
  121. <Form
  122. form={form}
  123. layout={'vertical'}
  124. onFinish={() => {
  125. handleSave();
  126. }}
  127. labelCol={{
  128. style: { width: 100 },
  129. }}
  130. >
  131. <Row>
  132. <Col span={24}>
  133. <Form.Item
  134. name={'channel'}
  135. label={'接入方式'}
  136. required
  137. rules={[{ required: true, message: '请选择接入方式' }]}
  138. >
  139. <RadioCard
  140. model={'singular'}
  141. itemStyle={{ width: '50%' }}
  142. onSelect={(key) => {
  143. setAccessType(key);
  144. queryProduct(key);
  145. form.resetFields(['id', 'productId']);
  146. }}
  147. disabled={id}
  148. options={[
  149. {
  150. label: 'GB/T28181',
  151. value: DefaultAccessType,
  152. },
  153. {
  154. label: '固定地址',
  155. value: 'fixed-media',
  156. },
  157. ]}
  158. />
  159. </Form.Item>
  160. </Col>
  161. </Row>
  162. <Row>
  163. <Col flex={'184px'}>
  164. <Form.Item name={'photoUrl'}>
  165. <UploadImage />
  166. </Form.Item>
  167. </Col>
  168. <Col flex={'auto'}>
  169. {accessType === DefaultAccessType ? (
  170. <Form.Item
  171. label={'ID'}
  172. name={'id'}
  173. rules={[
  174. { required: true, message: '请输入ID' },
  175. {
  176. pattern: /^[a-zA-Z0-9_\-]+$/,
  177. message: intl.formatMessage({
  178. id: 'pages.form.tip.id',
  179. defaultMessage: '请输入英文或者数字或者-或者_',
  180. }),
  181. },
  182. {
  183. max: 64,
  184. message: intl.formatMessage({
  185. id: 'pages.form.tip.max64',
  186. defaultMessage: '最多输入64个字符',
  187. }),
  188. },
  189. ]}
  190. >
  191. <Input placeholder={'请输入ID'} disabled={!!id} />
  192. </Form.Item>
  193. ) : (
  194. <Form.Item
  195. label={'ID'}
  196. name={'id'}
  197. rules={[
  198. {
  199. pattern: /^[a-zA-Z0-9_\-]+$/,
  200. message: intl.formatMessage({
  201. id: 'pages.form.tip.id',
  202. defaultMessage: '请输入英文或者数字或者-或者_',
  203. }),
  204. },
  205. {
  206. max: 64,
  207. message: intl.formatMessage({
  208. id: 'pages.form.tip.max64',
  209. defaultMessage: '最多输入64个字符',
  210. }),
  211. },
  212. ]}
  213. >
  214. <Input placeholder={'请输入ID'} disabled={!!id} />
  215. </Form.Item>
  216. )}
  217. <Form.Item
  218. label={'设备名称'}
  219. name={'name'}
  220. required
  221. rules={[
  222. { required: true, message: '请输入名称' },
  223. { max: 64, message: '最多可输入64个字符' },
  224. ]}
  225. >
  226. <Input placeholder={'请输入设备名称'} />
  227. </Form.Item>
  228. </Col>
  229. </Row>
  230. <Row>
  231. <Col span={24}>
  232. <Form.Item
  233. label={'所属产品'}
  234. required
  235. // rules={[{ required: true, message: '请选择所属产品' }]}
  236. >
  237. <Form.Item
  238. name={'productId'}
  239. noStyle
  240. rules={[{ required: true, message: '请选择所属产品' }]}
  241. >
  242. <Select
  243. showSearch
  244. allowClear
  245. fieldNames={{
  246. label: 'name',
  247. value: 'id',
  248. }}
  249. disabled={!!id}
  250. options={productList}
  251. placeholder={'请选择所属产品'}
  252. style={{ width: id ? '100%' : 'calc(100% - 36px)' }}
  253. filterOption={(input, option) =>
  254. (option!.name as unknown as string)
  255. .toLowerCase()
  256. .includes(input.toLowerCase())
  257. }
  258. onSelect={(_: any, node: any) => {
  259. const pwd = node.configuration ? node.configuration.access_pwd : '';
  260. form.setFieldsValue({
  261. others: {
  262. access_pwd: pwd,
  263. },
  264. });
  265. }}
  266. />
  267. </Form.Item>
  268. {!id && (
  269. <Form.Item noStyle>
  270. {getButtonPermission('device/Product', 'add') ? (
  271. <Tooltip title={'暂无权限,请联系管理员'}>
  272. <Button type={'link'} style={{ padding: '4px 10px' }} disabled>
  273. <PlusOutlined />
  274. </Button>
  275. </Tooltip>
  276. ) : (
  277. <Button
  278. type={'link'}
  279. style={{ padding: '4px 10px' }}
  280. onClick={() => {
  281. setProductVisible(true);
  282. }}
  283. >
  284. <PlusOutlined />
  285. </Button>
  286. )}
  287. </Form.Item>
  288. )}
  289. </Form.Item>
  290. </Col>
  291. {accessType === DefaultAccessType && (
  292. <Col span={24}>
  293. <Form.Item
  294. label={'接入密码'}
  295. name={['others', 'access_pwd']}
  296. required
  297. rules={[
  298. { required: true, message: '请输入接入密码' },
  299. { max: 64, message: '最大可输入64位' },
  300. ]}
  301. >
  302. <Input.Password placeholder={'请输入接入密码'} />
  303. </Form.Item>
  304. </Col>
  305. )}
  306. {id && (
  307. <>
  308. <Col span={24}>
  309. <Form.Item
  310. label={'流传输模式'}
  311. name={'streamMode'}
  312. required
  313. rules={[{ required: true, message: '请选择流传输模式' }]}
  314. >
  315. <Radio.Group
  316. optionType="button"
  317. buttonStyle="solid"
  318. options={[
  319. { label: 'UDP', value: 'UDP' },
  320. { label: 'TCP被动', value: 'TCP_PASSIVE' },
  321. ]}
  322. />
  323. </Form.Item>
  324. </Col>
  325. <Col span={24}>
  326. <Form.Item
  327. label={'设备厂商'}
  328. name={'manufacturer'}
  329. rules={[{ max: 64, message: '最多可输入64个字符' }]}
  330. >
  331. <Input placeholder={'请输入设备厂商'} />
  332. </Form.Item>
  333. </Col>
  334. <Col span={24}>
  335. <Form.Item
  336. label={'设备型号'}
  337. name={'model'}
  338. rules={[{ max: 64, message: '最多可输入64个字符' }]}
  339. >
  340. <Input placeholder={'请输入设备型号'} />
  341. </Form.Item>
  342. </Col>
  343. <Col span={24}>
  344. <Form.Item
  345. label={'固件版本'}
  346. name={'firmware'}
  347. rules={[{ max: 64, message: '最多可输入64个字符' }]}
  348. >
  349. <Input placeholder={'请输入固件版本'} />
  350. </Form.Item>
  351. </Col>
  352. </>
  353. )}
  354. <Col span={24}>
  355. <Form.Item label={'说明'} name={'description'}>
  356. <Input.TextArea
  357. // placeholder={intlFormat(
  358. // 'pages.form.tip.input.props',
  359. // '请输入',
  360. // 'pages.table.describe',
  361. // '说明',
  362. // )}
  363. placeholder="请输入说明"
  364. rows={4}
  365. style={{ width: '100%' }}
  366. maxLength={200}
  367. showCount={true}
  368. />
  369. </Form.Item>
  370. </Col>
  371. </Row>
  372. <Form.Item name={'id'} hidden>
  373. <Input />
  374. </Form.Item>
  375. <Col span={24}>
  376. <Form.Item>
  377. <Button type="primary" htmlType="submit" loading={loading}>
  378. 保存
  379. </Button>
  380. </Form.Item>
  381. </Col>
  382. </Form>
  383. </Col>
  384. <Col span={12}>
  385. {accessType === DefaultAccessType ? (
  386. <div className={styles.doc} style={{ height: 800 }}>
  387. <h1>1.概述</h1>
  388. <div>
  389. 视频设备通过GB/T28181接入平台整体分为2部分,包括平台端配置和设备端配置,不同的设备端配置的路径或页面存在差异,但配置项基本大同小异。
  390. </div>
  391. <h1>2.配置说明</h1>
  392. <h1>平台端配置</h1>
  393. <h2>1、ID</h2>
  394. <div>设备唯一标识,请填写设备端配置的设备编号。</div>
  395. <h2>2、所属产品</h2>
  396. <div>
  397. 只能选择接入方式为GB/T28281的产品,若当前无对应产品,可点击右侧快速添加按钮,填写产品名称和选择GB/T28181类型的网关完成产品创建
  398. </div>
  399. <h2>3、接入密码</h2>
  400. <div>
  401. 配置接入密码,设备端配置的密码需与该密码一致。该字段可在产品-设备接入页面进行统一配置,配置后所有设备将继承产品配置。设备单独修改后将脱离继承关系。
  402. </div>
  403. <h1>设备端配置</h1>
  404. <div>
  405. 各个厂家、不同设备型号的设备端配置页面布局存在差异,但配置项基本大同小异,此处以大华摄像头为例作为接入配置示例
  406. </div>
  407. <div className={styles.image}>
  408. <Image width="100%" src={img1} />
  409. </div>
  410. <h2>1、SIP服务器编号/SIP域</h2>
  411. <div>
  412. SIP服务器编号填入该设备所属产品-接入方式页面“连接信息”的SIP。
  413. SIP域通常为SIP服务器编号的前10位。
  414. </div>
  415. <div className={styles.image}>
  416. <Image width="100%" src={img2} />
  417. </div>
  418. <h2>2、SIP服务器IP/端口</h2>
  419. <div>SIP服务器IP/端口填入该设备所属产品-接入方式页面中“连接信息”的IP/端口。</div>
  420. <div className={styles.image}>
  421. <Image width="100%" src={img3} />
  422. </div>
  423. <h2>3、设备编号</h2>
  424. <div>
  425. 设备编号为设备唯一性标识,物联网平台的设备接入没有校验该字段,输入任意数字均不影响设备接入平台。
  426. </div>
  427. <h2>4、注册密码</h2>
  428. <div>填入该设备所属产品-接入方式页面中“GB28281配置”处的接入密码</div>
  429. <div className={styles.image}>
  430. <Image width="100%" src={img4} />
  431. </div>
  432. <h2>5、其他字段</h2>
  433. <div>不影响设备接入平台,可保持设备初始化值。</div>
  434. </div>
  435. ) : (
  436. <div className={styles.doc} style={{ height: 600 }}>
  437. <h1>1.概述</h1>
  438. <div>视频设备通过RTSP、RTMP固定地址接入平台分为2步。</div>
  439. <div>1、添加视频设备</div>
  440. <div>2、添加视频下的通道地址。</div>
  441. <div>注:当前页面为新增视频设备,新增完成后点击设备的“通道”按钮,添加通道。</div>
  442. <h1>2.配置说明</h1>
  443. <h2>1、ID</h2>
  444. <div>设备唯一标识,若不填写,系统将自动生成唯一标识。</div>
  445. <h2>2、所属产品</h2>
  446. <div>
  447. 只能选择接入方式为固定地址的产品,若当前无对应产品,可点击右侧快速添加按钮,填写产品名称和选择固定地址类型的网关完成产品创建。
  448. </div>
  449. </div>
  450. )}
  451. </Col>
  452. </Row>
  453. </Card>
  454. <SaveProductModal
  455. visible={productVisible}
  456. type={accessType}
  457. close={() => {
  458. setProductVisible(false);
  459. }}
  460. reload={(productId: string, data: any) => {
  461. form.setFieldsValue({ productId });
  462. productList.push({
  463. id: productId,
  464. name: data.name,
  465. });
  466. const pwd = data.configuration ? data.configuration.access_pwd : '';
  467. form.setFieldsValue({
  468. others: {
  469. access_pwd: pwd,
  470. },
  471. });
  472. setProductList([...productList]);
  473. }}
  474. />
  475. </PageContainer>
  476. </>
  477. );
  478. };
  479. export default Save;