Browse Source

feat:新增轮播图

lvkun 3 years ago
parent
commit
480ea28fd0

+ 25 - 8
config/config.ts

@@ -86,11 +86,10 @@ export default defineConfig({
             {
               path: '/agent',
               name: '代理商管理',
-              icon: 'dashboard',
+              icon: 'UserOutlined',
               routes: [
                 {
                   name: '代理商列表',
-                  icon: 'smile',
                   path: '/agent/list',
                   component: './agent/list',
                 },
@@ -99,7 +98,7 @@ export default defineConfig({
             {
               path: '/tieba',
               name: '贴吧管理',
-              icon: 'dashboard',
+              icon: 'HomeOutlined',
               routes: [
                 {
                   name: '分类管理',
@@ -123,14 +122,20 @@ export default defineConfig({
             },
             {
               path: '/notice',
-              name: '公告管理',
-              icon: 'dashboard',
-              component: './notice',
+              name: '通知管理',
+              icon: 'MessageOutlined',
+              routes: [
+                {
+                  name: '公告管理',
+                  path: '/notice/notice',
+                  component: './notice',
+                },
+              ],
             },
             {
               path: '/club',
               name: '俱乐部管理',
-              icon: 'dashboard',
+              icon: 'CoffeeOutlined',
               component: './club',
             },
             {
@@ -142,7 +147,7 @@ export default defineConfig({
             {
               path: '/complaint',
               name: '投诉管理',
-              icon: 'dashboard',
+              icon: 'AlertOutlined',
               routes: [
                 {
                   name: '帖子列表',
@@ -152,6 +157,18 @@ export default defineConfig({
                 },
               ],
             },
+            {
+              path: '/setting',
+              name: '设置',
+              icon: 'SettingOutlined',
+              routes: [
+                {
+                  name: '首页轮播图',
+                  path: '/setting/banner',
+                  component: './setting/banner/index',
+                },
+              ],
+            },
           ],
         },
       ],

+ 6 - 0
package.json

@@ -58,6 +58,7 @@
     "@types/lodash.isequal": "^4.5.5",
     "@umijs/route-utils": "^1.0.33",
     "antd": "^4.9.4",
+    "array-move": "^4.0.0",
     "bizcharts": "^3.5.3-beta.0",
     "bizcharts-plugin-slider": "^2.1.1-beta.1",
     "braft-editor": "^2.3.9",
@@ -65,6 +66,7 @@
     "dva": "^2.4.0",
     "gg-editor": "^2.0.2",
     "immer": "^9.0.3",
+    "immutability-helper": "^3.1.1",
     "lodash": "^4.17.11",
     "lodash-decorators": "^6.0.0",
     "lodash.debounce": "^4.0.8",
@@ -78,10 +80,13 @@
     "qs": "^6.9.0",
     "react": "^17.0.0",
     "react-dev-inspector": "^1.1.1",
+    "react-dnd": "^14.0.2",
+    "react-dnd-html5-backend": "^14.0.0",
     "react-dom": "^17.0.0",
     "react-fittext": "^1.0.0",
     "react-helmet-async": "^1.0.4",
     "react-router": "^4.3.1",
+    "react-sortable-hoc": "^2.0.0",
     "umi": "^3.2.14",
     "umi-request": "^1.0.8",
     "wangeditor": "^4.7.1",
@@ -120,6 +125,7 @@
     "pro-download": "1.0.1",
     "puppeteer-core": "^5.0.0",
     "stylelint": "^13.0.0",
+    "swiper": "^6.8.1",
     "typescript": "^4.0.3"
   },
   "engines": {

+ 1 - 1
src/models/user.ts

@@ -86,7 +86,7 @@ const UserModel: UserModelType = {
     saveRoleList(state, action) {
       return {
         ...state,
-        roleList: action.payload || [],
+        roleList: action.payload.slice(1) || [],
       };
     },
 

+ 0 - 27
src/pages/agent/agent-detail/index.tsx

@@ -1,27 +0,0 @@
-import React, { useEffect, useState } from 'react'
-import type { RouteChildrenProps } from  'react-router'
-import { getAgentDetail } from '@/services/agent'
-
-const AgentDetail: React.FC<RouteChildrenProps> = ({ location }) => {
-    const query = 'query'
-    
-    const GetAgentDetail = async () => {
-       const {code, data} =  await getAgentDetail(location[query].id)
-       if (code === 0) {
-           console.log(data);
-       }
-    } 
-
-    useEffect( () => {
-        GetAgentDetail()
-    }, [])
-
-    return (
-        <>
-          2222
-        </>
-    )
-}
-
-
-export default AgentDetail

+ 45 - 0
src/pages/setting/banner/common.less

@@ -0,0 +1,45 @@
+// @import "~swiper/dist/css/swiper.css";//样式文件
+
+// .ant-carousel .slick-prev,
+// .ant-carousel .slick-next,
+// .ant-carousel .slick-prev:hover,
+// .ant-carousel .slick-next:hover {
+//   font-size: 40px;
+//   position: absolute;
+//   right: 40px;
+//   z-index: 10;
+//   color:#fff;
+// }
+
+// .ant-carousel .slick-prev:hover {
+//   font-size: 40px;
+//   position: absolute;
+//   left: 40px;
+//   z-index: 10;
+//   color:#fff;
+// }
+
+// .ant-carousel .slick-list {
+//   margin-left: 24px;
+// }
+
+// .ant-carousel .slick-prev,
+// .ant-carousel .slick-next {
+//   font-size: 40px;
+//   position: absolute;
+//   right: 40px;
+//   z-index: 10;
+//   color:#fff;
+// }
+
+#components-table-demo-drag-sorting tr.drop-over-downward td {
+  border-bottom: 2px dashed #1890ff;
+}
+
+#components-table-demo-drag-sorting tr.drop-over-upward td {
+  border-top: 2px dashed #1890ff;
+}
+
+// .ant-modal-body {
+//   background-color: #999;
+// }

+ 345 - 0
src/pages/setting/banner/index.tsx

@@ -0,0 +1,345 @@
+import React, { useCallback, useEffect, useState, memo, useRef } from 'react';
+
+import { PageContainer } from '@ant-design/pro-layout';
+
+import { Card, Row, Col, Table, Button, message, Space, Tag, Image, Popconfirm } from 'antd';
+
+import { ConnectState } from '@/models/connect';
+
+import { connect, Dispatch } from 'umi';
+
+import { IBanner } from './types';
+
+import { Role } from '@/models/user';
+
+import { getBanner, deleteBanner, opraBanner } from '@/services/notice';
+
+import { DndProvider, useDrag, useDrop } from 'react-dnd';
+
+import { HTML5Backend } from 'react-dnd-html5-backend';
+
+import update from 'immutability-helper';
+
+import BannerDrawer from './modal';
+
+import SwiperModal from './swiper';
+
+import './common.less';
+
+const type = 'DraggableBodyRow';
+
+/**@ts-ignore */
+
+const DraggableBodyRow = ({ index, moveRow, className, style, ...restProps }) => {
+  const ref = useRef();
+  const [{ isOver, dropClassName }, drop] = useDrop({
+    accept: type,
+    collect: (monitor) => {
+      const { index: dragIndex } = monitor.getItem() || {};
+      if (dragIndex === index) {
+        return {};
+      }
+      return {
+        isOver: monitor.isOver(),
+        dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
+      };
+    },
+    drop: (item) => {
+      moveRow(item.index, index);
+    },
+  });
+  const [, drag] = useDrag({
+    type,
+    item: { index },
+    collect: (monitor) => ({
+      isDragging: monitor.isDragging(),
+    }),
+  });
+  drop(drag(ref));
+
+  return (
+    <tr
+      ref={ref}
+      className={`${className}${isOver ? dropClassName : ''}`}
+      style={{ cursor: 'move', ...style }}
+      {...restProps}
+    />
+  );
+};
+
+interface IProps {
+  dispatch: Dispatch;
+  roleList: Role[];
+}
+
+interface IState {
+  roleId: string;
+  visible: boolean;
+  bannerList: IBanner[];
+  loading: boolean;
+  modalVisible: boolean;
+}
+
+const MemoBannerDrawer = memo(BannerDrawer);
+
+const MemoSwiperModal = memo(SwiperModal);
+
+const Banner: React.FC<IProps> = ({ roleList, dispatch }) => {
+  const [state, setState] = useState<IState>({
+    roleId: '',
+    visible: false,
+    modalVisible: false,
+    loading: false,
+    bannerList: [],
+  });
+
+  const { roleId, visible, bannerList, loading, modalVisible } = state;
+
+  // 请求身份分类
+  useEffect(() => {
+    dispatch({
+      type: 'user/getRoleList',
+      payload: {
+        curPage: 1,
+        passSize: 100,
+      },
+    });
+  }, []);
+
+  // 设置默认的roleId
+  useEffect(() => {
+    if (roleList.length === 0) return;
+
+    setState({
+      ...state,
+      roleId: roleList[0].id,
+    });
+  }, [roleList]);
+
+  useEffect(() => {
+    if (!roleId) return;
+    GetBanner();
+  }, [roleId]);
+
+  // 打开或者关闭新增轮播图面板
+  const opraModal = (_state: boolean, reloadTable = false) => {
+    console.log(_state, '_state');
+
+    setState({
+      ...state,
+      visible: _state,
+    });
+
+    if (reloadTable) {
+      GetBanner();
+    }
+  };
+
+  // 查询banner
+  const GetBanner = async () => {
+    setState({
+      ...state,
+      visible: false,
+      loading: true,
+    });
+    const { status, data } = await getBanner(roleId);
+    console.log(status, data);
+    if (status === 200) {
+      setState({
+        ...state,
+        visible: false,
+        loading: false,
+        bannerList: data.map((item: IBanner, index: number) => {
+          return {
+            ...item,
+            sort: index + 1,
+          };
+        }),
+      });
+    }
+  };
+
+  // 删除banner
+  const DeleteBanner = async (id: string) => {
+    const { status, data } = await deleteBanner(id);
+    console.log(status, data);
+    if (status === 200 && data) {
+      GetBanner();
+    }
+  };
+
+  // 对banner进行添加或者修改
+  const OpraBanner = async (_bannerList: IBanner[]) => {
+    const { status, data } = await opraBanner(_bannerList, roleId);
+
+    if (status === 200 && data) {
+      message.success('轮播顺序已调整');
+    }
+  };
+
+  // 打开预览弹窗
+  const opraPreviewModal = (_visible: boolean) => {
+    setState({
+      ...state,
+      modalVisible: _visible,
+    });
+  };
+
+  const columns = [
+    {
+      title: '封面',
+      dataIndex: 'imgUrl',
+      key: 'imgUrl',
+      render: (text: any, record: IBanner) => (
+        <Image
+          src={record.imgUrl}
+          width={150}
+          height={80}
+          alt="我炸裂了"
+          style={{ objectFit: 'cover' }}
+        />
+      ),
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createTime',
+      key: 'createTime',
+    },
+    {
+      title: '轮播顺序',
+      dataIndex: 'sort',
+      key: 'sort',
+    },
+    {
+      title: '创建人',
+      dataIndex: 'username',
+      key: 'username',
+    },
+    {
+      title: '是否启用',
+      dataIndex: 'isShow',
+      key: 'isShow',
+      render: (text: any, record: IBanner) => (
+        <Tag color={record.isShow ? 'blue' : 'warning'}>{record.isShow ? '启用' : '禁用'}</Tag>
+      ),
+    },
+    {
+      title: '操作',
+      dataIndex: 'action',
+      key: 'action',
+      render: (text: any, record: IBanner) => (
+        <>
+          <Space>
+            <a onClick={() => opraModal(true)}>查看</a>{' '}
+          </Space>
+          <Space>
+            <Popconfirm title="确定要删除这张轮播图吗" onConfirm={() => DeleteBanner(record.id)}>
+              <a>删除</a>
+            </Popconfirm>
+          </Space>
+        </>
+      ),
+    },
+  ];
+
+  const components = {
+    body: {
+      row: DraggableBodyRow,
+    },
+  };
+
+  const moveRow = useCallback(
+    (dragIndex, hoverIndex) => {
+      const dragRow = bannerList[dragIndex];
+      console.log(dragRow, 'dragRowdragRow');
+      const r = update(bannerList, {
+        $splice: [
+          [dragIndex, 1],
+          [hoverIndex, 0, dragRow],
+        ],
+      });
+
+      const $par = r.map((_banner, index) => {
+        return {
+          ..._banner,
+          sort: index + 1,
+        };
+      });
+
+      console.log(r, '我是r');
+      OpraBanner($par);
+      setState({
+        ...state,
+        bannerList: $par,
+      });
+    },
+    [bannerList],
+  );
+
+  return (
+    <PageContainer
+      title="轮播图管理"
+      tabActiveKey={roleId}
+      tabList={roleList.map((role) => {
+        return {
+          key: role.id,
+          tab: role.label,
+        };
+      })}
+      onTabChange={(activeKey) => {
+        setState({ ...state, roleId: activeKey });
+      }}
+    >
+      <Card
+        extra={
+          <Row gutter={6}>
+            <Col>
+              {' '}
+              <Button type="primary" onClick={() => opraPreviewModal(true)}>
+                预览
+              </Button>
+            </Col>
+            <Col>
+              {' '}
+              <Button type="primary" onClick={() => opraModal(true)}>
+                新增轮播图
+              </Button>
+            </Col>
+          </Row>
+        }
+      >
+        <DndProvider backend={HTML5Backend}>
+          <Table
+            loading={loading}
+            dataSource={bannerList}
+            columns={columns}
+            components={components}
+            onRow={(record, index) => ({
+              index,
+              moveRow,
+            })}
+          />
+        </DndProvider>
+      </Card>
+
+      {/* 弹出层 */}
+      <MemoBannerDrawer
+        visible={visible}
+        opraModal={opraModal}
+        roleData={roleList.filter((role) => role.id === roleId)[0]}
+      />
+
+      <div style={{ position: 'fixed', left: '50%', top: '50%', zIndex: 10000, color: '#fff' }}>
+        <MemoSwiperModal
+          visible={modalVisible}
+          bannerList={bannerList}
+          opraPreviewModal={opraPreviewModal}
+        />
+      </div>
+    </PageContainer>
+  );
+};
+
+export default connect(({ user }: ConnectState) => ({
+  roleList: user.roleList,
+}))(Banner);

+ 125 - 0
src/pages/setting/banner/item.tsx

@@ -0,0 +1,125 @@
+import React, {
+  useCallback,
+  useEffect,
+  useRef,
+  useState,
+  useImperativeHandle,
+  forwardRef,
+  useLayoutEffect,
+  memo,
+} from 'react';
+
+import { Button, Card, Col, Form, Input, Row, Select, Switch, Popconfirm } from 'antd';
+
+import CustomUpload from '@/components/upload/index';
+
+import { IBanner, EJumpType as Etype } from './types';
+
+const { Option } = Select;
+
+const layout = {
+  labelCol: { span: 6 },
+  wrapperCol: { span: 16 },
+};
+
+interface IProps {
+  itemData: IBanner;
+  collecrtItem: (itemData: IBanner) => void;
+  delItem: (id: string) => void;
+}
+
+const MemoCustomUpload = memo(CustomUpload);
+
+const BannerItem: React.FC<IProps> = ({ itemData, collecrtItem, delItem }) => {
+  const [form] = Form.useForm();
+
+  const [state, setState] = useState({});
+
+  useEffect(() => {
+    form.setFieldsValue({
+      ...itemData,
+    });
+
+    setState({ ...itemData });
+  }, [itemData]);
+
+  // change form
+  const changeForm = (key: string, value: any) => {
+    form.setFieldsValue({ key: value });
+
+    itemData[key] = value;
+
+    if (key === 'jumpType') {
+      form.setFieldsValue({ jumpPath: '' });
+      itemData['jumpPath'] = '';
+    }
+
+    collecrtItem(itemData);
+  };
+
+  return (
+    <Card title="配置信息" style={{ marginTop: 20 }}>
+      <Form {...layout} form={form} name="control-hooks">
+        <Form.Item label="俱乐部" name="roleName">
+          <Input disabled />
+        </Form.Item>
+        <Form.Item label="图片封面" name="imgUrl">
+          <MemoCustomUpload
+            key={itemData.imgUrl}
+            maxCount={1}
+            desc="上传图片"
+            setCoverFn={(imgUrl) => changeForm('imgUrl', imgUrl)}
+            imgUrl={itemData.imgUrl}
+          />
+        </Form.Item>
+        <Form.Item label="跳转类型" name="jumpType">
+          <Select
+            placeholder="请选择跳转类型"
+            allowClear
+            onChange={(value: Etype) => changeForm('jumpType', value)}
+          >
+            <Option value={Etype.h5}>h5</Option>
+            <Option value={Etype.miniprogram}>小程序</Option>
+          </Select>
+        </Form.Item>
+        {/* 跳转到h5 */}
+        {form.getFieldValue('jumpType') === Etype.h5 && (
+          <Form.Item label="跳转地址" name="jumpPath">
+            <Input onChange={(e) => changeForm('jumpPath', e.target.value)} />
+          </Form.Item>
+        )}
+
+        {/* 跳转到小程序 */}
+        {form.getFieldValue('jumpType') === Etype.miniprogram && (
+          <>
+            <Form.Item label="appid" name="jumpAppId" required>
+              <Input onChange={(e) => changeForm('jumpAppId', e.target.value)} />
+            </Form.Item>
+
+            <Form.Item label="跳转地址" name="jumpPath">
+              <Input
+                placeholder="不填默认跳转到小程序首页"
+                onChange={(e) => changeForm('jumpPath', e.target.value)}
+              />
+            </Form.Item>
+          </>
+        )}
+
+        <Form.Item label="是否启用" name="isShow">
+          <Switch
+            checked={form.getFieldValue('isShow')}
+            onChange={(value) => changeForm('isShow', value)}
+          />
+        </Form.Item>
+
+        <Form.Item label="操作">
+          <Popconfirm title="确定要删除当前轮播图吗?" onConfirm={() => delItem(itemData.uid)}>
+            <Button>删除</Button>
+          </Popconfirm>
+        </Form.Item>
+      </Form>
+    </Card>
+  );
+};
+
+export default BannerItem;

+ 227 - 0
src/pages/setting/banner/modal.tsx

@@ -0,0 +1,227 @@
+import React, { memo, useCallback, useEffect, useState } from 'react';
+
+import { Button, Col, Drawer, Row, Empty, message, Spin } from 'antd';
+
+import BannerItem from './item';
+
+import { opraBanner, getBanner } from '@/services/notice';
+
+import { IBanner, EJumpType } from './types';
+
+import { Role } from '@/models/user';
+
+interface IProps {
+  visible: boolean;
+  opraModal: (_state: boolean, reloadTable?: boolean) => void;
+  roleData: Role;
+}
+
+interface IState {
+  bannerList: IBanner[];
+}
+
+const initBanner = {
+  imgUrl: '',
+  isShow: false,
+  jumpAppId: '',
+  jumpPath: '',
+  jumpType: EJumpType.h5,
+  sort: 0,
+  roleId: '',
+  roleName: '',
+};
+
+const BannerDrawer: React.FC<IProps> = ({ visible, opraModal, roleData }) => {
+  const [state, setState] = useState<IState>({
+    bannerList: [],
+  });
+
+  const [loading, setLoading] = useState<boolean>(false);
+
+  const { bannerList } = state;
+
+  useEffect(() => {
+    console.log('我触发么');
+    console.log(roleData, 'roleDataroleDataroleDataroleData');
+
+    if (typeof roleData === 'object' && Reflect.ownKeys(roleData).length && visible) {
+      GetBanner();
+    }
+  }, [visible]);
+
+  // 新增banner
+  const addBanner = () => {
+    if (bannerList.length >= 8) {
+      message.warn('最多添加八张轮播图');
+      return;
+    }
+
+    const $par = Object.assign({}, initBanner, {
+      sort: 0,
+      roleId: roleData.id,
+      roleName: roleData.label,
+      uid: Math.random().toString(36).slice(2),
+    });
+
+    bannerList.unshift($par);
+
+    console.log(bannerList, 'bannerListbannerList');
+
+    setState({
+      ...state,
+      bannerList: bannerList.map((item: IBanner, index: number) => {
+        return {
+          ...item,
+          sort: index,
+        };
+      }),
+    });
+  };
+
+  // 查询banner
+  const GetBanner = async () => {
+    setLoading(true);
+    const { status, data } = await getBanner(roleData.id);
+    setLoading(false);
+    console.log(status, data);
+    const $r = data.map((item: IBanner) => {
+      return {
+        ...item,
+        roleName: roleData.label,
+        uid: item.id,
+      };
+    });
+    if (status === 200) {
+      setState({
+        ...state,
+        bannerList: $r,
+      });
+    }
+  };
+
+  // 收集所有item的结果
+  const collecrtItem = (itemData: IBanner) => {
+    console.log(itemData, '收集所有item的结果');
+    const r = bannerList.map((_banner: IBanner) => {
+      if (_banner.id === itemData.id) {
+        return itemData;
+      } else {
+        return _banner;
+      }
+    });
+
+    setState({
+      bannerList: r,
+    });
+  };
+
+  // 删除当前的item(还未保存前的)
+  const delItem = (uid: string) => {
+    const r = bannerList.filter((_banner) => _banner.uid !== uid);
+
+    setState({
+      ...state,
+      bannerList: r,
+    });
+  };
+
+  // 验证当前banner数据是否完整
+  const verifyBanner = (bannerList: IBanner[]) => {
+    let obj = {
+      r: true,
+      msg: '保存成功',
+      index: 0,
+    };
+    bannerList.forEach((_banner, index) => {
+      if (_banner.jumpType === EJumpType.h5 && !_banner.jumpPath) {
+        obj = {
+          r: false,
+          msg: '存在跳转地址未填写',
+          index,
+        };
+      } else if (_banner.jumpType === EJumpType.h5 && !_banner.jumpPath) {
+        obj = {
+          r: false,
+          msg: '存在appid未填写',
+          index,
+        };
+      } else if (!_banner.imgUrl) {
+        obj = {
+          r: false,
+          msg: '图片封面未上传',
+          index,
+        };
+      }
+    });
+    if (!obj.r) {
+      message.error('第' + (obj.index + 1) + '项' + obj.msg);
+      return false;
+    }
+    return true;
+  };
+
+  // 对banner进行添加或者修改
+  const OpraBanner = async () => {
+    const r = verifyBanner(bannerList);
+    if (!r) return;
+    const { status, data } = await opraBanner(bannerList, roleData.id);
+
+    if (status === 200 && data) {
+      message.success('保存成功');
+      opraModal(false, true);
+    }
+  };
+
+  const DrawerFooter = () => {
+    return (
+      <Row justify="end">
+        <Col span={6}>
+          <Button onClick={() => opraModal(false)}> 关闭 </Button>
+        </Col>
+        <Col span={6}>
+          <Button type="primary" onClick={OpraBanner}>
+            保存
+          </Button>
+        </Col>
+      </Row>
+    );
+  };
+
+  const DrawerTitle = () => {
+    return (
+      <Row align="middle">
+        <Col span="16">编辑轮播图</Col>
+        <Col span="6">
+          <Button type="primary" onClick={addBanner}>
+            新增
+          </Button>
+        </Col>
+      </Row>
+    );
+  };
+
+  return (
+    <Drawer
+      title={DrawerTitle()}
+      placement="right"
+      visible={visible}
+      width={420}
+      closable={false}
+      onClose={() => opraModal(false)}
+      maskClosable
+      footer={DrawerFooter()}
+    >
+      <Spin spinning={loading}>
+        {bannerList.length ? (
+          bannerList.map((item, index) => (
+            <BannerItem key={index} itemData={item} delItem={delItem} collecrtItem={collecrtItem} />
+          ))
+        ) : (
+          <Empty description="这里空空如也" image={Empty.PRESENTED_IMAGE_SIMPLE} />
+        )}
+      </Spin>
+    </Drawer>
+  );
+};
+
+export default BannerDrawer;

+ 71 - 0
src/pages/setting/banner/swiper.tsx

@@ -0,0 +1,71 @@
+import React from 'react';
+
+import { Image, Modal } from 'antd';
+
+import { IBanner } from './types';
+
+import { Swiper, SwiperSlide } from 'swiper/react';
+import './common.less';
+
+import 'swiper/swiper.less';
+
+interface IProps {
+  visible: boolean;
+  bannerList: IBanner[];
+  opraPreviewModal: (_visible: boolean) => void;
+}
+
+const SwiperCom: React.FC<IProps> = ({ bannerList, visible, opraPreviewModal }) => {
+  return (
+    <Modal
+      visible={visible}
+      width={512}
+      onCancel={() => opraPreviewModal(false)}
+      onOk={() => opraPreviewModal(false)}
+      closeIcon
+      maskClosable={false}
+    >
+      <Swiper
+        autoplay
+        spaceBetween={50}
+        slidesPerView={1}
+        onSlideChange={() => console.log('slide change')}
+        onSwiper={(swiper) => console.log(swiper)}
+      >
+        {bannerList
+          .filter((_banner) => _banner.isShow)
+          .map((banner) => (
+            <SwiperSlide key={banner.imgUrl}>
+              <Image
+                style={{ objectFit: 'cover' }}
+                key={banner.imgUrl}
+                preview={false}
+                src={banner.imgUrl}
+                width={466}
+                height={200}
+              />
+            </SwiperSlide>
+          ))}
+      </Swiper>
+
+      {/* <Carousel 
+        autoplay
+      >
+        {
+          bannerList.filter( _banner =>  _banner.isShow ).map( banner => 
+            <Image 
+              style={{objectFit: 'cover'}} 
+              key={banner.imgUrl} 
+              preview={false} 
+              src={banner.imgUrl} 
+              width={466} 
+              height={200} 
+            />
+         )
+        }
+      </Carousel> */}
+    </Modal>
+  );
+};
+
+export default SwiperCom;

+ 16 - 0
src/pages/setting/banner/types.ts

@@ -0,0 +1,16 @@
+export enum EJumpType {
+  'h5' = 'H5',
+  'miniprogram' = 'MINI_PROGRAM',
+}
+
+export interface IBanner {
+  imgUrl: string;
+  isShow: boolean;
+  jumpAppId?: string;
+  jumpPath?: string;
+  jumpType: EJumpType;
+  roleId: string;
+  sort: number;
+  id?: string;
+  [key: string]: any;
+}

+ 19 - 0
src/services/notice.ts

@@ -1,6 +1,8 @@
 import request from '@/utils/request';
 
 import type { PostOrPutNoticeType } from '@/pages/notice/data';
+import { IBanner } from '@/pages/notice/banner/types';
+
 // 查询公告
 type getNoticeType = {
   curPage: number;
@@ -26,3 +28,20 @@ export async function getNoticeDetail(id: string): Promise<any> {
 export async function deleteNotice(id: string): Promise<any> {
   return request(`/forum/admin/notice/${id}`, { method: 'DELETE' });
 }
+
+// 轮播图
+
+// 批量添加/修改轮播图
+export async function opraBanner(data: IBanner[], roleId: string) {
+  return request(`/forum/admin/banner/many?roleId=${roleId}`, { method: 'POST', data });
+}
+
+// 查询轮播图
+export async function getBanner(roleId: string) {
+  return request(`/forum/admin/banner?roleId=${roleId}`, { method: 'GET' });
+}
+
+// 删除轮播图
+export async function deleteBanner(id: string) {
+  return request(`/forum/admin/banner/${id}`, { method: 'DELETE' });
+}

+ 5 - 1
src/utils/request.ts

@@ -3,7 +3,7 @@
  * 更详细的 api 文档: https://github.com/umijs/umi-request
  */
 import { extend } from 'umi-request';
-import { notification } from 'antd';
+import { notification, message } from 'antd';
 import { Token } from '@/utils/eventkey';
 
 const codeMessage = {
@@ -90,6 +90,10 @@ request.interceptors.response.use(async (response, options) => {
     }
   }
 
+  if (r.code === 5000) {
+    message.error(r.message);
+  }
+
   return response;
 });