index.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. import { PageContainer } from '@ant-design/pro-layout';
  2. import {
  3. Button,
  4. Card,
  5. Checkbox,
  6. Col,
  7. Form,
  8. Input,
  9. InputNumber,
  10. message,
  11. Row,
  12. Select,
  13. Tooltip,
  14. } from 'antd';
  15. import { useEffect, useState } from 'react';
  16. import { service, StreamModel } from '@/pages/media/Stream';
  17. import { useParams } from 'umi';
  18. import { QuestionCircleOutlined } from '@ant-design/icons';
  19. const re =
  20. /^([0-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.([0-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.([0-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.([0-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$/;
  21. // API host
  22. interface APIComponentProps {
  23. onChange?: (data: any) => void;
  24. value?: {
  25. apiHost?: string;
  26. apiPort?: number;
  27. };
  28. }
  29. const APIComponent = (props: APIComponentProps) => {
  30. const { value, onChange } = props;
  31. const [data, setData] = useState<{ apiHost?: string; apiPort?: number } | undefined>(value);
  32. useEffect(() => {
  33. setData(value);
  34. }, [value]);
  35. return (
  36. <div style={{ display: 'flex', alignItems: 'center' }}>
  37. <Input
  38. onChange={(e) => {
  39. if (onChange) {
  40. const item = {
  41. ...data,
  42. apiHost: e.target.value,
  43. };
  44. setData(item);
  45. onChange(item);
  46. }
  47. }}
  48. value={data?.apiHost}
  49. style={{ marginRight: 10 }}
  50. placeholder="请输入API Host"
  51. />
  52. <InputNumber
  53. style={{ minWidth: 150 }}
  54. value={data?.apiPort}
  55. min={1}
  56. max={65535}
  57. onChange={(e: number) => {
  58. if (onChange) {
  59. const item = {
  60. ...data,
  61. apiPort: e,
  62. };
  63. setData(item);
  64. onChange(item);
  65. }
  66. }}
  67. />
  68. </div>
  69. );
  70. };
  71. interface RTPComponentProps {
  72. onChange?: (data: any) => void;
  73. value?: {
  74. rtpIp?: string;
  75. rtpPort?: number;
  76. dynamicRtpPort?: boolean;
  77. dynamicRtpPortRange?: number[];
  78. };
  79. }
  80. const RTPComponent = (props: RTPComponentProps) => {
  81. const { value, onChange } = props;
  82. const [checked, setChecked] = useState<boolean>(value?.dynamicRtpPort || false);
  83. const [data, setData] = useState<any>(value);
  84. useEffect(() => {
  85. setData(value);
  86. setChecked(!!value?.dynamicRtpPort);
  87. }, [value]);
  88. return (
  89. <div style={{ display: 'flex', alignItems: 'center' }}>
  90. <Input
  91. style={{ maxWidth: 400 }}
  92. placeholder="请输入RTP IP"
  93. value={data?.rtpIp}
  94. onChange={(e) => {
  95. if (onChange) {
  96. const item = {
  97. ...data,
  98. rtpIp: e.target.value,
  99. };
  100. setData(item);
  101. onChange(item);
  102. }
  103. }}
  104. />
  105. {!checked ? (
  106. <InputNumber
  107. style={{ minWidth: 150, margin: '0 10px' }}
  108. min={1}
  109. max={65535}
  110. value={data?.rtpPort}
  111. placeholder="请输入端口"
  112. onChange={(e) => {
  113. if (onChange) {
  114. const item = {
  115. ...data,
  116. rtpPort: e,
  117. };
  118. setData(item);
  119. onChange(item);
  120. }
  121. }}
  122. />
  123. ) : (
  124. <div style={{ margin: '0 10px' }}>
  125. <InputNumber
  126. style={{ minWidth: 150 }}
  127. min={1}
  128. max={65535}
  129. placeholder="起始端口"
  130. value={data?.dynamicRtpPortRange?.[0]}
  131. onChange={(e) => {
  132. if (onChange) {
  133. const item = {
  134. ...data,
  135. dynamicRtpPortRange: [e, data?.dynamicRtpPortRange?.[1]],
  136. };
  137. setData(item);
  138. onChange(item);
  139. }
  140. }}
  141. />{' '}
  142. 至{' '}
  143. <InputNumber
  144. style={{ minWidth: 150 }}
  145. min={1}
  146. max={65535}
  147. placeholder="终止端口"
  148. value={data?.dynamicRtpPortRange?.[1]}
  149. onChange={(e) => {
  150. if (onChange) {
  151. const item = {
  152. ...data,
  153. dynamicRtpPortRange: [data?.dynamicRtpPortRange?.[0], e],
  154. };
  155. setData(item);
  156. onChange(item);
  157. }
  158. }}
  159. />
  160. </div>
  161. )}
  162. <Checkbox
  163. checked={data?.dynamicRtpPort}
  164. onChange={(e) => {
  165. setChecked(e.target.checked);
  166. if (onChange) {
  167. const item = {
  168. ...data,
  169. dynamicRtpPort: e.target.checked,
  170. };
  171. setData(item);
  172. onChange(item);
  173. }
  174. }}
  175. >
  176. 动态端口
  177. </Checkbox>
  178. </div>
  179. );
  180. };
  181. const Detail = () => {
  182. const params = useParams<{ id: string }>();
  183. const [form] = Form.useForm();
  184. const [providers, setProviders] = useState<any[]>([]);
  185. useEffect(() => {
  186. service.queryProviders().then((resp) => {
  187. if (resp.status === 200) {
  188. setProviders(resp.result);
  189. }
  190. });
  191. if (params.id && params.id !== ':id') {
  192. service.detail(params.id).then((resp) => {
  193. if (resp.status === 200) {
  194. StreamModel.current = resp.result;
  195. form.setFieldsValue({
  196. name: StreamModel.current?.name,
  197. provider: StreamModel.current?.provider,
  198. secret: StreamModel.current?.configuration?.secret,
  199. api: {
  200. apiHost: StreamModel.current.configuration?.apiHost,
  201. apiPort: StreamModel.current.configuration?.apiPort,
  202. },
  203. rtp: {
  204. rtpIp: StreamModel.current.configuration?.rtpIp,
  205. rtpPort: StreamModel.current.configuration?.rtpPort,
  206. dynamicRtpPort: StreamModel.current.configuration?.dynamicRtpPort || false,
  207. dynamicRtpPortRange: StreamModel.current.configuration?.dynamicRtpPortRange || [],
  208. },
  209. });
  210. }
  211. });
  212. }
  213. }, [params.id]);
  214. const checkAPI = (_: any, value: { apiHost: string; apiPort: number }) => {
  215. if (Number(value.apiPort) < 1 || Number(value.apiPort) > 65535) {
  216. return Promise.reject(new Error('端口请输入1~65535之间的正整数'));
  217. }
  218. if (!re.test(value.apiHost)) {
  219. return Promise.reject(new Error('请输入正确的IP地址'));
  220. }
  221. return Promise.resolve();
  222. };
  223. const checkRIP = (
  224. _: any,
  225. value: {
  226. rtpIp: string;
  227. rtpPort: number;
  228. dynamicRtpPort: boolean;
  229. dynamicRtpPortRange: number[];
  230. },
  231. ) => {
  232. if (!re.test(value.rtpIp)) {
  233. return Promise.reject(new Error('请输入正确的IP地址'));
  234. }
  235. if (value.dynamicRtpPort) {
  236. if (value.dynamicRtpPortRange) {
  237. if (value.dynamicRtpPortRange?.[0]) {
  238. if (
  239. Number(value.dynamicRtpPortRange?.[0]) < 1 ||
  240. Number(value.dynamicRtpPortRange?.[0]) > 65535
  241. ) {
  242. return Promise.reject(new Error('端口请输入1~65535之间的正整数'));
  243. }
  244. }
  245. if (value.dynamicRtpPortRange?.[1]) {
  246. if (
  247. Number(value.dynamicRtpPortRange?.[1]) < 1 ||
  248. Number(value.dynamicRtpPortRange?.[1]) > 65535
  249. ) {
  250. return Promise.reject(new Error('端口请输入1~65535之间的正整数'));
  251. }
  252. }
  253. if (
  254. value.dynamicRtpPortRange?.[0] &&
  255. value.dynamicRtpPortRange?.[1] &&
  256. value.dynamicRtpPortRange?.[0] > value.dynamicRtpPortRange?.[1]
  257. ) {
  258. return Promise.reject(new Error('终止端口需大于等于起始端'));
  259. }
  260. }
  261. } else {
  262. if (Number(value.rtpPort) < 1 || Number(value.rtpPort) > 65535) {
  263. return Promise.reject(new Error('端口请输入1~65535之间的正整数'));
  264. }
  265. }
  266. return Promise.resolve();
  267. };
  268. return (
  269. <PageContainer>
  270. <Card>
  271. <Form
  272. layout="vertical"
  273. form={form}
  274. onFinish={async (values: any) => {
  275. const param = {
  276. name: values.name,
  277. provider: values.provider,
  278. configuration: {
  279. secret: values?.secret,
  280. apiHost: values.api?.apiHost,
  281. apiPort: values.api?.apiPort,
  282. rtpIp: values.rtp?.rtpIp,
  283. rtpPort: values.rtp?.rtpPort,
  284. dynamicRtpPort: values.rtp?.dynamicRtpPort,
  285. dynamicRtpPortRange: values.rtp?.dynamicRtpPortRange || [],
  286. },
  287. };
  288. let resp = undefined;
  289. if (params.id && params.id !== ':id') {
  290. resp = await service.update({ ...param, id: params.id });
  291. } else {
  292. resp = await service.save(param);
  293. }
  294. if (resp && resp.status === 200) {
  295. message.success('操作成功!');
  296. history.back();
  297. }
  298. }}
  299. >
  300. <Row gutter={[16, 16]}>
  301. <Col span={12}>
  302. <Form.Item
  303. label="流媒体名称"
  304. name="name"
  305. rules={[
  306. {
  307. required: true,
  308. message: '请输入流媒体名称',
  309. },
  310. {
  311. max: 64,
  312. message: '最多输入64个字符',
  313. },
  314. ]}
  315. >
  316. <Input placeholder="请输入流媒体名称" />
  317. </Form.Item>
  318. </Col>
  319. <Col span={12}>
  320. <Form.Item
  321. label="服务商"
  322. name="provider"
  323. rules={[{ required: true, message: '请选择服务商' }]}
  324. >
  325. <Select placeholder="请选择服务商">
  326. {providers.map((item) => (
  327. <Select.Option key={item.id} value={item.id}>
  328. {item.name}
  329. </Select.Option>
  330. ))}
  331. </Select>
  332. </Form.Item>
  333. </Col>
  334. <Col span={12}>
  335. <Form.Item
  336. label="密钥"
  337. name="secret"
  338. rules={[
  339. {
  340. max: 64,
  341. message: '最多输入64个字符',
  342. },
  343. ]}
  344. >
  345. <Input.Password placeholder="请输入密钥" />
  346. </Form.Item>
  347. </Col>
  348. <Col span={12}>
  349. <Form.Item
  350. label={
  351. <span>
  352. API Host
  353. <Tooltip style={{ marginLeft: 5 }} title="调用流媒体接口时请求的服务地址">
  354. <QuestionCircleOutlined />
  355. </Tooltip>
  356. </span>
  357. }
  358. name="api"
  359. rules={[{ required: true, message: '请输入API Host' }, { validator: checkAPI }]}
  360. >
  361. <APIComponent />
  362. </Form.Item>
  363. </Col>
  364. <Col span={24}>
  365. <Form.Item
  366. label={
  367. <span>
  368. RTP IP
  369. <Tooltip
  370. style={{ marginLeft: 5 }}
  371. title="视频设备将流推送到该IP地址下,部分设备仅支持IP地址,建议全是用IP地址"
  372. >
  373. <QuestionCircleOutlined />
  374. </Tooltip>
  375. </span>
  376. }
  377. name="rtp"
  378. rules={[{ required: true, message: '请输入RTP IP' }, { validator: checkRIP }]}
  379. >
  380. <RTPComponent />
  381. </Form.Item>
  382. </Col>
  383. <Col span={24}>
  384. <Form.Item>
  385. <Button type="primary" htmlType="submit">
  386. 保存
  387. </Button>
  388. </Form.Item>
  389. </Col>
  390. </Row>
  391. </Form>
  392. </Card>
  393. </PageContainer>
  394. );
  395. };
  396. export default Detail;