wjx преди 1 месец
родител
ревизия
5e047c2c7e

+ 113 - 0
src/pages/weComTask/API/chatRecordManage/index.ts

@@ -0,0 +1,113 @@
+import request from "@/utils/request";
+
+
+export interface GetChatMessageListProps {
+    "pageNum": number,
+    "pageSize": number
+    "corpId"?: string,
+    "corpUserId"?: string,
+    "chatType"?: 1 | 2,
+    "messageContent"?: string,
+    "messageTimeEnd"?: string,
+    "messageTimeStart"?: string,
+    chatName?: string
+    senderName?: string
+    riskLevel?: number
+}
+
+/**
+ * 获取聊天记录列表
+ * @param data 
+ * @returns 
+ */
+export async function getChatMessageListApi(data: GetChatMessageListProps) {
+    return request({
+        url: `/chat/message/list`,
+        method: 'POST',
+        data
+    });
+}
+
+export interface KeywordProps {
+    "corpId": string,
+    "corpUserId": string,
+    "keywordJson": string[],
+    "riskLevel": 1 | 2 | 3,
+    "id"?: number
+}
+
+/**
+ * 创建风险词
+ * @param data 
+ * @returns 
+ */
+export async function createKeywordApi(data: KeywordProps) {
+    return request({
+        url: `/chat/message/keyword/create`,
+        method: 'POST',
+        data
+    });
+}
+
+
+/**
+ * 修改风险词
+ * @param data 
+ * @returns 
+ */
+export async function updateKeywordApi(data: KeywordProps) {
+    return request({
+        url: `/chat/message/keyword/update`,
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 批量删除风险词
+ * @param data 
+ * @returns 
+ */
+export async function deleteKeywordApi(data: number[]) {
+    return request({
+        url: `/chat/message/keyword/deleteBatch`,
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 启用或禁用
+ * @param params 
+ * @returns 
+ */
+export async function enableKeywordApi(params: { ids: number[], enabled: boolean }) {
+    return request({
+        url: `/chat/message/keyword/enable?enabled=${params.enabled}`,
+        method: 'POST',
+        data: params.ids,
+    });
+}
+
+export interface GetKeywordListProps {
+    "pageNum": number,
+    "pageSize": number
+    "corpId"?: string,
+    "corpUserId"?: string,
+    "enabled"?: boolean,
+    "riskLevel"?: 1 | 2 | 3,
+    "userId"?: number
+}
+
+/**
+ * 获取风险词列表
+ * @param data 
+ * @returns 
+ */
+export async function getKeywordListApi(data: GetKeywordListProps) {
+    return request({
+        url: `/chat/message/keyword/pageList`,
+        method: 'POST',
+        data,
+    });
+}

+ 3 - 9
src/pages/weComTask/page/alarmGl/index.tsx

@@ -1,11 +1,10 @@
-import { api_corpUser_allOfUser, getAdAccountAllOfMember } from '@/API/global';
+import { api_corpUser_allOfUser } from '@/API/global';
 import { useAjax } from '@/Hook/useAjax';
 import React, { useEffect, useRef, useState } from 'react';
 import { getCorpPhoneMsgAlarmListApi, GetCorpPhoneMsgAlarmListProps, updateCorpPhoneMsgAlarmApi } from '../../API/alarmGl';
-import { MenuUnfoldOutlined, MenuFoldOutlined, SearchOutlined } from '@ant-design/icons';
+import { SearchOutlined } from '@ant-design/icons';
 import style from '../corpUserManage/index.less'
-import { App, Button, Card, DatePicker, Pagination, Popconfirm, Select, Table, Tabs, Tag } from 'antd';
-import TeamMembers from '@/components/Team/teamMembers';
+import { App, Button, Card, DatePicker, Pagination, Popconfirm, Select, Table } from 'antd';
 import SearchBox from '../../components/searchBox';
 import { TableConfig } from './tableConfig';
 import { useSize } from 'ahooks';
@@ -24,14 +23,9 @@ const AlarmGl: React.FC = () => {
 
     const corpUser_allOfUser = useAjax((params) => api_corpUser_allOfUser(params))
     const getCorpPhoneMsgAlarmList = useAjax((params) => getCorpPhoneMsgAlarmListApi(params))
-    const allOfMember = useAjax(() => getAdAccountAllOfMember())
     const updateCorpPhoneMsgAlarm = useAjax((params) => updateCorpPhoneMsgAlarmApi(params))
     /**************************************/
 
-    useEffect(() => {
-        allOfMember.run()
-    }, [])
-
     useEffect(() => {
         if (userId) {
             corpUser_allOfUser.run(userId)

+ 236 - 0
src/pages/weComTask/page/chatRecordManage/chatRecord/index.tsx

@@ -0,0 +1,236 @@
+import React, { useEffect, useRef, useState } from 'react';
+import style from '../../corpUserManage/index.less'
+import { Button, Card, DatePicker, Form, Input, Modal, Pagination, Select, Table } from 'antd';
+import { useSize } from 'ahooks';
+import SearchBox from '@/pages/weComTask/components/searchBox';
+import { getChatMessageListApi, GetChatMessageListProps } from '@/pages/weComTask/API/chatRecordManage';
+import { SearchOutlined } from '@ant-design/icons';
+import { api_corpUser_allOfUser } from '@/API/global';
+import { useAjax } from '@/Hook/useAjax';
+import { useForm } from 'antd/es/form/Form'
+import moment from 'dayjs'
+import tableConfig from './tableConfig';
+const { RangePicker } = DatePicker;
+
+const ChatRecord: React.FC = () => {
+
+    /*****************************************/
+    const ref = useRef<HTMLDivElement>(null)
+    const size = useSize(ref)
+    const userIdStr = sessionStorage.getItem('userId');
+    const chatRecordFilterValue = localStorage.getItem('chatRecordFilter')
+    const [userId] = useState<number>(userIdStr ? Number(userIdStr) : undefined);
+    const [activeCorp, setActiveCorp] = useState<any>()
+    const [filterOpen, setFilterOpen] = useState<boolean>(false)
+    const [filterForm] = useForm()
+
+    const [queryForm, setQueryForm] = useState<GetChatMessageListProps>({ 
+        pageNum: 1, 
+        pageSize: 20, 
+        messageTimeStart: moment().format('YYYY-MM-DD'), 
+        messageTimeEnd: moment().format('YYYY-MM-DD'),
+        ...(chatRecordFilterValue ? JSON.parse(chatRecordFilterValue) : {})
+    })
+
+    const corpUser_allOfUser = useAjax((params) => api_corpUser_allOfUser(params))
+    const getChatMessageList = useAjax((params) => getChatMessageListApi(params))
+    /*****************************************/
+
+    useEffect(() => {
+        if (chatRecordFilterValue && Object.keys(chatRecordFilterValue).length && filterOpen) {
+            filterForm.setFieldsValue(JSON.parse(chatRecordFilterValue))
+        }
+    }, [chatRecordFilterValue, filterOpen])
+
+    useEffect(() => {
+        if (userId) {
+            corpUser_allOfUser.run(userId)
+        }
+    }, [userId])
+
+    useEffect(() => {
+        getChatMessageList.run({ ...queryForm })
+    }, [queryForm])
+
+    //关闭弹窗存储
+    const formSave = () => {
+        let value = filterForm.getFieldsValue()
+        setQueryForm({ ...queryForm, ...value })
+        //存放到本地存储
+        localStorage.setItem('chatRecordFilter', JSON.stringify(value))
+        //关闭弹窗
+        setFilterOpen(false)
+    }
+
+    return (
+        <div className={style.corpUserManage}>
+            <div className={style.corpUserManage_bottom} style={{ height: '100%' }}>
+                <Card className={style.corpUserList} styles={{ body: { padding: 0, display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' } }}>
+                    <SearchBox
+                        bodyPadding={`16px 16px 12px`}
+                        buttons={<>
+                            <Button type="primary" onClick={() => getChatMessageList.refresh()} icon={<SearchOutlined />}>搜索</Button>
+                            <Button onClick={() => { setFilterOpen(true) }} type="primary">过滤配置</Button>
+                        </>}
+                    >
+                        <>
+                            <Select
+                                style={{ width: 180 }}
+                                placeholder="选择企业"
+                                value={queryForm?.corpId}
+                                showSearch
+                                maxTagCount={1}
+                                allowClear
+                                onChange={(e, option: any) => {
+                                    setQueryForm({ ...queryForm, corpId: e, pageNum: 1 })
+                                    setActiveCorp(option?.data)
+                                }}
+                                filterOption={(input: string, option?: { label: string; value: string }) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
+                                options={corpUser_allOfUser?.data?.data?.map((item: { t1: any }) => ({ label: item.t1?.corpName, value: item.t1?.corpId, data: item }))}
+                            />
+                            {activeCorp && <Select
+                                style={{ minWidth: 125 }}
+                                placeholder='企微号'
+                                allowClear
+                                autoClearSearchValue={false}
+                                onChange={(e) => setQueryForm({ ...queryForm, corpUserId: e, pageNum: 1 })}
+                                value={queryForm?.corpUserId}
+                                filterOption={(input, option) =>
+                                    ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                                }
+                                options={activeCorp?.t2?.map((item: any) => ({ label: item.name, value: item.corpUserId }))}
+                            />}
+
+                            <Input placeholder='聊天内容模糊查询' allowClear value={queryForm?.messageContent} onChange={(e) => {
+                                const value = e.target.value?.replace(/\s/ig, '')
+                                setQueryForm({ ...queryForm, messageContent: value, pageNum: 1 })
+                            }} />
+                            <Input placeholder='群名称模糊查询' allowClear value={queryForm?.chatName} onChange={(e) => {
+                                const value = e.target.value?.replace(/\s/ig, '')
+                                setQueryForm({ ...queryForm, chatName: value, pageNum: 1 })
+                            }} />
+                            <Input placeholder='发送人昵称模糊查询' allowClear value={queryForm?.senderName} onChange={(e) => {
+                                const value = e.target.value?.replace(/\s/ig, '')
+                                setQueryForm({ ...queryForm, senderName: value, pageNum: 1 })
+                            }} />
+                            <Select
+                                style={{ minWidth: 125 }}
+                                placeholder='聊天类型'
+                                allowClear
+                                autoClearSearchValue={false}
+                                onChange={(e) => { setQueryForm({ ...queryForm, chatType: e, pageNum: 1 }) }}
+                                value={queryForm?.chatType}
+                                filterOption={(input, option) =>
+                                    ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                                }
+                                options={[
+                                    {
+                                        label: '群聊',
+                                        value: 1
+                                    },
+                                    {
+                                        label: '单聊',
+                                        value: 2
+                                    }
+                                ]}
+                            />
+                            <Select
+                                style={{ minWidth: 125 }}
+                                placeholder='风险等级'
+                                allowClear
+                                autoClearSearchValue={false}
+                                onChange={(e) => { setQueryForm({ ...queryForm, riskLevel: e }) }}
+                                value={queryForm?.riskLevel}
+                                filterOption={(input, option) =>
+                                    ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                                }
+                                options={[
+                                    {
+                                        label: '低',
+                                        value: 1
+                                    },
+                                    {
+                                        label: '中',
+                                        value: 2
+                                    },
+                                    {
+                                        label: '高',
+                                        value: 3
+                                    }
+                                ]}
+                            />
+                            <RangePicker onChange={(date, dataString) => {
+                                setQueryForm({ ...queryForm, messageTimeStart: dataString[0], messageTimeEnd: dataString[1] })
+                            }} value={[queryForm.messageTimeStart ? moment(queryForm.messageTimeStart) : null, queryForm.messageTimeEnd ? moment(queryForm.messageTimeEnd) : null]} />
+                        </>
+                    </SearchBox>
+                    <div className={style.corpUserList_table} ref={ref}>
+                        <Table
+                            style={{ marginBottom: 1 }}
+                            dataSource={getChatMessageList?.data?.data?.records}
+                            loading={getChatMessageList?.loading}
+                            columns={tableConfig()}
+                            scroll={{ x: "max-content", y: size?.height && ref.current ? size?.height - 32 : 300 }}
+                            rowKey={(s) => {
+                                return s.id
+                            }}
+                            bordered
+                            size='small'
+                            pagination={false}
+                        />
+                    </div>
+                    <div className={style.corpUserList_footer}>
+                        <Pagination
+                            size="small"
+                            total={getChatMessageList?.data?.data?.total || 0}
+                            showSizeChanger
+                            showQuickJumper
+                            pageSize={getChatMessageList?.data?.data?.size || 20}
+                            current={getChatMessageList?.data?.data?.current || 1}
+                            onChange={(page: number, pageSize: number) => {
+                                ref.current?.scrollTo({ top: 0 })
+                                setQueryForm({ ...queryForm, pageNum: page, pageSize })
+                            }}
+                            showTotal={(total: number) => <span style={{ fontSize: 12 }}>共 {total} 条</span>}
+                        />
+                    </div>
+                </Card>
+            </div>
+            {/* 过滤配置 */}
+            <Modal
+                title="过滤配置-会保存在浏览器本地"
+                open={filterOpen}
+                onCancel={() => {
+                    formSave()
+                }}
+                footer={null}
+            >
+                <Form
+                    labelCol={{ span: 5 }}
+                    form={filterForm}
+                >
+                    <Form.Item label="排除包含的发送人" name="excludeSenderNames">
+                        <Select
+                            mode="tags"
+                            style={{ width: '100%' }}
+                            placeholder="输入内容按回车添加,可添加多个!"
+                            onChange={(value: any) => { console.log(value) }}
+                            options={[]}
+                        />
+                    </Form.Item>
+                    <Form.Item label="排除包含的内容" name="excludeContents">
+                        <Select
+                            mode="tags"
+                            style={{ width: '100%' }}
+                            placeholder="输入内容按回车添加,可添加多个!"
+                            onChange={(value: any) => { console.log(value) }}
+                            options={[]}
+                        />
+                    </Form.Item>
+                </Form>
+            </Modal>
+        </div>
+    );
+};
+
+export default ChatRecord;

+ 264 - 0
src/pages/weComTask/page/chatRecordManage/chatRecord/tableConfig.tsx

@@ -0,0 +1,264 @@
+import { Image, Tag } from 'antd';
+import { AudioOutlined, CheckCircleOutlined, ExclamationCircleOutlined, FileTextOutlined, FileUnknownOutlined, FileWordOutlined, InfoCircleOutlined, LinkOutlined, PaperClipOutlined, PictureOutlined, SmileOutlined, VideoCameraOutlined, WarningOutlined, YoutubeOutlined } from '@ant-design/icons'
+
+const EmojiCategory = {
+    // 人脸表情
+    '微笑': { name: '微笑' },
+    '撇嘴': { name: '撇嘴' },
+    '色': { name: '色' },
+    '发呆': { name: '发呆' },
+    '得意': { name: '得意' },
+    '流泪': { name: '流泪' },
+    '害羞': { name: '害羞' },
+    '闭嘴': { name: '闭嘴' },
+    '睡': { name: '睡' },
+    '大哭': { name: '大哭' },
+    '尴尬': { name: '尴尬' },
+    '发怒': { name: '发怒' },
+    '调皮': { name: '调皮' },
+    '呲牙': { name: '呲牙' },
+    '惊讶': { name: '惊讶' },
+    '难过': { name: '难过' },
+    '囧': { name: '囧' },
+    '抓狂': { name: '抓狂' },
+    '吐': { name: '吐' },
+    '偷笑': { name: '偷笑' },
+    '愉快': { name: '愉快' },
+    '白眼': { name: '白眼' },
+    '傲慢': { name: '傲慢' },
+    '困': { name: '困' },
+    '惊恐': { name: '惊恐' },
+    '憨笑': { name: '憨笑' },
+    '悠闲': { name: '悠闲' },
+    '咒骂': { name: '咒骂' },
+    '疑问': { name: '疑问' },
+    '嘘': { name: '嘘' },
+    '晕': { name: '晕' },
+    '衰': { name: '衰' },
+    '骷髅': { name: '骷髅' },
+    '敲打': { name: '敲打' },
+    '再见': { name: '再见' },
+    '擦汗': { name: '擦汗' },
+    '抠鼻': { name: '抠鼻' },
+    '鼓掌': { name: '鼓掌' },
+    '坏笑': { name: '坏笑' },
+    '右哼哼': { name: '右哼哼' },
+    '鄙视': { name: '鄙视' },
+    '委屈': { name: '委屈' },
+    '快哭了': { name: '快哭了' },
+    '阴险': { name: '阴险' },
+    '亲亲': { name: '亲亲' },
+    '可怜': { name: '可怜' },
+    '笑脸': { name: '笑脸' },
+    '生病': { name: '生病' },
+    '脸红': { name: '脸红' },
+    '破涕为笑': { name: '破涕为笑' },
+    '恐惧': { name: '恐惧' },
+    '失望': { name: '失望' },
+    '无语': { name: '无语' },
+    '嘿哈': { name: '嘿哈' },
+    '捂脸': { name: '捂脸' },
+    '机智': { name: '机智' },
+    '皱眉': { name: '皱眉' },
+    '耶': { name: '耶' },
+    '吃瓜': { name: '吃瓜' },
+    '加油': { name: '加油' },
+
+    '汗': { name: '汗' },
+    '天啊': { name: '天啊' },
+    'Emm': { name: 'Emm' },
+    '社会社会': { name: '社会社会' },
+    '旺柴': { name: '旺柴' },
+    '好的': { name: '好的' },
+    '打脸': { name: '打脸' },
+    '哇': { name: '哇' },
+    '翻白眼': { name: '翻白眼' },
+    '666': { name: '666' },
+    '让我看看': { name: '让我看看' },
+    '叹气': { name: '叹气' },
+    '苦涩': { name: '苦涩' },
+    '裂开': { name: '裂开' },
+    '奸笑': { name: '奸笑' },
+
+    // 手势表情
+    '握手': { name: '握手' },
+    '胜利': { name: '胜利' },
+    '抱拳': { name: '抱拳' },
+    '勾引': { name: '勾引' },
+    '拳头': { name: '拳头' },
+    'OK': { name: 'OK' },
+    '合十': { name: '合十' },
+    '强': { name: '强' },
+    '拥抱': { name: '拥抱' },
+    '弱': { name: '弱' },
+
+
+    // 动物表情
+    '猪头': { name: '猪头' },
+    '跳跳': { name: '跳跳' },
+    '发抖': { name: '发抖' },
+    '转圈': { name: '转圈' },
+
+    // 祝福表情
+    '庆祝': { name: '庆祝' },
+    '礼物': { name: '礼物' },
+    '红包': { name: '红包' },
+    '發': { name: '發' },
+    '福': { name: '福' },
+    '烟花': { name: '烟花' },
+    '爆竹': { name: '爆竹' },
+
+    // 其他表情
+    '嘴唇': { name: '嘴唇' },
+    '爱心': { name: '爱心' },
+    '心碎': { name: '心碎' },
+    '啤酒': { name: '啤酒' },
+    '咖啡': { name: '咖啡' },
+    '蛋糕': { name: '蛋糕' },
+    '凋谢': { name: '凋谢' },
+    '菜刀': { name: '菜刀' },
+    '炸弹': { name: '炸弹' },
+    '便便': { name: '便便' },
+    '太阳': { name: '太阳' },
+    '月亮': { name: '月亮' },
+    '玫瑰': { name: '玫瑰' }
+};
+function replaceEmojis(text) {
+    // 修正后的正则表达式,确保匹配中文括号内容
+    return text.replace(/\[([\u4e00-\u9fa5a-zA-Z0-9_]+)\]/g, (match, emojiName) => {
+        const emoji = EmojiCategory[emojiName];
+        return emoji ? `<img src="https://zx-web-dev-01.oss-cn-hangzhou.aliyuncs.com/apk/emo/${emojiName}.png" style="width:20px" alt="${emojiName}" class="emoji" />` : match;
+    });
+}
+const obj = {
+    "text": <div style={{ color: '#059669', backgroundColor: '#d1fae5', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><FileTextOutlined />文本</div>,
+    "voice": <div style={{ color: '#0e7490', backgroundColor: '#a5f3fc', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><AudioOutlined />语音</div>,
+    "file": <div style={{ color: '#ea580c', backgroundColor: '#fed7aa', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><FileWordOutlined />文件</div>,
+    "img": <div style={{ color: '#0891b2', backgroundColor: '#cffafe', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><PictureOutlined />图片</div>,
+    "video": <div style={{ color: '#dc2626', backgroundColor: '#fecaca', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><YoutubeOutlined />视频</div>,
+    "gif": <div style={{ color: '#701a75', backgroundColor: '#f5d0fe', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><SmileOutlined />动画表情</div>,
+    "link": <div style={{ color: '#0284c7', backgroundColor: '#e0f2fe', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><LinkOutlined />图文链接</div>,
+    "miniProgram": <div style={{ color: '#4338ca', backgroundColor: '#c7d2fe', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><PaperClipOutlined />小程序</div>,
+    "videoApp": <div style={{ color: '#0d9488', backgroundColor: '#ccfbf1', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><VideoCameraOutlined />视频号</div>,
+    "Unknown": <div style={{ color: '#4b5563', backgroundColor: '#e5e7eb', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><FileUnknownOutlined />未知</div>,
+}
+const tableConfig: any = () => {
+    let arr: any = [
+        {
+            title: 'ID',
+            dataIndex: 'id',
+            key: 'id',
+            width: 50,
+            align: 'center',
+        },
+        {
+            title: '企微号',
+            dataIndex: 'corpUserVO',
+            key: 'corpUserVO',
+            width: 100,
+            align: 'center',
+            ellipsis: true,
+            render: (a: any) => {
+                return a?.name || '--'
+            }
+        },
+        {
+            title: '发送人昵称(群成员/私聊用户)',
+            align: 'center',
+            dataIndex: 'senderName',
+            key: 'senderName',
+            width: 250,
+            ellipsis: true,
+            render: (a: any) => {
+                return a || "--"
+            }
+        },
+        {
+            title: '风险等级',
+            dataIndex: 'riskLevel',
+            key: 'riskLevel',
+            align: 'center',
+            width: 100,
+            render: (a: any, b: any) => {
+                return a ? [
+                    <div style={{ color: '#0284c7', backgroundColor: '#e0f2fe', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><CheckCircleOutlined /> 无风险</div>,
+                    <div style={{ color: '#16a34a', backgroundColor: '#dcfce7', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><InfoCircleOutlined /> 低风险</div>,
+                    <div style={{ color: '#d97706', backgroundColor: '#fef3c7', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><ExclamationCircleOutlined />中风险</div>,
+                    <div style={{ color: '#dc2626', backgroundColor: '#fee2e2', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><WarningOutlined /> 高风险</div>
+                ][a] : <div style={{ color: '#0284c7', backgroundColor: '#e0f2fe', padding: '1px 6px', fontSize: 12, borderRadius: '15px', display: 'inline-flex', alignItems: 'center', gap: 2 }}><CheckCircleOutlined /> 无风险</div>
+            }
+        },
+        {
+            title: '风险提示词',
+            dataIndex: 'riskMessage',
+            key: 'riskMessage',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+            render: (a: any, b: any) => {
+                return a ? <span style={{ color: '#e62929' }}>{a}</span> : "--"
+            }
+        },
+        {
+            title: '聊天类型',
+            dataIndex: 'chatType',
+            key: 'chatType',
+            align: 'center',
+            ellipsis: true,
+            width: 70,
+            render: (a: any, b: any) => {
+                return a === 1 ? <Tag color="#f50">群聊</Tag> : <Tag color="#108ee9">私聊</Tag>
+            }
+        },
+        {
+            title: '消息类型',
+            dataIndex: 'msgType',
+            key: 'msgType',
+            align: 'center',
+            width: 100,
+            render: (a: any, b: any) => {
+                return a? obj[a]:"--"
+            }
+        },
+        {
+            title: '消息内容',
+            dataIndex: 'messageContent',
+            key: 'messageContent',
+            ellipsis: true,
+            render: (a: any, b: any) => {
+                return a && !b.msgType? <div dangerouslySetInnerHTML={{ __html: replaceEmojis(a) }} style={{ display: 'flex', fontWeight: 'bold' }}></div> :b?.msgType === "text" || b?.msgType === "voice" ? <div dangerouslySetInnerHTML={{ __html: replaceEmojis(a) }} style={{ display: 'flex', justifyContent: 'left' }}></div> : <Image src={ b?.imgUrl} height={20}  />
+            }
+        },
+        {
+            title: '群名称',
+            dataIndex: 'chatName',
+            key: 'chatName',
+            width: 150,
+            ellipsis: true,
+            render: (a: any, b: any) => {
+                return a || "--"
+            }
+        },
+        {
+            title: 'uuid',
+            dataIndex: 'uuid',
+            key: 'uuid',
+            width: 140,
+            align: 'center',
+            ellipsis: true
+        },
+        {
+            title: '发送时间',
+            align: 'center',
+            dataIndex: 'messageTime',
+            key: 'messageTime',
+            ellipsis: true,
+            width: 140,
+        },
+
+
+    ]
+    return arr
+}
+
+export default tableConfig

+ 211 - 0
src/pages/weComTask/page/chatRecordManage/riskWord/AddModify.tsx

@@ -0,0 +1,211 @@
+import { useAjax } from "@/Hook/useAjax";
+import { App, Button, Card, Form, Input, Modal, Select } from "antd"
+import React from "react"
+import { DeleteOutlined, MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
+import { createKeywordApi, updateKeywordApi } from "@/pages/weComTask/API/chatRecordManage";
+
+interface Props {
+    corpList: any[]
+    initialValues?: any
+    visible?: boolean,
+    onClose?: () => void
+    onChange?: () => void
+}
+const AddModify: React.FC<Props> = ({ corpList, initialValues, visible, onClose, onChange }) => {
+
+    /*******************************/
+    const { message } = App.useApp();
+    const [form] = Form.useForm();
+    const corpUserDTOList = Form.useWatch('corpUserDTOList', form)
+    const addKeywordNews = useAjax((params) => createKeywordApi(params))
+    const updataKeywordNews = useAjax((params) => updateKeywordApi(params))
+    /*******************************/
+
+    console.log(initialValues)
+
+
+    const handleOk = () => {
+        form.validateFields().then(valid => {
+            let newValue = JSON.parse(JSON.stringify(valid))
+            if (newValue.replyJson) {
+                newValue.replyJson = newValue.replyJson.flat().map((item: { mediaType: any; textContent: any; imageUrl: any; videoUrl: any; }) => {
+                    let msgContent = null
+                    switch (item.mediaType) {
+                        case 'text':
+                            msgContent = item.textContent
+                            break
+                        case 'image':
+                            msgContent = item.imageUrl
+                            break;
+                        case 'video':
+                            msgContent = item.videoUrl
+                            break;
+                    }
+                    return { msgType: item.mediaType, msgContent }
+                })
+            }
+            console.log(newValue)
+            if (initialValues?.id) {
+                updataKeywordNews.run({ ...newValue, id: initialValues?.id }).then(res => {
+                    if (res?.data) {
+                        message.success('修改成功')
+                        onChange?.()
+                    }
+                })
+            } else {
+                addKeywordNews.run({ ...newValue }).then(res => {
+                    if (res?.data) {
+                        message.success('新增成功')
+                        onChange?.()
+                    }
+                })
+            }
+        })
+    }
+
+    return <Modal
+        title={`${initialValues?.id ? '修改' : (initialValues && Object.keys(initialValues).length > 0) ? '复制' : '新增'}风险词`}
+        open={visible}
+        onCancel={onClose}
+        onOk={handleOk}
+        confirmLoading={addKeywordNews.loading || updataKeywordNews.loading}
+    >
+        <Form
+            form={form}
+            name="keyword-hooks"
+            labelAlign='left'
+            labelCol={{ span: 4 }}
+            colon={false}
+            initialValues={initialValues}
+        >
+            {initialValues?.id ? <>
+                <Form.Item
+                    label={<strong>企业</strong>}
+                    name={'corpId'}
+                    rules={[{ required: true, message: '请选择企业!' }]}
+                >
+                    <Select
+                        placeholder="选择企业"
+                        showSearch
+                        allowClear
+                        disabled
+                        filterOption={(input: string, option?: { label: string; value: string }) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
+                        options={corpList?.map((item: { t1: any }) => ({ label: item.t1?.corpName, value: item.t1?.corpId, data: item }))}
+                    />
+                </Form.Item>
+                <Form.Item label={<strong>企微号</strong>} name='corpUserId' rules={[{ required: true, message: '请选择企微号' }]}>
+                    <Select
+                        placeholder="选择企微号"
+                        showSearch
+                        allowClear
+                        mode="multiple"
+                        filterOption={(input: string, option?: { label: string; value: string }) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
+                        options={corpList?.find(item => item?.t1?.corpId === initialValues?.corpId)?.t2?.map((item: any) => ({ label: item.name, value: item.corpUserId }))}
+                    />
+                </Form.Item>
+            </> : <Form.List name="corpUserDTOList">
+                {(fields, { add, remove }) => (
+                    <>
+                        {fields.map(({ key, name, ...restField }, index) => {
+                            const corpId = corpUserDTOList[index]?.corpId
+                            const corpUserList = corpList?.find(item => item?.t1?.corpId === corpId)?.t2
+                            return <Card
+                                key={key}
+                                title={<strong>企业{index + 1}</strong>}
+                                style={{ background: '#fff', marginBottom: 10 }}
+                                hoverable
+                                id={`strategy_${index}`}
+                                extra={corpUserDTOList?.length > 1 ? <Button icon={<DeleteOutlined />} type='link' style={{ color: 'red' }} onClick={() => remove(name)}></Button> : null}
+                            >
+                                <Form.Item
+                                    {...restField}
+                                    label={<strong>企业</strong>}
+                                    name={[name, 'corpId']}
+                                    rules={[{ required: true, message: '请选择企业!' }]}
+                                >
+                                    <Select
+                                        placeholder="选择企业"
+                                        showSearch
+                                        allowClear
+                                        filterOption={(input: string, option?: { label: string; value: string }) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
+                                        options={corpList?.map((item: { t1: any }) => ({ label: item.t1?.corpName, value: item.t1?.corpId, data: item }))}
+                                    />
+                                </Form.Item>
+                                {corpId && <Form.Item
+                                    {...restField}
+                                    label={<strong>企微号</strong>}
+                                    name={[name, 'corpUserId']}
+                                    rules={[{ required: true, message: '请选择企微号!' }]}
+                                >
+                                    <Select
+                                        placeholder="选择企微号"
+                                        showSearch
+                                        allowClear
+                                        mode="multiple"
+                                        filterOption={(input: string, option?: { label: string; value: string }) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
+                                        options={corpUserList?.map((item: any) => ({ label: item.name, value: item.corpUserId }))}
+                                    />
+                                </Form.Item>}
+                            </Card>
+                        })}
+                        <Form.Item>
+                            <Button type="primary" onClick={() => add()} block icon={<PlusOutlined />}>
+                                新增企业组
+                            </Button>
+                        </Form.Item>
+                    </>
+                )}
+            </Form.List>}
+
+            <Form.Item name="riskLevel" label={<strong>风险等级</strong>} rules={[{ required: true, message: '请选择风险等级' }]}>
+                <Select
+                    placeholder='风险等级'
+                    allowClear
+                    autoClearSearchValue={false}
+                    filterOption={(input, option) =>
+                        ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                    }
+                    options={[
+                        {
+                            label: '低',
+                            value: 1
+                        },
+                        {
+                            label: '中',
+                            value: 2
+                        },
+                        {
+                            label: '高',
+                            value: 3
+                        }
+                    ]}
+                />
+            </Form.Item>
+            <Form.List name="keywordJson">
+                {(fields, { add, remove }) => (
+                    <>
+                        {fields.map(({ key, name, ...restField }, index) => (
+                            <Form.Item
+                                key={key}
+                                label={<strong>风险词{index + 1}</strong>}
+                                {...restField}
+                                name={[name]}
+                                labelCol={{ span: 4 }}
+                                rules={[{ required: true, message: '请输入风险词' }]}
+                            >
+                                <Input placeholder="请输入风险词" addonAfter={fields.length > 1 ? <MinusCircleOutlined onClick={() => remove(name)} /> : null} />
+                            </Form.Item>
+                        ))}
+                        <Form.Item labelCol={{ span: 4 }}>
+                            <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
+                                添加风险词
+                            </Button>
+                        </Form.Item>
+                    </>
+                )}
+            </Form.List>
+        </Form>
+    </Modal>
+}
+
+export default React.memo(AddModify)

+ 220 - 0
src/pages/weComTask/page/chatRecordManage/riskWord/index.tsx

@@ -0,0 +1,220 @@
+import { api_corpUser_allOfUser } from '@/API/global';
+import { useAjax } from '@/Hook/useAjax';
+import { deleteKeywordApi, enableKeywordApi, getKeywordListApi, GetKeywordListProps } from '@/pages/weComTask/API/chatRecordManage';
+import { useSize } from 'ahooks';
+import React, { useEffect, useRef, useState } from 'react';
+import style from '../../corpUserManage/index.less'
+import { App, Button, Card, Pagination, Select, Table } from 'antd';
+import SearchBox from '@/pages/weComTask/components/searchBox';
+import { PlusOutlined, SearchOutlined } from '@ant-design/icons';
+import { TableConfig } from './tableConfig';
+import AddModify from './AddModify';
+
+const RiskWord: React.FC = () => {
+
+    /*****************************************/
+    const { message } = App.useApp()
+    const ref = useRef<HTMLDivElement>(null)
+    const size = useSize(ref)
+    const userIdStr = sessionStorage.getItem('userId');
+    const [userId] = useState<number>(userIdStr ? Number(userIdStr) : undefined);
+    const [activeCorp, setActiveCorp] = useState<any>()
+
+    const [queryForm, setQueryForm] = useState<GetKeywordListProps>({
+        pageNum: 1,
+        pageSize: 20
+    })
+    const [visible, setVisible] = useState<boolean>(false)
+    const [initialValues, setInitialValues] = useState<any>({ corpUserDTOList: [{}], keywordJson: [''], replyJson: [[]] })
+
+    const corpUser_allOfUser = useAjax((params) => api_corpUser_allOfUser(params))
+    const getKeywordList = useAjax((params) => getKeywordListApi(params))
+    const deleteKeyword = useAjax((params) => deleteKeywordApi(params))
+    const enableKeyword = useAjax((params) => enableKeywordApi(params))
+    /*****************************************/
+
+    useEffect(() => {
+        if (userId) {
+            corpUser_allOfUser.run(userId)
+        }
+    }, [userId])
+
+    useEffect(() => {
+        getKeywordList.run({ ...queryForm })
+    }, [queryForm])
+
+    // 编辑
+    const edit = (data: any, isCopy?: boolean) => {
+        let newValue = JSON.parse(JSON.stringify(data))
+        if (newValue.replyJson) {
+            newValue.replyJson = newValue.replyJson.map((item: { msgType: any; msgContent: any; }) => {
+                let obj: { [x: string]: any } = { mediaType: item.msgType }
+                switch (item.msgType) {
+                    case 'text':
+                        obj["textContent"] = item.msgContent
+                        break
+                    case 'image':
+                        obj["imageUrl"] = item.msgContent
+                        break;
+                    case 'video':
+                        obj["videoUrl"] = item.msgContent
+                        break;
+                }
+                return [obj]
+            })
+        }
+        let params: any = { ...newValue, tagIds: newValue?.tagIds?.map((item: { toString: () => any; }) => item?.toString()) }
+        if (isCopy) {
+            delete params?.id
+        }
+        setInitialValues(params)
+        setVisible(true)
+    }
+
+    /** 删除 */
+    const del = (id: number) => {
+        deleteKeyword.run([id]).then(res => {
+            if (res?.data) {
+                message.success('删除成功')
+                getKeywordList.refresh()
+            }
+        })
+    }
+
+    const taskPause = (pause: boolean, ids: number[]) => {
+        enableKeyword.run({ enabled: pause, ids }).then(res => {
+            message.success(pause ? "启用成功" : '禁用成功')
+            getKeywordList.refresh()
+        })
+    }
+
+    return (
+        <div className={style.corpUserManage}>
+            <div className={style.corpUserManage_bottom} style={{ height: '100%' }}>
+                <Card className={style.corpUserList} styles={{ body: { padding: 0, display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' } }}>
+                    <SearchBox
+                        bodyPadding={`16px 16px 12px`}
+                        buttons={<>
+                            <Button type="primary" onClick={() => getKeywordList.refresh()} icon={<SearchOutlined />}>搜索</Button>
+                            <Button type="primary" onClick={() => {
+                                setVisible(true)
+                            }}><PlusOutlined />新增风险词</Button>
+                        </>}
+                    >
+                        <>
+                            <Select
+                                style={{ width: 180 }}
+                                placeholder="选择企业"
+                                value={queryForm?.corpId}
+                                showSearch
+                                maxTagCount={1}
+                                allowClear
+                                onChange={(e, option: any) => {
+                                    setQueryForm({ ...queryForm, corpId: e, pageNum: 1 })
+                                    setActiveCorp(option?.data)
+                                }}
+                                filterOption={(input: string, option?: { label: string; value: string }) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
+                                options={corpUser_allOfUser?.data?.data?.map((item: { t1: any }) => ({ label: item.t1?.corpName, value: item.t1?.corpId, data: item }))}
+                            />
+                            {activeCorp && <Select
+                                style={{ minWidth: 125 }}
+                                placeholder='企微号'
+                                allowClear
+                                autoClearSearchValue={false}
+                                onChange={(e) => setQueryForm({ ...queryForm, corpUserId: e, pageNum: 1 })}
+                                value={queryForm?.corpUserId}
+                                filterOption={(input, option) =>
+                                    ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                                }
+                                options={activeCorp?.t2?.map((item: any) => ({ label: item.name, value: item.corpUserId }))}
+                            />}
+
+                            <Select
+                                style={{ width: 150 }}
+                                placeholder="启用状态"
+                                value={queryForm?.enabled}
+                                showSearch
+                                allowClear
+                                onChange={(e) => {
+                                    setQueryForm({ ...queryForm, enabled: e, pageNum: 1 })
+                                }}
+                                filterOption={(input: string, option?: { label: string; value: string }) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
+                                options={[{ label: '启用', value: "true" }, { label: '禁用', value: "false" }]}
+                            />
+                            <Select
+                                style={{ minWidth: 125 }}
+                                placeholder='风险等级'
+                                allowClear
+                                autoClearSearchValue={false}
+                                onChange={(e) => { setQueryForm({ ...queryForm, riskLevel: e }) }}
+                                value={queryForm?.riskLevel}
+                                filterOption={(input, option) =>
+                                    ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                                }
+                                options={[
+                                    {
+                                        label: '低',
+                                        value: 1
+                                    },
+                                    {
+                                        label: '中',
+                                        value: 2
+                                    },
+                                    {
+                                        label: '高',
+                                        value: 3
+                                    }
+                                ]}
+                            />
+                        </>
+                    </SearchBox>
+                    <div className={style.corpUserList_table} ref={ref}>
+                        <Table
+                            style={{ marginBottom: 1 }}
+                            dataSource={getKeywordList?.data?.data?.records}
+                            loading={getKeywordList?.loading}
+                            columns={TableConfig(edit, del, taskPause)}
+                            scroll={{ x: "max-content", y: size?.height && ref.current ? size?.height - 32 : 300 }}
+                            rowKey={'id'}
+                            bordered
+                            size='small'
+                            pagination={false}
+                        />
+                    </div>
+                    <div className={style.corpUserList_footer}>
+                        <Pagination
+                            size="small"
+                            total={getKeywordList?.data?.data?.total || 0}
+                            showSizeChanger
+                            showQuickJumper
+                            pageSize={getKeywordList?.data?.data?.size || 20}
+                            current={getKeywordList?.data?.data?.current || 1}
+                            onChange={(page: number, pageSize: number) => {
+                                ref.current?.scrollTo({ top: 0 })
+                                setQueryForm({ ...queryForm, pageNum: page, pageSize })
+                            }}
+                            showTotal={(total: number) => <span style={{ fontSize: 12 }}>共 {total} 条</span>}
+                        />
+                    </div>
+                </Card>
+            </div>
+
+            {visible && <AddModify
+                corpList={corpUser_allOfUser?.data?.data || []}
+                initialValues={initialValues}
+                visible={visible}
+                onClose={() => {
+                    setVisible(false)
+                    setInitialValues(undefined)
+                }}
+                onChange={() => {
+                    setVisible(false)
+                    setInitialValues(undefined)
+                    getKeywordList.refresh()
+                }}
+            />}
+        </div>
+    );
+};
+
+export default RiskWord;

+ 129 - 0
src/pages/weComTask/page/chatRecordManage/riskWord/tableConfig.tsx

@@ -0,0 +1,129 @@
+import { Button, Popconfirm, Space, Tag, Badge } from "antd";
+import { ColumnsType } from "antd/es/table";
+
+export function TableConfig(edit: (row: any, isCopy?: boolean) => void, del: (value: any) => void, taskPause: (pause: boolean, taskId: number[]) => void): ColumnsType<any> {
+
+    let arr: ColumnsType<any> = [
+        {
+            title: '企业ID',
+            dataIndex: 'corpId',
+            key: 'corpId',
+            align: 'center',
+            width: 100,
+            ellipsis: true
+        },
+        {
+            title: '企微号',
+            dataIndex: 'corpUserVO',
+            key: 'corpUserVO',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+            render(value) {
+                return value?.map((item: { name: any; }) => item.name).toString()
+            },
+        },
+        {
+            title: '企微号ID',
+            dataIndex: 'corpUserId',
+            key: 'corpUserId',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+            render(value) {
+                return value?.toString()
+            },
+        },
+        {
+            title: '风险等级',
+            dataIndex: 'riskLevel',
+            key: 'riskLevel',
+            align: 'center',
+            width: 100,
+            render: (a: any, b: any) => {
+                return a ? ["",<Badge color="#ffccc7" text="低" />,<Badge color="#ff7875" text="中" /> , <Badge color="#cf1322" text="高" /> ][a] : "--"
+            }
+        },
+        {
+            title: '风险词',
+            dataIndex: 'keywordJson',
+            key: 'keywordJson',
+            align: 'center',
+            ellipsis: true,
+            render: (a) => {
+                return <Space size={[0, 0]} wrap>
+                    {
+                        a?.map((str: any, index: number) => {
+                            return <Tag key={index}>{str}</Tag>
+                        })
+                    }
+                </Space>
+            }
+        },
+
+        {
+            title: '启用状态',
+            dataIndex: 'enabled',
+            key: 'enabled',
+            align: 'center',
+            width: 90,
+            render: (a: any) => {
+                return a ? <Tag color="success">启用</Tag> : <Tag color="error">禁用</Tag>
+            }
+        },
+        {
+            title: '创建人',
+            dataIndex: 'sysUserRpcVO',
+            key: 'sysUserRpcVO',
+            align: 'center',
+            width: 140,
+            ellipsis: true,
+            render(a) {
+                return a?.nickName || '--'
+            }
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            align: 'center',
+            width: 140,
+            ellipsis: true
+        },
+        {
+            title: '更新时间',
+            dataIndex: 'updateTime',
+            key: 'updateTime',
+            align: 'center',
+            width: 140,
+            ellipsis: true
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            width: 150,
+            fixed: 'right',
+            render: (_, b) => {
+                return <Space>
+                    <Button type="link" style={{ padding: 0 }} onClick={() => { edit(b, true) }}>复制</Button>
+                    {b?.sysUserRpcVO?.userId == sessionStorage.getItem('userId') ? <>
+                        {b?.enabled === false ? <>
+                            <Button type="link" style={{ padding: 0 }} onClick={() => { edit(b) }}>编辑</Button>
+                            <Button type="link" style={{ padding: 0 }} onClick={() => { taskPause(true, [b.id]) }}>启用</Button>
+                            <Popconfirm
+                                title='确定删除?'
+                                onConfirm={() => { del(b?.id) }}
+                            >
+                                <Button type="link" style={{ padding: 0 }} danger>删除</Button>
+                            </Popconfirm>
+                        </> : b?.enabled === true ? <>
+                            <Button type="link" style={{ padding: 0 }} danger onClick={() => { taskPause(false, [b.id]) }}>禁用</Button>
+                        </> : null}
+                    </> : <>--</>}
+                </Space>
+            }
+        },
+    ]
+    return arr
+}