开发: 添加表情匹配, 添加客群中心页面,修复弹框关闭
This commit is contained in:
		@@ -40,7 +40,7 @@ export default [
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: '/scrm/custom',
 | 
			
		||||
        name: '客户管理',
 | 
			
		||||
        name: '客群管理',
 | 
			
		||||
        routes: [
 | 
			
		||||
          {
 | 
			
		||||
            path: '/scrm/custom',
 | 
			
		||||
@@ -53,11 +53,17 @@ export default [
 | 
			
		||||
            component: './Workbench',
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            name: '客户列表',
 | 
			
		||||
            name: '客户中心',
 | 
			
		||||
            icon: 'table',
 | 
			
		||||
            path: '/scrm/custom/list',
 | 
			
		||||
            component: './CustomList',
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            name: '客群中心',
 | 
			
		||||
            icon: 'table',
 | 
			
		||||
            path: '/scrm/custom/grouplist',
 | 
			
		||||
            component: './GroupList',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								public/avatar.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								public/avatar.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1681890424579" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3288" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M531.46112 777.46176c0-72.93952 32.38912-138.27072 83.52256-182.53824a691.72736 691.72736 0 0 0-103.1936-7.68c-236.14976 0-427.5968 116.58752-427.5968 260.39296 0 143.8208 191.44704 119.51616 427.5968 119.51616 39.23456 0 77.18912 0.6656 113.28512 1.1264-56.9344-44.17024-93.61408-113.18784-93.61408-190.81728zM511.78496 50.29376c-142.94016 0-258.81088 115.8656-258.81088 258.79552 0 142.9504 115.87072 258.82112 258.81088 258.82112 142.94528 0 258.80576-115.87072 258.80576-258.82112 0-142.92992-115.86048-258.79552-258.80576-258.79552z m0 419.66592C462.336 469.95968 421.02272 450.56 410.5216 389.12h54.94272c8.53504 10.24 25.96864 28.87168 46.32064 28.87168 20.35712 0 37.8112-18.63168 46.33088-28.87168h54.94784c-10.50112 61.44-51.83488 80.83968-101.27872 80.83968z" fill="#1890ff" p-id="3289"></path><path d="M776.09472 582.66112c-106.83392 0-193.4336 86.59968-193.4336 193.4336s86.59968 193.4336 193.4336 193.4336 193.4336-86.59968 193.4336-193.4336-86.59968-193.4336-193.4336-193.4336zM875.52 849.92h-199.68v-46.08h199.68v46.08z m0-102.4h-199.68v-46.08h199.68v46.08z" fill="#1890ff" p-id="3290"></path></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.4 KiB  | 
@@ -6,6 +6,7 @@ import { history, Link } from '@umijs/max';
 | 
			
		||||
import { App } from 'antd';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import defaultSettings from '../config/defaultSettings';
 | 
			
		||||
import AvatarSvg from '../public/avatar.svg';
 | 
			
		||||
import { AvatarDropdown, AvatarName } from './components/RightContent/AvatarDropdown';
 | 
			
		||||
import { errorConfig } from './requestErrorConfig';
 | 
			
		||||
import { post } from './services/ajax';
 | 
			
		||||
@@ -57,9 +58,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
 | 
			
		||||
    // actionsRender: () => [<Question key="doc" />, <SelectLang key="SelectLang" />],
 | 
			
		||||
    actionsRender: () => [],
 | 
			
		||||
    avatarProps: {
 | 
			
		||||
      src: initialState?.currentUser?.avatar
 | 
			
		||||
        ? '/api/' + initialState?.currentUser?.avatar
 | 
			
		||||
        : 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
 | 
			
		||||
      src: initialState?.currentUser?.avatar ? '/api/' + initialState?.currentUser?.avatar : AvatarSvg,
 | 
			
		||||
      title: <AvatarName />,
 | 
			
		||||
      render: (_, avatarChildren) => {
 | 
			
		||||
        return <AvatarDropdown>{avatarChildren}</AvatarDropdown>;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import type { MenuInfo } from 'rc-menu/lib/interface';
 | 
			
		||||
import React, { useCallback, useState } from 'react';
 | 
			
		||||
import { flushSync } from 'react-dom';
 | 
			
		||||
import HeaderDropdown from '../HeaderDropdown';
 | 
			
		||||
import { EditPassword } from './EditPassWord';
 | 
			
		||||
 | 
			
		||||
export type GlobalHeaderRightProps = {
 | 
			
		||||
  menu?: boolean;
 | 
			
		||||
@@ -17,7 +18,7 @@ export type GlobalHeaderRightProps = {
 | 
			
		||||
export const AvatarName = () => {
 | 
			
		||||
  const { initialState } = useModel('@@initialState');
 | 
			
		||||
  const { currentUser } = initialState || {};
 | 
			
		||||
  return <span className="anticon">{currentUser?.login_name}</span>;
 | 
			
		||||
  return <span className="anticon">{currentUser?.user_name}</span>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, children }) => {
 | 
			
		||||
@@ -65,6 +66,7 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, childre
 | 
			
		||||
  });
 | 
			
		||||
  const { initialState, setInitialState } = useModel('@@initialState');
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
  const [visiblePwd, setVisiblePwd] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const onMenuClick = useCallback(
 | 
			
		||||
    (event: MenuInfo) => {
 | 
			
		||||
@@ -72,6 +74,9 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, childre
 | 
			
		||||
      if (key === 'logout') {
 | 
			
		||||
        setVisible(true);
 | 
			
		||||
        return;
 | 
			
		||||
      } else if (key === 'editPassword') {
 | 
			
		||||
        setVisiblePwd(true);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      history.push(`/account/${key}`);
 | 
			
		||||
    },
 | 
			
		||||
@@ -118,6 +123,11 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, childre
 | 
			
		||||
          },
 | 
			
		||||
        ]
 | 
			
		||||
      : []),
 | 
			
		||||
    {
 | 
			
		||||
      key: 'editPassword',
 | 
			
		||||
      icon: <SettingOutlined />,
 | 
			
		||||
      label: '修改密码',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      key: 'logout',
 | 
			
		||||
      icon: <LogoutOutlined />,
 | 
			
		||||
@@ -151,6 +161,18 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, childre
 | 
			
		||||
      >
 | 
			
		||||
        确认退出账号?
 | 
			
		||||
      </Modal>
 | 
			
		||||
      <Modal
 | 
			
		||||
        width={500}
 | 
			
		||||
        open={visiblePwd}
 | 
			
		||||
        title={'修改密码'}
 | 
			
		||||
        centered
 | 
			
		||||
        onCancel={() => setVisiblePwd(false)}
 | 
			
		||||
        footer={false}
 | 
			
		||||
        maskClosable={false}
 | 
			
		||||
        destroyOnClose={true}
 | 
			
		||||
      >
 | 
			
		||||
        <EditPassword close={() => setVisiblePwd(false)} />
 | 
			
		||||
      </Modal>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										184
									
								
								src/components/RightContent/EditPassWord.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								src/components/RightContent/EditPassWord.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,184 @@
 | 
			
		||||
import { post } from '@/services/ajax';
 | 
			
		||||
import { App, Button, Input, Spin } from 'antd';
 | 
			
		||||
import { stringify } from 'qs';
 | 
			
		||||
import React, { useEffect, useRef, useState } from 'react';
 | 
			
		||||
import styles from './index.module.scss';
 | 
			
		||||
 | 
			
		||||
type IProps = {
 | 
			
		||||
  close: () => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type IProps2 = {
 | 
			
		||||
  value: string;
 | 
			
		||||
};
 | 
			
		||||
const StrokeCheck: React.FC<IProps2> = (props) => {
 | 
			
		||||
  const { value } = props;
 | 
			
		||||
  const strokeColor = ['#e74242', '#ffa500', '#EFBD47', '#1bbf1b', '#008000'];
 | 
			
		||||
  const [strong, setStrong] = useState(0);
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    let s = 0;
 | 
			
		||||
    if (value.length >= 6) {
 | 
			
		||||
      if (value.length >= 6) {
 | 
			
		||||
        s++;
 | 
			
		||||
      }
 | 
			
		||||
      let pattern = new RegExp(
 | 
			
		||||
        "[`~!@#$%^&?*()_\\-\\+=|{}':;',\\[\\].<>《》./~!@#¥……&*()——|{}【】‘;:”“'。,、? ]",
 | 
			
		||||
      );
 | 
			
		||||
      if (value.match(/\d/g)) {
 | 
			
		||||
        s++;
 | 
			
		||||
      }
 | 
			
		||||
      if (value.match(/[a-z]/g)) {
 | 
			
		||||
        s++;
 | 
			
		||||
      }
 | 
			
		||||
      if (value.match(/[A-Z]/g)) {
 | 
			
		||||
        s++;
 | 
			
		||||
      }
 | 
			
		||||
      if (pattern.test(value)) {
 | 
			
		||||
        s++;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    setStrong(s);
 | 
			
		||||
  }, [value]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles.passwordStrong}>
 | 
			
		||||
      <div
 | 
			
		||||
        className={styles.strokeColorMask}
 | 
			
		||||
        style={{ width: `${((5 - strong) / 5) * 100}%` }}
 | 
			
		||||
      ></div>
 | 
			
		||||
      <div className={styles.mark}></div>
 | 
			
		||||
      <div className={styles.mark}></div>
 | 
			
		||||
      <div className={styles.mark}></div>
 | 
			
		||||
      <div className={styles.mark}></div>
 | 
			
		||||
      {strokeColor.map((item) => {
 | 
			
		||||
        return <div className={styles.strokeColor} key={item} style={{ background: item }}></div>;
 | 
			
		||||
      })}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
export const EditPassword: React.FC<IProps> = (props) => {
 | 
			
		||||
  const [param, setParam] = useState({
 | 
			
		||||
    old_pass: '',
 | 
			
		||||
    new_pass: '',
 | 
			
		||||
    new_pass1: '',
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const [loading, setLoading] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const pwdFocusRef = useRef([0, 0, 0]);
 | 
			
		||||
  const { notification } = App.useApp();
 | 
			
		||||
 | 
			
		||||
  const save = () => {
 | 
			
		||||
    if (
 | 
			
		||||
      param.old_pass.length < 6 ||
 | 
			
		||||
      param.new_pass.length < 6 ||
 | 
			
		||||
      param.new_pass1 !== param.new_pass ||
 | 
			
		||||
      param.new_pass == param.old_pass
 | 
			
		||||
    ) {
 | 
			
		||||
      pwdFocusRef.current = [1, 1, 1];
 | 
			
		||||
      setParam({ ...param });
 | 
			
		||||
    } else {
 | 
			
		||||
      setLoading(true);
 | 
			
		||||
      post({ url: '/User/ChangePass', data: stringify(param) }).then((res) => {
 | 
			
		||||
        setLoading(false);
 | 
			
		||||
        if (res.err_code == 0) {
 | 
			
		||||
          notification.success({
 | 
			
		||||
            message: '密码修改成功',
 | 
			
		||||
          });
 | 
			
		||||
          props.close();
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Spin spinning={loading}>
 | 
			
		||||
      <div style={{ marginTop: 16 }}>
 | 
			
		||||
        <Input.Password
 | 
			
		||||
          autoFocus
 | 
			
		||||
          defaultValue={param.old_pass}
 | 
			
		||||
          addonBefore="原先密码"
 | 
			
		||||
          placeholder="原先账号密码"
 | 
			
		||||
          maxLength={20}
 | 
			
		||||
          autoComplete="off"
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            param.old_pass = e.target.value;
 | 
			
		||||
            pwdFocusRef.current[0] = 1;
 | 
			
		||||
            setParam({ ...param });
 | 
			
		||||
          }}
 | 
			
		||||
          status={
 | 
			
		||||
            (pwdFocusRef.current[0] == 1 && param.old_pass.length < 6) ||
 | 
			
		||||
            (param.old_pass.length >= 6 && param.new_pass == param.old_pass)
 | 
			
		||||
              ? 'error'
 | 
			
		||||
              : ''
 | 
			
		||||
          }
 | 
			
		||||
        />
 | 
			
		||||
        <div className={styles.errMsg}>
 | 
			
		||||
          {pwdFocusRef.current[0] == 1 ? (
 | 
			
		||||
            <>
 | 
			
		||||
              {param.old_pass.length < 6 ? '密码长度不能小于6位' : ''}
 | 
			
		||||
              {param.old_pass == '' ? ',该字段是必填字段' : ''}
 | 
			
		||||
              {param.old_pass.length >= 6 && param.new_pass == param.old_pass
 | 
			
		||||
                ? '新密码和旧密码不能一样'
 | 
			
		||||
                : ''}
 | 
			
		||||
            </>
 | 
			
		||||
          ) : null}
 | 
			
		||||
        </div>
 | 
			
		||||
        <Input.Password
 | 
			
		||||
          defaultValue={param.new_pass}
 | 
			
		||||
          addonBefore="修改密码"
 | 
			
		||||
          placeholder="账号新密码"
 | 
			
		||||
          maxLength={20}
 | 
			
		||||
          autoComplete="off"
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            param.new_pass = e.target.value;
 | 
			
		||||
            pwdFocusRef.current[1] = 1;
 | 
			
		||||
            setParam({ ...param });
 | 
			
		||||
          }}
 | 
			
		||||
          status={pwdFocusRef.current[1] == 1 && param.new_pass.length < 6 ? 'error' : ''}
 | 
			
		||||
        />
 | 
			
		||||
        <StrokeCheck value={param.new_pass} />
 | 
			
		||||
        <div className={styles.errMsg} style={{ marginTop: 4 }}>
 | 
			
		||||
          {pwdFocusRef.current[1] == 1 ? (
 | 
			
		||||
            <>
 | 
			
		||||
              {param.new_pass.length < 6 ? '密码长度不能小于6位' : ''}
 | 
			
		||||
              {param.new_pass == '' ? ',该字段是必填字段' : ''}
 | 
			
		||||
            </>
 | 
			
		||||
          ) : null}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <Input.Password
 | 
			
		||||
          defaultValue={param.new_pass1}
 | 
			
		||||
          addonBefore="确认密码"
 | 
			
		||||
          placeholder="确认新密码"
 | 
			
		||||
          maxLength={20}
 | 
			
		||||
          autoComplete="off"
 | 
			
		||||
          onChange={(e) => {
 | 
			
		||||
            param.new_pass1 = e.target.value;
 | 
			
		||||
            pwdFocusRef.current[2] = 1;
 | 
			
		||||
            setParam({ ...param });
 | 
			
		||||
          }}
 | 
			
		||||
          status={pwdFocusRef.current[2] == 1 && param.new_pass1 !== param.new_pass ? 'error' : ''}
 | 
			
		||||
        />
 | 
			
		||||
        <div className={styles.errMsg}>
 | 
			
		||||
          {pwdFocusRef.current[2] == 1 ? (
 | 
			
		||||
            <>{param.new_pass1 !== param.new_pass ? '新密码与确认密码输入不一致' : ''}</>
 | 
			
		||||
          ) : null}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
 | 
			
		||||
          <Button type="primary" style={{ marginRight: 12 }} onClick={save}>
 | 
			
		||||
            修改密码
 | 
			
		||||
          </Button>
 | 
			
		||||
          <Button
 | 
			
		||||
            type="default"
 | 
			
		||||
            onClick={() => {
 | 
			
		||||
              props.close();
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            关闭
 | 
			
		||||
          </Button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </Spin>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										46
									
								
								src/components/RightContent/index.module.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/components/RightContent/index.module.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
.errMsg {
 | 
			
		||||
  margin-bottom: 16px;
 | 
			
		||||
  color: red;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.passwordStrong {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 8px;
 | 
			
		||||
  margin-top: 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.strokeColorMask {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  z-index: 1;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  background-color: #ddd;
 | 
			
		||||
  transition: width 0.3s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mark {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: 20%;
 | 
			
		||||
  z-index: 3;
 | 
			
		||||
  width: 4px;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  background-color: #fff;
 | 
			
		||||
  transform: translateX(-50%);
 | 
			
		||||
  &:nth-child(2n) {
 | 
			
		||||
    left: 40%;
 | 
			
		||||
  }
 | 
			
		||||
  &:nth-child(3n) {
 | 
			
		||||
    left: 60%;
 | 
			
		||||
  }
 | 
			
		||||
  &:nth-child(4n) {
 | 
			
		||||
    left: 80%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.strokeColor {
 | 
			
		||||
  flex: 1;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
}
 | 
			
		||||
@@ -44,6 +44,7 @@ export interface ICustFollow {
 | 
			
		||||
 | 
			
		||||
  gender: number;
 | 
			
		||||
  name: string;
 | 
			
		||||
  staff_name?: string;
 | 
			
		||||
 | 
			
		||||
  type: number;
 | 
			
		||||
 | 
			
		||||
@@ -69,6 +70,7 @@ export interface IGroup {
 | 
			
		||||
  group_id: string;
 | 
			
		||||
  name: string;
 | 
			
		||||
  owner: string;
 | 
			
		||||
  staff_name?: string;
 | 
			
		||||
  state: number;
 | 
			
		||||
  status: number;
 | 
			
		||||
  sync_time: string;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import { IGroup, IGroupMembers } from './ChatLogsType';
 | 
			
		||||
import { AdminSvg, OwnerSvg } from './components/Svgs';
 | 
			
		||||
 | 
			
		||||
export const groupMembersCount = (data: any[], state: any) => {
 | 
			
		||||
  let count = 0;
 | 
			
		||||
@@ -36,6 +38,72 @@ export const groupMembersCount2 = (data: any[], group_members_type: any, state:
 | 
			
		||||
  return count;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 群 Drawer 群员
 | 
			
		||||
export const groupMembersListItem = (item: IGroupMembers, selectGroup: IGroup) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      key={item.user_id}
 | 
			
		||||
      style={{
 | 
			
		||||
        display: 'inline-flex',
 | 
			
		||||
        justifyContent: 'flex-start',
 | 
			
		||||
        alignItems: 'center',
 | 
			
		||||
        width: 80,
 | 
			
		||||
        flexDirection: 'column',
 | 
			
		||||
        verticalAlign: 'top',
 | 
			
		||||
        marginTop: 12,
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <div
 | 
			
		||||
        style={{
 | 
			
		||||
          position: 'relative',
 | 
			
		||||
          display: 'flex',
 | 
			
		||||
          flexShrink: 0,
 | 
			
		||||
          alignItems: 'center',
 | 
			
		||||
          justifyContent: 'center',
 | 
			
		||||
          width: 56,
 | 
			
		||||
          height: 56,
 | 
			
		||||
          lineHeight: 1,
 | 
			
		||||
          background: '#69b1ff',
 | 
			
		||||
          borderRadius: 4,
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        {item.avatar ? (
 | 
			
		||||
          <img
 | 
			
		||||
            style={{
 | 
			
		||||
              maxWidth: '100%',
 | 
			
		||||
              maxHeight: '100%',
 | 
			
		||||
              objectFit: 'cover',
 | 
			
		||||
              borderRadius: 4,
 | 
			
		||||
            }}
 | 
			
		||||
            src={item.avatar}
 | 
			
		||||
            alt=""
 | 
			
		||||
          />
 | 
			
		||||
        ) : item.name ? (
 | 
			
		||||
          item.name[0]
 | 
			
		||||
        ) : (
 | 
			
		||||
          ''
 | 
			
		||||
        )}
 | 
			
		||||
        {item.user_id == selectGroup?.owner ? <OwnerSvg /> : null}
 | 
			
		||||
        {selectGroup?.adminUserIDs?.includes(item.user_id) ? <AdminSvg /> : null}
 | 
			
		||||
      </div>
 | 
			
		||||
      <div
 | 
			
		||||
        style={{
 | 
			
		||||
          padding: '0 4px',
 | 
			
		||||
          whiteSpace: 'nowrap',
 | 
			
		||||
          minWidth: 0,
 | 
			
		||||
          textOverflow: 'ellipsis',
 | 
			
		||||
          overflow: 'hidden',
 | 
			
		||||
          width: 72,
 | 
			
		||||
          textAlign: 'center',
 | 
			
		||||
        }}
 | 
			
		||||
        title={item.name}
 | 
			
		||||
      >
 | 
			
		||||
        {item.name}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 群管理者:
 | 
			
		||||
 * @param data
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { Modal } from 'antd';
 | 
			
		||||
import { useEffect, useState } from 'react';
 | 
			
		||||
import { IChatItem } from '../ChatLogsType';
 | 
			
		||||
import { EmojiFormat } from './EmojiFormat';
 | 
			
		||||
 | 
			
		||||
export const ChatRecord: React.FC<IChatItem> = (props) => {
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
@@ -8,6 +9,7 @@ export const ChatRecord: React.FC<IChatItem> = (props) => {
 | 
			
		||||
  function chatRecordContent(data: any, type: string) {
 | 
			
		||||
    if (data.type == 'ChatRecordText') {
 | 
			
		||||
      const content = JSON.parse(data.content);
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
        <div
 | 
			
		||||
          style={{
 | 
			
		||||
@@ -18,7 +20,7 @@ export const ChatRecord: React.FC<IChatItem> = (props) => {
 | 
			
		||||
            textOverflow: type == 'ellipsis' ? 'ellipsis' : 'inherit',
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          {content.content}
 | 
			
		||||
          {type == 'ellipsis' ? content.content : <EmojiFormat content={content.content || ''} />}
 | 
			
		||||
        </div>
 | 
			
		||||
      );
 | 
			
		||||
    } else if (data.type == 'ChatRecordImage') {
 | 
			
		||||
@@ -97,6 +99,7 @@ export const ChatRecord: React.FC<IChatItem> = (props) => {
 | 
			
		||||
          setVisible(false);
 | 
			
		||||
        }}
 | 
			
		||||
        centered
 | 
			
		||||
        destroyOnClose
 | 
			
		||||
      >
 | 
			
		||||
        <div style={{ maxHeight: '75vh', overflow: 'auto' }}>
 | 
			
		||||
          {record ? (
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { IChatItem } from '../ChatLogsType';
 | 
			
		||||
import { EmojiFormat } from './EmojiFormat';
 | 
			
		||||
 | 
			
		||||
export const ChatText: React.FC<IChatItem> = (props) => {
 | 
			
		||||
  return <>{props.chat?.content}</>;
 | 
			
		||||
  return <EmojiFormat content={props.chat?.content || ''} />;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								src/pages/ChatLogs/components/EmojiFormat.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/pages/ChatLogs/components/EmojiFormat.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
import { emoji } from '@/services/config';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
type IProps = {
 | 
			
		||||
  content: string;
 | 
			
		||||
};
 | 
			
		||||
export const EmojiFormat: React.FC<IProps> = (props) => {
 | 
			
		||||
  const format = () => {
 | 
			
		||||
    let txt = props.content;
 | 
			
		||||
    emoji.forEach((item) => {
 | 
			
		||||
      const reg = new RegExp(`\\[${item}\\]`, 'g');
 | 
			
		||||
      txt = txt.replace(
 | 
			
		||||
        reg,
 | 
			
		||||
        `<img style="width: 24px;height:24px" src="/api/assets/wechat/emoji/${item}.png" alt="" />`,
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
    return txt;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      {typeof props.content === 'string' ? (
 | 
			
		||||
        <span
 | 
			
		||||
          style={{ display: 'inline-flex', flexWrap: 'wrap' }}
 | 
			
		||||
          dangerouslySetInnerHTML={{ __html: format() }}
 | 
			
		||||
        ></span>
 | 
			
		||||
      ) : (
 | 
			
		||||
        props.content
 | 
			
		||||
      )}
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -66,19 +66,6 @@
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.inGroupAvatar {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-shrink: 0;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  width: 56px;
 | 
			
		||||
  height: 56px;
 | 
			
		||||
  line-height: 1;
 | 
			
		||||
  background-color: #69b1ff;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chatBBox {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  height: calc(100vh - 212px - 164px);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import { post } from '@/services/ajax';
 | 
			
		||||
import { groupStatus } from '@/services/config';
 | 
			
		||||
import { DownOutlined, UpOutlined } from '@ant-design/icons';
 | 
			
		||||
import { PageContainer } from '@ant-design/pro-components';
 | 
			
		||||
import { Drawer, Form, Input, Tabs } from 'antd';
 | 
			
		||||
@@ -8,10 +9,15 @@ import React, { useEffect, useRef, useState } from 'react';
 | 
			
		||||
import { CustDetailContent } from '../CustomList/components/CustDetailContent';
 | 
			
		||||
import { DepartmentMembersDetail } from '../DepartmentsList/components/DepartmentMemberDetail';
 | 
			
		||||
import { IChat, ICustFollow, IGroup, IGroupMembers, IStaffsItem } from './ChatLogsType';
 | 
			
		||||
import { adminList, getAdminList, groupMembersCount, groupMembersCount2 } from './ChatUtils';
 | 
			
		||||
import {
 | 
			
		||||
  adminList,
 | 
			
		||||
  getAdminList,
 | 
			
		||||
  groupMembersCount,
 | 
			
		||||
  groupMembersCount2,
 | 
			
		||||
  groupMembersListItem,
 | 
			
		||||
} from './ChatUtils';
 | 
			
		||||
import { ChatBar } from './components/ChatBar';
 | 
			
		||||
import { ChatTime } from './components/ChatTime';
 | 
			
		||||
import { AdminSvg, OwnerSvg } from './components/Svgs';
 | 
			
		||||
import styles from './index.module.scss';
 | 
			
		||||
 | 
			
		||||
const ChatLogs: React.FC = () => {
 | 
			
		||||
@@ -70,13 +76,6 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  const groupStatus: any = {
 | 
			
		||||
    '0': '跟进人正常',
 | 
			
		||||
    '1': '跟进人离职',
 | 
			
		||||
    '2': '离职继承中',
 | 
			
		||||
    '3': '离职继承完成',
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const [searchWord, setSearchWord] = useState<any>({
 | 
			
		||||
    '0': '',
 | 
			
		||||
    '1': '',
 | 
			
		||||
@@ -163,7 +162,7 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
 | 
			
		||||
  const getGroupList = () => {
 | 
			
		||||
    post({
 | 
			
		||||
      url: '/Groups/GroupsList',
 | 
			
		||||
      url: '/Groups/ChatGroups',
 | 
			
		||||
      data: stringify({ user_id: selectStaffRef.current?.user_id }),
 | 
			
		||||
    }).then((res) => {
 | 
			
		||||
      if (res.err_code == 0) {
 | 
			
		||||
@@ -298,7 +297,11 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
        style={{ display: item.name.includes(searchWord['1']) ? '' : 'none' }}
 | 
			
		||||
      >
 | 
			
		||||
        <div className={styles.avatar} style={{ background: item?.avatar ? '#fff' : '' }}>
 | 
			
		||||
          <img src={item.avatar} alt="" style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'cover' }} />
 | 
			
		||||
          <img
 | 
			
		||||
            src={item.avatar}
 | 
			
		||||
            alt=""
 | 
			
		||||
            style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'cover' }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className={styles.chatAMsg} style={{ flexDirection: 'column' }}>
 | 
			
		||||
          <div className={styles.chatAName} title={item.name}>
 | 
			
		||||
@@ -315,7 +318,9 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
    return (
 | 
			
		||||
      <div
 | 
			
		||||
        key={item.group_id}
 | 
			
		||||
        className={`${styles.chatB} ${item.state == 0 ? styles.state0 : ''} ${selectGroup?.group_id == item.group_id ? styles.active : ''}`}
 | 
			
		||||
        className={`${styles.chatB} ${item.state == 0 ? styles.state0 : ''} ${
 | 
			
		||||
          selectGroup?.group_id == item.group_id ? styles.active : ''
 | 
			
		||||
        }`}
 | 
			
		||||
        onClick={() => {
 | 
			
		||||
          tabKeyRef.current = tabKey;
 | 
			
		||||
          setSelectCustFollow(undefined);
 | 
			
		||||
@@ -333,7 +338,7 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
        <div className={styles.avatar}>{item.name ? item.name[0] : '群'}</div>
 | 
			
		||||
        <div className={styles.chatAMsg} style={{ flexDirection: 'column' }}>
 | 
			
		||||
          <div className={styles.chatAName} title={item.name}>
 | 
			
		||||
            {item.name || '未定义群名'}
 | 
			
		||||
            {item.name || '未知群名'}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div style={{ color: '#999' }}>{groupStatus[item.status]}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -355,7 +360,9 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
              return (
 | 
			
		||||
                <div
 | 
			
		||||
                  key={`${item.user_id}_${item.name}`}
 | 
			
		||||
                  className={`${styles.chatB} ${selectInnerStaff?.user_id == item.user_id ? styles.active : ''}`}
 | 
			
		||||
                  className={`${styles.chatB} ${
 | 
			
		||||
                    selectInnerStaff?.user_id == item.user_id ? styles.active : ''
 | 
			
		||||
                  }`}
 | 
			
		||||
                  onClick={() => {
 | 
			
		||||
                    tabKeyRef.current = tabKey;
 | 
			
		||||
 | 
			
		||||
@@ -395,7 +402,9 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
              );
 | 
			
		||||
            })
 | 
			
		||||
          ) : (
 | 
			
		||||
            <div style={{ lineHeight: '44px', textAlign: 'center', color: '#999' }}>暂无内部联系人</div>
 | 
			
		||||
            <div style={{ lineHeight: '44px', textAlign: 'center', color: '#999' }}>
 | 
			
		||||
              暂无内部联系人
 | 
			
		||||
            </div>
 | 
			
		||||
          )}
 | 
			
		||||
        </>
 | 
			
		||||
      );
 | 
			
		||||
@@ -407,9 +416,15 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
              return item.state == 0 ? null : custFollowsListItem(item);
 | 
			
		||||
            })
 | 
			
		||||
          ) : (
 | 
			
		||||
            <div style={{ lineHeight: '44px', textAlign: 'center', color: '#999' }}>暂无外部联系人</div>
 | 
			
		||||
            <div style={{ lineHeight: '44px', textAlign: 'center', color: '#999' }}>
 | 
			
		||||
              暂无外部联系人
 | 
			
		||||
            </div>
 | 
			
		||||
          )}
 | 
			
		||||
          <div className={`${styles.delFollowList} ${delFollowListShow ? styles.delFollowListShow : ''}`}>
 | 
			
		||||
          <div
 | 
			
		||||
            className={`${styles.delFollowList} ${
 | 
			
		||||
              delFollowListShow ? styles.delFollowListShow : ''
 | 
			
		||||
            }`}
 | 
			
		||||
          >
 | 
			
		||||
            <div
 | 
			
		||||
              className={styles.delFollowListBar}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
@@ -419,7 +434,11 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
              <span>已删联系人</span>
 | 
			
		||||
              {delFollowListShow ? <UpOutlined /> : <DownOutlined />}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className={`${styles.delFollowListBox} ${delFollowListShow ? styles.delFollowListBoxShow : ''}`}>
 | 
			
		||||
            <div
 | 
			
		||||
              className={`${styles.delFollowListBox} ${
 | 
			
		||||
                delFollowListShow ? styles.delFollowListBoxShow : ''
 | 
			
		||||
              }`}
 | 
			
		||||
            >
 | 
			
		||||
              {custFollowsList.map((item) => {
 | 
			
		||||
                return item.state == 1 ? null : custFollowsListItem(item);
 | 
			
		||||
              })}
 | 
			
		||||
@@ -435,9 +454,15 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
              return item.state == 0 ? null : groupListItem(item);
 | 
			
		||||
            })
 | 
			
		||||
          ) : (
 | 
			
		||||
            <div style={{ lineHeight: '44px', textAlign: 'center', color: '#999' }}>暂无客户群聊</div>
 | 
			
		||||
            <div style={{ lineHeight: '44px', textAlign: 'center', color: '#999' }}>
 | 
			
		||||
              暂无客户群聊
 | 
			
		||||
            </div>
 | 
			
		||||
          )}
 | 
			
		||||
          <div className={`${styles.delFollowList} ${delGroupListShow ? styles.delFollowListShow : ''}`}>
 | 
			
		||||
          <div
 | 
			
		||||
            className={`${styles.delFollowList} ${
 | 
			
		||||
              delGroupListShow ? styles.delFollowListShow : ''
 | 
			
		||||
            }`}
 | 
			
		||||
          >
 | 
			
		||||
            <div
 | 
			
		||||
              className={styles.delFollowListBar}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
@@ -447,7 +472,11 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
              <span>已解散的群</span>
 | 
			
		||||
              {delGroupListShow ? <UpOutlined /> : <DownOutlined />}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className={`${styles.delFollowListBox} ${delGroupListShow ? styles.delFollowListBoxShow : ''}`}>
 | 
			
		||||
            <div
 | 
			
		||||
              className={`${styles.delFollowListBox} ${
 | 
			
		||||
                delGroupListShow ? styles.delFollowListBoxShow : ''
 | 
			
		||||
              }`}
 | 
			
		||||
            >
 | 
			
		||||
              {groupList.map((item) => {
 | 
			
		||||
                return item.state == 1 ? null : groupListItem(item);
 | 
			
		||||
              })}
 | 
			
		||||
@@ -464,7 +493,9 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
      return <></>;
 | 
			
		||||
    } else if (tabKey == '1') {
 | 
			
		||||
      return (
 | 
			
		||||
        <div className={`${styles.delFollowList} ${delFollowListShow ? styles.delFollowListShow : ''}`}>
 | 
			
		||||
        <div
 | 
			
		||||
          className={`${styles.delFollowList} ${delFollowListShow ? styles.delFollowListShow : ''}`}
 | 
			
		||||
        >
 | 
			
		||||
          <div
 | 
			
		||||
            className={styles.delFollowListBar}
 | 
			
		||||
            onClick={() => {
 | 
			
		||||
@@ -474,7 +505,11 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
            <span>已删联系人</span>
 | 
			
		||||
            {delFollowListShow ? <UpOutlined /> : <DownOutlined />}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div className={`${styles.delFollowListBox} ${delFollowListShow ? styles.delFollowListBoxShow : ''}`}>
 | 
			
		||||
          <div
 | 
			
		||||
            className={`${styles.delFollowListBox} ${
 | 
			
		||||
              delFollowListShow ? styles.delFollowListBoxShow : ''
 | 
			
		||||
            }`}
 | 
			
		||||
          >
 | 
			
		||||
            {custFollowsList.map((item) => {
 | 
			
		||||
              return item.state == 1 ? null : custFollowsListItem(item);
 | 
			
		||||
            })}
 | 
			
		||||
@@ -483,7 +518,9 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      return (
 | 
			
		||||
        <div className={`${styles.delFollowList} ${delGroupListShow ? styles.delFollowListShow : ''}`}>
 | 
			
		||||
        <div
 | 
			
		||||
          className={`${styles.delFollowList} ${delGroupListShow ? styles.delFollowListShow : ''}`}
 | 
			
		||||
        >
 | 
			
		||||
          <div
 | 
			
		||||
            className={styles.delFollowListBar}
 | 
			
		||||
            onClick={() => {
 | 
			
		||||
@@ -493,7 +530,11 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
            <span>已解散的群</span>
 | 
			
		||||
            {delGroupListShow ? <UpOutlined /> : <DownOutlined />}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div className={`${styles.delFollowListBox} ${delGroupListShow ? styles.delFollowListBoxShow : ''}`}>
 | 
			
		||||
          <div
 | 
			
		||||
            className={`${styles.delFollowListBox} ${
 | 
			
		||||
              delGroupListShow ? styles.delFollowListBoxShow : ''
 | 
			
		||||
            }`}
 | 
			
		||||
          >
 | 
			
		||||
            {groupList.map((item) => {
 | 
			
		||||
              return item.state == 1 ? null : groupListItem(item);
 | 
			
		||||
            })}
 | 
			
		||||
@@ -503,59 +544,6 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // 群 Drawer 群员
 | 
			
		||||
  const groupMembersListItem = (item: IGroupMembers) => {
 | 
			
		||||
    return (
 | 
			
		||||
      <div
 | 
			
		||||
        key={item.user_id}
 | 
			
		||||
        style={{
 | 
			
		||||
          display: 'inline-flex',
 | 
			
		||||
          justifyContent: 'flex-start',
 | 
			
		||||
          alignItems: 'center',
 | 
			
		||||
          width: 80,
 | 
			
		||||
          flexDirection: 'column',
 | 
			
		||||
          verticalAlign: 'top',
 | 
			
		||||
          marginTop: 12,
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <div className={styles.inGroupAvatar}>
 | 
			
		||||
          {item.avatar ? (
 | 
			
		||||
            <img
 | 
			
		||||
              style={{
 | 
			
		||||
                maxWidth: '100%',
 | 
			
		||||
                maxHeight: '100%',
 | 
			
		||||
                objectFit: 'cover',
 | 
			
		||||
                borderRadius: 4,
 | 
			
		||||
              }}
 | 
			
		||||
              src={item.avatar}
 | 
			
		||||
              alt=""
 | 
			
		||||
            />
 | 
			
		||||
          ) : item.name ? (
 | 
			
		||||
            item.name[0]
 | 
			
		||||
          ) : (
 | 
			
		||||
            ''
 | 
			
		||||
          )}
 | 
			
		||||
          {item.user_id == selectGroupRef.current?.owner ? <OwnerSvg /> : null}
 | 
			
		||||
          {selectGroupRef.current?.adminUserIDs?.includes(item.user_id) ? <AdminSvg /> : null}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div
 | 
			
		||||
          style={{
 | 
			
		||||
            padding: '0 4px',
 | 
			
		||||
            whiteSpace: 'nowrap',
 | 
			
		||||
            minWidth: 0,
 | 
			
		||||
            textOverflow: 'ellipsis',
 | 
			
		||||
            overflow: 'hidden',
 | 
			
		||||
            width: 72,
 | 
			
		||||
            textAlign: 'center',
 | 
			
		||||
          }}
 | 
			
		||||
          title={item.name}
 | 
			
		||||
        >
 | 
			
		||||
          {item.name}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <PageContainer>
 | 
			
		||||
      <div className={styles.box}>
 | 
			
		||||
@@ -682,7 +670,7 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
                <>{selectCustFollowRef.current?.name}</>
 | 
			
		||||
              ) : (
 | 
			
		||||
                <>
 | 
			
		||||
                  {selectGroupRef.current ? selectGroupRef.current?.name || '未定义群名' : ''}
 | 
			
		||||
                  {selectGroupRef.current ? selectGroupRef.current?.name || '未知群名' : ''}
 | 
			
		||||
                  {selectGroupRef.current ? <>({groupMembersList.length})</> : null}
 | 
			
		||||
                </>
 | 
			
		||||
              )}
 | 
			
		||||
@@ -695,7 +683,7 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
                  <>{selectCustFollowRef.current?.name} 详细信息</>
 | 
			
		||||
                ) : (
 | 
			
		||||
                  <>
 | 
			
		||||
                    {selectGroupRef.current?.name || '未定义群名'} ({groupMembersList.length}
 | 
			
		||||
                    {selectGroupRef.current?.name || '未知群名'} ({groupMembersList.length}
 | 
			
		||||
                    )详细信息
 | 
			
		||||
                  </>
 | 
			
		||||
                )
 | 
			
		||||
@@ -715,8 +703,12 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
                    群主:
 | 
			
		||||
                    {groupMembersObjRef.current[selectGroupRef.current?.owner as string]?.name}
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div style={{ marginBottom: 8 }}>{adminList(selectGroupRef.current?.admin_list, groupMembersObjRef.current)}</div>
 | 
			
		||||
                  <div style={{ marginBottom: 8 }}>创建时间:{selectGroupRef.current?.create_time}</div>
 | 
			
		||||
                  <div style={{ marginBottom: 8 }}>
 | 
			
		||||
                    {adminList(selectGroupRef.current?.admin_list, groupMembersObjRef.current)}
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div style={{ marginBottom: 8 }}>
 | 
			
		||||
                    创建时间:{selectGroupRef.current?.create_time}
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div style={{ textIndent: '1em' }}>群公告:{selectGroupRef.current?.notice}</div>
 | 
			
		||||
                  <div
 | 
			
		||||
                    style={{
 | 
			
		||||
@@ -730,7 +722,9 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
                    群成员 • {groupMembersList.length}
 | 
			
		||||
                  </div>
 | 
			
		||||
                  {groupMembersList.map((item) => {
 | 
			
		||||
                    return item.group_members_type == '2' || item.state == 0 ? null : groupMembersListItem(item);
 | 
			
		||||
                    return item.group_members_type == '2' || item.state == 0
 | 
			
		||||
                      ? null
 | 
			
		||||
                      : groupMembersListItem(item, selectGroupRef.current);
 | 
			
		||||
                  })}
 | 
			
		||||
                  {groupMembersCount2(groupMembersList, '2', 1) != 0 ? (
 | 
			
		||||
                    <div
 | 
			
		||||
@@ -746,7 +740,9 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
                    </div>
 | 
			
		||||
                  ) : null}
 | 
			
		||||
                  {groupMembersList.map((item) => {
 | 
			
		||||
                    return item.group_members_type == '1' || item.state == 0 ? null : groupMembersListItem(item);
 | 
			
		||||
                    return item.group_members_type == '1' || item.state == 0
 | 
			
		||||
                      ? null
 | 
			
		||||
                      : groupMembersListItem(item, selectGroupRef.current);
 | 
			
		||||
                  })}
 | 
			
		||||
                  {groupMembersCount(groupMembersList, 0) != 0 ? (
 | 
			
		||||
                    <div
 | 
			
		||||
@@ -762,7 +758,9 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
                    </div>
 | 
			
		||||
                  ) : null}
 | 
			
		||||
                  {groupMembersList.map((item) => {
 | 
			
		||||
                    return item.state == 1 ? null : groupMembersListItem(item);
 | 
			
		||||
                    return item.state == 1
 | 
			
		||||
                      ? null
 | 
			
		||||
                      : groupMembersListItem(item, selectGroupRef.current);
 | 
			
		||||
                  })}
 | 
			
		||||
                </div>
 | 
			
		||||
              )}
 | 
			
		||||
@@ -773,26 +771,48 @@ const ChatLogs: React.FC = () => {
 | 
			
		||||
              className={styles.chatLogBox}
 | 
			
		||||
              ref={chatBoxRef}
 | 
			
		||||
              onScroll={(e: any) => {
 | 
			
		||||
                if (e.target?.scrollTop == 0 && !isAllChatRef.current && !chatLogLoadingRef.current) {
 | 
			
		||||
                if (
 | 
			
		||||
                  e.target?.scrollTop == 0 &&
 | 
			
		||||
                  !isAllChatRef.current &&
 | 
			
		||||
                  !chatLogLoadingRef.current
 | 
			
		||||
                ) {
 | 
			
		||||
                  page(param.curr_page + 1);
 | 
			
		||||
                }
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              {isAllChatRef.current ? <div style={{ marginBottom: 12, textAlign: 'center', color: '#999' }}>没有更多聊天记录了</div> : null}
 | 
			
		||||
              {isAllChatRef.current ? (
 | 
			
		||||
                <div style={{ marginBottom: 12, textAlign: 'center', color: '#999' }}>
 | 
			
		||||
                  没有更多聊天记录了
 | 
			
		||||
                </div>
 | 
			
		||||
              ) : null}
 | 
			
		||||
              {chatLogs.map((item) => {
 | 
			
		||||
                if (item.curr_page) {
 | 
			
		||||
                  return <div key={item.curr_page} className={`curr_page${param.curr_page}`} style={{ height: 0 }} />;
 | 
			
		||||
                  return (
 | 
			
		||||
                    <div
 | 
			
		||||
                      key={item.curr_page}
 | 
			
		||||
                      className={`curr_page${param.curr_page}`}
 | 
			
		||||
                      style={{ height: 0 }}
 | 
			
		||||
                    />
 | 
			
		||||
                  );
 | 
			
		||||
                } else {
 | 
			
		||||
                  return (
 | 
			
		||||
                    <div key={item.msg_id}>
 | 
			
		||||
                      {/* {item.show_time ? <ChatTime msgtime={item.msg_time}></ChatTime> : null} */}
 | 
			
		||||
                      <ChatTime msgtime={item.msg_time} />
 | 
			
		||||
                      {tabKey == '2' ? (
 | 
			
		||||
                        <ChatBar from={selectStaff} to={groupMembersObjRef.current[item.msg_from]} chat={item} />
 | 
			
		||||
                        <ChatBar
 | 
			
		||||
                          from={selectStaff}
 | 
			
		||||
                          to={groupMembersObjRef.current[item.msg_from]}
 | 
			
		||||
                          chat={item}
 | 
			
		||||
                        />
 | 
			
		||||
                      ) : (
 | 
			
		||||
                        <ChatBar
 | 
			
		||||
                          from={selectStaff}
 | 
			
		||||
                          to={tabKeyRef.current == '0' ? selectInnerStaffRef.current : selectCustFollowRef.current}
 | 
			
		||||
                          to={
 | 
			
		||||
                            tabKeyRef.current == '0'
 | 
			
		||||
                              ? selectInnerStaffRef.current
 | 
			
		||||
                              : selectCustFollowRef.current
 | 
			
		||||
                          }
 | 
			
		||||
                          chat={item}
 | 
			
		||||
                        />
 | 
			
		||||
                      )}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,11 +2,25 @@ import { SearchBarPlugin, SearchBottonsCardPlugin } from '@/components/SearchBar
 | 
			
		||||
import { post } from '@/services/ajax';
 | 
			
		||||
import { AddWay, CustType } from '@/services/config';
 | 
			
		||||
import { PageContainer } from '@ant-design/pro-components';
 | 
			
		||||
import { Button, Col, DatePicker, Drawer, Form, Image, Input, Pagination, Popover, Row, Select, Table, Tag } from 'antd';
 | 
			
		||||
import {
 | 
			
		||||
  Button,
 | 
			
		||||
  Col,
 | 
			
		||||
  DatePicker,
 | 
			
		||||
  Drawer,
 | 
			
		||||
  Form,
 | 
			
		||||
  Image,
 | 
			
		||||
  Input,
 | 
			
		||||
  Pagination,
 | 
			
		||||
  Popover,
 | 
			
		||||
  Row,
 | 
			
		||||
  Select,
 | 
			
		||||
  Table,
 | 
			
		||||
  Tag,
 | 
			
		||||
} from 'antd';
 | 
			
		||||
import { stringify } from 'qs';
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
 | 
			
		||||
import { ICustFollow } from '../ChatLogs/ChatLogsType';
 | 
			
		||||
import { ICustFollow, IStaffsItem } from '../ChatLogs/ChatLogsType';
 | 
			
		||||
import { Gender } from '../ChatLogs/components/Gender';
 | 
			
		||||
import { CustDetailContent } from './components/CustDetailContent';
 | 
			
		||||
 | 
			
		||||
@@ -19,9 +33,11 @@ const CustomList: React.FC = () => {
 | 
			
		||||
    add_way: '',
 | 
			
		||||
    create_timeL: '',
 | 
			
		||||
    create_timeU: '',
 | 
			
		||||
    user_id: '',
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const [custsList, setCustsList] = useState<ICustFollow[]>([]);
 | 
			
		||||
  const [staffsList, setStaffsList] = useState<IStaffsItem[]>([]);
 | 
			
		||||
  const [count, setCount] = useState(0);
 | 
			
		||||
  const [loading, setLoading] = useState(false);
 | 
			
		||||
  const [open, setOpen] = useState(false);
 | 
			
		||||
@@ -29,7 +45,7 @@ const CustomList: React.FC = () => {
 | 
			
		||||
 | 
			
		||||
  const getCustsList = () => {
 | 
			
		||||
    setLoading(true);
 | 
			
		||||
    post({ url: '/CustFollows/List', data: stringify(param) }).then((res) => {
 | 
			
		||||
    post({ url: '/Cust/CustList', data: stringify(param) }).then((res) => {
 | 
			
		||||
      setLoading(false);
 | 
			
		||||
      if (res.err_code == 0) {
 | 
			
		||||
        if (Array.isArray(res.data)) {
 | 
			
		||||
@@ -45,7 +61,18 @@ const CustomList: React.FC = () => {
 | 
			
		||||
    getCustsList();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getStaffsList = () => {
 | 
			
		||||
    post({ url: '/Staffs/Data' }).then((res) => {
 | 
			
		||||
      if (res.err_code == 0) {
 | 
			
		||||
        if (Array.isArray(res.data)) {
 | 
			
		||||
          setStaffsList(res.data);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    getStaffsList();
 | 
			
		||||
    getCustsList();
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
@@ -53,13 +80,12 @@ const CustomList: React.FC = () => {
 | 
			
		||||
    <PageContainer>
 | 
			
		||||
      <div style={{ flexGrow: 1, minWidth: 0 }}>
 | 
			
		||||
        <SearchBarPlugin>
 | 
			
		||||
          <Form autoComplete="off">
 | 
			
		||||
          <Form>
 | 
			
		||||
            <Row gutter={{ xs: 0, sm: 16 }}>
 | 
			
		||||
              <Col xs={24} sm={12} lg={8} xxl={6}>
 | 
			
		||||
                <Form.Item label="客户名称">
 | 
			
		||||
                  <Input
 | 
			
		||||
                    placeholder="请输入客户名称"
 | 
			
		||||
                    autoComplete="off"
 | 
			
		||||
                    onChange={(e) => {
 | 
			
		||||
                      param.name = e.target.value.trim();
 | 
			
		||||
                    }}
 | 
			
		||||
@@ -88,6 +114,27 @@ const CustomList: React.FC = () => {
 | 
			
		||||
                  </Select>
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
              </Col>
 | 
			
		||||
              <Col xs={24} sm={12} lg={8} xxl={6}>
 | 
			
		||||
                <Form.Item label="跟进员工">
 | 
			
		||||
                  <Select
 | 
			
		||||
                    defaultValue={param.user_id}
 | 
			
		||||
                    style={{ width: '100%' }}
 | 
			
		||||
                    showSearch
 | 
			
		||||
                    onChange={(_val, option: any) => {
 | 
			
		||||
                      param.user_id = option?.key;
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    <Select.Option value="">全部</Select.Option>
 | 
			
		||||
                    {staffsList.map((item) => {
 | 
			
		||||
                      return (
 | 
			
		||||
                        <Select.Option key={item.user_id} value={item.name}>
 | 
			
		||||
                          {item.name}
 | 
			
		||||
                        </Select.Option>
 | 
			
		||||
                      );
 | 
			
		||||
                    })}
 | 
			
		||||
                  </Select>
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
              </Col>
 | 
			
		||||
              <Col xs={24} sm={12} lg={8} xxl={6}>
 | 
			
		||||
                <Form.Item label={<span>添加方式</span>}>
 | 
			
		||||
                  <Select
 | 
			
		||||
@@ -181,7 +228,9 @@ const CustomList: React.FC = () => {
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div style={{ display: 'flex', alignItems: 'center' }}>
 | 
			
		||||
                      <Gender gender={record.gender}></Gender>
 | 
			
		||||
                      <span style={{ marginLeft: 4, color: '#389e0d' }}>@{CustType[record.type]}</span>
 | 
			
		||||
                      <span style={{ marginLeft: 4, color: '#389e0d' }}>
 | 
			
		||||
                        @{CustType[record.type]}
 | 
			
		||||
                      </span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
@@ -225,11 +274,19 @@ const CustomList: React.FC = () => {
 | 
			
		||||
                            );
 | 
			
		||||
                          }}
 | 
			
		||||
                        >
 | 
			
		||||
                          <Tag color="green" title={`${arr[0]?.group_name}:${arr[0]?.tag_name}`} style={{ marginBottom: 4 }}>
 | 
			
		||||
                          <Tag
 | 
			
		||||
                            color="green"
 | 
			
		||||
                            title={`${arr[0]?.group_name}:${arr[0]?.tag_name}`}
 | 
			
		||||
                            style={{ marginBottom: 4 }}
 | 
			
		||||
                          >
 | 
			
		||||
                            {arr[0]?.tag_name}
 | 
			
		||||
                          </Tag>
 | 
			
		||||
                          {arr[1] ? (
 | 
			
		||||
                            <Tag color="green" title={`${arr[1]?.group_name}:${arr[1]?.tag_name}`} style={{ marginBottom: 4 }}>
 | 
			
		||||
                            <Tag
 | 
			
		||||
                              color="green"
 | 
			
		||||
                              title={`${arr[1]?.group_name}:${arr[1]?.tag_name}`}
 | 
			
		||||
                              style={{ marginBottom: 4 }}
 | 
			
		||||
                            >
 | 
			
		||||
                              {arr[1]?.tag_name}
 | 
			
		||||
                            </Tag>
 | 
			
		||||
                          ) : null}
 | 
			
		||||
@@ -239,11 +296,19 @@ const CustomList: React.FC = () => {
 | 
			
		||||
                        </Popover>
 | 
			
		||||
                      ) : (
 | 
			
		||||
                        <>
 | 
			
		||||
                          <Tag color="green" title={`${arr[0]?.group_name}:${arr[0]?.tag_name}`} style={{ marginBottom: 4 }}>
 | 
			
		||||
                          <Tag
 | 
			
		||||
                            color="green"
 | 
			
		||||
                            title={`${arr[0]?.group_name}:${arr[0]?.tag_name}`}
 | 
			
		||||
                            style={{ marginBottom: 4 }}
 | 
			
		||||
                          >
 | 
			
		||||
                            {arr[0]?.tag_name}
 | 
			
		||||
                          </Tag>
 | 
			
		||||
                          {arr[1] ? (
 | 
			
		||||
                            <Tag color="green" title={`${arr[1]?.group_name}:${arr[1]?.tag_name}`} style={{ marginBottom: 4 }}>
 | 
			
		||||
                            <Tag
 | 
			
		||||
                              color="green"
 | 
			
		||||
                              title={`${arr[1]?.group_name}:${arr[1]?.tag_name}`}
 | 
			
		||||
                              style={{ marginBottom: 4 }}
 | 
			
		||||
                            >
 | 
			
		||||
                              {arr[1]?.tag_name}
 | 
			
		||||
                            </Tag>
 | 
			
		||||
                          ) : null}
 | 
			
		||||
@@ -257,7 +322,14 @@ const CustomList: React.FC = () => {
 | 
			
		||||
            }}
 | 
			
		||||
            dataIndex={'tags'}
 | 
			
		||||
          />
 | 
			
		||||
          <Table.Column title="跟进员工" width={120} dataIndex={'user_id'} />
 | 
			
		||||
          <Table.Column
 | 
			
		||||
            title="跟进员工"
 | 
			
		||||
            width={120}
 | 
			
		||||
            render={(v, record: ICustFollow) => {
 | 
			
		||||
              return <>{record?.staff_name}</>;
 | 
			
		||||
            }}
 | 
			
		||||
            dataIndex={'user_id'}
 | 
			
		||||
          />
 | 
			
		||||
          {/* <Table.Column title="商机阶段" width={160} dataIndex={'position'} /> */}
 | 
			
		||||
          <Table.Column
 | 
			
		||||
            title="添加方式"
 | 
			
		||||
@@ -270,7 +342,12 @@ const CustomList: React.FC = () => {
 | 
			
		||||
          <Table.Column title="添加时间" width={140} dataIndex={'create_time'} />
 | 
			
		||||
          {/* <Table.Column title="操作" width={160} dataIndex={'position'} /> */}
 | 
			
		||||
        </Table>
 | 
			
		||||
        <Drawer title={`${record?.name} 客户详情`} open={open} onClose={() => setOpen(false)} width={800}>
 | 
			
		||||
        <Drawer
 | 
			
		||||
          title={`${record?.name} 客户详情`}
 | 
			
		||||
          open={open}
 | 
			
		||||
          onClose={() => setOpen(false)}
 | 
			
		||||
          width={800}
 | 
			
		||||
        >
 | 
			
		||||
          <CustDetailContent record={record as ICustFollow} />
 | 
			
		||||
        </Drawer>
 | 
			
		||||
        <Pagination
 | 
			
		||||
 
 | 
			
		||||
@@ -125,6 +125,7 @@ const DepartmentsList: React.FC = () => {
 | 
			
		||||
          notification.success({
 | 
			
		||||
            message: res.err_msg,
 | 
			
		||||
          });
 | 
			
		||||
          setSyncOpen('');
 | 
			
		||||
          getDepartmentsList();
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
@@ -135,6 +136,7 @@ const DepartmentsList: React.FC = () => {
 | 
			
		||||
          notification.success({
 | 
			
		||||
            message: res.err_msg,
 | 
			
		||||
          });
 | 
			
		||||
          setSyncOpen('');
 | 
			
		||||
          getStaffsList();
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										128
									
								
								src/pages/GroupList/components/GroupDetailContent.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/pages/GroupList/components/GroupDetailContent.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
import { IGroup, IGroupMembers } from '@/pages/ChatLogs/ChatLogsType';
 | 
			
		||||
import { post } from '@/services/ajax';
 | 
			
		||||
import { useEffect, useRef, useState } from 'react';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  adminList,
 | 
			
		||||
  getAdminList,
 | 
			
		||||
  groupMembersCount,
 | 
			
		||||
  groupMembersCount2,
 | 
			
		||||
  groupMembersListItem,
 | 
			
		||||
} from '@/pages/ChatLogs/ChatUtils';
 | 
			
		||||
import { Spin } from 'antd';
 | 
			
		||||
import { stringify } from 'qs';
 | 
			
		||||
 | 
			
		||||
type Iprops = {
 | 
			
		||||
  record: IGroup;
 | 
			
		||||
};
 | 
			
		||||
export const GroupDetailContent: React.FC<Iprops> = (props) => {
 | 
			
		||||
  const { record } = props;
 | 
			
		||||
 | 
			
		||||
  const [groupMembersList, setGroupMembersList] = useState<IGroupMembers[]>([]);
 | 
			
		||||
  const [loading, setLoading] = useState(false);
 | 
			
		||||
  const groupMembersObjRef = useRef<any>({});
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    record.adminUserIDs = getAdminList(record.admin_list);
 | 
			
		||||
    getList();
 | 
			
		||||
  }, [props.record]);
 | 
			
		||||
 | 
			
		||||
  const getList = () => {
 | 
			
		||||
    setLoading(true);
 | 
			
		||||
    post({
 | 
			
		||||
      url: '/GroupMembers/GroupMembersList',
 | 
			
		||||
      data: stringify({ group_id: props.record.group_id }),
 | 
			
		||||
    }).then((res) => {
 | 
			
		||||
      setLoading(false);
 | 
			
		||||
      if (res.err_code == 0) {
 | 
			
		||||
        if (Array.isArray(res.data)) {
 | 
			
		||||
          groupMembersObjRef.current = {};
 | 
			
		||||
          let owner: IGroupMembers[] = [];
 | 
			
		||||
          let admin_list: IGroupMembers[] = [];
 | 
			
		||||
          let other: IGroupMembers[] = [];
 | 
			
		||||
          res.data.forEach((item: IGroupMembers) => {
 | 
			
		||||
            item.avatar = item.staff_avatar || item.avatar;
 | 
			
		||||
            groupMembersObjRef.current[item.user_id] = item;
 | 
			
		||||
            if (item.user_id == record?.owner) {
 | 
			
		||||
              owner.push(item);
 | 
			
		||||
            } else if (record?.adminUserIDs.includes(item.user_id)) {
 | 
			
		||||
              admin_list.push(item);
 | 
			
		||||
            } else {
 | 
			
		||||
              other.push(item);
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
          // 对群员 创建者 > 管理员 > 普通成员 排序
 | 
			
		||||
          setGroupMembersList([...owner, ...admin_list, ...other]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Spin spinning={loading} style={{ minHeight: 400, display: 'block' }}>
 | 
			
		||||
      {groupMembersList.length ? (
 | 
			
		||||
        <div>
 | 
			
		||||
          <div style={{ marginBottom: 8, textIndent: '2em' }}>
 | 
			
		||||
            群主:
 | 
			
		||||
            {groupMembersObjRef.current[record?.owner as string]?.name}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div style={{ marginBottom: 8 }}>
 | 
			
		||||
            {adminList(record?.admin_list, groupMembersObjRef.current)}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div style={{ marginBottom: 8 }}>创建时间:{record?.create_time}</div>
 | 
			
		||||
          <div style={{ textIndent: '1em' }}>群公告:{record?.notice}</div>
 | 
			
		||||
          <div
 | 
			
		||||
            style={{
 | 
			
		||||
              fontWeight: 'bold',
 | 
			
		||||
              marginTop: 24,
 | 
			
		||||
              marginBottom: 8,
 | 
			
		||||
              borderBottom: '1px solid #ddd',
 | 
			
		||||
              paddingBottom: 8,
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            群成员 • {groupMembersList.length}
 | 
			
		||||
          </div>
 | 
			
		||||
          {groupMembersList.map((item) => {
 | 
			
		||||
            return item.group_members_type == '2' || item.state == 0
 | 
			
		||||
              ? null
 | 
			
		||||
              : groupMembersListItem(item, record);
 | 
			
		||||
          })}
 | 
			
		||||
          {groupMembersCount2(groupMembersList, '2', 1) != 0 ? (
 | 
			
		||||
            <div
 | 
			
		||||
              style={{
 | 
			
		||||
                fontWeight: 'bold',
 | 
			
		||||
                marginTop: 24,
 | 
			
		||||
                marginBottom: 8,
 | 
			
		||||
                borderBottom: '1px solid #ddd',
 | 
			
		||||
                paddingBottom: 8,
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              微信:
 | 
			
		||||
            </div>
 | 
			
		||||
          ) : null}
 | 
			
		||||
          {groupMembersList.map((item) => {
 | 
			
		||||
            return item.group_members_type == '1' || item.state == 0
 | 
			
		||||
              ? null
 | 
			
		||||
              : groupMembersListItem(item, record);
 | 
			
		||||
          })}
 | 
			
		||||
          {groupMembersCount(groupMembersList, 0) != 0 ? (
 | 
			
		||||
            <div
 | 
			
		||||
              style={{
 | 
			
		||||
                fontWeight: 'bold',
 | 
			
		||||
                marginTop: 24,
 | 
			
		||||
                marginBottom: 8,
 | 
			
		||||
                borderBottom: '1px solid #ddd',
 | 
			
		||||
                paddingBottom: 8,
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              已离群:
 | 
			
		||||
            </div>
 | 
			
		||||
          ) : null}
 | 
			
		||||
          {groupMembersList.map((item) => {
 | 
			
		||||
            return item.state == 1 ? null : groupMembersListItem(item, record);
 | 
			
		||||
          })}
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : null}
 | 
			
		||||
    </Spin>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										51
									
								
								src/pages/GroupList/index.module.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/pages/GroupList/index.module.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
.departmentItem {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-shrink: 0;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  flex: 1;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
 | 
			
		||||
  .name {
 | 
			
		||||
    flex-shrink: 0;
 | 
			
		||||
    min-width: 0;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
    text-overflow: ellipsis;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .btnsBox {
 | 
			
		||||
    display: none;
 | 
			
		||||
 | 
			
		||||
    .edit {
 | 
			
		||||
      width: 24px;
 | 
			
		||||
      color: #1890ff;
 | 
			
		||||
 | 
			
		||||
      &:hover {
 | 
			
		||||
        color: #40a9ff;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .del {
 | 
			
		||||
      width: 24px;
 | 
			
		||||
      color: #ff4d4f;
 | 
			
		||||
 | 
			
		||||
      &:hover {
 | 
			
		||||
        color: #ff7875;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &:hover .btnsBox {
 | 
			
		||||
    display: inline-flex;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.detailItem{
 | 
			
		||||
  margin-bottom: 12px;
 | 
			
		||||
  &> span{
 | 
			
		||||
    color: #999;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										260
									
								
								src/pages/GroupList/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								src/pages/GroupList/index.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,260 @@
 | 
			
		||||
import { SearchBarPlugin, SearchBottonsCardPlugin } from '@/components/SearchBarPlugin';
 | 
			
		||||
import { post } from '@/services/ajax';
 | 
			
		||||
import { groupStatus } from '@/services/config';
 | 
			
		||||
import { PageContainer } from '@ant-design/pro-components';
 | 
			
		||||
import { Button, Col, DatePicker, Drawer, Form, Input, Pagination, Row, Select, Table } from 'antd';
 | 
			
		||||
import { stringify } from 'qs';
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
 | 
			
		||||
import { IGroup } from '../ChatLogs/ChatLogsType';
 | 
			
		||||
import { GroupDetailContent } from './components/GroupDetailContent';
 | 
			
		||||
 | 
			
		||||
const GroupList: React.FC = () => {
 | 
			
		||||
  const [param] = useState({
 | 
			
		||||
    curr_page: 1,
 | 
			
		||||
    page_count: 20,
 | 
			
		||||
    owner: '',
 | 
			
		||||
    name: '',
 | 
			
		||||
    state: '',
 | 
			
		||||
    status: '',
 | 
			
		||||
    create_timeL: '',
 | 
			
		||||
    create_timeU: '',
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const [groupsList, setGroupsList] = useState<IGroup[]>([]);
 | 
			
		||||
  const [count, setCount] = useState(0);
 | 
			
		||||
  const [loading, setLoading] = useState(false);
 | 
			
		||||
  const [open, setOpen] = useState(false);
 | 
			
		||||
  const [record, setRecord] = useState<IGroup>();
 | 
			
		||||
 | 
			
		||||
  const getGroupsList = () => {
 | 
			
		||||
    setLoading(true);
 | 
			
		||||
    post({ url: '/Groups/GroupsList', data: stringify(param) }).then((res) => {
 | 
			
		||||
      setLoading(false);
 | 
			
		||||
      if (res.err_code == 0) {
 | 
			
		||||
        if (Array.isArray(res.data)) {
 | 
			
		||||
          setGroupsList(res.data);
 | 
			
		||||
        }
 | 
			
		||||
        setCount(res.count || 0);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const page = (page: number) => {
 | 
			
		||||
    param.curr_page = page;
 | 
			
		||||
    getGroupsList();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    getGroupsList();
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <PageContainer>
 | 
			
		||||
      <div style={{ flexGrow: 1, minWidth: 0 }}>
 | 
			
		||||
        <SearchBarPlugin>
 | 
			
		||||
          <Form>
 | 
			
		||||
            <Row gutter={{ xs: 0, sm: 16 }}>
 | 
			
		||||
              <Col xs={24} sm={12} lg={8} xxl={6}>
 | 
			
		||||
                <Form.Item label={<span style={{ textIndent: '2em' }}>群名</span>}>
 | 
			
		||||
                  <Input
 | 
			
		||||
                    placeholder="请输入群名"
 | 
			
		||||
                    onChange={(e) => {
 | 
			
		||||
                      param.name = e.target.value.trim();
 | 
			
		||||
                    }}
 | 
			
		||||
                    allowClear
 | 
			
		||||
                    onPressEnter={() => page(1)}
 | 
			
		||||
                  />
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
              </Col>
 | 
			
		||||
              <Col xs={24} sm={12} lg={8} xxl={6}>
 | 
			
		||||
                <Form.Item label={<span style={{ textIndent: '1em' }}>创建者</span>}>
 | 
			
		||||
                  <Input
 | 
			
		||||
                    placeholder="请输入创建者"
 | 
			
		||||
                    onChange={(e) => {
 | 
			
		||||
                      param.owner = e.target.value.trim();
 | 
			
		||||
                    }}
 | 
			
		||||
                    allowClear
 | 
			
		||||
                    onPressEnter={() => page(1)}
 | 
			
		||||
                  />
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
              </Col>
 | 
			
		||||
              <Col xs={24} sm={12} lg={8} xxl={6}>
 | 
			
		||||
                <Form.Item label={<span style={{ textIndent: '2em' }}>状态</span>}>
 | 
			
		||||
                  <Select
 | 
			
		||||
                    defaultValue={param.status}
 | 
			
		||||
                    style={{ width: '100%' }}
 | 
			
		||||
                    onChange={(val) => {
 | 
			
		||||
                      param.status = val;
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    <Select.Option value="">全部</Select.Option>
 | 
			
		||||
                    {Object.keys(groupStatus).map((key) => {
 | 
			
		||||
                      return (
 | 
			
		||||
                        <Select.Option key={key} value={key}>
 | 
			
		||||
                          {groupStatus[key]}
 | 
			
		||||
                        </Select.Option>
 | 
			
		||||
                      );
 | 
			
		||||
                    })}
 | 
			
		||||
                  </Select>
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
              </Col>
 | 
			
		||||
 | 
			
		||||
              <Col xs={24} sm={12} lg={8} xxl={6}>
 | 
			
		||||
                <Form.Item label={<span>是否解散</span>}>
 | 
			
		||||
                  <Select
 | 
			
		||||
                    defaultValue={param.state}
 | 
			
		||||
                    style={{ width: '100%' }}
 | 
			
		||||
                    onChange={(val) => {
 | 
			
		||||
                      param.state = val;
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    <Select.Option value="">全部</Select.Option>
 | 
			
		||||
                    <Select.Option value="1">正常</Select.Option>
 | 
			
		||||
                    <Select.Option value="0">已解散</Select.Option>
 | 
			
		||||
                  </Select>
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
              </Col>
 | 
			
		||||
              <Col xs={24} sm={12} lg={8} xxl={6}>
 | 
			
		||||
                <Form.Item label={<span>创建时间</span>}>
 | 
			
		||||
                  <DatePicker.RangePicker
 | 
			
		||||
                    style={{ width: '100%' }}
 | 
			
		||||
                    onChange={(dates, dateStrings) => {
 | 
			
		||||
                      // console.log(dateStrings);
 | 
			
		||||
                      param.create_timeL = dateStrings[0];
 | 
			
		||||
                      param.create_timeU = dateStrings[1];
 | 
			
		||||
                    }}
 | 
			
		||||
                  />
 | 
			
		||||
                </Form.Item>
 | 
			
		||||
              </Col>
 | 
			
		||||
            </Row>
 | 
			
		||||
          </Form>
 | 
			
		||||
        </SearchBarPlugin>
 | 
			
		||||
        <SearchBottonsCardPlugin>
 | 
			
		||||
          <Row justify={'center'}>
 | 
			
		||||
            <Button
 | 
			
		||||
              type="primary"
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                page(1);
 | 
			
		||||
              }}
 | 
			
		||||
              style={{ marginRight: 12 }}
 | 
			
		||||
            >
 | 
			
		||||
              搜索
 | 
			
		||||
            </Button>
 | 
			
		||||
          </Row>
 | 
			
		||||
        </SearchBottonsCardPlugin>
 | 
			
		||||
        <Table
 | 
			
		||||
          tableLayout="fixed"
 | 
			
		||||
          size="middle"
 | 
			
		||||
          dataSource={groupsList}
 | 
			
		||||
          style={{ padding: 16, borderRadius: 6, background: '#fff', marginTop: 16 }}
 | 
			
		||||
          pagination={false}
 | 
			
		||||
          bordered={true}
 | 
			
		||||
          rowKey={(record) => {
 | 
			
		||||
            return `${record.group_id}_${record.name}`;
 | 
			
		||||
          }}
 | 
			
		||||
          loading={loading}
 | 
			
		||||
        >
 | 
			
		||||
          <Table.Column
 | 
			
		||||
            title="群名"
 | 
			
		||||
            width={160}
 | 
			
		||||
            dataIndex={'name'}
 | 
			
		||||
            render={(value, record: IGroup) => {
 | 
			
		||||
              return (
 | 
			
		||||
                <div
 | 
			
		||||
                  onClick={() => {
 | 
			
		||||
                    setRecord(record);
 | 
			
		||||
                    setOpen(true);
 | 
			
		||||
                  }}
 | 
			
		||||
                  style={{ cursor: 'pointer', display: 'flex' }}
 | 
			
		||||
                >
 | 
			
		||||
                  {/* <div
 | 
			
		||||
                    style={{
 | 
			
		||||
                      width: 56,
 | 
			
		||||
                      height: 56,
 | 
			
		||||
                      flexShrink: 0,
 | 
			
		||||
                      borderRadius: 4,
 | 
			
		||||
                      overflow: 'hidden',
 | 
			
		||||
                    }}
 | 
			
		||||
                    onClick={(e) => {
 | 
			
		||||
                      e.stopPropagation();
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    <Image width={56} height={56} src={record.avatar}></Image>
 | 
			
		||||
                  </div> */}
 | 
			
		||||
                  <div style={{ marginLeft: 8 }}>
 | 
			
		||||
                    <div style={{ color: '#1890ff', wordBreak: 'break-all' }}>
 | 
			
		||||
                      {value || '未知群名'}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              );
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <Table.Column
 | 
			
		||||
            title="创建者"
 | 
			
		||||
            width={120}
 | 
			
		||||
            render={(v, r: IGroup) => {
 | 
			
		||||
              return <>{r?.staff_name}</>;
 | 
			
		||||
            }}
 | 
			
		||||
            dataIndex={'owner'}
 | 
			
		||||
          />
 | 
			
		||||
          <Table.Column
 | 
			
		||||
            title="状态"
 | 
			
		||||
            width={120}
 | 
			
		||||
            render={(v) => {
 | 
			
		||||
              return <>{groupStatus[v]}</>;
 | 
			
		||||
            }}
 | 
			
		||||
            dataIndex={'status'}
 | 
			
		||||
          />
 | 
			
		||||
          <Table.Column title="群人数" width={120} dataIndex={'members_tot'} />
 | 
			
		||||
          <Table.Column
 | 
			
		||||
            title="是否解散"
 | 
			
		||||
            width={140}
 | 
			
		||||
            render={(v) => {
 | 
			
		||||
              return <>{v == 1 ? '正常' : '已解散'}</>;
 | 
			
		||||
            }}
 | 
			
		||||
            dataIndex={'state'}
 | 
			
		||||
          />
 | 
			
		||||
          <Table.Column title="创建时间" width={140} dataIndex={'create_time'} />
 | 
			
		||||
          {/* <Table.Column title="操作" width={160} dataIndex={'position'} /> */}
 | 
			
		||||
        </Table>
 | 
			
		||||
        <Drawer
 | 
			
		||||
          title={`${record?.name} 群详情`}
 | 
			
		||||
          open={open}
 | 
			
		||||
          onClose={() => setOpen(false)}
 | 
			
		||||
          width={800}
 | 
			
		||||
        >
 | 
			
		||||
          <GroupDetailContent record={record as IGroup} />
 | 
			
		||||
        </Drawer>
 | 
			
		||||
        <Pagination
 | 
			
		||||
          style={{
 | 
			
		||||
            background: '#fff',
 | 
			
		||||
            borderRadius: 6,
 | 
			
		||||
            marginTop: 16,
 | 
			
		||||
            padding: 16,
 | 
			
		||||
            display: 'flex',
 | 
			
		||||
            justifyContent: 'center',
 | 
			
		||||
            alignItems: 'center',
 | 
			
		||||
          }}
 | 
			
		||||
          current={param.curr_page}
 | 
			
		||||
          pageSize={param.page_count}
 | 
			
		||||
          total={count}
 | 
			
		||||
          pageSizeOptions={[10, 20, 50, 100]}
 | 
			
		||||
          onShowSizeChange={(current, size) => {
 | 
			
		||||
            param.page_count = size;
 | 
			
		||||
            page(1);
 | 
			
		||||
          }}
 | 
			
		||||
          showTotal={(total) => {
 | 
			
		||||
            return <span style={{ lineHeight: 1 }}>共{total}条</span>;
 | 
			
		||||
          }}
 | 
			
		||||
          onChange={(curr) => {
 | 
			
		||||
            page(curr);
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </PageContainer>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default GroupList;
 | 
			
		||||
@@ -29,3 +29,126 @@ export const CustType: any = {
 | 
			
		||||
  1: '微信',
 | 
			
		||||
  2: '企业微信',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 群状态
 | 
			
		||||
 */
 | 
			
		||||
export const groupStatus: any = {
 | 
			
		||||
  '0': '跟进人正常',
 | 
			
		||||
  '1': '跟进人离职',
 | 
			
		||||
  '2': '离职继承中',
 | 
			
		||||
  '3': '离职继承完成',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const emoji = [
 | 
			
		||||
  '666',
 | 
			
		||||
  'Emm',
 | 
			
		||||
  'OK',
 | 
			
		||||
  '亲亲',
 | 
			
		||||
  '便便',
 | 
			
		||||
  '偷笑',
 | 
			
		||||
  '傲慢',
 | 
			
		||||
  '再见',
 | 
			
		||||
  '凋谢',
 | 
			
		||||
  '加油',
 | 
			
		||||
  '勾引',
 | 
			
		||||
  '发呆',
 | 
			
		||||
  '发怒',
 | 
			
		||||
  '发抖',
 | 
			
		||||
  '发财',
 | 
			
		||||
  '發',
 | 
			
		||||
  '可怜',
 | 
			
		||||
  '右哼哼',
 | 
			
		||||
  '叹气',
 | 
			
		||||
  '吃瓜',
 | 
			
		||||
  '合十',
 | 
			
		||||
  '吐',
 | 
			
		||||
  '呲牙',
 | 
			
		||||
  '咒骂',
 | 
			
		||||
  '咖啡',
 | 
			
		||||
  '哇',
 | 
			
		||||
  '啤酒',
 | 
			
		||||
  '嘘',
 | 
			
		||||
  '嘴唇',
 | 
			
		||||
  '嘿哈',
 | 
			
		||||
  '囧',
 | 
			
		||||
  '困',
 | 
			
		||||
  '坏笑',
 | 
			
		||||
  '大哭',
 | 
			
		||||
  '天啊',
 | 
			
		||||
  '太阳',
 | 
			
		||||
  '失望',
 | 
			
		||||
  '奸笑',
 | 
			
		||||
  '好的',
 | 
			
		||||
  '委屈',
 | 
			
		||||
  '害羞',
 | 
			
		||||
  '尴尬',
 | 
			
		||||
  '庆祝',
 | 
			
		||||
  '弱',
 | 
			
		||||
  '强',
 | 
			
		||||
  '得意',
 | 
			
		||||
  '微笑',
 | 
			
		||||
  '心碎',
 | 
			
		||||
  '快哭了',
 | 
			
		||||
  '恐惧',
 | 
			
		||||
  '悠闲',
 | 
			
		||||
  '惊恐',
 | 
			
		||||
  '惊讶',
 | 
			
		||||
  '愉快',
 | 
			
		||||
  '憨笑',
 | 
			
		||||
  '打脸',
 | 
			
		||||
  '抓狂',
 | 
			
		||||
  '抠鼻',
 | 
			
		||||
  '抱拳',
 | 
			
		||||
  '拥抱',
 | 
			
		||||
  '拳头',
 | 
			
		||||
  '捂脸',
 | 
			
		||||
  '握手',
 | 
			
		||||
  '撇嘴',
 | 
			
		||||
  '擦汗',
 | 
			
		||||
  '敲打',
 | 
			
		||||
  '无语',
 | 
			
		||||
  '旺柴',
 | 
			
		||||
  '晕',
 | 
			
		||||
  '月亮',
 | 
			
		||||
  '机智',
 | 
			
		||||
  '汗',
 | 
			
		||||
  '流泪',
 | 
			
		||||
  '炸弹',
 | 
			
		||||
  '烟花',
 | 
			
		||||
  '爆竹',
 | 
			
		||||
  '爱心',
 | 
			
		||||
  '猪头',
 | 
			
		||||
  '玫瑰',
 | 
			
		||||
  '生病',
 | 
			
		||||
  '疑问',
 | 
			
		||||
  '白眼',
 | 
			
		||||
  '皱眉',
 | 
			
		||||
  '睡',
 | 
			
		||||
  '破涕为笑',
 | 
			
		||||
  '礼物',
 | 
			
		||||
  '社会社会',
 | 
			
		||||
  '福',
 | 
			
		||||
  '笑脸',
 | 
			
		||||
  '红包',
 | 
			
		||||
  '翻白眼',
 | 
			
		||||
  '耶',
 | 
			
		||||
  '胜利',
 | 
			
		||||
  '脸红',
 | 
			
		||||
  '色',
 | 
			
		||||
  '苦涩',
 | 
			
		||||
  '菜刀',
 | 
			
		||||
  '蛋糕',
 | 
			
		||||
  '衰',
 | 
			
		||||
  '裂开',
 | 
			
		||||
  '让我看看',
 | 
			
		||||
  '调皮',
 | 
			
		||||
  '跳跳',
 | 
			
		||||
  '转圈',
 | 
			
		||||
  '鄙视',
 | 
			
		||||
  '闭嘴',
 | 
			
		||||
  '阴险',
 | 
			
		||||
  '难过',
 | 
			
		||||
  '骷髅',
 | 
			
		||||
  '鼓掌',
 | 
			
		||||
];
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user