Browse Source

feat:俱乐部管理

lvkun 4 years ago
parent
commit
fe2f85fa3f

+ 46 - 28
config/config.ts

@@ -6,6 +6,8 @@ import proxy from './proxy';
 const { REACT_APP_ENV } = process.env;
 
 export default defineConfig({
+  base: './',
+  publicPath: './',
   hash: true,
   antd: {},
   dva: {
@@ -42,27 +44,37 @@ export default defineConfig({
               name: 'login',
               component: './User/login',
             },
-            {
-              path: '/user',
-              redirect: '/user/login',
-            },
-            {
-              name: 'register-result',
-              icon: 'smile',
-              path: '/user/register-result',
-              component: './user/register-result',
-            },
-            {
-              name: 'register',
-              icon: 'smile',
-              path: '/user/register',
-              component: './user/register',
-            },
-            {
-              component: '404',
-            },
           ],
         },
+        // {
+        //   path: '/',
+        //   component: '../layouts/SecurityLayout',
+        //   routes: [
+        //     {
+        //       path: '/',
+        //       component: '../layouts/BasicLayout',
+        //       routes: [
+        //         {
+        //           path: '/',
+        //           redirect: '/agent/list',
+        //         },
+        //         {
+        //           path: '/agent',
+        //           name: '代理商管理',
+        //           icon: 'dashboard',
+        //           routes: [
+        //             {
+        //               name: '代理商列表',
+        //               icon: 'smile',
+        //               path: '/agent/list',
+        //               component: './agent/list',
+        //             }
+        //           ]
+        //         },
+        //       ]
+        //     }
+        //   ]
+        // }
         {
           path: '/',
           component: '../layouts/BasicLayout',
@@ -81,8 +93,8 @@ export default defineConfig({
                   icon: 'smile',
                   path: '/agent/list',
                   component: './agent/list',
-                }
-              ]
+                },
+              ],
             },
             {
               path: '/tieba',
@@ -107,7 +119,7 @@ export default defineConfig({
                   path: '/tieba/posts',
                   component: './tieba/posts',
                 },
-              ]
+              ],
             },
             {
               path: '/notice',
@@ -115,6 +127,12 @@ export default defineConfig({
               icon: 'dashboard',
               component: './notice',
             },
+            {
+              path: '/club',
+              name: '俱乐部管理',
+              icon: 'dashboard',
+              component: './club',
+            },
             {
               path: '/complaint',
               name: '投诉管理',
@@ -125,11 +143,11 @@ export default defineConfig({
                   icon: 'smile',
                   path: '/complaint/admin',
                   component: './complaint/admin',
-                }
-              ]
-            }
-          ]
-        }
+                },
+              ],
+            },
+          ],
+        },
       ],
     },
   ],
@@ -141,6 +159,6 @@ export default defineConfig({
   ignoreMomentLocale: true,
   proxy: proxy[REACT_APP_ENV || 'dev'],
   manifest: {
-    basePath: '/',
+    basePath: './',
   },
 });

+ 12 - 11
config/defaultSettings.ts

@@ -5,19 +5,20 @@ type DefaultSettings = Partial<ProSettings> & {
 };
 
 const proSettings: DefaultSettings = {
-  "navTheme": "dark",
-  "primaryColor": "#1890ff",
-  "layout": "side",
-  "contentWidth": "Fluid",
-  "fixedHeader": false,
+  navTheme: 'dark',
+  primaryColor: '#1890ff',
+  layout: 'side',
+  contentWidth: 'Fluid',
+  fixedHeader: false,
   // "iconfontUrl": "",
-  "menu": {
-    "locale": false
+  menu: {
+    locale: false,
   },
-  "headerHeight": 48,
-  "title": "中德论坛",
-  "menuHeaderRender": false
-}
+  pwa: false,
+  headerHeight: 48,
+  title: '中德论坛',
+  menuHeaderRender: false,
+};
 
 export type { DefaultSettings };
 

+ 6 - 1
config/proxy.ts

@@ -7,8 +7,13 @@
  */
 export default {
   dev: {
+    // '/api': {
+    //   target: 'http://192.168.1.21:38089',
+    //   changeOrigin: true,
+    //   pathRewrite: { '^/api': '' },
+    // },
     '/api': {
-      target: 'http://192.168.1.21:38089',
+      target: 'https://open.luojigou.vip',
       changeOrigin: true,
       pathRewrite: { '^/api': '' },
     },

+ 5 - 1
package.json

@@ -60,9 +60,11 @@
     "antd": "^4.9.4",
     "bizcharts": "^3.5.3-beta.0",
     "bizcharts-plugin-slider": "^2.1.1-beta.1",
+    "braft-editor": "^2.3.9",
     "classnames": "^2.2.6",
     "dva": "^2.4.0",
     "gg-editor": "^2.0.2",
+    "immer": "^9.0.3",
     "lodash": "^4.17.11",
     "lodash-decorators": "^6.0.0",
     "lodash.debounce": "^4.0.8",
@@ -81,7 +83,9 @@
     "react-helmet-async": "^1.0.4",
     "react-router": "^4.3.1",
     "umi": "^3.2.14",
-    "umi-request": "^1.0.8"
+    "umi-request": "^1.0.8",
+    "wangeditor": "^4.7.1",
+    "wangeditor-for-react": "^1.3.0"
   },
   "devDependencies": {
     "@ant-design/pro-cli": "^1.0.28",

+ 3 - 3
src/components/GlobalHeader/NoticeIconView.tsx

@@ -23,9 +23,9 @@ class GlobalHeaderRight extends Component<GlobalHeaderRightProps> {
     const { dispatch } = this.props;
 
     if (dispatch) {
-      dispatch({
-        type: 'global/fetchNotices',
-      });
+      // dispatch({
+      //   type: 'global/fetchNotices',
+      // });
     }
   }
 

+ 13 - 4
src/layouts/BasicLayout.tsx

@@ -94,11 +94,20 @@ const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
   } = props;
   const menuDataRef = useRef<MenuDataItem[]>([]);
   useEffect(() => {
-    if (dispatch) {
-      dispatch({
-        type: 'user/fetchCurrent',
-      });
+    console.log('hello');
+
+    if (!window.localStorage.getItem('token')) {
+      if (process.env.NODE_ENV === 'development') {
+        window.location.href = 'http://localhost:8000/#/user/login';
+      } else {
+        window.location.href = 'https://luojigou.vip/tieba-admin/#/user/login';
+      }
     }
+    // if (dispatch) {
+    //   dispatch({
+    //     type: 'user/fetchCurrent',
+    //   });
+    // }
   }, []);
   /**
    * init variables

+ 2 - 0
src/layouts/SecurityLayout.tsx

@@ -35,6 +35,8 @@ class SecurityLayout extends React.Component<SecurityLayoutProps, SecurityLayout
   render() {
     const { isReady } = this.state;
     const { children, loading, currentUser } = this.props;
+    console.log(currentUser, 'currentUser');
+
     // You can replace it to your authentication rule (such as check token exists)
     // 你可以把它替换成你自己的登录认证规则(比如判断 token 是否存在)
     const isLogin = currentUser && currentUser.userid;

+ 4 - 2
src/layouts/UserLayout.less

@@ -19,7 +19,9 @@
 }
 
 .content {
-  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
   padding: 32px 0;
 }
 
@@ -32,7 +34,7 @@
   }
 
   .content {
-    padding: 32px 0 24px;
+    padding: 132px 0 24px;
   }
 }
 

+ 6 - 6
src/layouts/UserLayout.tsx

@@ -48,20 +48,20 @@ const UserLayout: React.FC<UserLayoutProps> = (props) => {
           <div className={styles.top}>
             <div className={styles.header}>
               <Link to="/">
-                <img alt="logo" className={styles.logo} src={logo} />
-                <span className={styles.title}>Ant Design22</span>
+                {/* <img alt="logo" className={styles.logo} src={logo} /> */}
+                <span className={styles.title}>中德智慧精英俱乐部</span>
               </Link>
             </div>
             <div className={styles.desc}>
-              <FormattedMessage
+              {/* <FormattedMessage
                 id="pages.layouts.userLayout.title"
-                defaultMessage="Ant Design 是西湖区最具影响力的 Web 设计规范"
-              />
+                defaultMessage="中德智慧精英俱乐部是最好的俱乐部"
+              /> */}
             </div>
           </div>
           {children}
         </div>
-        <DefaultFooter />
+        {/* <DefaultFooter /> */}
       </div>
     </HelmetProvider>
   );

+ 22 - 5
src/models/login.ts

@@ -3,14 +3,23 @@ import type { Reducer, Effect } from 'umi';
 import { history } from 'umi';
 
 import { fakeAccountLogin } from '@/services/login';
+import { login } from '@/services/user';
 import { setAuthority } from '@/utils/authority';
 import { getPageQuery } from '@/utils/utils';
 import { message } from 'antd';
