index.tsx 18 KB

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