Переглянути джерело

Merge branch 'master' of lizhiqi/yongxu-web into master

lizhiqi 1 рік тому
батько
коміт
148aaa18bc

+ 1 - 0
config/proxy.ts

@@ -19,6 +19,7 @@ export default {
     '/web/': {
       // 要代理的地址
       target: 'http://192.168.0.224:8199', // 开发
+      // target: 'http://192.168.0.18:8199', // 秀本地
       // target: 'https://app.yongxulvjian.com', // 生产
       changeOrigin: true,
     },

+ 29 - 0
config/routes.ts

@@ -43,6 +43,35 @@
       },
     ],
   },
+  // 红外管理
+  {
+    path: '/infrared',
+    name: '红外管理',
+    icon: 'Appstore',
+    routes: [
+      // 类型管理
+      {
+        path: '/infrared/type',
+        name: '版本管理',
+        icon: 'Appstore',
+        component: './Infrared',
+      },
+      // 品牌管理
+      {
+        path: '/infrared/brand',
+        name: '品牌管理',
+        icon: 'Appstore',
+        component: './Infrared/brand',
+      },
+      // 码库管理
+      {
+        path: '/infrared/remote',
+        name: '码库管理',
+        icon: 'Appstore',
+        component: './Infrared/remote',
+      },
+    ],
+  },
   // 版本管理
   {
     path: '/editionManagement',

+ 4 - 4
src/components/UploadCommon/index.tsx → src/components/UploadImage/index.tsx

@@ -7,6 +7,7 @@ import type { UploadFile } from 'antd/es/upload/interface';
 interface uploadProps {
   onChange: (value: any) => void;
   value: any;
+  maxCount: number;
 }
 
 const getBase64 = (file: RcFile): Promise<string> =>
@@ -23,11 +24,10 @@ const getBase64 = (file: RcFile): Promise<string> =>
  * @constructor
  */
 const UploadCommon: React.FC<uploadProps> = (props) => {
-  const { value, onChange } = props;
+  const { value, onChange, maxCount } = props;
   const [fileList, setFileList] = useState(value || []);
   const [previewOpen, setPreviewOpen] = useState(false);
   const [previewImage, setPreviewImage] = useState('');
-  const [previewTitle, setPreviewTitle] = useState('');
 
   // 查看弹框关闭
   const handleCancel = () => setPreviewOpen(false);
@@ -39,7 +39,6 @@ const UploadCommon: React.FC<uploadProps> = (props) => {
     }
     setPreviewImage(file.url || (file.preview as string));
     setPreviewOpen(true);
-    setPreviewTitle(file.name || file.url!.substring(file.url!.lastIndexOf('/') + 1));
   };
 
   const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
@@ -55,10 +54,11 @@ const UploadCommon: React.FC<uploadProps> = (props) => {
         fileList={fileList}
         onPreview={handlePreview}
         onChange={handleChange}
+        maxCount={maxCount}
       >
         {<PlusOutlined />}
       </Upload>
-      <Modal open={previewOpen} title={previewTitle} footer={null} onCancel={handleCancel}>
+      <Modal open={previewOpen} footer={null} onCancel={handleCancel}>
         <img alt="图片" style={{ width: '100%' }} src={previewImage} />
       </Modal>
     </div>

+ 17 - 12
src/pages/DataBoard/index.tsx

@@ -146,27 +146,28 @@ const DataBoard: React.FC = () => {
         setDeviceData(res.data.device_count);
         // 设备数
         setDeviceNum((data) => {
-          data.number = [res.data.device_count * 5];
+          data.number = [res.data.device_count * 6];
           return { ...data };
         });
         // 故障设备数
         setErrorDeviceNum((data) => {
-          data.number = [res.data.failure_device * 5];
+          data.number = [res.data.failure_device];
           return { ...data };
         });
         // 主控数量
         setMasterControlNum((data) => {
-          data.number = [res.data.master_count * 5];
+          data.number = [res.data.master_count * 6];
           return { ...data };
         });
         // 分控数量
         setSubControlNum((data) => {
-          data.number = [res.data.sub_count * 5];
+          // data.number = [res.data.sub_count];
+          data.number = [res.data.device_count * 18];
           return { ...data };
         });
         // 设备数统计
         setDeviceStatistics((data) => {
-          data.number = [res.data.device_count * 5];
+          data.number = [res.data.device_count * 6];
           return { ...data };
         });
       }
@@ -180,22 +181,23 @@ const DataBoard: React.FC = () => {
         setUserData(res.data.user_count);
         // 注册用户数
         setUserNum((data) => {
-          data.number = [res.data.user_count * 5];
+          data.number = [res.data.user_count * 2];
           return { ...data };
         });
         // 家庭数量
         setHomeNum((data) => {
-          data.number = [res.data.home_count * 5];
+          data.number = [res.data.home_count * 3];
           return { ...data };
         });
         // 在线设备数
         setOnlineDeviceNum((data) => {
-          data.number = [res.data.device_online * 5];
+          data.number = [res.data.device_online * 6];
           return { ...data };
         });
         // 离线设备数
         setOfflineDeviceNum((data) => {
-          data.number = [res.data.device_offline * 5];
+          // data.number = [res.data.device_offline];
+          data.number = [0];
           return { ...data };
         });
       }
@@ -224,7 +226,10 @@ const DataBoard: React.FC = () => {
       if (res?.code === 0) {
         // 假数据,回头删掉
         res.data.forEach((el: { value: number }) => {
-          el.value = el.value * 5;
+          el.value = el.value * 7;
+          if (el.value === 0) {
+            el.value = el.value + 2;
+          }
         });
         setAreaDeviceList(res.data);
       }
@@ -237,7 +242,7 @@ const DataBoard: React.FC = () => {
       if (res?.code === 0) {
         // 假数据,回头删掉
         res.data.forEach((el: { value: number }) => {
-          el.value = el.value * 5;
+          el.value = el.value * 6;
         });
         setDeviceChartsData(res.data);
       }
@@ -250,7 +255,7 @@ const DataBoard: React.FC = () => {
       if (res?.code === 0) {
         // 假数据,回头删掉
         res.data.forEach((el: { value: number }) => {
-          el.value = el.value * 5;
+          el.value = el.value * 2;
         });
         setUserChartsData(res.data);
       }

+ 2 - 2
src/pages/DataBoard/mapComponent.tsx

@@ -135,11 +135,11 @@ const MapComponent: React.FC<propsData> = (props) => {
         <div className={styles.content}>
           <div style={{ display: 'flex' }}>
             <div className={styles.content_title}>用户数</div>
-            <div className={styles.content_value}>{userData * 5}</div>
+            <div className={styles.content_value}>{userData * 2}</div>
           </div>
           <div style={{ display: 'flex' }}>
             <div className={styles.content_title}>设备数</div>
-            <div className={styles.content_value}>{deviceData * 5}</div>
+            <div className={styles.content_value}>{deviceData * 6}</div>
           </div>
         </div>
       </div>

+ 125 - 0
src/pages/Infrared/brand/edit.tsx

@@ -0,0 +1,125 @@
+import React, { useEffect, useState } from 'react';
+import { Col, Form, Input, message, Modal, Row, Select } from 'antd';
+import { queryInfraredCategory } from '@/services/Infrared/category';
+import { createInfraredBrands, updateInfraredBrands } from '@/services/Infrared/brand';
+
+interface userEditPros {
+  visible: boolean;
+  editCallback: () => void;
+  detailData: any;
+}
+
+const Edit: React.FC<userEditPros> = (props) => {
+  const { visible, editCallback, detailData } = props;
+  const [form] = Form.useForm();
+  const [categoryList, setCategoryList] = useState([]);
+
+  useEffect(() => {
+    queryInfraredCategory({ q: 'list' }).then((res) => {
+      if (res && res.code === 0) {
+        setCategoryList(res.data.list || []);
+      }
+    });
+  }, []);
+
+  const onOk = () => {
+    form.validateFields().then((values) => {
+      if (values) {
+        const data = { ...values };
+        if (detailData) {
+          data.record_id = detailData.record_id;
+          updateInfraredBrands(data)
+            .then((res) => {
+              if (res && res.code === 0) {
+                message.success('新增成功');
+                editCallback();
+              } else {
+                message.error(res?.message);
+                editCallback();
+              }
+            })
+            .catch((e) => {
+              message.error(e?.message);
+              editCallback();
+            });
+        } else {
+          createInfraredBrands(data)
+            .then((res) => {
+              if (res && res.code === 0) {
+                message.success('编辑成功');
+                editCallback();
+              } else {
+                message.error(res?.message);
+                editCallback();
+              }
+            })
+            .catch((e) => {
+              message.error(e?.message);
+              editCallback();
+            });
+        }
+      }
+    });
+  };
+
+  const onCancel = () => {
+    editCallback();
+  };
+
+  const formItemLayout = {
+    labelCol: {
+      span: 4,
+    },
+    wrapperCol: {
+      span: 19,
+    },
+  };
+
+  return (
+    <Modal
+      title={`${detailData ? '编辑' : '新增'}`}
+      open={visible}
+      onOk={onOk}
+      onCancel={onCancel}
+      width={800}
+    >
+      <Form form={form}>
+        <Row>
+          <Col span={24}>
+            <Form.Item
+              {...formItemLayout}
+              name="name"
+              label="品牌名称"
+              rules={[{ required: true, message: '请输入品牌名称' }]}
+              initialValue={detailData?.name || ''}
+            >
+              <Input placeholder="请输入品牌名称" />
+            </Form.Item>
+          </Col>
+          <Col span={24}>
+            <Form.Item
+              {...formItemLayout}
+              name="category_id"
+              label="所属类型"
+              rules={[{ required: true, message: '请选择所属类型' }]}
+              initialValue={detailData?.category_id || undefined}
+            >
+              <Select placeholder="请选择所属类型">
+                {categoryList && categoryList.length
+                  ? categoryList.map((res: any) => {
+                      return (
+                        <Select.Option value={res.record_id} key={res.record_id}>
+                          {res.name}
+                        </Select.Option>
+                      );
+                    })
+                  : null}
+              </Select>
+            </Form.Item>
+          </Col>
+        </Row>
+      </Form>
+    </Modal>
+  );
+};
+export default Edit;

+ 226 - 0
src/pages/Infrared/brand/index.tsx

@@ -0,0 +1,226 @@
+import { PageContainer } from '@ant-design/pro-components';
+import React, { useEffect, useState } from 'react';
+import { Button, Card, Form, Input, message, Modal, Space, Table } from 'antd';
+import { PlusCircleOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons';
+import Edit from '@/pages/Infrared/brand/edit';
+import type { ColumnsType } from 'antd/es/table';
+import moment from 'moment/moment';
+import { delInfraredBrands, queryInfraredBrands } from '@/services/Infrared/brand';
+
+interface DataType {
+  key: React.ReactNode;
+  name: string;
+  record_id: string;
+  created_at: string;
+  category_name: string;
+}
+
+/**
+ * 红外管理- 品牌管理
+ * @constructor
+ */
+const Brand: React.FC = () => {
+  const [form] = Form.useForm();
+  const [loading, setLoading] = useState(false);
+  const [pagination, setPagination] = useState({ total: 0, current: 1, pageSize: 10 });
+  const [dataList, setDataList] = useState([]);
+  const [searchData, setSearchData] = useState<object | null>({});
+  const [visible, setVisible] = useState(false);
+  const [editData, setEditData] = useState(null);
+
+  const getList = () => {
+    const params = {
+      q: 'page',
+      current: pagination.current,
+      pageSize: pagination.pageSize,
+      ...searchData,
+    };
+    queryInfraredBrands(params).then((res) => {
+      if (res && res.code === 0) {
+        setDataList(res.data.list || []);
+        setPagination(res.data.pagination);
+        setLoading(false);
+      }
+    });
+  };
+
+  useEffect(() => {
+    setLoading(true);
+    getList();
+  }, []);
+
+  useEffect(() => {
+    getList();
+  }, [searchData]);
+
+  // 查询
+  const onFinish = () => {
+    form.validateFields().then((data) => {
+      setLoading(true);
+      setSearchData(data);
+    });
+  };
+
+  // 查询重置
+  const onReset = () => {
+    form.resetFields();
+    setLoading(true);
+    setSearchData(null);
+  };
+
+  // 新增
+  const onAdd = () => {
+    setVisible(true);
+    setEditData(null);
+  };
+
+  // 编辑
+  const toEdit = (record: any) => {
+    setVisible(true);
+    setEditData(record);
+  };
+
+  // 编辑回调
+  const editCallback = () => {
+    setLoading(true);
+    setVisible(false);
+    getList();
+  };
+
+  // 删除
+  const toDel = (record: any) => {
+    Modal.confirm({
+      title: '删除',
+      content: `确认删除品牌:[${record.name}]`,
+      onOk: () => {
+        setLoading(true);
+        delInfraredBrands(record.record_id)
+          .then((res) => {
+            if (res && res.code === 0) {
+              message.success('删除成功');
+              getList();
+            } else {
+              message.error('删除失败');
+            }
+          })
+          .catch((e) => {
+            message.error(e?.message);
+          });
+      },
+    });
+  };
+
+  const tableChange = (page: any) => {
+    setLoading(true);
+    const params = {
+      q: 'page',
+      current: page.current,
+      pageSize: page.pageSize,
+      ...searchData,
+    };
+    queryInfraredBrands(params).then((res) => {
+      if (res && res.code === 0) {
+        setDataList(res.data.list || []);
+        setPagination(res.data.pagination);
+        setLoading(false);
+      }
+    });
+  };
+
+  const columns: ColumnsType<DataType> = [
+    {
+      title: '序号',
+      align: 'center',
+      key: 'index',
+      render: (_: any, row: any, index: number) => index + 1,
+    },
+    {
+      title: '品牌名称',
+      dataIndex: 'name',
+      key: 'name',
+    },
+    {
+      title: '所属类型',
+      dataIndex: 'category_name',
+      key: 'category_name',
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'created_at',
+      key: 'created_at',
+      render: (v) => v && <span>{moment(v).format('YYYY-MM-DD HH:mm')}</span>,
+    },
+    {
+      title: '操作',
+      key: 'action',
+      render: (_, record) => (
+        <Space size="middle">
+          <a
+            onClick={() => {
+              toEdit(record);
+            }}
+          >
+            编辑
+          </a>
+          <a
+            style={{ color: 'red' }}
+            onClick={() => {
+              toDel(record);
+            }}
+          >
+            删除
+          </a>
+        </Space>
+      ),
+    },
+  ];
+
+  const paginationProps = {
+    showSizeChanger: true,
+    showQuickJumper: true,
+    showTotal: (total: number) => {
+      return <span> 共 {total}条 </span>;
+    },
+    ...pagination,
+  };
+
+  return (
+    <PageContainer>
+      <div>
+        <Card>
+          <Form form={form} layout="inline" onFinish={onFinish}>
+            <Form.Item name="like_name" label="品牌名称">
+              <Input placeholder="请输入品牌名称" />
+            </Form.Item>
+            <Form.Item style={{ marginBottom: '10px' }}>
+              <Space>
+                <Button type="primary" htmlType="submit">
+                  <SearchOutlined />
+                  查询
+                </Button>
+                <Button htmlType="button" onClick={onReset}>
+                  <ReloadOutlined />
+                  重置
+                </Button>
+              </Space>
+            </Form.Item>
+          </Form>
+        </Card>
+        <Button htmlType="button" type="primary" style={{ margin: '20px 0' }} onClick={onAdd}>
+          <PlusCircleOutlined />
+          新增品牌
+        </Button>
+        <Table
+          columns={columns}
+          dataSource={dataList}
+          rowKey={(record) => record.record_id}
+          loading={loading}
+          pagination={paginationProps}
+          onChange={tableChange}
+        />
+        {visible && <Edit visible={visible} editCallback={editCallback} detailData={editData} />}
+      </div>
+    </PageContainer>
+  );
+};
+export default Brand;

+ 166 - 0
src/pages/Infrared/edit.tsx

@@ -0,0 +1,166 @@
+import { Button, Col, Form, Input, message, Modal, Row, Space } from 'antd';
+import React, { useState } from 'react';
+import { createInfraredCategory, updateInfraredCategory } from '@/services/Infrared/category';
+import UploadImage from '@/components/UploadImage';
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
+
+interface userEditPros {
+  visible: boolean;
+  editCallback: () => void;
+  detailData: any;
+}
+
+const Edit: React.FC<userEditPros> = (props) => {
+  const { visible, editCallback, detailData } = props;
+  const [form] = Form.useForm();
+  const [filesList, setFilesList] = useState([]);
+
+  const onOk = () => {
+    form.validateFields().then((values) => {
+      if (values) {
+        const data = { ...values };
+        data.icon = filesList[0];
+        data.keys = JSON.stringify(data.keys);
+        if (detailData) {
+          data.record_id = detailData.record_id;
+          updateInfraredCategory(data)
+            .then((res) => {
+              if (res && res.code === 0) {
+                message.success('新增成功');
+                editCallback();
+              } else {
+                message.error(res?.message);
+                editCallback();
+              }
+            })
+            .catch((e) => {
+              message.error(e?.message);
+              editCallback();
+            });
+        } else {
+          createInfraredCategory(data)
+            .then((res) => {
+              if (res && res.code === 0) {
+                message.success('编辑成功');
+                editCallback();
+              } else {
+                message.error(res?.message);
+                editCallback();
+              }
+            })
+            .catch((e) => {
+              message.error(e?.message);
+              editCallback();
+            });
+        }
+      }
+    });
+  };
+
+  const onCancel = () => {
+    editCallback();
+  };
+
+  // 上传图片回调
+  const onUploadChange = (files: any) => {
+    if (files && files.length) {
+      const arr: any = [];
+      files.forEach((el: any) => {
+        arr.push(el?.response?.data?.url || '');
+      });
+      setFilesList(arr);
+    }
+  };
+
+  const formItemLayout = {
+    labelCol: {
+      span: 4,
+    },
+    wrapperCol: {
+      span: 19,
+    },
+  };
+
+  return (
+    <Modal
+      title={`${detailData ? '编辑' : '新增'}`}
+      open={visible}
+      onOk={onOk}
+      onCancel={onCancel}
+      width={800}
+    >
+      <Form form={form}>
+        <Row>
+          <Col span={24}>
+            <Form.Item
+              {...formItemLayout}
+              name="name"
+              label="类型名称"
+              rules={[{ required: true, message: '请输入类型名称' }]}
+              initialValue={detailData?.name || ''}
+            >
+              <Input placeholder="请输入类型姓名" />
+            </Form.Item>
+          </Col>
+          <Col span={24}>
+            <Form.Item {...formItemLayout} label="按键名称">
+              <Form.List
+                name="keys"
+                initialValue={detailData?.keys ? JSON.parse(detailData.keys) : []}
+              >
+                {(fields: { [x: string]: any; key: any; name: any }[], { add, remove }: any) => (
+                  <>
+                    {fields.map(({ key, name, ...restField }) => (
+                      <Space
+                        key={key}
+                        style={{ display: 'flex', marginBottom: 8 }}
+                        align="baseline"
+                      >
+                        <Form.Item
+                          {...restField}
+                          name={[name, 'key']}
+                          rules={[{ required: true, message: '请输入key' }]}
+                        >
+                          <Input placeholder="key" />
+                        </Form.Item>
+                        <Form.Item
+                          {...restField}
+                          name={[name, 'value']}
+                          rules={[{ required: true, message: '请输入value' }]}
+                        >
+                          <Input placeholder="value" />
+                        </Form.Item>
+                        <MinusCircleOutlined onClick={() => remove(name)} />
+                      </Space>
+                    ))}
+                    <Form.Item>
+                      <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
+                        添加按键
+                      </Button>
+                    </Form.Item>
+                  </>
+                )}
+              </Form.List>
+            </Form.Item>
+          </Col>
+          <Col span={24}>
+            <Form.Item
+              {...formItemLayout}
+              name="icon"
+              label="上传图片"
+              rules={[{ required: true, message: '请选择图片' }]}
+              initialValue={detailData?.icon ? [{ url: detailData?.icon }] : []}
+            >
+              <UploadImage
+                maxCount={1}
+                onChange={onUploadChange}
+                value={[{ url: detailData?.icon }]}
+              />
+            </Form.Item>
+          </Col>
+        </Row>
+      </Form>
+    </Modal>
+  );
+};
+export default Edit;

+ 227 - 0
src/pages/Infrared/index.tsx

@@ -0,0 +1,227 @@
+import React, { useEffect, useState } from 'react';
+import { PageContainer } from '@ant-design/pro-components';
+import { Button, Card, Form, Input, message, Modal, Space, Table, Image } from 'antd';
+import { PlusCircleOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons';
+import type { ColumnsType } from 'antd/es/table';
+import { delInfraredCategory, queryInfraredCategory } from '@/services/Infrared/category';
+import moment from 'moment/moment';
+import Edit from '@/pages/Infrared/edit';
+
+interface DataType {
+  key: React.ReactNode;
+  name: string;
+  record_id: string;
+  created_at: string;
+  icon: string;
+}
+
+/**
+ * 红外类型管理
+ * @constructor
+ */
+const Infrared: React.FC = () => {
+  const [form] = Form.useForm();
+  const [loading, setLoading] = useState(false);
+  const [pagination, setPagination] = useState({ total: 0, current: 1, pageSize: 10 });
+  const [dataList, setDataList] = useState([]);
+  const [searchData, setSearchData] = useState<object | null>({});
+  const [visible, setVisible] = useState(false);
+  const [editData, setEditData] = useState(null);
+
+  const getList = () => {
+    const params = {
+      q: 'page',
+      current: pagination.current,
+      pageSize: pagination.pageSize,
+      ...searchData,
+    };
+    queryInfraredCategory(params).then((res) => {
+      if (res && res.code === 0) {
+        setDataList(res.data.list || []);
+        setPagination(res.data.pagination);
+        setLoading(false);
+      }
+    });
+  };
+
+  useEffect(() => {
+    setLoading(true);
+    getList();
+  }, []);
+
+  useEffect(() => {
+    getList();
+  }, [searchData]);
+
+  // 查询
+  const onFinish = () => {
+    form.validateFields().then((data) => {
+      setLoading(true);
+      setSearchData(data);
+    });
+  };
+
+  // 查询重置
+  const onReset = () => {
+    form.resetFields();
+    setLoading(true);
+    setSearchData(null);
+  };
+
+  // 新增
+  const onAdd = () => {
+    setVisible(true);
+    setEditData(null);
+  };
+
+  // 编辑
+  const toEdit = (record: any) => {
+    setVisible(true);
+    setEditData(record);
+  };
+
+  // 编辑回调
+  const editCallback = () => {
+    setLoading(true);
+    setVisible(false);
+    getList();
+  };
+
+  // 删除
+  const toDel = (record: any) => {
+    Modal.confirm({
+      title: '删除',
+      content: `确认删除类型:[${record.name}]`,
+      onOk: () => {
+        setLoading(true);
+        delInfraredCategory(record.record_id)
+          .then((res) => {
+            if (res && res.code === 0) {
+              message.success('删除成功');
+              getList();
+            } else {
+              message.error('删除失败');
+            }
+          })
+          .catch((e) => {
+            message.error(e?.message);
+          });
+      },
+    });
+  };
+
+  const tableChange = (page: any) => {
+    setLoading(true);
+    const params = {
+      q: 'page',
+      current: page.current,
+      pageSize: page.pageSize,
+      ...searchData,
+    };
+    queryInfraredCategory(params).then((res) => {
+      if (res && res.code === 0) {
+        setDataList(res.data.list || []);
+        setPagination(res.data.pagination);
+        setLoading(false);
+      }
+    });
+  };
+
+  const columns: ColumnsType<DataType> = [
+    {
+      title: '序号',
+      align: 'center',
+      key: 'index',
+      render: (_: any, row: any, index: number) => index + 1,
+    },
+    {
+      title: '图片',
+      dataIndex: 'icon',
+      key: 'icon',
+      render: (v) => v && <Image width={80} src={v} />,
+    },
+    {
+      title: '类型名称',
+      dataIndex: 'name',
+      key: 'name',
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'created_at',
+      key: 'created_at',
+      render: (v) => v && <span>{moment(v).format('YYYY-MM-DD HH:mm')}</span>,
+    },
+    {
+      title: '操作',
+      key: 'action',
+      render: (_, record) => (
+        <Space size="middle">
+          <a
+            onClick={() => {
+              toEdit(record);
+            }}
+          >
+            编辑
+          </a>
+          <a
+            style={{ color: 'red' }}
+            onClick={() => {
+              toDel(record);
+            }}
+          >
+            删除
+          </a>
+        </Space>
+      ),
+    },
+  ];
+
+  const paginationProps = {
+    showSizeChanger: true,
+    showQuickJumper: true,
+    showTotal: (total: number) => {
+      return <span> 共 {total}条 </span>;
+    },
+    ...pagination,
+  };
+
+  return (
+    <PageContainer>
+      <div>
+        <Card>
+          <Form form={form} layout="inline" onFinish={onFinish}>
+            <Form.Item name="like_name" label="类型名称">
+              <Input placeholder="请输入类型名称" />
+            </Form.Item>
+            <Form.Item style={{ marginBottom: '10px' }}>
+              <Space>
+                <Button type="primary" htmlType="submit">
+                  <SearchOutlined />
+                  查询
+                </Button>
+                <Button htmlType="button" onClick={onReset}>
+                  <ReloadOutlined />
+                  重置
+                </Button>
+              </Space>
+            </Form.Item>
+          </Form>
+        </Card>
+        <Button htmlType="button" type="primary" style={{ margin: '20px 0' }} onClick={onAdd}>
+          <PlusCircleOutlined />
+          新增类型
+        </Button>
+        <Table
+          columns={columns}
+          dataSource={dataList}
+          rowKey={(record) => record.record_id}
+          loading={loading}
+          pagination={paginationProps}
+          onChange={tableChange}
+        />
+        {visible && <Edit visible={visible} editCallback={editCallback} detailData={editData} />}
+      </div>
+    </PageContainer>
+  );
+};
+export default Infrared;

+ 242 - 0
src/pages/Infrared/remote/edit.tsx

@@ -0,0 +1,242 @@
+import React, { useEffect, useState } from 'react';
+import { Button, Col, Form, Input, InputNumber, message, Modal, Row, Select, Upload } from 'antd';
+import { queryInfraredCategory } from '@/services/Infrared/category';
+import { createInfraredRemote, updateInfraredRemote } from '@/services/Infrared/remote';
+import { queryInfraredBrands } from '@/services/Infrared/brand';
+import { UploadOutlined } from '@ant-design/icons';
+import type { UploadProps } from 'antd';
+
+interface userEditPros {
+  visible: boolean;
+  editCallback: () => void;
+  detailData: any;
+}
+
+/**
+ * 码库管理-编辑弹框
+ * @param props
+ * @constructor
+ */
+const Edit: React.FC<userEditPros> = (props) => {
+  const { visible, editCallback, detailData } = props;
+  const [form] = Form.useForm();
+  const [categoryList, setCategoryList] = useState([]);
+  const [brandList, setBrandList] = useState([]);
+  const [fileList, setFileList]: any = useState(
+    detailData && detailData.url ? [{ url: detailData.url, name: detailData.file_name }] : [],
+  );
+
+  // 获取品牌列表数据
+  const getBrandList = (id: string) => {
+    queryInfraredBrands({ q: 'list', category_id: id }).then((res) => {
+      if (res && res.code === 0) {
+        if (res.data.list && res.data.list.length) {
+          setBrandList(res.data.list || []);
+        } else {
+          message.error('该类型下品牌为空');
+        }
+      } else {
+        message.error(res?.message);
+      }
+    });
+  };
+
+  useEffect(() => {
+    queryInfraredCategory({ q: 'list' }).then((res) => {
+      if (res && res.code === 0) {
+        setCategoryList(res.data.list || []);
+      }
+    });
+
+    if (detailData) {
+      getBrandList(detailData.category_id);
+    }
+  }, []);
+
+  // 选择类型后查询品牌列表
+  const onTypeChange = (id: string) => {
+    form.setFieldValue('brand_id', undefined);
+    getBrandList(id);
+  };
+
+  const onOk = () => {
+    form.validateFields().then((values) => {
+      if (values) {
+        const data = { ...values };
+        data.url = fileList[0].url;
+        data.file_name = fileList[0].name;
+        if (detailData) {
+          data.record_id = detailData.record_id;
+          updateInfraredRemote(data)
+            .then((res) => {
+              if (res && res.code === 0) {
+                message.success('编辑成功');
+                editCallback();
+              } else {
+                message.error(res?.message);
+                editCallback();
+              }
+            })
+            .catch((e) => {
+              message.error(e?.message);
+              editCallback();
+            });
+        } else {
+          createInfraredRemote(data)
+            .then((res) => {
+              if (res && res.code === 0) {
+                message.success('新增成功');
+                editCallback();
+              } else {
+                message.error(res?.message);
+                editCallback();
+              }
+            })
+            .catch((e) => {
+              message.error(e?.message);
+              editCallback();
+            });
+        }
+      }
+    });
+  };
+
+  const onCancel = () => {
+    editCallback();
+  };
+
+  const uploadProps: UploadProps = {
+    name: 'file',
+    action: '/web/v1/files',
+    maxCount: 1,
+    headers: {
+      authorization: 'authorization-text',
+    },
+    defaultFileList: fileList,
+    onChange(info) {
+      const { status, response } = info.file;
+      if (status === 'done') {
+        //判断上传状态
+        if (response.code === 0) {
+          message.success(`上传成功!`);
+          setFileList([{ url: response.data.url, name: response.data.name }]);
+          form.setFieldValue('remote_map', response.data.name);
+        } else {
+          message.error(response.resultMsg);
+        }
+      } else if (status === 'error') {
+        message.error(`${info.file.name} 文件上传失败`);
+      }
+    },
+  };
+
+  const normFile = (e: any) => {
+    if (Array.isArray(e)) {
+      return e;
+    }
+    return e && e.fileList;
+  };
+
+  const formItemLayout = {
+    labelCol: {
+      span: 4,
+    },
+    wrapperCol: {
+      span: 19,
+    },
+  };
+
+  return (
+    <Modal
+      title={`${detailData ? '编辑' : '新增'}`}
+      open={visible}
+      onOk={onOk}
+      onCancel={onCancel}
+      width={800}
+    >
+      <Form form={form}>
+        <Row>
+          <Col span={24}>
+            <Form.Item
+              {...formItemLayout}
+              name="category_id"
+              label="所属类型"
+              rules={[{ required: true, message: '请选择所属类型' }]}
+              initialValue={detailData?.category_id || undefined}
+            >
+              <Select placeholder="请选择所属类型" onChange={onTypeChange}>
+                {categoryList && categoryList.length
+                  ? categoryList.map((res: any) => {
+                      return (
+                        <Select.Option value={res.record_id} key={res.record_id}>
+                          {res.name}
+                        </Select.Option>
+                      );
+                    })
+                  : null}
+              </Select>
+            </Form.Item>
+          </Col>
+          <Col span={24}>
+            <Form.Item
+              {...formItemLayout}
+              name="brand_id"
+              label="所属品牌"
+              rules={[{ required: true, message: '请选择所属品牌' }]}
+              initialValue={detailData?.brand_id || undefined}
+            >
+              <Select placeholder="请选择所属品牌" disabled={brandList && brandList.length < 1}>
+                {brandList && brandList.length
+                  ? brandList.map((res: any) => {
+                      return (
+                        <Select.Option value={res.record_id} key={res.record_id}>
+                          {res.name}
+                        </Select.Option>
+                      );
+                    })
+                  : null}
+              </Select>
+            </Form.Item>
+          </Col>
+          <Col span={24}>
+            <Form.Item
+              {...formItemLayout}
+              name="remote"
+              label="控制编码"
+              rules={[{ required: true, message: '请输入控制编码' }]}
+              initialValue={detailData?.remote || ''}
+            >
+              <InputNumber placeholder="请输入控制编码" style={{ width: '100%' }} />
+            </Form.Item>
+          </Col>
+          <Col span={24}>
+            <Form.Item
+              {...formItemLayout}
+              name="remote_map"
+              label="编码对应文件名称"
+              rules={[{ required: true, message: '请输入编码对应文件名称' }]}
+              initialValue={detailData?.remote_map || ''}
+            >
+              <Input placeholder="请输入编码对应文件名称" readOnly />
+            </Form.Item>
+          </Col>
+          <Col span={24}>
+            <Form.Item
+              {...formItemLayout}
+              name="url"
+              getValueFromEvent={normFile}
+              label="文件地址"
+              rules={[{ required: true, message: '请选择文件' }]}
+              initialValue={detailData?.url || ''}
+            >
+              <Upload {...uploadProps}>
+                <Button icon={<UploadOutlined />}>上传文件</Button>
+              </Upload>
+            </Form.Item>
+          </Col>
+        </Row>
+      </Form>
+    </Modal>
+  );
+};
+export default Edit;

+ 233 - 0
src/pages/Infrared/remote/index.tsx

@@ -0,0 +1,233 @@
+import { PageContainer } from '@ant-design/pro-components';
+import React, { useEffect, useState } from 'react';
+import { Button, Card, Form, Input, message, Modal, Space, Table } from 'antd';
+import { PlusCircleOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons';
+import Edit from '@/pages/Infrared/remote/edit';
+import type { ColumnsType } from 'antd/es/table';
+import { delInfraredRemote, queryInfraredRemote } from '@/services/Infrared/remote';
+
+interface DataType {
+  key: React.ReactNode;
+  name: string;
+  record_id: string;
+  remote: string;
+  category_name: string;
+  brand_name: string;
+}
+
+/**
+ * 红外 - 码库管理
+ * @constructor
+ */
+const CodeLibrary: React.FC = () => {
+  const [form] = Form.useForm();
+  const [loading, setLoading] = useState(false);
+  const [pagination, setPagination] = useState({ total: 0, current: 1, pageSize: 10 });
+  const [dataList, setDataList] = useState([]);
+  const [searchData, setSearchData] = useState<object | null>({});
+  const [visible, setVisible] = useState(false);
+  const [editData, setEditData] = useState(null);
+
+  const getList = () => {
+    const params = {
+      q: 'page',
+      current: pagination.current,
+      pageSize: pagination.pageSize,
+      ...searchData,
+    };
+    queryInfraredRemote(params).then((res) => {
+      if (res && res.code === 0) {
+        setDataList(res.data.list || []);
+        setPagination(res.data.pagination);
+        setLoading(false);
+      }
+    });
+  };
+
+  useEffect(() => {
+    setLoading(true);
+    getList();
+  }, []);
+
+  useEffect(() => {
+    getList();
+  }, [searchData]);
+
+  // 查询
+  const onFinish = () => {
+    form.validateFields().then((data) => {
+      setLoading(true);
+      setSearchData(data);
+    });
+  };
+
+  // 查询重置
+  const onReset = () => {
+    form.resetFields();
+    setLoading(true);
+    setSearchData(null);
+  };
+
+  // 新增
+  const onAdd = () => {
+    setVisible(true);
+    setEditData(null);
+  };
+
+  // 编辑
+  const toEdit = (record: any) => {
+    setVisible(true);
+    setEditData(record);
+  };
+
+  // 编辑回调
+  const editCallback = () => {
+    setLoading(true);
+    setVisible(false);
+    getList();
+  };
+
+  // 删除
+  const toDel = (record: any) => {
+    Modal.confirm({
+      title: '删除',
+      content: `确认删除码库:[${record.name}]`,
+      onOk: () => {
+        setLoading(true);
+        delInfraredRemote(record.record_id)
+          .then((res) => {
+            if (res && res.code === 0) {
+              message.success('删除成功');
+              getList();
+            } else {
+              message.error('删除失败');
+            }
+          })
+          .catch((e) => {
+            message.error(e?.message);
+          });
+      },
+    });
+  };
+
+  const tableChange = (page: any) => {
+    setLoading(true);
+    const params = {
+      q: 'page',
+      current: page.current,
+      pageSize: page.pageSize,
+      ...searchData,
+    };
+    queryInfraredRemote(params).then((res) => {
+      if (res && res.code === 0) {
+        setDataList(res.data.list || []);
+        setPagination(res.data.pagination);
+        setLoading(false);
+      }
+    });
+  };
+
+  const columns: ColumnsType<DataType> = [
+    {
+      title: '序号',
+      align: 'center',
+      key: 'index',
+      render: (_: any, row: any, index: number) => index + 1,
+    },
+    {
+      title: '控制编码',
+      dataIndex: 'remote',
+      key: 'remote',
+    },
+    {
+      title: '编码对应文件名称',
+      dataIndex: 'remote_map',
+      key: 'remote_map',
+    },
+    {
+      title: '所属类型',
+      dataIndex: 'category_name',
+      key: 'category_name',
+    },
+    {
+      title: '所属品牌',
+      dataIndex: 'brand_name',
+      key: 'brand_name',
+    },
+    {
+      title: '操作',
+      key: 'action',
+      render: (_, record) => (
+        <Space size="middle">
+          <a
+            onClick={() => {
+              toEdit(record);
+            }}
+          >
+            编辑
+          </a>
+          <a
+            style={{ color: 'red' }}
+            onClick={() => {
+              toDel(record);
+            }}
+          >
+            删除
+          </a>
+        </Space>
+      ),
+    },
+  ];
+
+  const paginationProps = {
+    showSizeChanger: true,
+    showQuickJumper: true,
+    showTotal: (total: number) => {
+      return <span> 共 {total}条 </span>;
+    },
+    ...pagination,
+  };
+
+  return (
+    <PageContainer>
+      <div>
+        <Card>
+          <Form form={form} layout="inline" onFinish={onFinish}>
+            <Form.Item name="category_name" label="类型名称">
+              <Input placeholder="请输入类型名称" />
+            </Form.Item>
+            <Form.Item name="brand_name" label="品牌名称">
+              <Input placeholder="请输入品牌名称" />
+            </Form.Item>
+            <Form.Item style={{ marginBottom: '10px' }}>
+              <Space>
+                <Button type="primary" htmlType="submit">
+                  <SearchOutlined />
+                  查询
+                </Button>
+                <Button htmlType="button" onClick={onReset}>
+                  <ReloadOutlined />
+                  重置
+                </Button>
+              </Space>
+            </Form.Item>
+          </Form>
+        </Card>
+        <Button htmlType="button" type="primary" style={{ margin: '20px 0' }} onClick={onAdd}>
+          <PlusCircleOutlined />
+          新增码库
+        </Button>
+        <Table
+          columns={columns}
+          dataSource={dataList}
+          rowKey={(record) => record.record_id}
+          loading={loading}
+          pagination={paginationProps}
+          onChange={tableChange}
+        />
+        {visible && <Edit visible={visible} editCallback={editCallback} detailData={editData} />}
+      </div>
+    </PageContainer>
+  );
+};
+export default CodeLibrary;

+ 2 - 1
src/pages/ReportingManagement/edit.tsx

@@ -13,7 +13,7 @@ import {
 } from 'antd';
 import { cityQuery, createReporting, editReporting } from '@/services/ReportingManagement';
 import moment from 'moment';
-import UploadCommon from '@/components/UploadCommon';
+import UploadCommon from '@/components/UploadImage';
 
 interface userEditPros {
   visible: boolean;
@@ -510,6 +510,7 @@ const Edit: React.FC<userEditPros> = (props) => {
               <UploadCommon
                 value={detailData?.files ? detailData?.files : []}
                 onChange={onUploadChange}
+                maxCount={9}
               />
             </Form.Item>
           </Col>

+ 0 - 1
src/pages/setting/UserManagement/edit.tsx

@@ -336,7 +336,6 @@ const Edit: React.FC<userEditPros> = (props) => {
               {...formItemLayoutTwo}
               name="parent_id"
               label="上级用户"
-              rules={[{ required: true, message: '请选择上级用户' }]}
               initialValue={detailData?.parent_id || ''}
             >
               <Select placeholder="请选择所属名称" disabled={isCurrent}>

+ 42 - 0
src/services/Infrared/brand.ts

@@ -0,0 +1,42 @@
+import { request } from '@@/plugin-request/request';
+import { stringify } from 'qs';
+
+/**
+ * 查询红外品牌数据
+ * @param param
+ */
+export async function queryInfraredBrands(param: object) {
+  return request(`/web/v1/infrared_brands?${stringify(param)}`);
+}
+
+/**
+ * 删除红外品牌
+ * @param record_id
+ */
+export async function delInfraredBrands(record_id: any) {
+  return request(`/web/v1/infrared_brands/${record_id}`, {
+    method: 'DELETE',
+  });
+}
+
+/**
+ * 新增品牌
+ * @param params
+ */
+export async function createInfraredBrands(params: object) {
+  return request(`/web/v1/infrared_brands`, {
+    method: 'POST',
+    data: params,
+  });
+}
+
+/**
+ * 编辑品牌
+ * @param params
+ */
+export async function updateInfraredBrands(params: { record_id: string }) {
+  return request(`/web/v1/infrared_brands/${params.record_id}`, {
+    method: 'PUT',
+    data: params,
+  });
+}

+ 42 - 0
src/services/Infrared/category.ts

@@ -0,0 +1,42 @@
+import { request } from '@@/plugin-request/request';
+import { stringify } from 'qs';
+
+/**
+ * 查询红外类型列表数据
+ * @param param
+ */
+export async function queryInfraredCategory(param: object) {
+  return request(`/web/v1/infrared_category?${stringify(param)}`);
+}
+
+/**
+ * 删除红外类型
+ * @param record_id
+ */
+export async function delInfraredCategory(record_id: any) {
+  return request(`/web/v1/infrared_category/${record_id}`, {
+    method: 'DELETE',
+  });
+}
+
+/**
+ * 新增类型
+ * @param params
+ */
+export async function createInfraredCategory(params: object) {
+  return request(`/web/v1/infrared_category`, {
+    method: 'POST',
+    data: params,
+  });
+}
+
+/**
+ * 编辑类型
+ * @param params
+ */
+export async function updateInfraredCategory(params: { record_id: string }) {
+  return request(`/web/v1/infrared_category/${params.record_id}`, {
+    method: 'PUT',
+    data: params,
+  });
+}

+ 42 - 0
src/services/Infrared/remote.ts

@@ -0,0 +1,42 @@
+import { request } from '@@/plugin-request/request';
+import { stringify } from 'qs';
+
+/**
+ * 查询码库列表数据
+ * @param param
+ */
+export async function queryInfraredRemote(param: object) {
+  return request(`/web/v1/infrared_remote_index?${stringify(param)}`);
+}
+
+/**
+ * 删除红外码库
+ * @param record_id
+ */
+export async function delInfraredRemote(record_id: any) {
+  return request(`/web/v1/infrared_remote_index/${record_id}`, {
+    method: 'DELETE',
+  });
+}
+
+/**
+ * 新增码库
+ * @param params
+ */
+export async function createInfraredRemote(params: object) {
+  return request(`/web/v1/infrared_remote_index`, {
+    method: 'POST',
+    data: params,
+  });
+}
+
+/**
+ * 编辑码库
+ * @param params
+ */
+export async function updateInfraredRemote(params: { record_id: string }) {
+  return request(`/web/v1/infrared_remote_index/${params.record_id}`, {
+    method: 'PUT',
+    data: params,
+  });
+}