+import { Token } from '@/utils/eventkey';
+
+type UserInfo = {
+  id: string;
+  username: string;
+  [key: string]: any;
+};
 
 export type StateType = {
   status?: 'ok' | 'error';
   type?: string;
   currentAuthority?: 'user' | 'guest' | 'admin';
+  userInfo: UserInfo;
 };
 
 export type LoginModelType = {
@@ -30,21 +39,27 @@ const Model: LoginModelType = {
 
   state: {
     status: undefined,
+    userInfo: {
+      id: '',
+      username: 'admin',
+    },
   },
 
   effects: {
     *login({ payload }, { call, put }) {
-      const response = yield call(fakeAccountLogin, payload);
+      const response = yield call(login, payload);
       yield put({
         type: 'changeLoginStatus',
         payload: response,
       });
       // Login successfully
-      if (response.status === 'ok') {
+      if (response.code === 0) {
         const urlParams = new URL(window.location.href);
         const params = getPageQuery();
         message.success('🎉 🎉 🎉  登录成功!');
         let { redirect } = params as { redirect: string };
+        console.log(redirect, 'redirect');
+
         if (redirect) {
           const redirectUrlParams = new URL(redirect);
           if (redirectUrlParams.origin === urlParams.origin) {
@@ -77,11 +92,13 @@ const Model: LoginModelType = {
 
   reducers: {
     changeLoginStatus(state, { payload }) {
-      setAuthority(payload.currentAuthority);
+      console.log(payload, 'payloadpayloadpayload');
+      const { data } = payload;
+      // setAuthority(payload.currentAuthority);
+      localStorage.setItem(Token, data.id);
       return {
         ...state,
-        status: payload.status,
-        type: payload.type,
+        userInfo: data,
       };
     },
   },

+ 32 - 0
src/models/user.ts

@@ -2,6 +2,8 @@ import type { Effect, Reducer } from 'umi';
 
 import { queryCurrent, query as queryUsers } from '@/services/user';
 
+import { getRoleList } from '@/services/role';
+
 export type CurrentUser = {
   avatar?: string;
   name?: string;
@@ -16,8 +18,15 @@ export type CurrentUser = {
   unreadCount?: number;
 };
 
+export type Role = {
+  createTime: number | string | null;
+  id: string;
+  label: string;
+};
+
 export type UserModelState = {
   currentUser?: CurrentUser;
+  roleList?: Role[];
 };
 
 export type UserModelType = {
@@ -26,8 +35,10 @@ export type UserModelType = {
   effects: {
     fetch: Effect;
     fetchCurrent: Effect;
+    getRoleList: Effect;
   };
   reducers: {
+    saveRoleList: Reducer<UserModelState>;
     saveCurrentUser: Reducer<UserModelState>;
     changeNotifyCount: Reducer<UserModelState>;
   };
@@ -38,6 +49,7 @@ const UserModel: UserModelType = {
 
   state: {
     currentUser: {},
+    roleList: [],
   },
 
   effects: {
@@ -55,9 +67,29 @@ const UserModel: UserModelType = {
         payload: response,
       });
     },
+    *getRoleList(_, { call, put }) {
+      console.log(_, 'call(getRoleList)');
+      const { payload } = _;
+      const r = yield call(getRoleList, payload);
+      console.log(r);
+      yield put({
+        type: 'saveRoleList',
+        payload: r.data.records,
+      });
+    },
   },
 
   reducers: {
+    /**
+     * @description 保存权限列表
+     */
+    saveRoleList(state, action) {
+      return {
+        ...state,
+        roleList: action.payload || [],
+      };
+    },
+
     saveCurrentUser(state, action) {
       return {
         ...state,

+ 11 - 15
src/pages/User/login/index.tsx

@@ -45,6 +45,8 @@ const Login: React.FC<LoginProps> = (props) => {
   const intl = useIntl();
 
   const handleSubmit = (values: LoginParamsType) => {
+    console.log(values, 'valuesvaluesvalues');
+
     const { dispatch } = props;
     dispatch({
       type: 'login/login',
@@ -80,13 +82,13 @@ const Login: React.FC<LoginProps> = (props) => {
               defaultMessage: '账户密码登录',
             })}
           />
-          <Tabs.TabPane
+          {/* <Tabs.TabPane
             key="mobile"
             tab={intl.formatMessage({
               id: 'pages.login.phoneLogin.tab',
               defaultMessage: '手机号登录',
             })}
-          />
+          /> */}
         </Tabs>
 
         {status === 'error' && loginType === 'account' && !submitting && (
@@ -100,15 +102,12 @@ const Login: React.FC<LoginProps> = (props) => {
         {type === 'account' && (
           <>
             <ProFormText
-              name="userName"
+              name="username"
               fieldProps={{
                 size: 'large',
                 prefix: <UserOutlined className={styles.prefixIcon} />,
               }}
-              placeholder={intl.formatMessage({
-                id: 'pages.login.username.placeholder',
-                defaultMessage: '用户名: admin or user',
-              })}
+              placeholder="请输入用户名"
               rules={[
                 {
                   required: true,
@@ -127,10 +126,7 @@ const Login: React.FC<LoginProps> = (props) => {
                 size: 'large',
                 prefix: <LockTwoTone className={styles.prefixIcon} />,
               }}
-              placeholder={intl.formatMessage({
-                id: 'pages.login.password.placeholder',
-                defaultMessage: '密码: ant.design',
-              })}
+              placeholder="请输入密码"
               rules={[
                 {
                   required: true,
@@ -228,7 +224,7 @@ const Login: React.FC<LoginProps> = (props) => {
             />
           </>
         )}
-        <div
+        {/* <div
           style={{
             marginBottom: 24,
           }}
@@ -243,14 +239,14 @@ const Login: React.FC<LoginProps> = (props) => {
           >
             <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
           </a>
-        </div>
+        </div> */}
       </ProForm>
-      <Space className={styles.other}>
+      {/* <Space className={styles.other}>
         <FormattedMessage id="pages.login.loginWith" defaultMessage="其他登录方式" />
         <AlipayCircleOutlined className={styles.icon} />
         <TaobaoCircleOutlined className={styles.icon} />
         <WeiboCircleOutlined className={styles.icon} />
-      </Space>
+      </Space> */}
     </div>
   );
 };

+ 13 - 12
src/pages/account/center/index.tsx

@@ -2,13 +2,14 @@ import { PlusOutlined, HomeOutlined, ContactsOutlined, ClusterOutlined } from '@
 import { Avatar, Card, Col, Divider, Input, Row, Tag } from 'antd';
 import React, { Component, useState, useRef } from 'react';
 import { GridContent } from '@ant-design/pro-layout';
-import { Link, connect, Dispatch } from 'umi';
-import { RouteChildrenProps } from 'react-router';
-import { ModalState } from './model';
+import type { Dispatch } from 'umi';
+import { Link, connect } from 'umi';
+import type { RouteChildrenProps } from 'react-router';
+import type { ModalState } from './model';
 import Projects from './components/Projects';
 import Articles from './components/Articles';
 import Applications from './components/Applications';
-import { CurrentUser, TagType } from './data.d';
+import type { CurrentUser, TagType } from './data.d';
 import styles from './Center.less';
 
 const operationTabList = [
@@ -128,13 +129,13 @@ class Center extends Component<CenterProps, CenterState> {
   public input: Input | null | undefined = undefined;
 
   componentDidMount() {
-    const { dispatch } = this.props;
-    dispatch({
-      type: 'accountAndcenter/fetchCurrent',
-    });
-    dispatch({
-      type: 'accountAndcenter/fetch',
-    });
+    // const { dispatch } = this.props;
+    // dispatch({
+    //   type: 'accountAndcenter/fetchCurrent',
+    // });
+    // dispatch({
+    //   type: 'accountAndcenter/fetch',
+    // });
   }
 
   onTabChange = (key: string) => {
@@ -257,7 +258,7 @@ export default connect(
     loading,
     accountAndcenter,
   }: {
-    loading: { effects: { [key: string]: boolean } };
+    loading: { effects: Record<string, boolean> };
     accountAndcenter: ModalState;
   }) => ({
     currentUser: accountAndcenter.currentUser,

+ 1 - 1
src/pages/account/center/service.ts

@@ -1,7 +1,7 @@
 import request from 'umi-request';
 
 export async function queryCurrent() {
-  return request('/api/currentUser');
+  // return request('/api/currentUser');
 }
 
 export async function queryFakeList(params: { count: number }) {

+ 7 - 8
src/pages/account/settings/index.tsx

@@ -1,11 +1,12 @@
 import React, { Component } from 'react';
 
-import { FormattedMessage, Dispatch, connect } from 'umi';
+import type { Dispatch } from 'umi';
+import { FormattedMessage, connect } from 'umi';
 import { GridContent } from '@ant-design/pro-layout';
 import { Menu } from 'antd';
 import BaseView from './components/base';
 import BindingView from './components/binding';
-import { CurrentUser } from './data.d';
+import type { CurrentUser } from './data.d';
 import NotificationView from './components/notification';
 import SecurityView from './components/security';
 import styles from './style.less';
@@ -20,9 +21,7 @@ interface SettingsProps {
 type SettingsStateKeys = 'base' | 'security' | 'binding' | 'notification';
 interface SettingsState {
   mode: 'inline' | 'horizontal';
-  menuMap: {
-    [key: string]: React.ReactNode;
-  };
+  menuMap: Record<string, React.ReactNode>;
   selectKey: SettingsStateKeys;
 }
 
@@ -63,9 +62,9 @@ class Settings extends Component<SettingsProps, SettingsState> {
 
   componentDidMount() {
     const { dispatch } = this.props;
-    dispatch({
-      type: 'accountAndsettings/fetchCurrent',
-    });
+    // dispatch({
+    //   type: 'accountAndsettings/fetchCurrent',
+    // });
     window.addEventListener('resize', this.resize);
     this.resize();
   }

+ 121 - 112
src/pages/agent/list/detail.tsx

@@ -1,112 +1,121 @@
-import React, { useEffect, useState } from 'react'
-import { Col, Modal, Row, Image, Avatar, Space, Card, Descriptions, Tag, Spin } from 'antd';
-import { getAgentDetail } from '@/services/agent'
-
-import type { roleListType } from './index'
-
-type DetailModalType = {
-    visible: boolean,
-    id: string,
-    closeModal: () => void
-}
-
-type followBarsType = {
-    avatarUrl: string,
-    label: string,
-    [key: string]: any
-}
-
-type userInfoType = {
-    id: string,
-    avatarUrl: string,
-    nickName: string,
-    authRoles: any,
-    phoneNumber: string,
-    followBarNum: number | null,
-    followBars: followBarsType[]
-    [key: string]: any
-}
-
-const DetailModal: React.FC<DetailModalType> = ({visible, id, closeModal}) => {
-
-    const [userInfo, setUserInfo]: [userInfoType, (params: userInfoType) => void] = useState<Record<string, any>>({})
-    const [spinning, setSpinning ] = useState<boolean>(false)
-
-    // 获取详情
-    const GetAgentDetail = async () => {
-        setSpinning(true)
-        const {code, data} =  await getAgentDetail(id)
-        setSpinning(false)
-        if (code === 0) {
-            setUserInfo(data)
-            console.log(userInfo.authRoles, '获取详情');
-            
-        }
-    }
-
-    useEffect( () => {
-        GetAgentDetail()
-    }, [])
-
-    // 渲染关注的贴吧
-    const renderFollowBar = (): React.ReactNode => {
-        return (
-            <Row gutter={[16, 16]}>
-                {
-                   userInfo.followBars && userInfo.followBars.map( item => (
-                        <Col key={item.id} span={4}>
-                            <Space direction="vertical" align="center">
-                                <Image width={50} height={50}  src={item.avatarUrl} alt="我炸裂了"></Image>
-                                <span>{item.label}</span>
-                            </Space>
-                        </Col>
-                   ))
-                }
-                
-            </Row>
-        )
-    }
-
-    // 渲染用户角色
-    const renderAuthRoles = (): React.ReactNode => {
-        return (
-            <Space>
-                {
-                    userInfo.authRoles.map( (item: roleListType) => (
-                        <Tag key={item.id} color="magenta">{item.roleLabel}</Tag>
-                    ))
-                }
-            </Space>
-        )
-    }
-
-    return (
-        <Modal title="详细信息" visible={visible} width={1200} onOk={closeModal} onCancel={closeModal}>
-            <Spin spinning={spinning}>
-                <Row gutter={16}>
-                    <Col span={8}>
-                        <Space direction="vertical" align="center">
-                            <Avatar 
-                                src={userInfo.avatarUrl} 
-                                alt="我炸裂了" 
-                                size={{ xs: 24, sm: 32, md: 40, lg: 64, xl: 80, xxl: 100 }}
-                            />
-                            <Descriptions layout="horizontal"  column={1}>
-                                    <Descriptions.Item span={1} label="姓名">{userInfo.nickName}</Descriptions.Item>
-                                    <Descriptions.Item span={1} label="手机号">{userInfo.phoneNumber}</Descriptions.Item>
-                                    <Descriptions.Item span={1} label="地址">{userInfo.region}</Descriptions.Item>
-                                    <Descriptions.Item span={1} label="角色">{ userInfo.authRoles && renderAuthRoles()}</Descriptions.Item>
-                            </Descriptions>
-                        </Space>
-                    </Col>
-                    <Col span={16}>
-                        <Card title={`关注的贴吧板块${userInfo?.followBars?.length}`} style={{width: '100%', height: '100%'}} >
-                            { renderFollowBar() }
-                        </Card>
-                    </Col>
-                </Row>
-            </Spin>
-        </Modal>
-    );
-}
-export default DetailModal
+import React, { useEffect, useState } from 'react';
+import { Col, Modal, Row, Image, Avatar, Space, Card, Descriptions, Tag, Spin } from 'antd';
+import { getAgentDetail } from '@/services/agent';
+
+import type { roleListType } from './index';
+
+type DetailModalType = {
+  visible: boolean;
+  id: string;
+  closeModal: () => void;
+};
+
+type followBarsType = {
+  avatarUrl: string;
+  label: string;
+  [key: string]: any;
+};
+
+type userInfoType = {
+  id: string;
+  avatarUrl: string;
+  nickName: string;
+  authRoles: any;
+  phoneNumber: string;
+  followBarNum: number | null;
+  followBars: followBarsType[];
+  [key: string]: any;
+};
+
+const DetailModal: React.FC<DetailModalType> = ({ visible, id, closeModal }) => {
+  const [userInfo, setUserInfo]: [userInfoType, (params: userInfoType) => void] = useState<
+    Record<string, any>
+  >({});
+  const [spinning, setSpinning] = useState<boolean>(false);
+
+  // 获取详情
+  const GetAgentDetail = async () => {
+    setSpinning(true);
+    const { code, data } = await getAgentDetail(id);
+    setSpinning(false);
+    if (code === 0) {
+      setUserInfo(data);
+      console.log(userInfo.authRoles, '获取详情');
+    }
+  };
+
+  useEffect(() => {
+    GetAgentDetail();
+  }, []);
+
+  // 渲染关注的贴吧
+  const renderFollowBar = (): React.ReactNode => {
+    return (
+      <Row gutter={[16, 16]}>
+        {userInfo.followBars &&
+          userInfo.followBars.map((item) => (
+            <Col key={item.id} span={4}>
+              <Space direction="vertical" align="center">
+                <Image width={50} height={50} src={item.avatarUrl} alt="我炸裂了"></Image>
+                <span>{item.label}</span>
+              </Space>
+            </Col>
+          ))}
+      </Row>
+    );
+  };
+
+  // 渲染用户角色
+  const renderAuthRoles = (): React.ReactNode => {
+    return (
+      <Space>
+        {userInfo.authRoles.map((item: roleListType) => (
+          <Tag key={item.id} color="magenta">
+            {item.roleLabel}
+          </Tag>
+        ))}
+      </Space>
+    );
+  };
+
+  return (
+    <Modal title="详细信息" visible={visible} width={1200} onOk={closeModal} onCancel={closeModal}>
+      <Spin spinning={spinning}>
+        <Row gutter={16}>
+          <Col span={8}>
+            <Space direction="vertical" align="center">
+              <Avatar
+                src={userInfo.avatarUrl}
+                alt="我炸裂了"
+                size={{ xs: 24, sm: 32, md: 40, lg: 64, xl: 80, xxl: 100 }}
+              />
+              <Descriptions layout="horizontal" column={1}>
+                <Descriptions.Item span={1} label="姓名">
+                  {userInfo.nickName}
+                </Descriptions.Item>
+                <Descriptions.Item span={1} label="手机号">
+                  {userInfo.phoneNumber}
+                </Descriptions.Item>
+                <Descriptions.Item span={1} label="地址">
+                  {userInfo.region || '暂无地址'}
+                </Descriptions.Item>
+                <Descriptions.Item span={1} label="角色">
+                  {userInfo.authRoles && renderAuthRoles()}
+                </Descriptions.Item>
+              </Descriptions>
+            </Space>
+          </Col>
+          <Col span={16}>
+            <Card
+              title={`关注的贴吧板块${userInfo?.followBars?.length}`}
+              style={{ width: '100%', height: '100%' }}
+            >
+              {renderFollowBar()}
+            </Card>
+          </Col>
+        </Row>
+      </Spin>
+    </Modal>
+  );
+};
+export default DetailModal;

+ 158 - 0
src/pages/club/index.tsx

@@ -0,0 +1,158 @@
+import React, { useEffect, useState } from 'react';
+import { Card, Row, Col, Table, Space, Divider, message } from 'antd';
+import { PageContainer } from '@ant-design/pro-layout';
+import { produce } from 'immer';
+
+import { ClubState } from '@/types/club';
+
+import { connect, Dispatch } from 'umi';
+
+import { Role } from '@/models/user';
+
+import { ConnectState } from '@/models/connect';
+
+import { role } from '@/types/role';
+
+import { putRoleList } from '@/services/role';
+
+import ClubModal from './model';
+
+interface IProps {
+  dispatch: Dispatch;
+  roleList: Role[];
+}
+
+const Club: React.FC<IProps> = ({ dispatch, roleList }) => {
+  console.log(roleList, 'roleList');
+
+  const [state, setState] = useState<ClubState>({
+    loading: false,
+    visable: false,
+    curClub: {
+      id: '',
+      isForbidden: null,
+      label: '',
+      previousLabel: '',
+    },
+  });
+
+  const { loading, visable, curClub } = state;
+
+  console.log(loading, curClub, 'curClub');
+
+  useEffect(() => {
+    getRoleList();
+  }, []);
+
+  // 获取role列表
+  const getRoleList = () => {
+    dispatch({
+      type: 'user/getRoleList',
+      payload: {
+        curPage: 1,
+        passSize: 100,
+      },
+    });
+  };
+
+  // 打开修改弹窗
+  const openModal = (records: role) => {
+    console.log(records, 'records');
+
+    setState(
+      produce(state, (draft) => {
+        draft.visable = true;
+        draft.curClub = records;
+      }),
+    );
+  };
+  // 关闭修改弹窗
+  const closeModal = () => {
+    setState(
+      produce(state, (draft) => {
+        draft.visable = false;
+      }),
+    );
+  };
+  // 修改提交
+  const onsubmit = async () => {
+    setState(
+      produce(state, (draft) => {
+        draft.loading = true;
+      }),
+    );
+
+    const { code, data } = await putRoleList(curClub);
+
+    if (code === 0 && data === 1) {
+      message.success('修改成功');
+    }
+    setState(
+      produce(state, (draft) => {
+        draft.visable = false;
+        draft.loading = false;
+      }),
+    );
+
+    getRoleList();
+  };
+
+  // 修改label
+  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    console.log(e);
+
+    setState(
+      produce(state, (draft) => {
+        draft.curClub = {
+          ...curClub,
+          label: e.target.value,
+        };
+      }),
+    );
+  };
+
+  return (
+    <PageContainer title="俱乐部管理">
+      <Card>
+        <Table loading={loading} dataSource={roleList} style={{ marginTop: 20 }}>
+          <Table.Column
+            title="公告标题"
+            ellipsis
+            width={300}
+            key="previousLabel"
+            dataIndex="previousLabel"
+          />
+          <Table.Column title="俱乐部名称" ellipsis width={300} key="label" dataIndex="label" />
+          <Table.Column title="人数" ellipsis width={300} key="label" dataIndex="label" />
+          <Table.Column
+            title="操作"
+            ellipsis
+            width={300}
+            key="action"
+            dataIndex="action"
+            render={(text, records) => (
+              <Space>
+                <a onClick={() => openModal(records as role)}>修改</a>
+                <Divider type="vertical" />
+                <a>禁用</a>
+              </Space>
+            )}
+          />
+        </Table>
+      </Card>
+
+      {/* 修改名称 */}
+      <ClubModal
+        visible={visable}
+        curClub={curClub}
+        closeModal={closeModal}
+        onsubmit={onsubmit}
+        onChange={onChange}
+      />
+    </PageContainer>
+  );
+};
+
+export default connect(({ user }: ConnectState) => ({
+  roleList: user.roleList,
+}))(Club);

+ 40 - 0
src/pages/club/model.tsx

@@ -0,0 +1,40 @@
+import React from 'react';
+import { Modal, Input, Row, Col } from 'antd';
+import { role } from '@/types/role';
+const layout = {
+  labelCol: { span: 5 },
+  wrapperCol: { span: 16 },
+};
+
+interface IProps {
+  visible: boolean;
+  curClub: role;
+  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
+  onsubmit: () => void;
+  closeModal: () => void;
+}
+
+const ClubModal: React.FC<IProps> = ({ visible, curClub, onsubmit, onChange, closeModal }) => {
+  return (
+    <Modal
+      width="400px"
+      visible={visible}
+      title="修改俱乐部名称"
+      onOk={() => onsubmit()}
+      onCancel={closeModal}
+    >
+      <Row align="middle">
+        <Col span={6}>俱乐部分类:</Col>
+        <Col span={12}>{curClub.previousLabel}</Col>
+      </Row>
+      <Row align="middle" style={{ marginTop: '14px' }}>
+        <Col span={6}>俱乐部名称:</Col>
+        <Col span={12}>
+          <Input value={curClub.label} onChange={(e) => onChange(e)} />
+        </Col>
+      </Row>
+    </Modal>
+  );
+};
+
+export default ClubModal;

+ 20 - 20
src/pages/notice/data.d.ts

@@ -1,20 +1,20 @@
-export enum Type  {
-    add,
-    edit
-}
-
-export type PostOrPutNoticeType = {
-    id?: string,
-    content: string,
-    label: string,
-    type: Type
-}
-
-
-export type noticeModalProps = {
-    closeModal: () => void,
-    curNoticeData: Record<string, any>
-    visible: boolean,
-    type: Type,
-    GetNotice: () => void
-}
+declare module 'wangeditor-for-react';
+export enum Type {
+  add,
+  edit,
+}
+
+export type PostOrPutNoticeType = {
+  id?: string;
+  content: string;
+  label: string;
+  type: Type;
+};
+
+export interface noticeModalProps {
+  closeModal: () => void;
+  curNoticeData: Record<string, any>;
+  visible: boolean;
+  type: Type;
+  GetNotice: () => void;
+}

+ 59 - 44
src/pages/notice/detailModal.tsx

@@ -1,44 +1,59 @@
-import React, {useEffect, useState} from 'react'
-
-import { getNoticeDetail } from '@/services/notice'
-
-import { Modal, Timeline  } from 'antd'
-
-import { filterTimestamp } from '@/filters/index'
-
-type DetailModalProps = {
-    id: string,
-    DetailModalVisible: boolean,
-    closeModal: () => void
-} 
-
-const DetailModal: React.FC<DetailModalProps> = ({id, DetailModalVisible, closeModal}) => {
-
-    const [detailData, setDetailData] = useState<Record<string, any>>({})
-
-    // 获取详情
-    const GetNoticeDetail = async (): Promise<void> => {
-       const {code, data} =  await getNoticeDetail(id)
-       if (code === 0) {
-           console.log(data);
-           setDetailData(data)
-       }
-    }
-
-    useEffect( () => {
-        GetNoticeDetail()
-    }, [])
-    return (
-        <Modal visible={DetailModalVisible} title="查看详情" onOk={closeModal} onCancel={closeModal}>
-             <Timeline>
-                <Timeline.Item ><p>{detailData.label}</p> <p>{filterTimestamp(detailData.createTime)}</p></Timeline.Item>
-                <Timeline.Item >
-                    <p>公告内容</p>
-                    <p>{detailData.content}</p>
-                </Timeline.Item>
-                <Timeline.Item >已有{detailData.readNum}人查看</Timeline.Item>
-            </Timeline>
-        </Modal>
-    )
-}
-export default DetailModal
+import React, { useEffect, useState } from 'react';
+
+import { getNoticeDetail } from '@/services/notice';
+
+import { Modal, Timeline, Spin } from 'antd';
+
+import { filterTimestamp } from '@/filters/index';
+
+import './index.less';
+
+type DetailModalProps = {
+  id: string;
+  DetailModalVisible: boolean;
+  closeModal: () => void;
+};
+
+const DetailModal: React.FC<DetailModalProps> = ({ id, DetailModalVisible, closeModal }) => {
+  const [detailData, setDetailData] = useState<Record<string, any>>({});
+
+  // 加载等待样式、
+  const [loading, setloading] = useState<boolean>(false);
+
+  // 获取详情
+  const GetNoticeDetail = async (): Promise<void> => {
+    setloading(true);
+    const { code, data } = await getNoticeDetail(id);
+    setloading(false);
+    if (code === 0) {
+      console.log(data);
+      setDetailData(data);
+    }
+  };
+
+  useEffect(() => {
+    GetNoticeDetail();
+  }, []);
+  return (
+    <Modal visible={DetailModalVisible} title="查看详情" onOk={closeModal} onCancel={closeModal}>
+      <Spin spinning={loading}>
+        <Timeline>
+          <Timeline.Item>
+            <p>{detailData.label}</p> <p>{filterTimestamp(detailData.createTime)}</p>
+          </Timeline.Item>
+          <Timeline.Item>
+            <p>公告内容</p>
+            <div className="notice-container">
+              <p
+                style={{ listStyleType: 'decimal' }}
+                dangerouslySetInnerHTML={{ __html: detailData.content }}
+              ></p>
+            </div>
+          </Timeline.Item>
+          <Timeline.Item>已有{detailData.readNum}人查看</Timeline.Item>
+        </Timeline>
+      </Spin>
+    </Modal>
+  );
+};
+export default DetailModal;

+ 21 - 0
src/pages/notice/index.less

@@ -0,0 +1,21 @@
+.cancel-stop-ol {
+  list-style-type: decimal;
+}
+
+.custom-Tooltip-over {
+  width: 350px;
+}
+
+.ant-tooltip-open {
+  width: 650px;
+}
+
+.notice-container {
+  height: 300px;
+  overflow: hidden;
+  overflow-y: auto;
+}
+
+.my-component {
+  font-size: 16px;
+}

+ 199 - 148
src/pages/notice/index.tsx

@@ -1,148 +1,199 @@
-import React, { useState, useEffect } from 'react'
-import { PageContainer } from '@ant-design/pro-layout'
-import { Button, Card, Row, Col, Input, Table, Tooltip, Space, Popconfirm, message} from 'antd'
-
-import { getNotice, deleteNotice } from '@/services/notice'
-
-import NoticeModal from './modal'
-
-import DetailModal from './detailModal'
-
-import { filterTimestamp } from '@/filters/index'
-
-const Notice: React.FC = () =>{
-
-    const [label, setLabel] = useState('')
-    const [curPage, setCurpage ] = useState(1)
-    const [total, setTotal ] = useState(0)
-    const [noticeList, setNoticeList] = useState([])
-    const [ loading, setLoading] = useState(false)
-    const [type, setType] = useState(0)
-    const [visible, setVisible] = useState(false)
-    const [ curNoticeData, setCurNoticeData ] = useState({})
-    const [ DetailModalVisible, setDetailModalVisible] = useState(false)
-    const [ id, setId] = useState('')
-
-
-    // 获取公告列表
-    const GetNotice = async (): Promise<void> => {
-      const $par = {
-          curPage,
-          label
-      }
-      setLoading(true)
-      const { code, data } =  await getNotice($par)
-      setLoading(false)
-      if (code === 0) {
-          console.log(data);
-          const { records, total: Total } = data
-          setTotal(Total)
-          setNoticeList(records)
-      }
-    }
-    useEffect( () => {
-        GetNotice()
-    }, [label, curPage])
-
-    // 打开弹窗
-    const openModal = (value: number, record: Record<string, any>) => {
-        console.log(record,'打开弹窗 ');
-        setType(value)
-        setVisible(true)
-        setCurNoticeData(record)
-    }
-
-    // 删除公告
-    const DeleteNotice = async (value: string): Promise<void> => {
-        const {code, data} =  await deleteNotice(value)
-        if (code === 0 ) {
-            console.log(data);
-            message.success('删除成功')
-        }
-        GetNotice()
-    } 
-    // 关闭弹窗
-    const closeModal = (): void => setVisible(false)
-    // 搜索
-    const onSearch = (value: string): void => setLabel(value)
-
-    // 分页
-    const changePagination = (page: number): void => setCurpage(page)
-    return (
-        <PageContainer title="公告管理">
-            <Card>
-                <Row justify="space-between">
-                    <Col>
-                        <Input.Search  style={{width: 250}} placeholder="请输入搜索公告名称/内容" onSearch={onSearch} enterButton />
-                    </Col>
-                    <Col>
-                        <Button onClick={() => openModal(0, {})}>发布公告</Button>
-                    </Col>
-                </Row>
-                <Table 
-                    loading={loading} 
-                    dataSource={noticeList} 
-                    style={{marginTop: 20}}
-                    pagination={{total, onChange: changePagination}}
-                >
-                    <Table.Column title="公告标题" ellipsis width={300} key="label" dataIndex="label" 
-                         render={ (text, record: Record<string, any>) => (
-                            <Tooltip title={record.label} placement="topLeft">
-                                <span>{record.label}</span>
-                            </Tooltip>
-                        )}
-                    ></Table.Column>
-                    <Table.Column title="公告内容" 
-                        ellipsis width={300} 
-                        key="content" 
-                        dataIndex="content" 
-                        render={ (text, record: Record<string, any>) => (
-                            <Tooltip title={record.content} placement="topLeft">
-                                <span>{record.content}</span>
-                            </Tooltip>
-                        )}
-                    >
-
-                    </Table.Column>
-                    <Table.Column title="发布人" key="" dataIndex="" render={() => (<span>管理员</span>)}></Table.Column>
-                    <Table.Column title="发布时间" key="createTime" dataIndex="createTime"  
-                        render={(text) => (
-                            <span>{filterTimestamp(text)}</span>
-                        )}
-                    ></Table.Column>
-                    <Table.Column title="操作" key="action" dataIndex="action" 
-                        render={ (text, record: Record<string, any>) => (
-                            <Space>
-                                <a onClick={ () => { setDetailModalVisible(true); setId(record.id)}}>查看</a>
-                                <a onClick={ () => openModal(1, record as Record<string, any>) }>编辑</a>
-                                
-                                <Popconfirm
-                                    title="你确定要删除这条公告吗?"
-                                    onConfirm={() => DeleteNotice(record.id)}
-                                    okText="Yes"
-                                    cancelText="No"
-                                >
-                                    <a>删除</a>
-                                </Popconfirm>
-                            </Space>
-                        )}
-                    ></Table.Column>
-                </Table>
-            </Card>
-            {
-               visible &&  <NoticeModal GetNotice={GetNotice} curNoticeData={curNoticeData} closeModal={closeModal} visible={visible} type={type}/>
-            }
-
-            {/*  详情弹窗 */}
-            {
-               DetailModalVisible && <DetailModal 
-                                    DetailModalVisible={DetailModalVisible}  
-                                    id={id}
-                                    closeModal={() => setDetailModalVisible(false)}
-                                    />
-            }
-        </PageContainer>
-    )
-}
-
-export default Notice
+import React, { useState, useEffect } from 'react';
+import { PageContainer } from '@ant-design/pro-layout';
+import { Button, Card, Row, Col, Input, Table, Tooltip, Space, Popconfirm, message } from 'antd';
+
+import { getNotice, deleteNotice } from '@/services/notice';
+
+import NoticeModal from './modal';
+
+import DetailModal from './detailModal';
+
+import { filterTimestamp } from '@/filters/index';
+
+import { connect, Dispatch } from 'umi';
+
+import type { ConnectState } from '@/models/connect';
+
+import type { Role } from '@/models/user';
+
+interface IProps {
+  dispatch: Dispatch;
+  roleList: Role[];
+}
+
+const Notice: React.FC<IProps> = ({ dispatch, roleList }) => {
+  const [label, setLabel] = useState('');
+  const [curPage, setCurpage] = useState(1);
+  const [total, setTotal] = useState(0);
+  const [noticeList, setNoticeList] = useState([]);
+  const [loading, setLoading] = useState(false);
+  const [type, setType] = useState(0);
+  const [visible, setVisible] = useState(false);
+  const [curNoticeData, setCurNoticeData] = useState({});
+  const [DetailModalVisible, setDetailModalVisible] = useState(false);
+  const [id, setId] = useState('');
+
+  // 获取公告列表
+  const GetNotice = async (): Promise<void> => {
+    const $par = {
+      curPage,
+      label,
+    };
+    setLoading(true);
+    const { code, data } = await getNotice($par);
+    setLoading(false);
+    if (code === 0) {
+      console.log(data);
+      const { records, total: Total } = data;
+      setTotal(Total);
+      setNoticeList(records);
+    }
+  };
+  useEffect(() => {
+    GetNotice();
+  }, [label, curPage]);
+
+  // 打开弹窗
+  const openModal = (value: number, record: Record<string, any>) => {
+    console.log(record, '打开弹窗 ');
+    setType(value);
+    setVisible(true);
+    setCurNoticeData(record);
+  };
+
+  // 删除公告
+  const DeleteNotice = async (value: string): Promise<void> => {
+    const { code, data } = await deleteNotice(value);
+    if (code === 0) {
+      console.log(data);
+      message.success('删除成功');
+    }
+    GetNotice();
+  };
+  // 关闭弹窗
+  const closeModal = (): void => setVisible(false);
+  // 搜索
+  const onSearch = (value: string): void => setLabel(value);
+
+  // 分页
+  const changePagination = (page: number): void => setCurpage(page);
+  return (
+    <PageContainer title="公告管理">
+      <Card>
+        <Row justify="space-between">
+          <Col>
+            <Input.Search
+              style={{ width: 250 }}
+              placeholder="请输入搜索公告名称/内容"
+              onSearch={onSearch}
+              enterButton
+            />
+          </Col>
+          <Col>
+            <Button onClick={() => openModal(0, {})}>发布公告</Button>
+          </Col>
+        </Row>
+        <Table
+          loading={loading}
+          dataSource={noticeList}
+          style={{ marginTop: 20 }}
+          pagination={{ total, onChange: changePagination }}
+        >
+          <Table.Column
+            title="公告标题"
+            ellipsis
+            width={300}
+            key="label"
+            dataIndex="label"
+            render={(text, record: Record<string, any>) => (
+              <Tooltip title={record.label} placement="topLeft">
+                <span>{record.label}</span>
+              </Tooltip>
+            )}
+          ></Table.Column>
+          <Table.Column
+            title="公告内容"
+            ellipsis
+            width={300}
+            key="content"
+            dataIndex="content"
+            render={(text, record: Record<string, any>) => (
+              <a
+                onClick={() => {
+                  setDetailModalVisible(true);
+                  setId(record.id);
+                }}
+              >
+                {' '}
+                查看公告内容{' '}
+              </a>
+            )}
+          />
+          <Table.Column
+            title="发布人"
+            key="1"
+            dataIndex="1"
+            render={() => <span>管理员</span>}
+          ></Table.Column>
+          <Table.Column
+            title="发布时间"
+            key="createTime"
+            dataIndex="createTime"
+            render={(text) => <span>{filterTimestamp(text)}</span>}
+          ></Table.Column>
+          <Table.Column
+            title="操作"
+            key="action"
+            dataIndex="action"
+            render={(text, record: Record<string, any>) => (
+              <Space>
+                <a
+                  onClick={() => {
+                    setDetailModalVisible(true);
+                    setId(record.id);
+                  }}
+                >
+                  查看
+                </a>
+                {/* <a onClick={ () => openModal(1, record as Record<string, any>) }>编辑</a> */}
+
+                <Popconfirm
+                  title="你确定要删除这条公告吗?"
+                  onConfirm={() => DeleteNotice(record.id)}
+                  okText="Yes"
+                  cancelText="No"
+                >
+                  <a>删除</a>
+                </Popconfirm>
+              </Space>
+            )}
+          ></Table.Column>
+        </Table>
+      </Card>
+      {visible && (
+        <NoticeModal
+          GetNotice={GetNotice}
+          curNoticeData={curNoticeData}
+          closeModal={closeModal}
+          visible={visible}
+          type={type}
+          roleList={roleList}
+          dispatch={dispatch}
+        />
+      )}
+
+      {/*  详情弹窗 */}
+      {DetailModalVisible && (
+        <DetailModal
+          DetailModalVisible={DetailModalVisible}
+          id={id}
+          closeModal={() => setDetailModalVisible(false)}
+        />
+      )}
+    </PageContainer>
+  );
+};
+
+export default connect(({ user }: ConnectState) => ({
+  roleList: user.roleList,
+}))(Notice);

+ 308 - 104
src/pages/notice/modal.tsx

@@ -1,104 +1,308 @@
-import React, { useState, useEffect } from 'react'
-import { Modal, Row, Col, Space, Form, Input, Select, message} from 'antd'
-
-import { getAgent } from '@/services/agent'
-
-import { postOrPutNotice } from '@/services/notice'
-
-import type { noticeModalProps } from './data'
-
-const layout = {
-    labelCol: { span: 5 },
-    wrapperCol: { span: 16 },
-};
-
-const NoticeModal: React.FC<noticeModalProps> = ({GetNotice, type, closeModal, visible, curNoticeData}) => {
-    
-    const [form] = Form.useForm();
-
-    const [agentList, setAgentList ] = useState([])
-
-    const [label, setLabel ] = useState('')
-
-    const [ content, setContent ] = useState('')
-
-
-    // 获取所有的代理商
-    const GetAgent = async (): Promise<void> => {
-        const $par = {
-            curPage: 1,
-            pageSize: 10000
-        }
-      const { code, data } = await getAgent($par)
-      if (code === 0) {
-          console.log(data);
-          const { records } = data
-          setAgentList(records)
-      }
-    }
-    
-    // 新增或者编辑公告
-    const PostOrPutNotice = async (record: any): Promise<void> => {
-
-       const { code, data } =  await postOrPutNotice(record)
-       if (code === 0) {
-           console.log(data);
-            message.success('新增成功')
-       }
-       closeModal()
-       GetNotice()
-    }  
-    
-    
-    // 反填表单
-    const onFill = (): void => {
-        console.log(curNoticeData);
-        const $par = {...curNoticeData, userIds: curNoticeData.userIds.join('')}
-        form.setFieldsValue($par)
-    }
-
-    const onFinish = (values: any) => {
-        const $par = {
-            ...values,
-            userIds: values.userIds.split(''),
-            type
-          }
-          PostOrPutNotice($par)
-      };
-    
-    // 提交表单
-    const onsubmit = (): void => form.submit()
-
-    // 设置标题
-    const changeLabel = (e: React.ChangeEvent<HTMLInputElement>): void => setLabel(e.target.value)
-
-    // 内容
-    const changeContent = (e: React.ChangeEvent<HTMLInputElement>): void => setContent(e.target.value)
-
-    useEffect (() => {
-        GetAgent()
-        // onFill()
-    }, []) 
-    return (
-        <Modal visible={visible} title="发布公告" onOk={() => onsubmit()} onCancel={closeModal}>
-            <Form  {...layout} form={form} onFinish={onFinish}>
-                <Form.Item name="label" label="公告标题:"><Input onChange={changeLabel} maxLength={80}></Input></Form.Item>
-                <Form.Item name="content" label="公告内容:"><Input.TextArea rows={4} onChange={changeContent} ></Input.TextArea></Form.Item>
-                <Form.Item name="userIds" label="可看人员:">
-                    <Select>
-                        {
-                            agentList.length && agentList.map( (item: Record<string, any>) => (
-                                <Select.Option key={item.id} value={item.id}>
-                                    {item.nickName}
-                                </Select.Option>
-                            ))
-                        }
-                        
-                    </Select>
-                </Form.Item>
-            </Form>
-        </Modal>
-    )
-}
-
-export default NoticeModal
+import React, { useState, useEffect, useLayoutEffect } from 'react';
+import { Modal, Row, Col, Space, Form, Input, Select, message, Upload, Button, Icon } from 'antd';
+
+import { ScissorOutlined } from '@ant-design/icons';
+
+import { getAgent } from '@/services/agent';
+
+import { postOrPutNotice } from '@/services/notice';
+
+import { uploadFile } from '@/services/user';
+
+// import ReactWEditor from 'wangeditor-for-react';
+
+// 引入编辑器组件
+import BraftEditor, { ControlType, ExtendControlType, ImageControlType } from 'braft-editor';
+// 引入编辑器样式
+import 'braft-editor/dist/index.css';
+
+import { ContentUtils } from 'braft-utils';
+
+import { baseUrl } from '@/utils/request';
+
+import type { noticeModalProps } from './data';
+
+import type { Dispatch } from 'umi';
+
+import { useIntl } from 'react-intl';
+
+import { Role } from '@/models/user';
+
+const layout = {
+  labelCol: { span: 5 },
+  wrapperCol: { span: 16 },
+};
+
+// 定义rem基准值
+const sizeBase = 23.4375;
+
+interface IProps {
+  dispatch: Dispatch;
+  roleList: Role[];
+}
+
+interface EditorDemoProps {
+  editorContent: string;
+  changeContent: (content: string) => void;
+}
+
+class EditorDemo extends React.Component<EditorDemoProps> {
+  state = {
+    // 创建一个空的editorState作为初始值
+    editorState: BraftEditor.createEditorState(null),
+    editorInstance: null,
+  };
+
+  async componentDidMount() {
+    // 假设此处从服务端获取html格式的编辑器内容
+    const htmlContent = this.props.editorContent;
+
+    console.log(htmlContent, 'htmlContenthtmlContenthtmlContent');
+
+    // 使用BraftEditor.createEditorState将html字符串转换为编辑器需要的editorStat
+    this.setState({
+      editorState: BraftEditor.createEditorState(htmlContent),
+    });
+
+    console.log(this.state.editorInstance.getValue(), '222');
+  }
+
+  submitContent = async () => {
+    // 在编辑器获得焦点时按下ctrl+s会执行此方法
+    // 编辑器内容提交到服务端之前,可直接调用editorState.toHTML()来获取HTML格式的内容
+    // const htmlContent = this.state.editorState.toHTML()
+    // const result = await saveEditorContent(htmlContent)
+  };
+
+  handleEditorChange = (editorState) => {
+    console.log(editorState.toHTML(), 'editorState');
+
+    this.setState({ editorState });
+
+    this.props.changeContent(editorState.toHTML());
+  };
+
+  handleChange = ({ fileList }: { fileList: Record<string, any> }): void => {
+    console.log(fileList, 'fileList');
+    const len = fileList.length - 1;
+    if (fileList[len]?.status === 'done') {
+      if (fileList[len].response.code === 0) {
+        this.setState({
+          editorState: ContentUtils.insertMedias(this.state.editorState, [
+            {
+              type: 'IMAGE',
+              url: fileList[len].response.data,
+              width: '343px',
+              height: '192px',
+            },
+          ]),
+        });
+      }
+    }
+  };
+
+  circleImg = (mediaData) => {
+    const { url }: { url: string } = mediaData;
+    if (url.includes('?imageView2')) {
+      mediaData.url = mediaData.url.split('?imageView2')[0];
+    } else {
+      mediaData.url =
+        mediaData.url +
+        `?imageView2/1/w/${parseInt(mediaData.width)}/h/${parseInt(mediaData.height)}`;
+    }
+  };
+
+  render() {
+    const controls = [
+      'bold',
+      'italic',
+      'underline',
+      'text-color',
+      'separator',
+      'list-ul',
+      'list-ol',
+      'emoji',
+      'text-indent',
+      'headings',
+      'clear',
+      'hr',
+      'blockquote',
+      'font-size',
+      'line-height',
+    ];
+
+    const extendControls: ExtendControlType[] = [
+      {
+        key: 'antd-uploader',
+        type: 'component',
+        component: (
+          <Upload
+            accept="image/*"
+            action={`${baseUrl}/forum/file/uploadImage`}
+            showUploadList={false}
+            // customRequest={this.uploadHandler}
+            onChange={this.handleChange}
+          >
+            <button
+              type="button"
+              className="control-item button upload-button"
+              data-title="插入图片"
+            >
+              插入图片
+            </button>
+          </Upload>
+        ),
+      },
+    ];
+
+    const hooks = {
+      'set-image-size': ({ width, height }) => {
+        if (parseInt(width) !== 343) {
+          message.warning('图片宽度默认为343');
+          return false;
+        }
+      },
+    };
+
+    const imageControls: ImageControlType[] = [
+      'float-left',
+      'float-right',
+      {
+        text: '裁剪图片',
+        render: (mediaData) => (
+          <a>
+            <ScissorOutlined color="#fff" onClick={() => this.circleImg(mediaData)} />
+          </a>
+        ),
+      },
+      'size',
+      'remove',
+    ];
+
+    const { editorState } = this.state;
+
+    return (
+      <div className="my-component">
+        <BraftEditor
+          hooks={hooks}
+          imageResizable={false}
+          style={{ border: '1px solid #d9d9d9', width: '375px' }}
+          controls={controls}
+          imageControls={imageControls}
+          ref={(instance) => (this.state.editorInstance = instance)}
+          extendControls={extendControls}
+          value={editorState}
+          onChange={this.handleEditorChange}
+        />
+      </div>
+    );
+  }
+}
+
+type NoticeModalProps = noticeModalProps | IProps;
+
+const NoticeModal: React.FC<noticeModalProps & IProps> = ({
+  GetNotice,
+  type,
+  closeModal,
+  visible,
+  curNoticeData,
+  dispatch,
+  roleList,
+}) => {
+  console.log(roleList, 'roleListroleList');
+
+  const [form] = Form.useForm();
+
+  const [label, setLabel] = useState('');
+
+  const [content, setContent] = useState('');
+
+  // editContent 反填
+  const [editContent, setEditContent] = useState<string>('');
+
+  useEffect(() => {
+    onFill();
+    dispatch({
+      type: 'user/getRoleList',
+      payload: {
+        curPage: 1,
+        passSize: 100,
+      },
+    });
+  }, []);
+
+  // 新增或者编辑公告
+  const PostOrPutNotice = async (record: any): Promise<void> => {
+    const { code, data } = await postOrPutNotice(record);
+    if (code === 0) {
+      console.log(data);
+      message.success('新增成功');
+    }
+    closeModal();
+    GetNotice();
+  };
+
+  // 反填表单
+  const onFill = (): void => {
+    console.log(curNoticeData, '反填表单');
+    // , userIds: !!curNoticeData && curNoticeData.userIds.join('')
+    const $par = { ...curNoticeData };
+    form.setFieldsValue($par);
+    setEditContent(curNoticeData.content);
+  };
+
+  const onFinish = (values: any) => {
+    console.log(values, 'values');
+
+    const $par = {
+      ...values,
+      userIds: ['ALL'],
+      roles: values.userIds,
+      type: 'ANN',
+      methodType: type,
+      content,
+    };
+    PostOrPutNotice($par);
+  };
+
+  // 提交表单
+  const onsubmit = (): void => form.submit();
+
+  // 设置标题
+  const changeLabel = (e: React.ChangeEvent<HTMLInputElement>): void => setLabel(e.target.value);
+
+  // 内容
+  const changeContent = (content: string): void => setContent(content);
+
+  return (
+    <Modal
+      width="700px"
+      visible={visible}
+      title="发布公告"
+      onOk={() => onsubmit()}
+      onCancel={closeModal}
+    >
+      <Form {...layout} form={form} onFinish={onFinish}>
+        <Form.Item name="userIds" label="可看人员:">
+          <Select style={{ width: '375px' }} mode="multiple">
+            {roleList.length &&
+              roleList.map((item: Record<string, any>) => (
+                <Select.Option key={item.id} value={item.id}>
+                  {item.label}
+                </Select.Option>
+              ))}
+          </Select>
+        </Form.Item>
+        <Form.Item name="label" label="公告标题:">
+          <Input style={{ width: '375px' }} onChange={changeLabel} maxLength={80}></Input>
+        </Form.Item>
+        <Form.Item name="content" label="公告内容:">
+          <EditorDemo key={editContent} changeContent={changeContent} editorContent={editContent} />
+        </Form.Item>
+      </Form>
+    </Modal>
+  );
+};
+
+export default NoticeModal;

+ 224 - 182
src/pages/tieba/classify/index.tsx

@@ -1,182 +1,224 @@
-import React, { Component } from 'react'
-import { Input, Space, Row,Col, Card , Button, Table, Modal, message, Popconfirm} from 'antd';
-
-import { PageContainer } from '@ant-design/pro-layout'
-
-import { getClassify, postOrPutClassify, deleteClassify } from '@/services/tieba'
-import { filterTimestamp } from '@/filters/index'
-
-const { Search } = Input
-
-type ModalEleType = {
-    visible: boolean,
-    addOrEditClassify: () => Promise<void>,
-    closeModal: () => void,
-    changeLabel: (value: string) => void,
-    loading: boolean,
-    label: string,
-    type: number
-}  
-
-const ModalEle: React.FC<ModalEleType> = ({visible, addOrEditClassify, closeModal, changeLabel, type, loading, label}) => {
-
-    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => changeLabel( e.target.value)
-
-    return (
-        <Modal confirmLoading={loading} visible={visible} title={type === 0? '新增类目' : '编辑类目'} width={400} onOk={addOrEditClassify} onCancel={closeModal}>
-            <Row align="middle">
-                <Col span={4}>类目名:</Col>
-                <Col span={16}><Input maxLength={4} value={label} placeholder="最多4个字符" onChange={onChange}></Input></Col>
-            </Row>
-        </Modal>
-    )
-}
-
-
-class Classify extends Component<React.Component> {
-
-    state = {
-        queryParams: {
-            curPage: 1,
-            label: '',
-            total: 0,
-            type: 0,
-            id: ''
-        },
-        visible: false, // 弹窗
-        loading: false, 
-        classifyList: []
-    }
-
-    componentDidMount () {
-        this.getClassify()
-    }
-
-    // change label
-    changeLabel = (value: string): void => this.setState({queryParams: {...this.state.queryParams, label: value}})
-
-    // 获取贴吧分类
-    async getClassify (): Promise<void> {
-       this.setState({loading: true})
-       const { code, data } =  await getClassify(this.state.queryParams)
-       this.setState({loading: false})
-       if (code === 0) {
-           const {  records, total } = data
-           this.setState({
-                classifyList: records,
-                queryParams: { ...this.state.queryParams, total}
-           })
-       }
-    }
-    
-    // 新增或者修改类目名
-     addOrEditClassify = async (): Promise<void> => {
-       const { type } = this.state.queryParams
-       this.setState({loading: true})
-       const {code} =  await postOrPutClassify(this.state.queryParams)
-       this.setState({loading: false})
-       if ( code === 0) {
-           message.success(`${type=== 0? '新增' : '编辑'}成功`)
-
-           this.setState({
-               queryParams: {...this.state.queryParams, label: '', id: ''}
-           })
-
-           this.getClassify()
-       }
-       this.closeModal()
-    }
-    // 删除分类
-    deleteClassify = async (record: Record<string, any>): Promise<void>  => {
-      const { code, data } = await deleteClassify(record.id)
-      if (code === 0) {
-        console.log(data);
-        message.success('删除成功')
-      }
-      this.getClassify()
-    }
-
-    // 打开弹窗
-    openModal = (  type: number, record?: Record<string, any>): void => {
-        this.setState({ 
-            visible: true, 
-            queryParams: {...this.state.queryParams, type, label: record?.label,  id: type === 1 ? record?.id : ''},
-        })
-    }
-    
-
-    // 关闭弹窗
-    closeModal = (): void => this.setState({visible: false})
-
-    // 分页
-    changePagination = (page: number): void => {
-        this.setState({queryParams: {...this.state.queryParams, curPage: page}}, () => {
-            this.getClassify()
-        })
-    }
-
-    // 搜索
-    onSearch = (value: string): void => {
-        this.setState({
-            queryParams: { ...this.state.queryParams , label: value, curPage: 1}
-        }, () => {
-            this.getClassify()
-        })
-    }
-
-    render () {
-        const {  classifyList, visible, queryParams, loading } = this.state
-        const {  type, total, label } = queryParams
-        const { onSearch ,
-                openModal, 
-                addOrEditClassify, 
-                closeModal, 
-                changeLabel, 
-                changePagination,
-                deleteClassify
-        } = this
-        return (
-           <PageContainer title="贴吧的分类管理">
-               <Card>
-                    <Row justify="space-between">
-                        <Col>
-                            <Search placeholder="输入分类名称" onSearch={onSearch} enterButton />
-                        </Col>
-                        <Col >
-                            <Button type="primary" onClick={() => openModal(0)}>新增分类</Button>
-                        </Col>
-                    </Row>
-
-                    <Table loading={loading} style={{marginTop: 20 }}  dataSource={classifyList} pagination={{total, onChange: changePagination}}>
-                        <Table.Column title="类目名" key="label" dataIndex="label" ></Table.Column>
-                        <Table.Column title="创建人" key="creator" dataIndex="creator" ></Table.Column>
-                        <Table.Column title="创建时间" key="createTime" dataIndex="createTime" 
-                            render={text => <span>{filterTimestamp(text)}</span>}
-                        ></Table.Column>
-                        <Table.Column title="操作" key="action" dataIndex="action"
-                            render={ (text, record) => (
-                                <Space>
-                                    <a onClick={() => openModal(1, record as Record<string, any>)}>编辑</a>
-                                    <Popconfirm
-                                        title="确定要删除吗?"
-                                        onConfirm={() => deleteClassify(record as Record<string, any>)}
-                                        okText="Yes"
-                                        cancelText="No"
-                                    >
-                                          <a>删除</a>
-                                    </Popconfirm>
-                                </Space>
-                            )}
-                        >
-                        </Table.Column>
-                    </Table>
-               </Card>
-               {/* 弹窗 */}
-               <ModalEle type={type}  label={label} loading={loading} changeLabel={changeLabel} visible={visible} addOrEditClassify={addOrEditClassify} closeModal={closeModal}/>
-           </PageContainer>
-        )
-    }
-}
-
-export default Classify
-
+import React, { Component } from 'react';
+import { Input, Space, Row, Col, Card, Button, Table, Modal, message, Popconfirm } from 'antd';
+
+import { PageContainer } from '@ant-design/pro-layout';
+
+import { getClassify, postOrPutClassify, deleteClassify } from '@/services/tieba';
+import { filterTimestamp } from '@/filters/index';
+
+const { Search } = Input;
+
+type ModalEleType = {
+  visible: boolean;
+  addOrEditClassify: () => Promise<void>;
+  closeModal: () => void;
+  changeLabel: (value: string) => void;
+  loading: boolean;
+  label: string;
+  type: number;
+};
+
+const ModalEle: React.FC<ModalEleType> = ({
+  visible,
+  addOrEditClassify,
+  closeModal,
+  changeLabel,
+  type,
+  loading,
+  label,
+}) => {
+  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => changeLabel(e.target.value);
+
+  return (
+    <Modal
+      confirmLoading={loading}
+      visible={visible}
+      title={type === 0 ? '新增类目' : '编辑类目'}
+      width={400}
+      onOk={addOrEditClassify}
+      onCancel={closeModal}
+    >
+      <Row align="middle">
+        <Col span={4}>类目名:</Col>
+        <Col span={16}>
+          <Input maxLength={8} value={label} placeholder="最多8个字符" onChange={onChange}></Input>
+        </Col>
+      </Row>
+    </Modal>
+  );
+};
+
+class Classify extends Component<React.Component> {
+  state = {
+    queryParams: {
+      curPage: 1,
+      label: '',
+      total: 0,
+      type: 0,
+      id: '',
+    },
+    visible: false, // 弹窗
+    loading: false,
+    classifyList: [],
+  };
+
+  componentDidMount() {
+    this.getClassify();
+  }
+
+  // change label
+  changeLabel = (value: string): void =>
+    this.setState({ queryParams: { ...this.state.queryParams, label: value } });
+
+  // 获取贴吧分类
+  async getClassify(): Promise<void> {
+    this.setState({ loading: true });
+    const { code, data } = await getClassify(this.state.queryParams);
+    this.setState({ loading: false });
+    if (code === 0) {
+      const { records, total } = data;
+      this.setState({
+        classifyList: records,
+        queryParams: { ...this.state.queryParams, total },
+      });
+    }
+  }
+
+  // 新增或者修改类目名
+  addOrEditClassify = async (): Promise<void> => {
+    const { type } = this.state.queryParams;
+    this.setState({ loading: true });
+    const { code } = await postOrPutClassify(this.state.queryParams);
+    this.setState({ loading: false });
+    if (code === 0) {
+      message.success(`${type === 0 ? '新增' : '编辑'}成功`);
+
+      this.setState({
+        queryParams: { ...this.state.queryParams, label: '', id: '' },
+      });
+
+      this.getClassify();
+    }
+    this.closeModal();
+  };
+  // 删除分类
+  deleteClassify = async (record: Record<string, any>): Promise<void> => {
+    const { code, data } = await deleteClassify(record.id);
+    if (code === 0) {
+      console.log(data);
+      message.success('删除成功');
+    }
+    this.getClassify();
+  };
+
+  // 打开弹窗
+  openModal = (type: number, record?: Record<string, any>): void => {
+    this.setState({
+      visible: true,
+      queryParams: {
+        ...this.state.queryParams,
+        type,
+        label: record?.label,
+        id: type === 1 ? record?.id : '',
+      },
+    });
+  };
+
+  // 关闭弹窗
+  closeModal = (): void => this.setState({ visible: false });
+
+  // 分页
+  changePagination = (page: number): void => {
+    this.setState({ queryParams: { ...this.state.queryParams, curPage: page } }, () => {
+      this.getClassify();
+    });
+  };
+
+  // 搜索
+  onSearch = (value: string): void => {
+    this.setState(
+      {
+        queryParams: { ...this.state.queryParams, label: value, curPage: 1 },
+      },
+      () => {
+        this.getClassify();
+      },
+    );
+  };
+
+  render() {
+    const { classifyList, visible, queryParams, loading } = this.state;
+    const { type, total, label } = queryParams;
+    const {
+      onSearch,
+      openModal,
+      addOrEditClassify,
+      closeModal,
+      changeLabel,
+      changePagination,
+      deleteClassify,
+    } = this;
+    return (
+      <PageContainer title="贴吧的分类管理">
+        <Card>
+          <Row justify="space-between">
+            <Col>
+              <Search placeholder="输入分类名称" onSearch={onSearch} enterButton />
+            </Col>
+            <Col>
+              <Button type="primary" onClick={() => openModal(0)}>
+                新增分类
+              </Button>
+            </Col>
+          </Row>
+
+          <Table
+            loading={loading}
+            style={{ marginTop: 20 }}
+            dataSource={classifyList}
+            pagination={{ total, onChange: changePagination }}
+          >
+            <Table.Column title="类目名" key="label" dataIndex="label"></Table.Column>
+            <Table.Column title="创建人" key="creator" dataIndex="creator"></Table.Column>
+            <Table.Column
+              title="创建时间"
+              key="createTime"
+              dataIndex="createTime"
+              render={(text) => <span>{filterTimestamp(text)}</span>}
+            ></Table.Column>
+            <Table.Column
+              title="操作"
+              key="action"
+              dataIndex="action"
+              render={(text, record) => (
+                <Space>
+                  <a onClick={() => openModal(1, record as Record<string, any>)}>编辑</a>
+                  <Popconfirm
+                    title="确定要删除吗?"
+                    onConfirm={() => deleteClassify(record as Record<string, any>)}
+                    okText="Yes"
+                    cancelText="No"
+                  >
+                    <a>删除</a>
+                  </Popconfirm>
+                </Space>
+              )}
+            ></Table.Column>
+          </Table>
+        </Card>
+        {/* 弹窗 */}
+        <ModalEle
+          type={type}
+          label={label}
+          loading={loading}
+          changeLabel={changeLabel}
+          visible={visible}
+          addOrEditClassify={addOrEditClassify}
+          closeModal={closeModal}
+        />
+      </PageContainer>
+    );
+  }
+}
+
+export default Classify;

+ 148 - 156
src/pages/tieba/plate/ModalForm.tsx

@@ -1,156 +1,148 @@
-import React, { useState, useEffect } from 'react'
-import { Form, Input, Button, Select, Modal, Upload } from 'antd';
-
-import type { ModalFormType } from './data'
-
-
-import PicturesWall from '@/components/upload'
-
-import { getClassify } from '@/services/tieba'
-
-const { Option } = Select
-
-const layout = {
-    labelCol: { span: 4 },
-    wrapperCol: { span: 16 },
-};
-
-const normFile = (e: any) => {
-  console.log('Upload event:', e);
-  if (Array.isArray(e)) {
-    return e;
-  }
-  return e && e.fileList;
-};
-
-
-const ModalForm: React.FC<ModalFormType> = ({
-    visible, 
-    changeForm, 
-    closeModal, 
-    type, 
-    formData,
-    roleList
-   }) => {
-    const [form] = Form.useForm();
-  
-    const [avatarUrl, setAvatarUrl] = useState<string>('')
-    const [cover, setCover] = useState<string>('')
-    const [classifyList, setClassifyList] = useState<Record<string, any>[]>([])
-    const [state, setState] = useState<number>(0)
-
-    // 获取贴吧分类
-    const GetClassify = async (): Promise<void> => {
-      const {code, data} =  await getClassify({curPage: 1, label: ''})
-      if (code === 0) {
-        console.log(data);
-        const { records } = data
-        setClassifyList(records)
-      }
-    }
-
-    // 反填表单
-    const onFill = (): void => {
-      console.log(formData, 'formData');
-      
-      form.setFieldsValue(formData);
-      setAvatarUrl(formData.avatarUrl)
-      setCover(formData.cover)
-    };
- 
-
-    useEffect( () => {
-      GetClassify()
-      
-      if (type === 1) {
-        onFill() 
-      }
-    }, [])
-
-    const onFinish = (values: any) => {
-
-      const $par = {
-        ...values,
-        avatarUrl,
-        cover,
-        state  
-      }
-      changeForm($par)
-    };
-        
-      
-    // 获取ava
-    const setAvaFn = (url: string) => setAvatarUrl(url)
-    // 获取cover
-    const setCoverFn = (url: string) => setCover(url)
-    // 提交表单
-    const onsubmit = (value: number): void => {
-      setState(value)
-      form.submit()
-    }
-
-    return (
-      <Modal 
-        visible={visible} 
-        width={800}  
-        footer={[
-          <Button key="back" onClick={closeModal}>
-            取消
-          </Button>,
-          <Button key="back" onClick={() => onsubmit(0)}>
-            保存
-          </Button>,
-          <Button key="submit" type="primary" onClick={() => onsubmit(1)}>
-            保存并发布
-          </Button>
-        ]}
-      >
-        <Form {...layout} form={form} name="control-hooks" onFinish={onFinish}>
-          <Form.Item name="label" label="贴吧板块标题" rules={[{ required: true }]}>
-            <Input maxLength={50}/>
-          </Form.Item>
-          <Form.Item name="cover" label="图标">
-              <PicturesWall key="1"  imgUrl={formData.avatarUrl}  setCoverFn={setAvaFn} maxCount={1}/>
-          </Form.Item>
-          <Form.Item name="categoryId" label="分类" rules={[{ required: true }]}>
-            <Select
-              placeholder="请选择分类"
-              allowClear
-            >
-              {
-                classifyList.length && classifyList.map( item => (
-                  <Option key={item.id} value={item.id}>{item.label}</Option>
-                ))
-              }
-            </Select>
-          </Form.Item>
-          <Form.Item name="roleIds" label="展示可见范围" rules={[{ required: true }]}>
-            {/*        onChange={handleChange} */}
-            <Select
-              mode="multiple"
-              allowClear
-              style={{ width: '100%' }}
-              placeholder="请选择可见范围"
-            >
-              {
-                roleList.length && roleList.map( item => (
-                  <Option key={item.id} value={item.id}>{item.label}</Option>
-                ))
-              }
-            </Select>
-          </Form.Item>
-          <Form.Item name="description" label="板块介绍" rules={[{ required: true }]}>
-              <Input.TextArea maxLength={100} ></Input.TextArea>
-          </Form.Item>
-
-          <Form.Item label="封面">
-            <Form.Item name="cover"  valuePropName="fileList" getValueFromEvent={normFile} noStyle>
-              <PicturesWall imgUrl={formData.cover} key="2" setCoverFn={setCoverFn} maxCount={1}/>
-            </Form.Item>
-          </Form.Item>
-        </Form>
-      </Modal>
-    )
-}
-
-export default ModalForm
+import React, { useState, useEffect } from 'react';
+import { Form, Input, Button, Select, Modal, Upload } from 'antd';
+
+import type { ModalFormType } from './data';
+
+import PicturesWall from '@/components/upload';
+
+import { getClassify } from '@/services/tieba';
+
+const { Option } = Select;
+
+const layout = {
+  labelCol: { span: 4 },
+  wrapperCol: { span: 16 },
+};
+
+const normFile = (e: any) => {
+  if (Array.isArray(e)) {
+    return e;
+  }
+  return e && e.fileList;
+};
+
+const ModalForm: React.FC<ModalFormType> = ({
+  visible,
+  changeForm,
+  closeModal,
+  type,
+  formData,
+  roleList,
+}) => {
+  const [form] = Form.useForm();
+
+  const [avatarUrl, setAvatarUrl] = useState<string>('');
+  const [cover, setCover] = useState<string>('');
+  const [classifyList, setClassifyList] = useState<Record<string, any>[]>([]);
+  const [state, setState] = useState<number>(0);
+
+  // 获取贴吧分类
+  const GetClassify = async (): Promise<void> => {
+    const { code, data } = await getClassify({ curPage: 1, label: '' });
+    if (code === 0) {
+      console.log(data);
+      const { records } = data;
+      setClassifyList(records);
+    }
+  };
+
+  // 反填表单
+  const onFill = (): void => {
+    console.log(formData, 'formData');
+
+    form.setFieldsValue(formData);
+    setAvatarUrl(formData.avatarUrl);
+    setCover(formData.cover);
+  };
+
+  useEffect(() => {
+    GetClassify();
+
+    if (type === 1) {
+      onFill();
+    }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, []);
+
+  const onFinish = (values: any) => {
+    const $par = {
+      ...values,
+      avatarUrl,
+      cover,
+      state,
+    };
+    changeForm($par);
+  };
+
+  // 获取ava
+  const setAvaFn = (url: string) => setAvatarUrl(url);
+
+  // 获取cover
+  const setCoverFn = (url: string) => setCover(url);
+
+  // 提交表单
+  const onsubmit = (value: number): void => {
+    setState(value);
+    form.submit();
+  };
+
+  return (
+    <Modal
+      visible={visible}
+      onCancel={closeModal}
+      width={800}
+      footer={[
+        <Button key="back" onClick={closeModal}>
+          取消
+        </Button>,
+        <Button key="back" onClick={() => onsubmit(0)}>
+          保存
+        </Button>,
+        <Button key="submit" type="primary" onClick={() => onsubmit(1)}>
+          保存并发布
+        </Button>,
+      ]}
+    >
+      <Form {...layout} form={form} name="control-hooks" onFinish={onFinish}>
+        <Form.Item name="label" label="贴吧板块标题" rules={[{ required: true }]}>
+          <Input maxLength={50} />
+        </Form.Item>
+        <Form.Item name="cover" label="图标">
+          <PicturesWall key="1" imgUrl={formData.avatarUrl} setCoverFn={setAvaFn} maxCount={1} />
+        </Form.Item>
+        <Form.Item name="categoryId" label="分类" rules={[{ required: true }]}>
+          <Select placeholder="请选择分类" allowClear>
+            {classifyList.length &&
+              classifyList.map((item) => (
+                <Option key={item.id} value={item.id}>
+                  {item.label}
+                </Option>
+              ))}
+          </Select>
+        </Form.Item>
+        <Form.Item name="roleIds" label="展示可见范围" rules={[{ required: true }]}>
+          {/*        onChange={handleChange} */}
+          <Select mode="multiple" allowClear style={{ width: '100%' }} placeholder="请选择可见范围">
+            {roleList.length &&
+              roleList.map((item) => (
+                <Option key={item.id} value={item.id}>
+                  {item.label}
+                </Option>
+              ))}
+          </Select>
+        </Form.Item>
+        <Form.Item name="description" label="板块介绍" rules={[{ required: true }]}>
+          <Input.TextArea maxLength={50}></Input.TextArea>
+        </Form.Item>
+
+        <Form.Item label="封面">
+          <Form.Item name="cover" valuePropName="fileList" getValueFromEvent={normFile} noStyle>
+            <PicturesWall imgUrl={formData.cover} key="2" setCoverFn={setCoverFn} maxCount={1} />
+          </Form.Item>
+        </Form.Item>
+      </Form>
+    </Modal>
+  );
+};
+
+export default ModalForm;

+ 223 - 183
src/pages/tieba/plate/index.tsx

@@ -1,183 +1,223 @@
-import React,  { Component } from 'react'
-import { Row, Col, Card, Space, Select, Button, Input, Table, Image, message } from 'antd'
-import { PageContainer } from '@ant-design/pro-layout'
-import { getPlate, postOrPutPlate, changeTiebaState } from '@/services/tieba'
-import ModalForm from './ModalForm'
-
-import type { ColumnType, typeEnum, postOrPutPlateType } from './data'
-
-import { getRoleList } from '@/services/role'
-
-
-class Plate extends Component<React.Component> {
-
-    state = {
-        queryParams: {
-            curPage: 1,
-            label: '',
-            typeId: ''
-        },
-        id: '',
-        visible: false,
-        type: 0,
-        form: {},
-        total: 0,
-        plateList: [],
-        roleList: []
-    }
-
-    componentDidMount () {
-        this.getPlate()
-        this.GetRoleList()
-    }
-
-    // 改变上架状态
-
-    changeTiebaState = async (record: Record<string, any>): Promise<void> => {
-      const $par = {
-          id: record.id,
-          state: record.state === 1 ? 0 : 1
-      }
-      const { code, data } =  await changeTiebaState($par)
-      if (code === 0) {
-          console.log(data);
-          message.success(record.state === 1 ? '下架成功' : '上架成功')
-      }
-      this.getPlate()
-    }
-
-    // 选择认证角色
-    changeRole = (value: string): void => {
-        console.log(value);
-        
-        this.setState({
-            queryParams: {...this.state.queryParams, typeId: value}
-        })
-    }
-    // 搜索
-    onSearch = (): void => {
-        this.getPlate()
-    }
-
-    // 打开弹窗
-    openModal = (type: typeEnum, record?: ColumnType): void => {
-        console.log(record, 'form');
-        this.setState({
-            type,
-            visible: true,
-            form: record,
-            id: record?.id
-        })
-    }
-    // 关闭弹窗
-    closeModal = (): void => this.setState({visible: false})
-
-
-    // 提交表单
-    changeForm = async (params: postOrPutPlateType): Promise<void> => {
-      const { code, data } =  await postOrPutPlate({...params, type: this.state.type, id: this.state.id})
-      if (code === 0) {
-          console.log(data);
-          message.success(this.state.type === 0 ? '新增成功' : '编辑成功')
-          this.setState({
-            visible: false
-          })
-      }
-      this.getPlate()
-    }   
-
-      // 用户角色
-    GetRoleList = async (): Promise<void>  => {
-        const { code, data } = await getRoleList({curPage: 1})
-        if (code === 0) {
-          console.log(data);
-          const { records } = data
-          this.setState({
-            roleList: records
-          })
-        }
-      }
-
-    // 获取板块列表
-    getPlate = async (): Promise<void> => {
-      const { queryParams } = this.state 
-      const { code, data} =  await getPlate(queryParams)
-      if (code === 0) {
-          const { records, total } = data
-          this.setState({
-            plateList: records,
-            total
-          })
-      }
-    }
-
-    render () {
-        const { plateList, visible , type, form, roleList, queryParams} = this.state
-        const { openModal, changeForm, closeModal, onSearch, changeRole, changeTiebaState} = this
-        return (
-            <PageContainer title="贴吧板块管理">
-                <Card>
-                    <Row justify="space-between">
-                        <Col>
-                            <Space>
-                                <Select 
-                                    style={{width: 200}} 
-                                    placeholder="请输入认证角色"
-                                    onChange={changeRole}
-                                    value={queryParams.typeId}
-                                >
-                                    {
-                                        roleList.length && roleList.map( (item: Record<string, any>) => (
-                                            <Select.Option key={item.id}  value={item.id} >
-                                                {item.label} 
-                                            </Select.Option>
-                                        ))
-                                    }
-                                   
-                                </Select>
-                                <Input 
-                                    value={queryParams.label} 
-                                    onChange={ (e: React.ChangeEvent<HTMLInputElement>) => {
-                                        this.setState({queryParams: {...queryParams, label: e.target.value}}) } 
-                                    }
-                                    placeholder="请输入贴吧板块名称" 
-                                />
-                            </Space>
-                        </Col>
-                        <Col>
-                            <Space>
-                                <Button danger onClick={() => {this.setState({queryParams: {...queryParams, label: '', typeId: ''}})}}>重置</Button>
-                                <Button type="primary" onClick={onSearch}>搜索</Button>
-                                <Button onClick={() => openModal(0)}>创建</Button>
-                            </Space>
-                        </Col>
-                    </Row>
-                    <Table dataSource={plateList} style={{marginTop: 20}}>
-                        <Table.Column<ColumnType> key="avatarUrl"  title="贴吧图标" 
-                            render={ (text, record): React.ReactNode => (
-                                <Image width={50} height={50} preview src={record.avatarUrl} alt="我炸裂了" />
-                            )}
-                        >
-                        </Table.Column>
-                        <Table.Column<ColumnType> key="label" dataIndex="label" title="贴吧名" ></Table.Column>
-                        <Table.Column<ColumnType> key="label" dataIndex="label" title="分类名" ></Table.Column>
-                        <Table.Column<ColumnType> key="creator" dataIndex="creator" title="创建人" ></Table.Column>
-                        <Table.Column<ColumnType> key="postNum" dataIndex="postNum" title="帖子数量" ></Table.Column>
-                        <Table.Column<ColumnType> key="action" dataIndex="action" title="操作" 
-                            render={ (text, record): React.ReactNode  => (
-                                <Space>
-                                    {/* <a>查看</a> */}
-                                    <a onClick={() => openModal(1, record)}>编辑</a>
-                                    <a onClick={() => changeTiebaState(record)}>{record.state === 0 ? '发布' : '下架'}</a>
-                                </Space>
-                            )}
-                        ></Table.Column>
-                    </Table>
-                </Card>
-                {/* 新增或者编辑弹窗 */}
-                { visible && <ModalForm roleList={roleList} type={type} formData={form as postOrPutPlateType} visible={visible} changeForm={changeForm} closeModal={closeModal}/>} 
-            </PageContainer>
-        )
-    }
-}
-export default Plate
+import React, { Component } from 'react';
+import { Row, Col, Card, Space, Select, Button, Input, Table, Image, message } from 'antd';
+import { PageContainer } from '@ant-design/pro-layout';
+import { getPlate, postOrPutPlate, changeTiebaState } from '@/services/tieba';
+import ModalForm from './ModalForm';
+
+import type { ColumnType, typeEnum, postOrPutPlateType } from './data';
+
+import { getRoleList } from '@/services/role';
+
+class Plate extends Component<React.Component> {
+  state = {
+    queryParams: {
+      curPage: 1,
+      label: '',
+      typeId: '',
+    },
+    id: '',
+    visible: false,
+    type: 0,
+    form: {},
+    total: 0,
+    plateList: [],
+    roleList: [],
+  };
+
+  componentDidMount() {
+    this.getPlate();
+    this.GetRoleList();
+  }
+
+  // 改变上架状态
+  changeTiebaState = async (record: Record<string, any>): Promise<void> => {
+    const $par = {
+      id: record.id,
+      state: record.state === 1 ? 0 : 1,
+    };
+    const { code, data } = await changeTiebaState($par);
+    if (code === 0) {
+      console.log(data);
+      message.success(record.state === 1 ? '下架成功' : '上架成功');
+    }
+    this.getPlate();
+  };
+
+  // 选择认证角色
+  changeRole = (value: string): void => {
+    console.log(value);
+
+    this.setState({
+      queryParams: { ...this.state.queryParams, typeId: value },
+    });
+  };
+  // 搜索
+  onSearch = (): void => {
+    this.getPlate();
+  };
+
+  // 打开弹窗
+  openModal = (type: typeEnum, record?: ColumnType): void => {
+    console.log(record, type, 'record');
+
+    this.setState({
+      type,
+      visible: true,
+      form: record,
+      id: record?.id,
+    });
+  };
+  // 关闭弹窗
+  closeModal = (): void => this.setState({ visible: false });
+
+  // 提交表单
+  changeForm = async (params: postOrPutPlateType): Promise<void> => {
+    const { code, data } = await postOrPutPlate({
+      ...params,
+      type: this.state.type,
+      id: this.state.id,
+    });
+    console.log(code, data, '提交表单');
+
+    if (code === 0) {
+      console.log(data);
+      message.success(this.state.type === 0 ? '新增成功' : '编辑成功');
+      this.setState({
+        visible: false,
+      });
+    }
+    this.getPlate();
+  };
+
+  // 用户角色
+  GetRoleList = async (): Promise<void> => {
+    const { code, data } = await getRoleList({ curPage: 1 });
+    if (code === 0) {
+      console.log(data);
+      const { records } = data;
+      this.setState({
+        roleList: records,
+      });
+    }
+  };
+
+  // 获取板块列表
+  getPlate = async (): Promise<void> => {
+    const { queryParams } = this.state;
+    const { code, data } = await getPlate(queryParams);
+    if (code === 0) {
+      const { records, total } = data;
+      console.log(records, 'recordsrecordsrecords');
+
+      this.setState({
+        plateList: records,
+        total,
+      });
+    }
+  };
+
+  render() {
+    const { plateList, visible, type, form, roleList, queryParams } = this.state;
+    const { openModal, changeForm, closeModal, onSearch, changeRole, changeTiebaState } = this;
+    return (
+      <PageContainer title="贴吧板块管理">
+        <Card>
+          <Row justify="space-between">
+            <Col>
+              <Space>
+                <Select
+                  style={{ width: 200 }}
+                  placeholder="请输入认证角色"
+                  onChange={changeRole}
+                  value={queryParams.typeId}
+                  allowClear
+                >
+                  {roleList.length &&
+                    roleList.map((item: Record<string, any>) => (
+                      <Select.Option key={item.id} value={item.id}>
+                        {item.label}
+                      </Select.Option>
+                    ))}
+                </Select>
+                <Input
+                  value={queryParams.label}
+                  onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+                    this.setState({ queryParams: { ...queryParams, label: e.target.value } });
+                  }}
+                  placeholder="请输入贴吧板块名称"
+                />
+              </Space>
+            </Col>
+            <Col>
+              <Space>
+                <Button
+                  danger
+                  onClick={() => {
+                    this.setState({ queryParams: { ...queryParams, label: '', typeId: '' } });
+                  }}
+                >
+                  重置
+                </Button>
+                <Button type="primary" onClick={onSearch}>
+                  搜索
+                </Button>
+                <Button onClick={() => openModal(0, {} as ColumnType)}>创建</Button>
+              </Space>
+            </Col>
+          </Row>
+          <Table dataSource={plateList} style={{ marginTop: 20 }}>
+            <Table.Column<ColumnType>
+              key="avatarUrl"
+              title="贴吧图标"
+              render={(text, record): React.ReactNode => (
+                <Image width={50} height={50} preview src={text.avatarUrl} alt="我炸裂了" />
+              )}
+            ></Table.Column>
+            <Table.Column<ColumnType> key="label" dataIndex="label" title="贴吧名"></Table.Column>
+            <Table.Column<ColumnType>
+              key="categoryLabel"
+              dataIndex="categoryLabel"
+              title="分类名"
+            ></Table.Column>
+            <Table.Column<ColumnType>
+              key="creator"
+              dataIndex="creator"
+              title="创建人"
+            ></Table.Column>
+            <Table.Column<ColumnType>
+              key="postNum"
+              dataIndex="postNum"
+              title="帖子数量"
+            ></Table.Column>
+            <Table.Column<ColumnType>
+              key="action"
+              dataIndex="action"
+              title="操作"
+              render={(text, record): React.ReactNode => (
+                <Space>
+                  {/* <a>查看</a> */}
+                  <a onClick={() => openModal(1, record)}>编辑</a>
+                  <a onClick={() => changeTiebaState(record)}>
+                    {record.state === 0 ? '发布' : '下架'}
+                  </a>
+                </Space>
+              )}
+            ></Table.Column>
+          </Table>
+        </Card>
+        {/* 新增或者编辑弹窗 */}
+        {visible && (
+          <ModalForm
+            roleList={roleList}
+            type={type}
+            formData={form as postOrPutPlateType}
+            visible={visible}
+            changeForm={changeForm}
+            closeModal={closeModal}
+          />
+        )}
+      </PageContainer>
+    );
+  }
+}
+export default Plate;

+ 247 - 226
src/pages/tieba/posts/index.tsx

@@ -1,226 +1,247 @@
-import React, { useEffect, useState, useReducer } from 'react'
-import { PageContainer } from '@ant-design/pro-layout'
-import { Row, Col, Space, Button, Input, Table, Card, Select, message, Tooltip } from 'antd'
-
-import { getPlate, getPosts, enablePosts } from '@/services/tieba'
-
-import { useClassify } from '@/hooks/tieba/index'
-
-import type { initstateType, actionType }  from './data'
-
-import DetailModal from './modal'
-
-const request = (url: string): Promise<any>  => {
-    return new Promise( (resolve, reject) => {
-        const xhr = new XMLHttpRequest()
-        xhr.open('GET', url, false)
-        xhr.onreadystatechange =  function ()  {
-            if (xhr.readyState === 4 && xhr.status === 200) {
-                resolve(xhr.response)
-            } else {
-                reject(new Error('置换contenturl出错'))
-            }
-        }
-        xhr.send()
-    }).catch(e => e)
-}
-
-const sendRequest = async (url: string ) => await request(url)
-
-const initstate: initstateType = {
-    curPage: 1,
-    barId: '',
-    categoryId: '',
-    label: '',
-    type: 0
-}
-
-const reduce: React.Reducer<initstateType, actionType> = (state, action): initstateType => {
-    const { type, value} = action
-    
-    switch (type) {
-        case 'reload':
-           return {...state, barId: '', categoryId: '', label: ''} 
-        case 'barId':
-            return {...state, barId: value}
-        case 'curPage':
-            return {...state, curPage: value}
-        case 'categoryId':
-            return {...state, categoryId: value}
-        case 'label':
-            return {...state, label: value}
-        case 'type':
-            return {...state, type: value}
-        default:
-           throw new Error('posts/index.tsx => reduce 参数 action 下, type类型不存在')
-    }
-
-}
-
-const Posts: React.FC = () => {
-
-    const [plateList, setPlateList] = useState([])
-
-    const [postsList, setPostsList] = useState([])
-
-    const classIfy =  useClassify()
-    
-    const [total, setTotal] = useState(0)
-
-    const [queryParams, dispatch] = useReducer(reduce, initstate)
-
-    const [visible, setVissible] = useState(false)
-
-    const [detailData, setDetailData] = useState({})
-
-    const [loading, setLoading] = useState(false)
-    // 当前点击的id
-    // const [id, setId] = useState('')
-    // 获取贴吧板块
-    const GetPlate = async (): Promise<void> => {
-      const { code, data } =  await getPlate({curPage: 1, pageSize: 10000})
-
-      if (code === 0) {
-          const { records } = data
-          setPlateList(records)
-      }
-    }
-
-    // 获取帖子
-    const GetPosts = async (): Promise<void> => {
-      setLoading(true)
-      const { code, data} =  await getPosts(queryParams)
-      setLoading(false)
-      if (code === 0) {
-          const { records, total: Total } = data
-
-            // eslint-disable-next-line no-plusplus
-            for(let i = 0 ; i < records.length; i++) {
-                // eslint-disable-next-line no-await-in-loop
-                records[i].content = records[i].contentUrl && await sendRequest(records[i]?.contentUrl)
-            }
-          setPostsList(records)
-          setTotal(Total)
-      }
-    } 
-
-    // 封禁帖子
-    const EnablePosts = async (record: Record<string, any>): Promise<void> => {
-        const $par = {
-            id: record.id,
-            enable: record.enable ? 0 : 1,
-            type: queryParams.type
-        }
-      const { code } =  await enablePosts($par)
-      if (code === 0) {
-          message.success('操作成功')
-      }
-      GetPosts()
-    }
-
-    useEffect (() =>{
-        GetPlate()
-        GetPosts()
-    }, [])
-    // 打开弹窗
-    const openModal = (record: Record<string, any>): void => {
-        setVissible(true)
-        setDetailData(record)
-    } 
-    // 关闭弹窗
-    const closeModal = (): void => setVissible(false)
-
-    // 选择barId
-    const changeBarId = (value: string): void =>  dispatch({type: 'barId', value})
-    // 填写标题
-    const changeLabel = (e: React.ChangeEvent<HTMLInputElement>): void => dispatch({type: 'label', value: e.target.value})
-
-    return (
-        <PageContainer title="帖子管理">
-            <Card>
-                <Row gutter={[16, 16]} justify="space-between">
-                    <Col>
-                        <Space>
-                            <Select 
-                                style={{width: 200}} 
-                                placeholder="请选择贴吧板块" 
-                                value={queryParams.barId || undefined}
-                                onChange={changeBarId}
-                            >
-                                {
-                                    plateList.length && plateList.map( (item: Record<string, any> ) => (
-                                        <Select.Option key={item.id} value={item.id}>
-                                            {item.label}
-                                        </Select.Option>
-                                    ))
-                                }
-                            </Select>
-                            <Select 
-                                style={{width: 200}} 
-                                placeholder="请选择分类"
-                                value={queryParams.categoryId || undefined}
-                                onChange={(value: string) => dispatch({type: 'categoryId', value})}
-                            >
-                                {
-                                    classIfy.length && classIfy.map( (item: Record<string, any> ) => (
-                                        <Select.Option key={item.id} value={item.id}>
-                                            {item.label}
-                                        </Select.Option>
-                                    ))
-                                }
-                            </Select>
-                            <Input onChange={changeLabel} placeholder="请输入帖子标题"></Input>
-                            <Select 
-                                style={{width: 200}} 
-                                placeholder="请选择类型" 
-                                value={queryParams.type}
-                                onChange={(value: number) => dispatch({type: 'type', value})}
-                            >
-                                <Select.Option value={0}>帖子</Select.Option>
-                                <Select.Option value={1}>问答</Select.Option>
-                            </Select>
-                        </Space>
-                    </Col>
-                    <Col>
-                        <Space>
-                            <Button onClick={() => dispatch({type: 'reload'})}>重置</Button>
-                            <Button type="primary" onClick={GetPosts}>搜索</Button>
-                        </Space>
-                    </Col>
-                </Row>
-                    <Table rowKey={ record => record.id } loading={loading} style={{marginTop: 20}} dataSource={postsList} pagination={{total, onChange: (page) =>  dispatch({type: 'curPage', value: page})}}>
-                        <Table.Column key="label" dataIndex="label" title="帖子标题"  ></Table.Column>
-                        <Table.Column key="content" width={320} ellipsis dataIndex="content" title="内容" 
-                            render={(text, record: Record<string, any>) => (
-                                <Tooltip placement="topLeft" title={record.content}>
-                                    <span>{record.content}</span>
-                                </Tooltip>
-                            )}
-                        ></Table.Column>
-                        <Table.Column key="authorName" dataIndex="authorName" title="发布人" 
-                            render={(text, record: Record<string, any>) => (
-                                <Row >
-                                    <Col>{record.authorName}</Col>
-                                    <Col>{record.createTime}</Col>
-                                </Row>
-                            )} />
-                        <Table.Column key="answerNum" dataIndex="answerNum" title="回帖数"  ></Table.Column>
-                        <Table.Column key="praiseNum" dataIndex="praiseNum" title="点赞数"  ></Table.Column>
-                        <Table.Column key="action" dataIndex="action" title="操作"
-                            render={(text, record: Record<string, any>)  => (
-                                <Space>
-                                    <a onClick={() => openModal(record)}>查看</a>
-                                    <a onClick={() => EnablePosts(record)}>{record.enable? '封禁' : '解封'}</a>
-                                </Space>
-                            )}
-                        />
-                    </Table>
-            </Card>
-            {/* 详情弹窗 */}
-            <DetailModal detailData={detailData} visible={visible} closeModal={closeModal} />
-        </PageContainer>
-    )
-    
-}
-
-export default Posts
+import React, { useEffect, useState, useReducer } from 'react';
+import { PageContainer } from '@ant-design/pro-layout';
+import { Row, Col, Space, Button, Input, Table, Card, Select, message, Tooltip, Image } from 'antd';
+
+import { getPlate, getPosts, enablePosts } from '@/services/tieba';
+
+import { useClassify } from '@/hooks/tieba/index';
+
+import type { initstateType, actionType } from './data';
+
+import DetailModal from './modal';
+
+const request = (url: string): Promise<any> => {
+  return new Promise((resolve, reject) => {
+    const xhr = new XMLHttpRequest();
+    xhr.open('GET', url, false);
+    xhr.onreadystatechange = function () {
+      if (xhr.readyState === 4 && xhr.status === 200) {
+        resolve(xhr.response);
+      } else {
+        reject(new Error('置换contenturl出错'));
+      }
+    };
+    xhr.send();
+  }).catch((e) => e);
+};
+
+const sendRequest = async (url: string) => await request(url);
+
+const initstate: initstateType = {
+  curPage: 1,
+  barId: '',
+  categoryId: '',
+  label: '',
+  type: 0,
+};
+
+const reduce: React.Reducer<initstateType, actionType> = (state, action): initstateType => {
+  const { type, value } = action;
+
+  switch (type) {
+    case 'reload':
+      return { ...state, barId: '', categoryId: '', label: '' };
+    case 'barId':
+      return { ...state, barId: value };
+    case 'curPage':
+      return { ...state, curPage: value };
+    case 'categoryId':
+      return { ...state, categoryId: value };
+    case 'label':
+      return { ...state, label: value };
+    case 'type':
+      return { ...state, type: value };
+    default:
+      throw new Error('posts/index.tsx => reduce 参数 action 下, type类型不存在');
+  }
+};
+
+const Posts: React.FC = () => {
+  const [plateList, setPlateList] = useState([]);
+
+  const [postsList, setPostsList] = useState([]);
+
+  const classIfy = useClassify();
+
+  const [total, setTotal] = useState(0);
+
+  const [queryParams, dispatch] = useReducer(reduce, initstate);
+
+  const [visible, setVissible] = useState(false);
+
+  const [detailData, setDetailData] = useState({});
+
+  const [loading, setLoading] = useState(false);
+  // 当前点击的id
+  // const [id, setId] = useState('')
+  // 获取贴吧板块
+  const GetPlate = async (): Promise<void> => {
+    const { code, data } = await getPlate({ curPage: 1, pageSize: 10000 });
+
+    if (code === 0) {
+      const { records } = data;
+      setPlateList(records);
+    }
+  };
+
+  // 获取帖子
+  const GetPosts = async (): Promise<void> => {
+    setLoading(true);
+    const { code, data } = await getPosts(queryParams);
+    setLoading(false);
+    if (code === 0) {
+      const { records, total: Total } = data;
+
+      // eslint-disable-next-line no-plusplus
+      for (let i = 0; i < records.length; i++) {
+        // eslint-disable-next-line no-await-in-loop
+        // records[i].content = records[i].contentUrl && await sendRequest(records[i]?.contentUrl)
+      }
+      console.log(records, 'recordsrecordsrecords');
+
+      setPostsList(records);
+      setTotal(Total);
+    }
+  };
+
+  // 封禁帖子
+  const EnablePosts = async (record: Record<string, any>): Promise<void> => {
+    const $par = {
+      id: record.id,
+      enable: record.enable ? 0 : 1,
+      type: queryParams.type,
+    };
+    const { code } = await enablePosts($par);
+    if (code === 0) {
+      message.success('操作成功');
+    }
+    GetPosts();
+  };
+
+  useEffect(() => {
+    GetPlate();
+    GetPosts();
+  }, []);
+  // 打开弹窗
+  const openModal = (record: Record<string, any>): void => {
+    setVissible(true);
+    setDetailData(record);
+  };
+  // 关闭弹窗
+  const closeModal = (): void => setVissible(false);
+
+  // 选择barId
+  const changeBarId = (value: string): void => dispatch({ type: 'barId', value });
+  // 填写标题
+  const changeLabel = (e: React.ChangeEvent<HTMLInputElement>): void =>
+    dispatch({ type: 'label', value: e.target.value });
+
+  return (
+    <PageContainer title="帖子管理">
+      <Card>
+        <Row gutter={[16, 16]} justify="space-between">
+          <Col>
+            <Space>
+              <Select
+                style={{ width: 200 }}
+                placeholder="请选择贴吧板块"
+                value={queryParams.barId || undefined}
+                onChange={changeBarId}
+                allowClear
+              >
+                {plateList.length &&
+                  plateList.map((item: Record<string, any>) => (
+                    <Select.Option key={item.id} value={item.id}>
+                      {item.label}
+                    </Select.Option>
+                  ))}
+              </Select>
+              <Select
+                style={{ width: 200 }}
+                placeholder="请选择分类"
+                value={queryParams.categoryId || undefined}
+                onChange={(value: string) => dispatch({ type: 'categoryId', value })}
+                allowClear
+              >
+                {classIfy.length &&
+                  classIfy.map((item: Record<string, any>) => (
+                    <Select.Option key={item.id} value={item.id}>
+                      {item.label}
+                    </Select.Option>
+                  ))}
+              </Select>
+              <Input onChange={changeLabel} placeholder="请输入帖子标题"></Input>
+              <Select
+                style={{ width: 200 }}
+                placeholder="请选择类型"
+                value={queryParams.type}
+                onChange={(value: number) => dispatch({ type: 'type', value })}
+                allowClear
+              >
+                <Select.Option value={0}>帖子</Select.Option>
+                <Select.Option value={1}>问答</Select.Option>
+              </Select>
+            </Space>
+          </Col>
+          <Col>
+            <Space>
+              <Button onClick={() => dispatch({ type: 'reload' })}>重置</Button>
+              <Button type="primary" onClick={GetPosts}>
+                搜索
+              </Button>
+            </Space>
+          </Col>
+        </Row>
+        <Table
+          rowKey={(record) => record.id}
+          loading={loading}
+          style={{ marginTop: 20 }}
+          dataSource={postsList}
+          pagination={{ total, onChange: (page) => dispatch({ type: 'curPage', value: page }) }}
+        >
+          <Table.Column key="label" dataIndex="label" title="帖子标题"></Table.Column>
+          <Table.Column
+            key="content"
+            width={320}
+            ellipsis
+            dataIndex="content"
+            title="内容"
+            render={(text, record: Record<string, any>) => (
+              <Tooltip placement="topLeft" title={record.content}>
+                <span>{record.content}</span>
+              </Tooltip>
+            )}
+          ></Table.Column>
+          <Table.Column
+            key="authorName"
+            dataIndex="authorName"
+            title="发布人"
+            render={(text, record: Record<string, any>) => (
+              <Space>
+                <Col>{record.authorName}</Col>
+                <Col>{record.createTime}</Col>
+              </Space>
+            )}
+          />
+          <Table.Column key="answerNum" dataIndex="answerNum" title="回帖数"></Table.Column>
+          <Table.Column key="praiseNum" dataIndex="praiseNum" title="点赞数"></Table.Column>
+          <Table.Column
+            key="action"
+            dataIndex="action"
+            title="操作"
+            render={(text, record: Record<string, any>) => (
+              <Space>
+                <a onClick={() => openModal(record)}>查看</a>
+                <a onClick={() => EnablePosts(record)}>{record.enable ? '封禁' : '解封'}</a>
+              </Space>
+            )}
+          />
+        </Table>
+      </Card>
+      {/* 详情弹窗 */}
+      <DetailModal detailData={detailData} visible={visible} closeModal={closeModal} />
+    </PageContainer>
+  );
+};
+
+export default Posts;

+ 27 - 28
src/services/notice.ts

@@ -1,28 +1,27 @@
-import request from '@/utils/request';
-
-import type { PostOrPutNoticeType } from '@/pages/notice/data' 
-// 查询公告
-type getNoticeType = {
-    curPage: number,
-    label: string
-}
-export async function getNotice ( params: getNoticeType ): Promise<any> {
-  return request('/forum/admin/notice', { params });
-}
-// 新增公告
-
-export async function postOrPutNotice ( data: PostOrPutNoticeType ): Promise<any> {
-  const { type } = data
-  return request('/forum/admin/notice', { method: type === 0 ? 'POST' : 'PUT', data });
-}
-
-// 查看详情
-export async function getNoticeDetail ( id: string): Promise<any> {
-  return request(`/forum/admin/notice/${id}`);
-}
-
-// 删除公告
-export async function deleteNotice ( id: string): Promise<any> {
-  return request(`/forum/admin/notice/${id}`, {method: 'DELETE'});
-}
-
+import request from '@/utils/request';
+
+import type { PostOrPutNoticeType } from '@/pages/notice/data';
+// 查询公告
+type getNoticeType = {
+  curPage: number;
+  label: string;
+};
+export async function getNotice(params: getNoticeType): Promise<any> {
+  return request('/forum/admin/notice', { params });
+}
+// 新增公告
+
+export async function postOrPutNotice(data: PostOrPutNoticeType): Promise<any> {
+  const { methodType } = data;
+  return request('/forum/admin/notice', { method: methodType === 0 ? 'POST' : 'PUT', data });
+}
+
+// 查看详情
+export async function getNoticeDetail(id: string): Promise<any> {
+  return request(`/forum/admin/notice/${id}`);
+}
+
+// 删除公告
+export async function deleteNotice(id: string): Promise<any> {
+  return request(`/forum/admin/notice/${id}`, { method: 'DELETE' });
+}

+ 15 - 10
src/services/role.ts

@@ -1,10 +1,15 @@
-import request from '@/utils/request';
-
-// 查询用户角色
-type getRoleListType = {
-    curPage: number
-}
-export async function getRoleList(params: getRoleListType): Promise<any> {
-    return request('/forum/admin/role', { params });
-  }
-  
+import request from '@/utils/request';
+import { role } from '@/types/role';
+// 查询用户角色
+type getRoleListType = {
+  curPage: number;
+};
+export async function getRoleList(params: getRoleListType): Promise<any> {
+  return request('/forum/admin/role', { params });
+}
+
+//   修改角色名称
+// /forum/admin/role
+export async function putRoleList(data: role): Promise<any> {
+  return request('/forum/admin/role', { data, method: 'PUT' });
+}

+ 23 - 1
src/services/user.ts

@@ -1,6 +1,5 @@
 import request from '@/utils/request';
 
-
 export async function query(): Promise<any> {
   return request('/api/users');
 }
@@ -12,3 +11,26 @@ export async function queryCurrent(): Promise<any> {
 export async function queryNotices(): Promise<any> {
   return request('/api/notices');
 }
+
+/**
+ * @description 用户登录
+ */
+interface loginI {
+  username: string;
+  password: number | string;
+}
+
+export async function login(data: loginI): Promise<any> {
+  return request('/forum/admin/user/login', { method: 'POST', data });
+}
+
+/**
+ * @description 上传图片
+ */
+export async function uploadFile(data) {
+  return request('/forum/file/uploadImage', {
+    method: 'POST',
+    data,
+    headers: { 'Content-Type': ' multipart/form-data' },
+  });
+}

+ 6 - 0
src/types/club.ts

@@ -0,0 +1,6 @@
+import { role } from './role';
+export interface ClubState {
+  loading: boolean;
+  visable: boolean;
+  curClub: role;
+}

+ 7 - 0
src/types/role.ts

@@ -0,0 +1,7 @@
+export interface role {
+  id: string;
+  isForbidden: boolean | null;
+  label: string;
+  previousLabel?: string;
+  [key: string]: any;
+}

+ 5 - 0
src/utils/eventkey.ts

@@ -0,0 +1,5 @@
+/**
+ * @description token
+ */
+
+export const Token = 'token';

+ 7 - 0
src/utils/global.ts

@@ -0,0 +1,7 @@
+import { baseUrl } from '@/utils/request';
+
+/**
+ * @description 文件上传地址
+ */
+
+export const fileUploadAddress = `${baseUrl}/forum/file/uploadImage`;

+ 32 - 9
src/utils/request.ts

@@ -4,6 +4,7 @@
  */
 import { extend } from 'umi-request';
 import { notification } from 'antd';
+import { Token } from '@/utils/eventkey';
 
 const codeMessage = {
   200: '服务器成功返回请求的数据。',
@@ -28,6 +29,8 @@ const codeMessage = {
  */
 const errorHandler = (error: { response: Response }): Response => {
   const { response } = error;
+  console.log(response, '我是异常处理');
+
   if (response && response.status) {
     const errorText = codeMessage[response.status] || response.statusText;
     const { status, url } = response;
@@ -56,18 +59,38 @@ const request = extend({
 const headers = {
   Accept: 'application/json',
   'Content-Type': 'application/json',
-  token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlSWQiOiIwIiwiZXhwIjoxNjExNzQ0NjY2LCJ1c2VySWQiOiIxIn0.EiYOpQeETTqeUT_skDR4-MSRAHa2YUSQPBS_gqJu9GY'
+  token: '',
 };
 
-export const baseUrl: string = 'https://open.luojigou.vip'
+// https://open.luojigou.vip
+
+export const baseUrl: string =
+  process.env.NODE_ENV === 'development' ? '/api' : 'https://open.luojigou.vip';
+
+request.interceptors.request.use(
+  (url: string, options: any, mutUrl?: string) => {
+    const requesturl = mutUrl || `${baseUrl}${url}`;
+    const token = localStorage.getItem(Token);
+    const Headers = { ...headers, token };
+    return {
+      url: requesturl,
+      options: { ...options, headers: Headers, interceptors: true },
+    };
+  },
+  { global: true },
+);
 
-request.interceptors.request.use((url: string, options: any, mutUrl?: string) => {
-  const requesturl = mutUrl || `${baseUrl}${url}`
-  return ({
-    url: requesturl,
-    options: {...options, headers, interceptors: true}
-  });
-}, {global: true})
+request.interceptors.response.use(async (response, options) => {
+  const r = await response.clone().json();
+  if (r.code === 5001) {
+    if (process.env.NODE_ENV === 'development') {
+      window.location.href = 'http://localhost:8000/#/user/login';
+    } else {
+      window.location.href = 'https://luojigou.vip/tieba-admin/#/user/login';
+    }
+  }
 
+  return response;
+});
 
 export default request;