From e71b17b9cf037a36a0a320b2369cdb741ecc8b94 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 29 Apr 2022 16:21:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8E=E5=8D=97=E9=99=A2-=E6=99=BA=E6=85=A7?= =?UTF-8?q?=E5=AE=A2=E6=9C=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.UAT.ts | 11 + config/config.dev.ts | 11 + config/config.prod.ts | 11 + config/config.sim.ts | 11 + src/assets/img/cs_client.png | Bin 0 -> 6657 bytes src/assets/img/cs_user.png | Bin 0 -> 11192 bytes src/customerServiceHelpers/constants/http.js | 25 + src/customerServiceHelpers/request.js | 110 ++++ .../conversation/components/Chat/Chat.jsx | 53 ++ .../components/Chat/style.module.css | 9 + .../components/ChatHeader/ChatHeader.jsx | 53 ++ .../components/ChatHeader/style.module.css | 42 ++ .../components/ChatInput/ChatInput.jsx | 565 ++++++++++++++++++ .../components/ChatInput/style.module.css | 54 ++ .../ChatRecordList/ChatRecordList.jsx | 31 + .../ChatRecordList/style.module.css | 29 + .../components/ChatToolsBar/ChatToolBar.jsx | 34 ++ .../components/ChatToolsBar/style.module.css | 8 + .../components/EmojiPopover/EmojiPopover.jsx | 141 +++++ .../components/EmojiPopover/style.module.css | 55 ++ .../components/FilePopover/FilePopover.jsx | 69 +++ .../components/FilePopover/style.module.css | 16 + .../components/ImgPopover/ImgPopover.jsx | 86 +++ .../components/ImgPopover/style.module.css | 16 + .../components/ImgPreview/ImgPreview.jsx | 56 ++ .../components/ImgPreview/style.module.css | 6 + .../components/MsgBubble/MsgBubble.jsx | 100 ++++ .../components/MsgBubble/style.module.css | 83 +++ .../components/MsgItem/MsgItem.jsx | 44 ++ .../components/MsgItem/style.module.css | 39 ++ .../components/RatePopover/RatePopover.jsx | 39 ++ .../components/RatePopover/style.module.css | 28 + .../ScrollWrapper/ScrollWrapper.jsx | 143 +++++ .../components/ScrollWrapper/style.module.css | 49 ++ .../conversation/image/emoji_black.png | Bin 0 -> 5436 bytes .../conversation/image/emoji_light.png | Bin 0 -> 5874 bytes .../support/conversation/image/file_black.png | Bin 0 -> 1929 bytes .../support/conversation/image/file_light.png | Bin 0 -> 2083 bytes .../support/conversation/image/pic_black.png | Bin 0 -> 2532 bytes .../support/conversation/image/pic_light.png | Bin 0 -> 2675 bytes .../conversation/image/server-avatar.jpg | Bin 0 -> 11192 bytes .../support/conversation/image/uploadFile.png | Bin 0 -> 8705 bytes .../support/conversation/index.jsx | 372 ++++++++++++ .../support/conversation/style/common.css | 21 + .../support/conversation/utils/toClass.js | 3 + .../support/conversation/utils/utils.jsx | 537 +++++++++++++++++ src/services/customerservice.js | 60 ++ 47 files changed, 3020 insertions(+) create mode 100644 src/assets/img/cs_client.png create mode 100644 src/assets/img/cs_user.png create mode 100644 src/customerServiceHelpers/constants/http.js create mode 100644 src/customerServiceHelpers/request.js create mode 100644 src/pages/customerservice/support/conversation/components/Chat/Chat.jsx create mode 100644 src/pages/customerservice/support/conversation/components/Chat/style.module.css create mode 100644 src/pages/customerservice/support/conversation/components/ChatHeader/ChatHeader.jsx create mode 100644 src/pages/customerservice/support/conversation/components/ChatHeader/style.module.css create mode 100644 src/pages/customerservice/support/conversation/components/ChatInput/ChatInput.jsx create mode 100644 src/pages/customerservice/support/conversation/components/ChatInput/style.module.css create mode 100644 src/pages/customerservice/support/conversation/components/ChatRecordList/ChatRecordList.jsx create mode 100644 src/pages/customerservice/support/conversation/components/ChatRecordList/style.module.css create mode 100644 src/pages/customerservice/support/conversation/components/ChatToolsBar/ChatToolBar.jsx create mode 100644 src/pages/customerservice/support/conversation/components/ChatToolsBar/style.module.css create mode 100644 src/pages/customerservice/support/conversation/components/EmojiPopover/EmojiPopover.jsx create mode 100644 src/pages/customerservice/support/conversation/components/EmojiPopover/style.module.css create mode 100644 src/pages/customerservice/support/conversation/components/FilePopover/FilePopover.jsx create mode 100644 src/pages/customerservice/support/conversation/components/FilePopover/style.module.css create mode 100644 src/pages/customerservice/support/conversation/components/ImgPopover/ImgPopover.jsx create mode 100644 src/pages/customerservice/support/conversation/components/ImgPopover/style.module.css create mode 100644 src/pages/customerservice/support/conversation/components/ImgPreview/ImgPreview.jsx create mode 100644 src/pages/customerservice/support/conversation/components/ImgPreview/style.module.css create mode 100644 src/pages/customerservice/support/conversation/components/MsgBubble/MsgBubble.jsx create mode 100644 src/pages/customerservice/support/conversation/components/MsgBubble/style.module.css create mode 100644 src/pages/customerservice/support/conversation/components/MsgItem/MsgItem.jsx create mode 100644 src/pages/customerservice/support/conversation/components/MsgItem/style.module.css create mode 100644 src/pages/customerservice/support/conversation/components/RatePopover/RatePopover.jsx create mode 100644 src/pages/customerservice/support/conversation/components/RatePopover/style.module.css create mode 100644 src/pages/customerservice/support/conversation/components/ScrollWrapper/ScrollWrapper.jsx create mode 100644 src/pages/customerservice/support/conversation/components/ScrollWrapper/style.module.css create mode 100644 src/pages/customerservice/support/conversation/image/emoji_black.png create mode 100644 src/pages/customerservice/support/conversation/image/emoji_light.png create mode 100644 src/pages/customerservice/support/conversation/image/file_black.png create mode 100644 src/pages/customerservice/support/conversation/image/file_light.png create mode 100644 src/pages/customerservice/support/conversation/image/pic_black.png create mode 100644 src/pages/customerservice/support/conversation/image/pic_light.png create mode 100644 src/pages/customerservice/support/conversation/image/server-avatar.jpg create mode 100644 src/pages/customerservice/support/conversation/image/uploadFile.png create mode 100644 src/pages/customerservice/support/conversation/index.jsx create mode 100644 src/pages/customerservice/support/conversation/style/common.css create mode 100644 src/pages/customerservice/support/conversation/utils/toClass.js create mode 100644 src/pages/customerservice/support/conversation/utils/utils.jsx create mode 100644 src/services/customerservice.js diff --git a/config/config.UAT.ts b/config/config.UAT.ts index 22b07c1..de9d1a0 100644 --- a/config/config.UAT.ts +++ b/config/config.UAT.ts @@ -19,5 +19,16 @@ export default defineConfig({ //询价查看报价跳转地址 REACT_APP_XUNJIA_REDIRECT: 'http://10.0.204.215:8280/provider_uat', + + //智慧客服ws地址 + REACT_APP_CUSTOMERSERVICE_WS_REDIRECT: 'ws://10.242.31.158:18022/api/api/biz-customer-service', + + //智慧客服文档中心查看图片地址 + REACT_APP_CUSTOMERSERVICE_DOC_REDIRECT: 'http://cos.gz-tst.cos.tg.unicom.local/349553515466:mall/', + + //智慧客服加密公钥私钥ciphercode + REACT_APP_CUSTOMERSERVICE_PUBLICKEY : '0428D625CEEB71CE823BD7D78DFEE7B122F2DA5C4D21E32253AD684D0FE21810394A799639C0CDFBFEB535A1DFD6A366A637E582CE0B1466A5FE7858841135DE6B', + REACT_APP_CUSTOMERSERVICE_PRIVATEKEY : '4F7144028D4DCF88FA50F0E2B3FFDDCF63BBE17D1700537DCE037687D3AA3DA7', + REACT_APP_CUSTOMERSERVICE_CIPHERCODE : 1, }, }); diff --git a/config/config.dev.ts b/config/config.dev.ts index 971d818..574135e 100644 --- a/config/config.dev.ts +++ b/config/config.dev.ts @@ -19,5 +19,16 @@ export default defineConfig({ //询价查看报价跳转地址 REACT_APP_XUNJIA_REDIRECT: 'http://10.0.204.215:8080/provider_dev', + + //智慧客服ws地址 + REACT_APP_CUSTOMERSERVICE_WS_REDIRECT: 'ws://10.242.37.148:18022/api/api/biz-customer-service', + + //智慧客服文档中心查看图片地址 + REACT_APP_CUSTOMERSERVICE_DOC_REDIRECT: 'http://cos.gz-tst.cos.tg.unicom.local/349553515466:mall/', + + //智慧客服加密公钥私钥ciphercode + REACT_APP_CUSTOMERSERVICE_PUBLICKEY : '0428D625CEEB71CE823BD7D78DFEE7B122F2DA5C4D21E32253AD684D0FE21810394A799639C0CDFBFEB535A1DFD6A366A637E582CE0B1466A5FE7858841135DE6B', + REACT_APP_CUSTOMERSERVICE_PRIVATEKEY : '4F7144028D4DCF88FA50F0E2B3FFDDCF63BBE17D1700537DCE037687D3AA3DA7', + REACT_APP_CUSTOMERSERVICE_CIPHERCODE : 1, }, }); diff --git a/config/config.prod.ts b/config/config.prod.ts index 99e7610..c81cdac 100644 --- a/config/config.prod.ts +++ b/config/config.prod.ts @@ -19,5 +19,16 @@ export default defineConfig({ //询价查看报价跳转地址 REACT_APP_XUNJIA_REDIRECT: 'https://60.10.26.178/provider', + + //智慧客服ws地址 + REACT_APP_CUSTOMERSERVICE_WS_REDIRECT: 'ws://uscm.unicom.local:18022/api/api/biz-customer-service', + + //智慧客服文档中心查看图片地址 + REACT_APP_CUSTOMERSERVICE_DOC_REDIRECT: 'http://cos.xx-pbc.cos.tg.unicom.local/349553515466:mall/', + + //智慧客服加密公钥私钥ciphercode + REACT_APP_CUSTOMERSERVICE_PUBLICKEY : '0428D625CEEB71CE823BD7D78DFEE7B122F2DA5C4D21E32253AD684D0FE21810394A799639C0CDFBFEB535A1DFD6A366A637E582CE0B1466A5FE7858841135DE6B', + REACT_APP_CUSTOMERSERVICE_PRIVATEKEY : '4F7144028D4DCF88FA50F0E2B3FFDDCF63BBE17D1700537DCE037687D3AA3DA7', + REACT_APP_CUSTOMERSERVICE_CIPHERCODE : 1, }, }); diff --git a/config/config.sim.ts b/config/config.sim.ts index bce3024..9117142 100644 --- a/config/config.sim.ts +++ b/config/config.sim.ts @@ -19,5 +19,16 @@ export default defineConfig({ //询价查看报价跳转地址 REACT_APP_XUNJIA_REDIRECT: 'http://10.0.204.215:8080/provider', + + //智慧客服ws地址 + REACT_APP_CUSTOMERSERVICE_WS_REDIRECT: 'ws://10.242.31.54:18022/api/api/biz-customer-service', + + //智慧客服文档中心查看图片地址 + REACT_APP_CUSTOMERSERVICE_DOC_REDIRECT: 'http://cos.xx-pbc.cos.tg.unicom.local/349553515466:mall/', + + //智慧客服加密公钥私钥ciphercode + REACT_APP_CUSTOMERSERVICE_PUBLICKEY : '0428D625CEEB71CE823BD7D78DFEE7B122F2DA5C4D21E32253AD684D0FE21810394A799639C0CDFBFEB535A1DFD6A366A637E582CE0B1466A5FE7858841135DE6B', + REACT_APP_CUSTOMERSERVICE_PRIVATEKEY : '4F7144028D4DCF88FA50F0E2B3FFDDCF63BBE17D1700537DCE037687D3AA3DA7', + REACT_APP_CUSTOMERSERVICE_CIPHERCODE : 1, }, }); diff --git a/src/assets/img/cs_client.png b/src/assets/img/cs_client.png new file mode 100644 index 0000000000000000000000000000000000000000..97cb5d7b87f2e7a2d32be41dcae04d4d6c059222 GIT binary patch literal 6657 zcmV+c8vf;pP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 literal 0 HcmV?d00001 diff --git a/src/customerServiceHelpers/constants/http.js b/src/customerServiceHelpers/constants/http.js new file mode 100644 index 0000000..bfff3b2 --- /dev/null +++ b/src/customerServiceHelpers/constants/http.js @@ -0,0 +1,25 @@ +// HTTP状态码映射(可补充) +export const HTTP_STATUS_CODE_MAP = { + 200: '服务请求成功!', + 201: '新建或修改数据成功!', + 202: '请求已进入后台排队中!', + 204: '删除数据成功!', + 400: '发出的请求有错误,未能执行相关操作!', + 401: '无权限访问,请重新登录!', + 403: '已得到授权,但访问被禁止!', + 404: '请求失败,未找到相关资源!', + 406: '请求格式不可得!', + 410: '请求的资源已被永久删除!', + 422: '请求过程中出现验证错误!', + 500: '服务器发生错误!', + 502: '网关错误!', + 503: '服务不可用,服务器暂时过载或正在维护!', + 504: '网关超时!', + }; + + // 携带着请求体的请求方法 + export const WITH_REQUEST_BODY_METHODS = ['post', 'put', 'patch']; + + // 请求后端接口失败时对应的几种code值(与后端协定) + export const RESPONSE_FAILED_CODES = [400, 500]; + \ No newline at end of file diff --git a/src/customerServiceHelpers/request.js b/src/customerServiceHelpers/request.js new file mode 100644 index 0000000..cf9cba8 --- /dev/null +++ b/src/customerServiceHelpers/request.js @@ -0,0 +1,110 @@ +import axios from 'axios'; +import { WITH_REQUEST_BODY_METHODS } from './constants/http'; + + +function paddingBaseURL(config) { + return Object.assign(config); +} + +/** + * 封装通用的axios公共方法 (需要结合业务逻辑来进一步完善) + * @param {Object} config - 非通用性(自定义)的一些axios请求配置 + * @returns {Promise} + */ +const $axios = function (config) { + + let defaultConfig = paddingBaseURL(config); + + if (WITH_REQUEST_BODY_METHODS.includes(config.method)) { + if (!config.data && config.params) { + defaultConfig.data = config.params; + defaultConfig.params = undefined; + } + } + + return new Promise((resolve, reject) => { + axios(defaultConfig) + .then(result => { + const { data } = result; + resolve(data); + }) + .catch(err => { + reject(err); + }); + }); +}; + +export function $get(url, options) { + return $axios({ ...options, url, method: 'get' }); +} + +export function $post(url, options) { + return $axios({ ...options, url, method: 'post' }); +} + +export function $put(url, options) { + return $axios({ ...options, url, method: 'put' }); +} + +export function $patch(url, options) { + return $axios({ ...options, url, method: 'patch' }); +} + +export function $delete(url, options) { + return $axios({ ...options, url, method: 'delete' }); +} + +export function $head(url, options) { + return $axios({ ...options, url, method: 'head' }); +} + +export function $options(url, options) { + return $axios({ ...options, url, method: 'options' }); +} + + +/** + * 下载专用请求 + * @param {downloadUrl} 接口路径 params:接口参数 + * @returns {Promise} + */ +export function $download(downloadUrl, params) { + return new Promise((resolve, reject) => { + axios( + { + method: "post", + url: downloadUrl, + data: params, + responseType: "blob", + } + ).then(result => { + resolve(result); + }).catch(err => { + reject(err); + }); + }); +} +/** + * 上传附件 + * @param {uploadUrl} 接口路径 params:接口参数 + * @params formData封装的数据 + * @returns {Promise} + */ + export function $upload(uploadUrl, params) { + return new Promise((resolve, reject) => { + fetch(uploadUrl, { + method: 'POST', + body: params.params, + headers:{ + 'Authorization':sessionStorage.getItem('token')?'Bearer '+sessionStorage.getItem('token'):null + } + }) + .then(response => { + return response.json() + }).then(json => { + resolve(json) + }).catch(error => { + reject(error) + }) + }) +} \ No newline at end of file diff --git a/src/pages/customerservice/support/conversation/components/Chat/Chat.jsx b/src/pages/customerservice/support/conversation/components/Chat/Chat.jsx new file mode 100644 index 0000000..4ccf1a6 --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/Chat/Chat.jsx @@ -0,0 +1,53 @@ +import React from 'react' +import PropTypes from 'prop-types' +import ChatHeader from '../ChatHeader/ChatHeader' +import ChatInput from '../ChatInput/ChatInput' +import ChatRecordList from '../ChatRecordList/ChatRecordList' +import ScrollWrapper from '../ScrollWrapper/ScrollWrapper' +import styles from './style.module.css' + +const WrappedChatRecordList = ScrollWrapper(ChatRecordList) + +const Chat =((props) => { + + const {style, contact, closeWin, hideWin, chatList, listHeight, textHeight, onSend, onImage, changeHeight, setPostion } = props + + return ( +
+ + + onSend(msgData)} + onImage={onImage} + changeHeight={changeHeight} + setPostion={setPostion} + /> +
+ ) +}) +Chat.propTypes = { + onSend: PropTypes.func.isRequired, + me: PropTypes.object.isRequired, + contact: PropTypes.object, + style: PropTypes.object.isRequired, + chatList:PropTypes.array, + onImage: PropTypes.func, + changeHeight: PropTypes.func.isRequired, + setPostion: PropTypes.func.isRequired, + closeWin: PropTypes.func.isRequired, + hideWin: PropTypes.func.isRequired, + isModalVisible:PropTypes.bool, + chatUI:PropTypes.any, + listHeight: PropTypes.number, + textHeight:PropTypes.number, + supplierNo:PropTypes.any, +} + +export default Chat; \ No newline at end of file diff --git a/src/pages/customerservice/support/conversation/components/Chat/style.module.css b/src/pages/customerservice/support/conversation/components/Chat/style.module.css new file mode 100644 index 0000000..ddd2b3c --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/Chat/style.module.css @@ -0,0 +1,9 @@ +.customer_service_content { + background-color: rgb(245,245,245); + display: flex; + flex-direction: column; + overflow: hidden; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + border: 1px solid rgb(245,245,245); +} \ No newline at end of file diff --git a/src/pages/customerservice/support/conversation/components/ChatHeader/ChatHeader.jsx b/src/pages/customerservice/support/conversation/components/ChatHeader/ChatHeader.jsx new file mode 100644 index 0000000..217a695 --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/ChatHeader/ChatHeader.jsx @@ -0,0 +1,53 @@ +import React from 'react' +import style from './style.module.css' +import PropTypes from 'prop-types'; +import { Button, Tooltip, Popconfirm } from 'antd'; +import { CloseOutlined, MinusOutlined } from '@ant-design/icons'; +import { mouseOverHandle, mouseMoveHandle, mouseDrag,} from '../../utils/utils' + +export default function ChatHeader(props) { + return ( +
{ + if(e.target.id=='chatHeader'){ + mouseOverHandle(e,props.chatUI) + } + }} + onMouseMove={(e)=>{ + if(e.target.id=='chatHeader'){ + mouseMoveHandle(e,props.chatUI) + } + }} + onMouseDown={(e)=>{ + if(e.target.id=='chatHeader'){ + mouseDrag(e,150,props.chatUI,null,null) + } + }} + > + +
+ 中国联通供应链智慧客服 + 客户至上 产品至上 +
+
+ +
+
+ ) +} + +ChatHeader.propTypes = { + data: PropTypes.object, + closeWin: PropTypes.func, + hideWin: PropTypes.func, + chatUI: PropTypes.any, +} diff --git a/src/pages/customerservice/support/conversation/components/ChatHeader/style.module.css b/src/pages/customerservice/support/conversation/components/ChatHeader/style.module.css new file mode 100644 index 0000000..fba1ccb --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/ChatHeader/style.module.css @@ -0,0 +1,42 @@ +.customer_service_content { + border-top-left-radius: 10px; + border-top-right-radius: 10px; + display: flex; + align-items: center; + padding: 10px; + -moz-user-select: none; + -khtml-user-select: none; + user-select: none; + background-color: rgb(235, 34, 32); + color: white; + -border-bottom: 1.5px solid rgb(226,226,226); +} +.customer_service_content .avatar { + width: 40px; + height: 40px; + background-image: url('../../image/server-avatar.jpg'); + background-size: cover; + -background-color: aqua; + + border-radius: 50%; + overflow: hidden; +} +.customer_service_content .desc_area { + display: flex; + flex-direction: column; + padding-left: 10px; +} +.customer_service_content .btn{ + background-color: rgb(235, 34, 32); + position: absolute; + right: 5px; + top:10px +} +.customer_service_content .name { + font-size: 14px; + -color: #333; +} +.customer_service_content .sologan { + font-size: 12px; + -color: #999; +} diff --git a/src/pages/customerservice/support/conversation/components/ChatInput/ChatInput.jsx b/src/pages/customerservice/support/conversation/components/ChatInput/ChatInput.jsx new file mode 100644 index 0000000..b8ae6e3 --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/ChatInput/ChatInput.jsx @@ -0,0 +1,565 @@ +import React, { + useState, + useRef, + useEffect, +} from 'react' +import PropTypes from 'prop-types'; +import style from './style.module.css' +//import {doEncrypt} from '@/utils/utils' +import ChatToolBar from '../ChatToolsBar/ChatToolBar' +import { mouseOverHandle, mouseMoveHandle, mouseDrag, utf16toEntities, closeHTML, imageHandleUtil, doEncrypt} from '../../utils/utils' +import { customerserviceAPI as API} from '@/services/customerservice'; +import { Spin, Modal, message } from 'antd'; +import { useLocation } from 'react-router-dom' +//import {docUp} from '@config/host' +import lodash from 'lodash'; +import moment from "moment"; + +const ChatInput = ((props) => { + const { + me, + onSend = () => {}, + height, + showPic, + changeHeight, + setPostion, + isModalVisible, + supplierNo, + conversationId, + isSend, + sendMsg, + cancelSend, + } = props + + const innerRef = useRef(); + const [msg, setMsg] = useState([]) + const [isShift, setIsShift] = useState(false) + const [isCtrl, seIsCtrl] = useState(false) + const [isAlt, setIsAlt] = useState(false) + const [isAllowSend, setIsAllowSend] = useState(false) + const [cachedRange, setCachedRange] = useState(null) + const [loading, setLoading] = useState(false) + const [uploadFile, setUploadFile] = useState({}) + const [isSendFile, setIsSendFile] = useState(false) //发送文件之前确认 + + const location = useLocation() + + //imgInputSize:输入框图片尺寸 + //imgMsgSize:聊天界面图片尺寸 + //imgPreviewSize:预览图片尺寸 + const imgInputSize = 100 + const imgMsgSize = 150 + const imgPreviewSize = 400 + + + //递归给图片resize + const nextSrc = (srcArr, sig, msgArr)=>{ + var img=new Image(); + img.src=srcArr[sig].src; + + img.onload=function(){ + //递归处理消息数组 + for(let i = 0; i0 + setMsg(msgArr) + setIsAllowSend(isAllowSend) + } + } + + } + + //输入框内容重新组合,图片单独发送形成聊天记录 + const reformMsg = ()=>{ + let msgArr= [] + var div = innerRef.current + var imgs = div.getElementsByTagName('img') + var allElements = div.getElementsByTagName('*') + var judge_div = document.getElementById('judgeNull') + + //输入框内容去除样式 + if(allElements && allElements.length>0){ + for(var i=0; i0){ + for(var j=0; j]*src=['"]([^'"]+)[^>]+?>/) + if(img){ + var findImg = currentVal.indexOf('0 + setMsg(msgArr) + setIsAllowSend(isAllowSend) + } + } + const onChange = () => { + reformMsg() + } + // 图片blob转file + const getFile = (blob)=>{ + var timestamp=new Date().getTime() + var type = blob.type.split('/')[1] + var suffix = suffix == ''?'jpeg': type + let file = new File([blob], timestamp+"."+suffix,{ type: blob.type }) + const formData = new FormData(); + formData.append('multipartFiles', file); + return formData + } + //文档中心:图片上传 + const imgUpload = (blob, msgData)=>{ + API.fileUpload({ + multipartFiles: getFile(blob), + objectType:'img_public' + }).then(res => { + if (res && res.data && res.data.length>0 && res.data[0].sysStorageVO && res.data[0].sysStorageVO.filePath!=undefined && res.data[0].sysStorageVO.filePath!=null&& res.data[0].sysStorageVO.filePath!='') { + msgData.message.content.success = res.success + let vo = res.data[0].sysStorageVO + //文档中心返回的filePath重写content + msgData.message.content.content = `${REACT_APP_CUSTOMERSERVICE_DOC_REDIRECT}`+vo.filePath + msgData.message.fileId = vo.fileId + sendToCs(msgData) + }else{ + console.log(res && res.message) + } + }) + } + //文档中心:文件上传(除图片以外) + const fileUpload = (file, msgData)=>{ + const formData = new FormData(); + formData.append('multipartFiles', file); + API.fileUpload({ + multipartFiles: formData, + objectType:'file' + }).then(res => { + if (res && res.data && res.data.length>0 && res.data[0].sysStorageVO && res.data[0].sysStorageVO.filePath!=undefined && res.data[0].sysStorageVO.filePath!=null&& res.data[0].sysStorageVO.filePath!='') { + let vo = res.data[0].sysStorageVO + //文档中心返回的filePath重写content + msgData.message.content.content = `${REACT_APP_CUSTOMERSERVICE_DOC_REDIRECT}`+vo.filePath + vo.filePath + msgData.message.content.fileId = vo.fileId + msgData.message.content.success = res.success + sendToCs(msgData) + + }else{ + console.log(res && res.message) + } + }) + } + + //发送消息给客服-websocket + const sendToCs = (msg)=>{ + let params = { + "clientNo": msg.user.id, + "contentType": msg.message.type=="text"?1:msg.message.type=="image"?2:msg.message.type=="file"?3:'', + "custType": location.pathname.indexOf('/product-detail')!=-1? 2 : 1, + "message": msg.message.type=="image" || msg.message.type=="file" ?JSON.stringify(msg.message.content):msg.message.content, + //"questionType": location.pathname.indexOf('/home')!=-1? 3 : 1, + "questionType": 2, + "supplierNo": supplierNo + } + API.sendToCs(params).then(res => { + if (res && !res.data) { + console.log(res && res.message) + } + + }) + } + + //发送消息 + const sendHandle = () => { + if (!isAllowSend && !isSend && !sendMsg) { + return + } + let msgList = [] + let newmsg = sendMsg && sendMsg.length>0 ?sendMsg : lodash.cloneDeep(msg); + newmsg.map((item)=>{ + const randomNum = Math.floor(Math.random() * 1000) + //const date = dayjs().unix() + const date = new Date() + const encryption = doEncrypt(`${date}${randomNum}`) //有无更快的加密方法,可以加密图片?这个加密方法加密图片太慢! + //console.log('加密生成完毕') + let msgData = null + if(item.type=='text'){ + msgData = { + _id: encryption, + date: date, + user: me, + message: { + type: 'text', + content: item.content, + }, + } + sendToCs(msgData) + msgList.push(msgData) + }else if(item.type=='image'){ + let content = { + content: item.content, + realWidth: item.realWidth, + realHeight:item.realHeight, + msgWidth: item.msgWidth, + msgHeight:item.msgHeight, + previewidth: item.previewidth, + previewHeight:item.previewHeight, + success:false, + fileId:'', + } + msgData = { + _id: encryption, + date: date, + user: me, + message: { + type: 'image', + content: content + }, + } + msgList.push(msgData) + + //调用文档中心存储图片 + //消息图片src、消息(文档中心返回后修改消息中的content)、上传消息图片、从文档中心下载 + imageHandleUtil.uploadImage(msgData, imgUpload) + } + }) + onSend(msgList) + resetText() + innerRef.current.focus() + } + //发送文件 + const sendFileHandle = (msg) => { + let msgList = [] + + const randomNum = Math.floor(Math.random() * 1000) + //const date = dayjs().unix() + const date = new Date() + const encryption = doEncrypt(`${date}${randomNum}`) //有无更快的加密方法,可以加密图片?这个加密方法加密图片太慢! + //console.log('加密生成完毕') + const msgData = { + _id: encryption, + date: date, + user: me, + message: { + type: msg.type, + content: {content:msg.content, success:false, fileId:'', name:msg.name, size:msg.size}, + }, + } + msgList.push(msgData) + onSend(msgList) + fileUpload(msg.file,msgData) + } +//设置cachedRange +const resetCachedRange = (range) =>{ + setCachedRange(range) +} +//设置loading +const resetLoadling = ()=>{ + setLoading(false) + } +const resetText = () => { + setMsg([]) + innerRef.current.innerText='' + setIsAllowSend(false) +} + +const keyDownHandle = (e) => { + if (e.keyCode === 16) { + setIsShift(true) + } + if (e.keyCode === 17) { + seIsCtrl(true) + } + if(e.keyCode === 18) { + setIsAlt(true) + } + if ((e.nativeEvent.keyCode === 13 || e.keyCode === 13) && ((!isCtrl && !isShift && !isAlt)||(!e.ctrlKey && !e.shiftKey &&!e.altKey))) { + e.preventDefault() + sendHandle() + } +} + +const keyUpHandle = (e) => { + window.getSelection().rangeCount>0 && setCachedRange(window.getSelection().getRangeAt(0)) + + if(e.nativeEvent.keyCode === 18 || e.keyCode === 18){ + setIsAlt(false) + } + if (e.nativeEvent.keyCode === 16 || e.keyCode === 16){ + setIsShift(false) + } + if (e.nativeEvent.keyCode === 17 || e.keyCode === 17){ + seIsCtrl(false) + } +} + +const mouseUpHandle = () => { + window.getSelection().rangeCount>0 && setCachedRange(window.getSelection().getRangeAt(0)) +} + +const mouseDownHandle= ()=>{ + +} +//取特定字符串之间的字符串 +const str_substr = (start, end, str)=> { + let temp = str.split(start, 2); + let content = temp[1].split(end, 2); + return content[0]; +} +//粘贴操作 + const pasteHandle = (e) =>{ + setLoading(true) + var clipboardData = e.clipboardData; + var txt = clipboardData.getData('text/html') || clipboardData.getData('text/plain') + if(txt){ + var imgReg = /|\/>)/gi; + //匹配src属性 + //var srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i; + var arr = txt.match(imgReg); + if((arr && arr.length>0) || txt.indexOf('')!=-1){ + let newtxt = str_substr('','',txt) + txt = newtxt + } + //去除 style + let styleReg = "style=\"(.*?)\"" + let styleArr = txt.match(styleReg) + if(styleArr && styleArr.length>0) txt = txt.replace(styleArr[0],"") + + var el = document.createElement('span') + el.innerHTML=txt + var allImgs = el.getElementsByTagName('img') + + if(allImgs && allImgs.length>0){ + window.getSelection().deleteFromDocument() //如果有选中内容,粘贴时替换 + let range = cachedRange + if(!cachedRange && innerRef.current){ + innerRef.current.focus() + range = window.getSelection().getRangeAt(0) + } + if(range) { + let params={srcArr:allImgs, sig:0, el, range, reformMsg, showPic, resetCachedRange, resetLoadling, imgInputSize, imgMsgSize, imgPreviewSize} + imageHandleUtil.pasteImages(params) + } + } + } + }else if(clipboardData){//如果不是Files就直接粘贴,Files要先处理后再粘贴 + e.preventDefault() + var items = e.clipboardData.items; + let item=null; + if(items && items.length) { + for(var i=0; i { + let range = cachedRange + if(!cachedRange){ + innerRef.current.focus() + range = window.getSelection().getRangeAt(0) + } + if (range && innerRef.current) { + var obj = document.createElement('span') + obj.innerHTML=emoji + range.insertNode(obj) + setCachedRange(range) + range.collapse(false) + window.getSelection().removeAllRanges(); + window.getSelection().addRange(range); + reformMsg() + } + } + //插入图片 + const onImageHandle = (image)=>{ + setLoading(true) + window.getSelection().deleteFromDocument() //如果有选中内容,粘贴时替换 + let range = cachedRange + if(!cachedRange && innerRef.current){ + innerRef.current.focus() + range = window.getSelection().getRangeAt(0) + } + //image:文档中心返回的图片路径 + //range:插入图片位置 + //reformMsg:插入后,处理input里的内容,为信息发送做准备(去除文本样式、每张图片作为单条消息发送) + //showPic:点击图片,预览事件 + //resetCachedRange:cachedRange赋值 + //resetLoadling:loading=false + if(range){ + let params={image, range, reformMsg, showPic, resetCachedRange, resetLoadling:null, imgInputSize, imgMsgSize, imgPreviewSize} + imageHandleUtil.insertImageHandle(params) + } + } + //文件发送 + const sendFile = ()=>{ + let file = uploadFile + var reader = new FileReader(); + reader.readAsDataURL(file); + reader.onloadend = function (e) { + let isLt50M = file.size / 1024 + sendFileHandle({type:'file',content:e.target.result, name:file.name,size:isLt50M>=1024?(isLt50M/1024).toFixed(1)+"M":(isLt50M).toFixed(1)+"K", file:file}) + }; + } + //文件发送前确认 + const onFileHandle = (file)=>{ + setUploadFile(file) + setIsSendFile(true) + } + //评价 + const onRateHandle = (score)=>{ + // const conversationId = '1506204072738394114' + if(conversationId==''){ + message.warn('您还没有发起咨询,无需评价') + }else{ + API.estimate({id: conversationId, remarkScore: score*2, remarkTime: moment(new Date()).format('yyyy-MM-DD HH:mm:ss')}).then(res=>{ + if (res && res.success) { + message.success('感谢您的评价!') + }else{ + message.warn(res && res.message) + } + }) + } + } + useEffect(()=>{ + if(isModalVisible){ + resetText() + innerRef.current.focus() + } + },[isModalVisible]) + + useEffect(()=>{ + if(isSend){ + sendHandle() + } + },[isSend]) + return ( + +
{ + if(e.target.id=='chatInput'){ + mouseOverHandle(e,props.chatUI) + } + }} + onMouseMove={(e)=>{ + if(e.target.id=='chatInput'){ + mouseMoveHandle(e,props.chatUI) + } + }} + onMouseDown={(e)=>{ + if(e.target.id=='chatInput'){ + mouseDrag(e,150,props.chatUI,changeHeight,setPostion) + } + }} + > + +
{e.preventDefault()}} + className={style.input_area} + contentEditable + onKeyUp={keyUpHandle} + onKeyDown={keyDownHandle} + onInput={onChange} + onMouseUp={mouseUpHandle} + onMouseDown={mouseDownHandle} + onPaste={pasteHandle} + onMouseMove={(e)=>{ + //防止cursor显示异常 + mouseMoveHandle(e,props.chatUI) + }} + ref={innerRef} + id='chatInputArea' + > +
+ +
+ +
+
+
+ {isSendFile && + {setIsSendFile(false);sendFile()}} onCancel={()=>setIsSendFile(false)} > +

您确定要上传文件么?【确定】后,文件将无法撤回!

+
+ } +
+ ) +}) +ChatInput.propTypes = { + me:PropTypes.object.isRequired, + onSend:PropTypes.func.isRequired, + height:PropTypes.number.isRequired, + showPic:PropTypes.func.isRequired, + changeHeight:PropTypes.func.isRequired, + setPostion:PropTypes.func.isRequired, + isModalVisible:PropTypes.bool, + style:PropTypes.any, + chatUI:PropTypes.any, + supplierNo:PropTypes.any, +} +export default ChatInput; \ No newline at end of file diff --git a/src/pages/customerservice/support/conversation/components/ChatInput/style.module.css b/src/pages/customerservice/support/conversation/components/ChatInput/style.module.css new file mode 100644 index 0000000..2d3e84c --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/ChatInput/style.module.css @@ -0,0 +1,54 @@ +.customer_service_content { + background-color: white; + box-sizing: border-box; + width: 100%; + height: 100%; + min-height: 150px; + display: flex; + flex-direction: column; + /* box-shadow: 0px -5px 10px -5px rgba(0, 0, 0, 0.2); */ +} +.customer_service_content .input_area { + height: 100%; + overflow-y:auto; + border: none; + outline: none; + padding: 10px; + margin-right: 10px; + font-size: 13px; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + letter-spacing: 2px; +} +.customer_service_content .input_area::placeholder { + color: #999; +} +.customer_service_content .but_area { + align-self: flex-end; + margin-bottom: 10px; + margin-right: 20px; +} +.customer_service_content .but:hover{ + cursor: pointer; +} +.customer_service_content .but { + padding: 3px 20px; + border: none; + outline: none; + -background-color: #2ba245; + background-color: rgb(235, 34, 32); + color: white; + border-radius: 3px; +} +.customer_service_content .but:active { + background-color: rgb(235, 34, 32); +} +.customer_service_content .but:disabled { + background-color: #ddd; + cursor:auto; +} +.customer_service_content .bar_block{ + width: 8px; + /* border-radius: 4px; */ + box-sizing: border-box; + background-color: white +} \ No newline at end of file diff --git a/src/pages/customerservice/support/conversation/components/ChatRecordList/ChatRecordList.jsx b/src/pages/customerservice/support/conversation/components/ChatRecordList/ChatRecordList.jsx new file mode 100644 index 0000000..8e91dbb --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/ChatRecordList/ChatRecordList.jsx @@ -0,0 +1,31 @@ +import React from 'react' +import style from './style.module.css' +import MsgItem from '../MsgItem/MsgItem' +import PropTypes from 'prop-types'; + +const ChatRecordList = (props) => { + let { data, me, showPic} = props + return ( +
+ {/* +
+ +
+ */} + {data.map((bubble) => ( + + ))} +
+ ) +} +ChatRecordList.propTypes = { + loading: PropTypes.bool, + showPic: PropTypes.func, + contact: PropTypes.object, + data:PropTypes.array, + me:PropTypes.any, +} +export default ChatRecordList + diff --git a/src/pages/customerservice/support/conversation/components/ChatRecordList/style.module.css b/src/pages/customerservice/support/conversation/components/ChatRecordList/style.module.css new file mode 100644 index 0000000..ba10780 --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/ChatRecordList/style.module.css @@ -0,0 +1,29 @@ +.customer_service_content_list_area { + flex: 1; + overflow: auto; + -ms-overflow-style: none; + overflow: -moz-scrollbars-none; + padding: 10px; + background-color: rgb(245,245,245) +} +.customer_service_content_list_area .load_more { + display: flex; + justify-content: center; + align-items: center; + height: 20px; + width: 100px; + text-align: center; + color: #999; + background-color: #f1f1f1; + font-size: 12px; + margin: 0 auto; + border: none; + outline: none; + border-radius: 10px; + -moz-user-select: none; + -khtml-user-select: none; + user-select: none; +} +.customer_service_content_list_area .load_more:hover { + cursor: pointer; +} \ No newline at end of file diff --git a/src/pages/customerservice/support/conversation/components/ChatToolsBar/ChatToolBar.jsx b/src/pages/customerservice/support/conversation/components/ChatToolsBar/ChatToolBar.jsx new file mode 100644 index 0000000..8c9ffae --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/ChatToolsBar/ChatToolBar.jsx @@ -0,0 +1,34 @@ +import React from 'react' +import PropTypes from 'prop-types' +import style from './style.module.css' +import EmojiPopover from '../EmojiPopover/EmojiPopover' +import ImgPopover from '../ImgPopover/ImgPopover' +import FilePopover from '../FilePopover/FilePopover' +import RatePopover from '../RatePopover/RatePopover' + +export default function ChatToolBar(props) { + const { tools = [], + onEmojiSelect, + onImage, + onFile, + onRate, + } = props + return ( +
+ {typeof onEmojiSelect === 'function' && ( + + )} + {typeof onImage === 'function' && } + {typeof onFile === 'function' && } + {typeof onRate === 'function' && } + {tools.map((tool) => tool)} +
+ ) +} + +ChatToolBar.propTypes = { + tools: PropTypes.array, + onEmojiSelect: PropTypes.func, + onImage: PropTypes.func, + onFile:PropTypes.func, +} diff --git a/src/pages/customerservice/support/conversation/components/ChatToolsBar/style.module.css b/src/pages/customerservice/support/conversation/components/ChatToolsBar/style.module.css new file mode 100644 index 0000000..3b95871 --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/ChatToolsBar/style.module.css @@ -0,0 +1,8 @@ +.customer_service_content { + height: 30px; + display: flex; + align-items: center; + padding: 0px 10px; + margin-top: 8px; + margin-right: 10px; +} \ No newline at end of file diff --git a/src/pages/customerservice/support/conversation/components/EmojiPopover/EmojiPopover.jsx b/src/pages/customerservice/support/conversation/components/EmojiPopover/EmojiPopover.jsx new file mode 100644 index 0000000..56e8429 --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/EmojiPopover/EmojiPopover.jsx @@ -0,0 +1,141 @@ +import React, { useEffect, useState } from 'react' +import PropTypes from 'prop-types' +import style from './style.module.css' +import { cns } from '../../utils/toClass' + +const emojiList = [ + '😀', + '😃', + '😄', + '😁', + '😆', + '😅', + '😂', + '🤣', + '😊', + '😇', + '🙂', + '🙃', + '😉', + '😌', + '😍', + '🥰', + '😘', + '😗', + '😙', + '😚', + '😋', + '😛', + '😝', + '😜', + '🤪', + '🤨', + '🧐', + '🤓', + '😎', + '🤩', + '🥳', + '😏', + '😒', + '😞', + '😔', + '😟', + '😕', + '🙁', + '😣', + '😖', + '😫', + '😩', + '🥺', + '😢', + '😭', + '😤', + '😠', + '😡', + '🤬', + '🤯', + '😳', + '🥵', + '🥶', + '😱', + '😨', + '😰', + '😥', + '😓', + '🤗', + '🤔', + '🤭', + '🤫', + '🤥', + '😶', + '😐', + '😑', + '😬', + '🙄', + '😯', + '😦', + '😧', + '😮', + '😲', + '🥱', + '😴', + '🤤', + '😪', + '😵', + '🤐', + '🥴', + '🤢', + '🤮', + '🤧', + '😷', + '🤒', + '🤕', +] + +export default function EmojiPopover(props) { + const {onSelect} = props + const [visible, setVisible] = useState(false) + + const switchEmojiModal = (vis) => { + setVisible(vis) + } + + const iconClickHandle = (emoji) => { + onSelect(emoji) + } + + useEffect(() => { + addEventListener('click', (e) => { + if (e.target.getAttribute('datatype') === 'emoji') { + switchEmojiModal(true) + } else { + switchEmojiModal(false) + } + }) + }, []) + + return ( +
+
+ {emojiList.map((emoji) => ( + + {emoji} + + ))} +
+
+
+ ) +} + +EmojiPopover.propTypes = { + onSelect: PropTypes.func.isRequired, +} diff --git a/src/pages/customerservice/support/conversation/components/EmojiPopover/style.module.css b/src/pages/customerservice/support/conversation/components/EmojiPopover/style.module.css new file mode 100644 index 0000000..301bb93 --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/EmojiPopover/style.module.css @@ -0,0 +1,55 @@ +.customer_service_content { + position: relative; +} +.customer_service_content .emoji_wrapper { + position: absolute; + bottom: -90px; + left: 30px; + display: flex; + flex-wrap: wrap; + width: 288px; + background-color: white; + border: solid 1px #aaaaaa; + border-radius: 5px; + padding: 3px; +} +.customer_service_content .emoji_wrapper::before { + content: ''; + border: solid 8px transparent; + border-right: solid 8px #aaaaaa; + position: absolute; + top: 50%; + left: -16px; +} +.customer_service_content .emoji_wrapper::after { + content: ''; + border: solid 8px transparent; + border-right: solid 8px white; + position: absolute; + top: 50%; + left: -14.5px; +} +.customer_service_content .emoji_item { + box-sizing: border-box; + padding: 0px 7px 0px 3px; +} +.customer_service_content .emoji_item:hover { + background-color: #f1f1f1; + border-radius: 50%; + cursor: pointer; +} +.customer_service_content .tool_icon { + height: 20px; + width: 20px; + background-size: 100%; + background-repeat: no-repeat; + background-position: center; + margin-right: 10px; +} +.customer_service_content .emoji { + background-image: url('../../image/emoji_black.png'); +} +.customer_service_content .emoji:hover { + cursor: pointer; + background-image: url('../../image/emoji_light.png'); +} diff --git a/src/pages/customerservice/support/conversation/components/FilePopover/FilePopover.jsx b/src/pages/customerservice/support/conversation/components/FilePopover/FilePopover.jsx new file mode 100644 index 0000000..dc66605 --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/FilePopover/FilePopover.jsx @@ -0,0 +1,69 @@ +import React, {useRef } from 'react' +import PropTypes from 'prop-types' +import style from './style.module.css' +import { message, } from 'antd'; + +export default function FilePopover(props) { + const { onFile } = props + const fileSelector = useRef() + + const isUpload = (file) => { + /* + let name = file.name; + let type; + if (name.lastIndexOf(".") > 0) { + type = name + .substr(name.lastIndexOf(".") + 1, name.length) + .toLocaleLowerCase(); + } + let isType = ["pdf", "png", "jpg", "jpeg"].indexOf(type) === -1; + if (isType) { + message.error("上传格式错误"); + }*/ + const isType = false + const isLt50M = file.size / 1024 / 1024 < 50; + if (!isLt50M) { + message.error("文件大小不能超过50MB"); + } + const bool = !isType && isLt50M; + return bool + } + + const beforeUpload = (file) => { + let bool = isUpload(file) + if (bool) { + onFile(file) + } + return false; + }; + const selectImg = () => { + if (fileSelector.current) { + fileSelector.current.value='' + fileSelector.current.click() + } + } + + const fileHandle = (event) => { + const files = event.target.files + //console.log(files[0]) + if(files && files[0] && (files[0].type.indexOf('application/')!=-1 || files[0].type.indexOf('text/plain')!=-1 )){ + beforeUpload(files[0]) + }else{ + message.error('上传文件格式不支持') + } + } + return ( +
+ +
+ ) +} + +FilePopover.propTypes = { + onFile: PropTypes.func.isRequired, +} diff --git a/src/pages/customerservice/support/conversation/components/FilePopover/style.module.css b/src/pages/customerservice/support/conversation/components/FilePopover/style.module.css new file mode 100644 index 0000000..e9d5bca --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/FilePopover/style.module.css @@ -0,0 +1,16 @@ +.customer_service_content { + width: 20px; + height: 20px; + margin-right: 10px; + background-image: url('../../image/file_black.png'); + background-size: 100%; + background-position: center; + background-repeat: no-repeat; +} +.customer_service_content:hover { + background-image: url('../../image/file_light.png'); + cursor: pointer; +} +.customer_service_content > input { + display: none; +} diff --git a/src/pages/customerservice/support/conversation/components/ImgPopover/ImgPopover.jsx b/src/pages/customerservice/support/conversation/components/ImgPopover/ImgPopover.jsx new file mode 100644 index 0000000..64f6821 --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/ImgPopover/ImgPopover.jsx @@ -0,0 +1,86 @@ +import React, {useRef } from 'react' +import PropTypes from 'prop-types' +import style from './style.module.css' +import { message } from "antd"; +//import { customerserviceAPI as API} from '@/services/customerservice'; + +export default function ImgPopover(props) { + const { onImage } = props + const fileSelector = useRef() + + const isUpload = (file) => { + /* + let name = file.name; + let type; + if (name.lastIndexOf(".") > 0) { + type = name + .substr(name.lastIndexOf(".") + 1, name.length) + .toLocaleLowerCase(); + } + let isType = ["pdf", "png", "jpg", "jpeg"].indexOf(type) === -1; + if (isType) { + message.error("上传格式错误"); + }*/ + const isType = false + const isLt50M = file.size / 1024 / 1024 < 50; + if (!isLt50M) { + message.error("文件大小不能超过50MB"); + } + const bool = !isType && isLt50M; + return bool + } + + const beforeUpload = (file) => { + let bool = isUpload(file) + if (bool) { + onImage(file) + /* + const formData = new FormData(); + console.log(file.getOriginalFilename()) + formData.append('multipartFiles', file); + API.fileUpload({ + multipartFiles: formData + }).then(res => { + if (res && res.data && res.data.length>0 && res.data[0].sysStorageVO && res.data[0].sysStorageVO.filePath!=undefined && res.data[0].sysStorageVO.filePath!=null&& res.data[0].sysStorageVO.filePath!='') { + console.log(res) + console.log(res.data) + + //文档中心返回的filePath重写content + + }else{ + console.log(res && res.message) + } + })*/ + } + return false; + }; + const selectImg = () => { + if (fileSelector.current) { + fileSelector.current.value='' + fileSelector.current.click() + } + } + + const fileHandle = (event) => { + const files = event.target.files + if(files && files[0] && files[0].type.indexOf('image/')!=-1){ + beforeUpload(files[0]) + }else{ + message.error("上传格式错误"); + } + } + return ( +
+ +
+ ) +} + +ImgPopover.propTypes = { + onImage: PropTypes.func.isRequired, +} diff --git a/src/pages/customerservice/support/conversation/components/ImgPopover/style.module.css b/src/pages/customerservice/support/conversation/components/ImgPopover/style.module.css new file mode 100644 index 0000000..7c95726 --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/ImgPopover/style.module.css @@ -0,0 +1,16 @@ +.customer_service_content { + width: 20px; + height: 20px; + margin-right: 10px; + background-image: url('../../image/pic_black.png'); + background-size: 100%; + background-position: center; + background-repeat: no-repeat; +} +.customer_service_content:hover { + background-image: url('../../image/pic_light.png'); + cursor: pointer; +} +.customer_service_content > input { + display: none; +} diff --git a/src/pages/customerservice/support/conversation/components/ImgPreview/ImgPreview.jsx b/src/pages/customerservice/support/conversation/components/ImgPreview/ImgPreview.jsx new file mode 100644 index 0000000..94ca9e6 --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/ImgPreview/ImgPreview.jsx @@ -0,0 +1,56 @@ +import React, { useRef, useState, useEffect } from 'react' +import PropTypes from 'prop-types' +import {handleElement } from '../../utils/utils' +import { Button,} from 'antd'; +import { CloseOutlined, BorderOutlined, MinusOutlined } from '@ant-design/icons'; +import style from './style.module.css' + +export default function ImgPreview(props) { + const { isImgPreviewVisible, imageParams, closePreview, maxSizePreview, normalSizePreview,} = props + const imgRef = useRef() + const [scales,setScales] = useState(1) + //const refs = useRef() + + const changeScales = (a) =>{ + setScales(a) + } + useEffect(()=>{ + if(isImgPreviewVisible) { + var html=document.querySelector("html"); + var body=document.querySelector("body"); + html.style.overflow="hidden"; + body.style.overflow="hidden"; + } + + },[isImgPreviewVisible]) + + return ( + <> +
+
+
+ handleElement.handleStopDrag(e)} + onMouseDown={(e) => { e.persist(); handleElement.handleDrag(e,imgRef) }} + onContextMenu={(e)=>e.preventDefault()} + onWheel={(e)=>handleElement.handleZoom(e, imgRef, scales, changeScales)} + /> +
+ + ) +} + +ImgPreview.propTypes = { + isImgPreviewVisible:PropTypes.bool, + closePreview:PropTypes.func, + maxSizePreview:PropTypes.func, + normalSizePreview:PropTypes.func, + imageParams:PropTypes.any, +} diff --git a/src/pages/customerservice/support/conversation/components/ImgPreview/style.module.css b/src/pages/customerservice/support/conversation/components/ImgPreview/style.module.css new file mode 100644 index 0000000..87998db --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/ImgPreview/style.module.css @@ -0,0 +1,6 @@ +.customer_service_btn{ + height: 20px; + background-color: rgb(245, 245, 245); + position: relative; + align-self: flex-end; +} diff --git a/src/pages/customerservice/support/conversation/components/MsgBubble/MsgBubble.jsx b/src/pages/customerservice/support/conversation/components/MsgBubble/MsgBubble.jsx new file mode 100644 index 0000000..916545c --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/MsgBubble/MsgBubble.jsx @@ -0,0 +1,100 @@ +import React from 'react' +import PropTypes from 'prop-types' +import style from './style.module.css' +import { cns } from '../../utils/toClass' +import {parseBase64ToBlob,downloadFile} from '../../utils/utils' +import { customerserviceAPI as API} from '@/services/customerservice'; +import { message, Tooltip } from "antd"; +//import { downloadFile } from "@utils/utils"; + +export default function MsgBubble(props) { + const { data, isMe, showPic} = props + + const onDownload = (fileId) => { + API.getSecretKey({fileId: fileId}).then(res => { + if(res.success==true){ + API.getDownload({ fileId: fileId, documentSecretKey: res.data }).then(buffer => { + const dotIndex = data.content.name.lastIndexOf('.') + const ext = data.content.name.substr(dotIndex) + const fileName = data.content.name.substring(0, dotIndex) + downloadFile(buffer, fileName, ext) + }) + }else{ + message.warning(res.message) + } + }) + } + const openFile = ()=>{ + const element = document.createElement("a"); + if(data.content.content.indexOf('base64')!=-1){ + var arr = data.content.content.split(',') //分割为数组,分割到第一个逗号 + let mime = arr[0].match(/:(.*?);/)[1] //获取分割后的base64前缀中的类型 + let myBlob = parseBase64ToBlob(arr[1], mime); + element.href = URL.createObjectURL(myBlob); + element.download = data.content.name; + document.body.appendChild(element); // Required for this to work in FireFox + element.click(); + document.body.removeChild(element); + }else{ + if(data.content.fileId!=''){ + onDownload(data.content.fileId) + } + } + } + const renderContent = (message) => { + switch (message.type) { + case 'text': + return( +
{e.preventDefault()}} dangerouslySetInnerHTML={{ __html: message.content }} /> + ) + case 'image': + //console.log(message.content.content) + return ( +
{e.preventDefault()}}> + { + let src = message.content.content + let realWidth = message.content.realWidth + let realHeight = message.content.realHeight + let msgWidth = message.content.msgWidth + let msgHeight = message.content.msgHeight + let previewidth = message.content.previewidth + let previewHeight = message.content.previewHeight + let param = { + src, realWidth, realHeight, msgWidth, msgHeight, previewidth, previewHeight + } + showPic(param) + }} /> +
+ ) + case 'file': + return( + +
{e.preventDefault()}} className={style.customer_service_uploadfile} onClick={openFile}> +
{message.content.name}
+
{message.content.size}
+
+
+
+ ) + default: + break + } + + } + return ( +
+ {renderContent(data)} +
+ ) +} + +MsgBubble.propTypes = { + data: PropTypes.object.isRequired, + isMe: PropTypes.bool.isRequired, + showPic: PropTypes.func.isRequired +} diff --git a/src/pages/customerservice/support/conversation/components/MsgBubble/style.module.css b/src/pages/customerservice/support/conversation/components/MsgBubble/style.module.css new file mode 100644 index 0000000..5cd47b2 --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/MsgBubble/style.module.css @@ -0,0 +1,83 @@ +.customer_service_noselect{ + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Chrome/Safari/Opera */ + -khtml-user-select: none; /* Konqueror */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently + not supported by any browser */ +} +.customer_service_text_content { + background-color: rgb(216,216,216); + color: #333; + padding: 8px; + border-radius: 3px; + max-width: 100%; + word-wrap: break-word; + word-break: break-all; + font-size: 13px; + font-family:sans-serif; + letter-spacing: 2px; +} +.customer_service_uploadfile{ + border: 1px solid #CCC; + background-color: white; + width: 200px; + height: 120px; + display: flex; + flex-direction: column; + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Chrome/Safari/Opera */ + -khtml-user-select: none; /* Konqueror */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently + not supported by any browser */ +} +.customer_service_uploadfile:hover{ + cursor: pointer; +} +.customer_service_filename { + margin-left: 10px; + margin-top: 5px; +} +.customer_service_filesize { + margin-left: 10px; + margin-top: 5px; + color: rgb(153,153,153); +} +.customer_service_ellipsis { + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + display:-webkit-box; + -webkit-box-orient:vertical; + -webkit-line-clamp:2; + -white-space: nowrap; +} +.customer_service_uploadicon{ + align-self: flex-end; + margin-right: 10px; + width: 40px; + height: 40px; + background-image: url('../../image/uploadFile.png'); + background-size: cover; +} +.customer_service_arrow { + position: relative; +} +.customer_service_arrow::after { + content: ''; + position: absolute; + width: 8px; + height: 8px; + background-color: rgb(216,216,216); + transform: rotate(45deg); + top: 8px; +} +.customer_service_arrow_left::after { + left: -4px; +} +.customer_service_arrow_right::after { + right: -4px; +} \ No newline at end of file diff --git a/src/pages/customerservice/support/conversation/components/MsgItem/MsgItem.jsx b/src/pages/customerservice/support/conversation/components/MsgItem/MsgItem.jsx new file mode 100644 index 0000000..6c1f0ed --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/MsgItem/MsgItem.jsx @@ -0,0 +1,44 @@ +import React from 'react' +import PropTypes from 'prop-types' +import style from './style.module.css' +import MsgBubble from '../MsgBubble/MsgBubble' +//import dayjs from 'dayjs' +import moment from 'moment'; + + +export default function MsgItem(props) { + const { data, me ,showPic } = props + const isMe = data.user.id === me.id + + const isValidDate= (date)=>{ + return date instanceof Date && !isNaN(date.getTime()) + } + + return ( +
+
+ +
+
+
+ {data.user.name} + + {/*moment(data.date).format('MM-DD HH:mm:ss')*/} + {isValidDate(new Date(data.date))?moment(data.date).format('MM-DD HH:mm:ss'):data.date} + +
+ {} +
+
+ ) +} + +MsgItem.propTypes = { + data: PropTypes.object.isRequired, + me: PropTypes.object.isRequired, + showPic: PropTypes.func.isRequired +} diff --git a/src/pages/customerservice/support/conversation/components/MsgItem/style.module.css b/src/pages/customerservice/support/conversation/components/MsgItem/style.module.css new file mode 100644 index 0000000..3c82dde --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/MsgItem/style.module.css @@ -0,0 +1,39 @@ +@import '../../style/common.css'; + +.customer_service_content { + padding-top: 20px; + display: flex; +} +.customer_service_content .avatar { + min-width: 35px; + width: 35px; + padding-top: 14px; + -moz-user-select: none; + -khtml-user-select: none; + user-select: none; +} +.customer_service_content .avatar > img { + width: 100%; + height: auto; + border-radius: 50%; + overflow: hidden; +} +.customer_service_content .text_area { + flex: 1; + padding: 0px 10px; + display: flex; + flex-direction: column; + overflow: hidden; + max-width: 60%; +} +.customer_service_content .comment_area { + font-size: 12px; + color: #999; + padding-bottom: 4px; + -moz-user-select: none; + -khtml-user-select: none; + user-select: none; +} +.customer_service_content .date_text { + margin-left: 10px; +} diff --git a/src/pages/customerservice/support/conversation/components/RatePopover/RatePopover.jsx b/src/pages/customerservice/support/conversation/components/RatePopover/RatePopover.jsx new file mode 100644 index 0000000..b10429e --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/RatePopover/RatePopover.jsx @@ -0,0 +1,39 @@ +import React, {useRef, useState } from 'react' +import PropTypes from 'prop-types' +import style from './style.module.css' +import { HeartFilled } from '@ant-design/icons'; +import { Rate, Button, Modal, Tooltip } from 'antd'; +export default function RatePopover(props) { + + const { onRate } = props + const fileSelector = useRef() + const [score, setScore] = useState(0) + const [rateModal, setRateModal] = useState(false) + + return ( + <> +
setRateModal(true)} className={style.customer_service_btn}/> + {rateModal && + { + setRateModal(false) + }} + footer={[ + , + ]} + > + { + setScore(value) + }}/> + + } + + ) +} +RatePopover.propTypes = { + onRate: PropTypes.func.isRequired, +} diff --git a/src/pages/customerservice/support/conversation/components/RatePopover/style.module.css b/src/pages/customerservice/support/conversation/components/RatePopover/style.module.css new file mode 100644 index 0000000..8a2b54c --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/RatePopover/style.module.css @@ -0,0 +1,28 @@ +.customer_service_btn { + width: 12px; + height: 12px; + line-height: 12px; + margin-left: 5px; + position: relative; + background-color: #b30000; + transform: rotate(45deg); +} +.customer_service_btn::before, +.customer_service_btn::after { + position: absolute; + content: ''; + background-color: #b30000; + top: 0px; + left: -50%; + width: 100%; + height: 100%; + border-radius: 50%; +} + +.customer_service_btn::after { + left: 0; + top: -50%; +} +.customer_service_btn:hover{ + cursor: pointer; +} \ No newline at end of file diff --git a/src/pages/customerservice/support/conversation/components/ScrollWrapper/ScrollWrapper.jsx b/src/pages/customerservice/support/conversation/components/ScrollWrapper/ScrollWrapper.jsx new file mode 100644 index 0000000..5d5c05d --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/ScrollWrapper/ScrollWrapper.jsx @@ -0,0 +1,143 @@ +import React, { + useEffect, + useLayoutEffect, + useRef, + useState, + useMemo +} from 'react' +import { cns } from '../../utils/toClass' +import styles from './style.module.css' +import PropTypes from 'prop-types'; + +const ScrollWrapper = (Comp) => (props) => { + + //var data = this.props.data + const scrollView = useRef() + const thumb = useRef() + + const [viewPortH, setViewPortH] = useState(1) + const [scrollH, setScrollH] = useState(1) + const [scrollT, setScrollT] = useState(0) + const [scrollR, setScrollR] = useState(1) + // const [isReset, setIsReset] = useState(false) + const [isPressing, setIsPressing] = useState(false) + const [showScrollBar, setShowScrollBar] = useState(false) + + const [shadowStyle, setShadowStyle] = useState('') + +let { data, viewH, scrollToBottom, switchFlag, style, isMove, isModalVisible} = this? this.props : props + //let data = props.data + const scrollHandle = () => { + if (scrollView.current) { + setScrollT(scrollView.current.scrollTop) + } + } + + const mouseUpHandle = () => { + setIsPressing(false) + } + + const mouseDownHandle = () => { + setIsPressing(true) + } + + const mouseMovingHandle = (e) => { + if (isPressing !== true) return + if (scrollT < 0) { + setScrollT(0) + } else if (scrollT > scrollH - viewPortH) { + setScrollT(scrollH - viewPortH) + } else { + setScrollT((preScrollT) => preScrollT + e.nativeEvent.movementY / scrollR) + } + } + + const thumbHeight = () => viewPortH * scrollR + + const transH = () => scrollT * scrollR + + useEffect(() => { + addEventListener('mouseup', mouseUpHandle) + + return () => { + removeEventListener('mouseup', mouseUpHandle) + } + }, []) + + useEffect(() => { + if(isModalVisible){ + if (scrollView.current) { + setViewPortH(scrollView.current.offsetHeight) + setScrollH(scrollView.current.scrollHeight) + } + } + }, [data, isModalVisible, viewH]) + + useLayoutEffect(() => { + if (scrollView.current) { + scrollView.current.scrollTop = scrollT + } + + if (showScrollBar !== true) return + + if (scrollT <= 0) { + setShadowStyle(styles.shadow_bottom) + } else if (scrollT >= scrollH - viewPortH) { + setShadowStyle(styles.shadow_top) + } else { + setShadowStyle(styles.shadow_vertical) + } + }, [scrollT]) + + useEffect(() => { + const sr = scrollH==0? 0: (viewPortH / scrollH) + setScrollR(sr) + if (viewPortH < scrollH) { + setShowScrollBar(true) + } else { + setShowScrollBar(false) + } + }, [scrollH, viewPortH]) + + useEffect(() => { + if (scrollToBottom) { + setScrollT(scrollH - viewPortH) + }else{ + setScrollT(0) + } + }, [scrollR, switchFlag]) + + return ( +
+
+ {useMemo(() => , [data])} +
+ +
+ ) +}; + +ScrollWrapper.propTypes = { + data: PropTypes.object, +}; +export default ScrollWrapper; \ No newline at end of file diff --git a/src/pages/customerservice/support/conversation/components/ScrollWrapper/style.module.css b/src/pages/customerservice/support/conversation/components/ScrollWrapper/style.module.css new file mode 100644 index 0000000..bdcf6e5 --- /dev/null +++ b/src/pages/customerservice/support/conversation/components/ScrollWrapper/style.module.css @@ -0,0 +1,49 @@ +@import '../../style/common.css'; + +.customer_service_content_wrapper_content { + display: flex; + box-sizing: border-box; + border-top: solid 1px rgb(226, 226, 226); + border-bottom: solid 1px rgb(226, 226, 226); +} +.customer_service_content_wrapper_content .list_block { + flex: auto; + overflow: auto; + overflow-x: hidden; + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE 10+ */ + box-sizing: border-box; +} +.customer_service_content_wrapper_content .list_block::-webkit-scrollbar { + display: none; /* Chrome Safari */ +} +.customer_service_content_wrapper_content .scroll_bar_block { + width: 8px; + /* border-radius: 4px; */ + overflow: hidden; + transition: all 0.3s; + box-sizing: border-box; + background-color: rgb(231,229,229); +} +.customer_service_content_wrapper_content .scroll_bar_block:hover { + background-color: #eee; +} +.customer_service_content_wrapper_content .scroll_thumb { + display: block; + width: 100%; + background-color: #aaa; + border-radius: 4px; +} +.customer_service_content_wrapper_content .scroll_thumb:hover { + cursor: pointer; +} +.customer_service_content_wrapper_content .shadow_top { + box-shadow: inset 0px 5px 10px -5px rgba(0, 0, 0, 0.2); +} +.customer_service_content_wrapper_content .shadow_bottom { + box-shadow: inset 0px -5px 10px -5px rgba(0, 0, 0, 0.2); +} +.customer_service_content_wrapper_content .shadow_vertical { + box-shadow: inset 0px 5px 10px -5px rgba(0, 0, 0, 0.2), + inset 0px -5px 10px -5px rgba(0, 0, 0, 0.2); +} diff --git a/src/pages/customerservice/support/conversation/image/emoji_black.png b/src/pages/customerservice/support/conversation/image/emoji_black.png new file mode 100644 index 0000000000000000000000000000000000000000..609510622c6b4ddf99797bf622a3caf84f4eb11a GIT binary patch literal 5436 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 literal 0 HcmV?d00001 diff --git a/src/pages/customerservice/support/conversation/image/file_black.png b/src/pages/customerservice/support/conversation/image/file_black.png new file mode 100644 index 0000000000000000000000000000000000000000..ac827b121b00fda6664c3e0f10ea3267687c1ee4 GIT binary patch literal 1929 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 wFc~Z;D7I_j{ht_nh-Q=Q-zl)z#SnDy=3B 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 literal 0 HcmV?d00001 diff --git a/src/pages/customerservice/support/conversation/image/pic_light.png b/src/pages/customerservice/support/conversation/image/pic_light.png new file mode 100644 index 0000000000000000000000000000000000000000..a3f301683e31b9b36790b7103845a100939c4423 GIT binary patch literal 2675 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 literal 0 HcmV?d00001 diff --git a/src/pages/customerservice/support/conversation/image/uploadFile.png b/src/pages/customerservice/support/conversation/image/uploadFile.png new file mode 100644 index 0000000000000000000000000000000000000000..bd61399ea5bb560eef63cee932dde0001d430388 GIT binary patch literal 8705 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 bHMy%7cOkoo3SEj#xx2Ew^5n6kaQJ@!%e><6 literal 0 HcmV?d00001 diff --git a/src/pages/customerservice/support/conversation/index.jsx b/src/pages/customerservice/support/conversation/index.jsx new file mode 100644 index 0000000..2bd5a70 --- /dev/null +++ b/src/pages/customerservice/support/conversation/index.jsx @@ -0,0 +1,372 @@ +import React, { useState, useEffect, useRef } from 'react' +import Chat from './components/Chat/Chat' +import ImgPreview from './components/ImgPreview/ImgPreview' +import {entitiestoUtf16, handleElement } from './utils/utils' +//import { checToken } from '@/services/login' +//import { bizServiceCustomService } from "@/config/host"; +import user from '@/assets/img/cs_user.png' +import client from '@/assets/img/cs_client.png' +import { customerserviceAPI as API} from '@/services/customerservice'; +import { message, Modal, Table, Button, Popconfirm } from 'antd'; + +const conversation = (props) => { + const [msgList, setMsgList] = useState([]) + const [scrollToBottom, setScrollToBottom] = useState(true) + const [switchFlag,setSwitchFlag] = useState(false) + const [viewH,setViewH] = useState(0) + const [curuser,setCuruser] = useState({}) + const [wsMsg, setWsMsg] = useState({}) + const [wsObj, setWsObj] = useState(null) + const [conversationId, setConversationId] = useState('') + const [isImgPreviewVisible, setIsImgPreviewVisible] = useState(false) //在线客服/图片预览 + const [imageParams, setImageParams] = useState({}) //图片属性 + const [show, setShow] = useState(false) + const [projectData, setProjectData] = useState([]) + const [showLoading, setShowLoading] = useState(false) + const [isSend, setIsSend] = useState(false) + const [sendMsg, setSendMsg] = useState([]) + const [agentNumber, setAgentNumber] = useState('') + //const [angle, setAngle] = useState(0) + const [pagination, setPagination] = useState({ + showQuickJumper: true, + showSizeChanger: false, + current: 1, + pageSize: 10, + total: 0, + }); + const refs = useRef() + const chatUI = document.getElementById('chatUI') + const record = document.getElementById('section') + const chatInput = document.getElementById('chatInput') + const listHeight = 290 + const textHeight = 150 + const wid = 600 + const hei = 500 + + let {isModalVisible, isCloseWs, closeWin, hideWin, msgAlert, supplierNumber, staffId, staffName, optionsModal, projectModal, closeOptionsModal, closeProjectModal, openWin, openProjectWin} = props + const onMsgRender = ()=>{ + let serverAvatar = user + let data = wsMsg + if(data && data.clientNo!=undefined){ + console.log('监听渲染') + let type = data.contentType==1?'text':data.contentType==2?'image':data.contentType==3?'file':'text' + let content={} + setConversationId(data.conversationId) + if(type=='text'){ + content = data.contentType==1?entitiestoUtf16(data.message) : data.message + }else if(type =='image'){ + let newcontent = data.message.indexOf('{')!= -1 ? JSON.parse(data.message):{} + content={ + content: newcontent.content, + realWidth: newcontent.realWidth, + realHeight:newcontent.realHeight, + msgWidth: newcontent.msgWidth, + msgHeight:newcontent.msgHeight, + previewidth: newcontent.previewidth, + previewHeight:newcontent.previewHeight, + success:false, + fileId:newcontent.fileId, + } + }else if(type =='file'){ + let newcontent = data.message.indexOf('{')!= -1 ? JSON.parse(data.message):{} + content={ + content: newcontent.content, + name: newcontent.name, + size:newcontent.size, + success:false, + fileId:newcontent.fileId, + } + } + let msgData={ + _id: Math.floor(Math.random() * 1000)+new Date(), + date: new Date(), + user: { + id: data.serverNo, + avatar: serverAvatar, + name: '供应链客服', + }, + message: { type: type, content: content }, + } + setMsgList([...msgList, msgData]) + msgAlert && msgAlert() + } + } + /*websocket*/ + const websocketInit = (id)=>{ + //const bizServiceCustomService = 'http://10.242.31.158:8100/api/biz-customer-service' + var addr = `${REACT_APP_CUSTOMERSERVICE_WS_REDIRECT}` + '/websocketClient/' + let no = agentNumber!=''?agentNumber: supplierNumber + var ws = new WebSocket(addr+id+'/'+no); + // 建立 web socket 连接成功触发事件 + ws.onopen = function () { + console.log("Connection open ..."); + //记录ws + setShow(true) //连接建立后,才可以显示弹窗、收发消息 + if(sendMsg && sendMsg.length>0) setIsSend(true) //连接建立后,sendMsg有值才可以发及时消息 + setWsObj(ws) + }; + + // 接收服务端数据时触发事件 + ws.onmessage = function (res) { + if(res.data){ + let data = JSON.parse(res.data) + console.log('onmessage') + console.log(data) + setWsMsg(data) + } + }; + + // 断开 web socket 连接成功触发事件 + ws.onclose = function () { + console.log("连接已关闭..."); + setWsObj(null) + }; + + } + //获取当前登录者token + const getToken = ()=>{ + //登录人的信息的关键字 顺序是固定的。 + let avatar = client + //当前用户 + setCuruser({ + id: staffId, + name: staffName, + avatar: avatar, + }) + API.check({ + clientNo: staffId, + supplierNo:agentNumber!=''?agentNumber: supplierNumber + }).then(res => { + if (res && res.success) { + websocketInit(staffId) + }else{ + closeWin && closeWin() + message.error(res && '已建立会话,无需重复建立') + } + }) + } +const onChange = (pagination) => { + setShowLoading(true) + getProject({ + pageNum: pagination.current, + pageSize: pagination.pageSize, + }); +} +const getProject = (params)=>{ + API.list({supplierNumber:supplierNumber}).then((res)=>{ + setShowLoading(false) + if(res && res.success && res.data){ + let data = res.data + setProjectData(data) + setPagination({ + showQuickJumper: true, + showSizeChanger: false, + pageSize: params.pageSize, + current: params.pageNum, + total: res.data.total, + showTotal: total => `共 ${total} 条`, + }) + }else{ + message.warn(res && res.message) + } + }) +} + useEffect(() => { + if(isModalVisible){ + //恢复初始值 + chatUI.style.width = wid + chatUI.style.height = hei + chatUI.style.left = (document.documentElement.clientWidth - wid)*0.5 +"px" + chatUI.style.top = (document.documentElement.clientHeight - hei)*0.5 +"px" + record.style.height = listHeight + "px" + chatInput.style.height = textHeight + "px" + }else{ + if(isCloseWs) setMsgList([]) + } + //获取当前用户token + if(isModalVisible && !isCloseWs && wsObj==null) getToken() + }, [isModalVisible]) + + useEffect(() => { + if(projectModal && supplierNumber!='EMPTY'){ + //获取项目 + setShowLoading(true) + getProject({ + pageNum: 1, + pageSize: 10, + }) + } + }, [projectModal]) + + useEffect(() => { + //关闭弹窗,清空消息列表 + if(isCloseWs && wsObj!=null) { + setShow(false) + wsObj.close() + } + }, [isCloseWs]) + + useEffect(() => { + //关闭弹窗,断开ws连接 + //防止ws还没open,就关闭弹窗了。加一次判断:建立好连接后,只要弹窗已关闭,就断开。 + if(isCloseWs && wsObj!=null) { + wsObj.close() + } + }, [wsObj]) + + useEffect(() => { + //onmessage时,触发,必须放到useEffect,直接写到onmessage里不渲染 + if(wsMsg && wsMsg.clientNo!=undefined) onMsgRender() + }, [wsMsg]) + + const columns=[ + { title: '项目名称', dataIndex: 'projectName', align: 'center', ellipsis:true, width:'40%'}, + { title: '代理公司', dataIndex: 'agentName', align: 'center', ellipsis:true, width:'40%'}, + { + title: '操作', align: 'center', width: '150px', + render: (text, record) => { + // let params = { + // "clientNo": curuser.id, + // "contentType":1, + // "custType": 1, + // "message": '项目名称:'+record.projectName+',项目编号:'+record.projectNumber+'代理公司:'+record.agentName+',代理公司编号:'+record.agentNumber, + // //"questionType": location.pathname.indexOf('/home')!=-1? 3 : 1, + // "questionType": 2, + // "supplierNo": supplierNumber + // } + setAgentNumber(record.agentNumber) + setSendMsg([{ + type:'text', + content:'项目名称:'+record.projectName+'
项目编号:'+record.projectNumber+'
项目状态:'+record.projectStatus + } + ]) + closeProjectModal && closeProjectModal() + openWin && openWin() + }} + okText="确定" + cancelText="取消" + >
+ }, + ] + return ( + <> +
+ {setIsSend(false); setSendMsg([])}} + chatUI={chatUI} + textHeight={textHeight} + listHeight={listHeight} + supplierNo={agentNumber!=''?agentNumber: supplierNumber} + conversationId={conversationId} + closeWin={()=>{ + closeWin && closeWin() + }} + hideWin={()=>{ + hideWin && hideWin() + }} + setPostion={()=>{ + setScrollToBottom(true) + setSwitchFlag(!switchFlag) + }} + changeHeight={(h)=>{ + setViewH(h) + }} + onSend={(msg) =>{ + setScrollToBottom(true) + setMsgList([...msgList, ...msg]) + setIsSend(false) + setSendMsg([]) + }} + showPic={(imageParams)=>{ + setImageParams(imageParams) + setIsImgPreviewVisible(true) + }} + style={{ + width: wid, + height: hei, + }} + /> +
+ {isImgPreviewVisible &&
{if(e.target.id=='blankArea' || e.target.id=='popUpImage'){e.persist(); handleElement.handleDrag(e,refs)} }}> + { + var html=document.querySelector("html") + var body=document.querySelector("body") + html.style.overflow="visible" + body.style.overflow="visible" + setIsImgPreviewVisible(false) + }} + maxSizePreview={()=>{ + refs.current.style.width=document.documentElement.clientWidth + "px" + refs.current.style.height=document.documentElement.clientHeight + "px" + refs.current.style.top= '50%' + refs.current.style.left= '50%' + refs.current.style.transform='translate(-50%,-50%)' + }} + normalSizePreview={()=>{ + refs.current.style.width= wid + "px" + refs.current.style.height= hei + "px" + refs.current.style.top= '50%' + refs.current.style.left= '50%' + refs.current.style.transform='translate(-50%,-50%)' + }} + /> +
} + {optionsModal && isCloseWs && + { + closeOptionsModal && closeOptionsModal() + }}> +

openWin && openWin()}>点击咨询平台客服(平台操作相关问题)

+

openWin && openWin()}>点击咨询智慧安全公司客服(iPASS及电子签章相关问题)

+

openProjectWin && openProjectWin()}>点击咨询代理机构客服(项目内容相关问题)

+ + } + {projectModal && + { + closeWin && closeWin() + }}> + {supplierNumber=='EMPTY'?

没有要咨询的项目

: record['projectNumber']} + loading={showLoading} + pagination={pagination} + onChange={onChange} + />} + + } + + ) +} +export default conversation diff --git a/src/pages/customerservice/support/conversation/style/common.css b/src/pages/customerservice/support/conversation/style/common.css new file mode 100644 index 0000000..fb6a08b --- /dev/null +++ b/src/pages/customerservice/support/conversation/style/common.css @@ -0,0 +1,21 @@ +/* flex */ +.flex { + display: flex; +} +.flex_1 { + flex: auto; +} +.jus_cen { + justify-content: center; +} +.ali_cen { + align-items: center; +} + +/* position */ +.pos_a { + position: absolute; +} +.pos_r { + position: relative; +} diff --git a/src/pages/customerservice/support/conversation/utils/toClass.js b/src/pages/customerservice/support/conversation/utils/toClass.js new file mode 100644 index 0000000..1e0ebdc --- /dev/null +++ b/src/pages/customerservice/support/conversation/utils/toClass.js @@ -0,0 +1,3 @@ +export function cns(classes = []) { + return classes.join(' ') +} diff --git a/src/pages/customerservice/support/conversation/utils/utils.jsx b/src/pages/customerservice/support/conversation/utils/utils.jsx new file mode 100644 index 0000000..8a8e701 --- /dev/null +++ b/src/pages/customerservice/support/conversation/utils/utils.jsx @@ -0,0 +1,537 @@ +const sm2 = require('sm-crypto').sm2 + +export function AutoResizeImage(maxWidth,maxHeight,objImg) { + var img = new Image(); + img.src = objImg.src; + var hRatio; + var wRatio; + var Ratio = 1; + var w = img.width; + var h = img.height; + wRatio = maxWidth / w; + hRatio = maxHeight / h; + if (maxWidth ==0 && maxHeight==0){ + Ratio = 1; + }else if (maxWidth==0){// + if (hRatio<1) Ratio = hRatio; + }else if (maxHeight==0){ + if (wRatio<1) Ratio = wRatio; + }else if (wRatio<1 || hRatio<1){ + Ratio = (wRatio<=hRatio?wRatio:hRatio); + } + if (Ratio<1){ + w = w * Ratio; + h = h * Ratio; + } + objImg.style.height = h+"px"; + objImg.style.width = w+"px"; +} +export function GetResizeImg(maxsize,obj) { + var img = new Image(); + img.src = obj.src; + var hRatio; + var wRatio; + var Ratio = 1; + var w = img.width; + var h = img.height; + var maxWidth; + var maxHeight; + + if(w>=h){ + maxWidth = maxsize + maxHeight = 0 + + }else{ + maxWidth = 0 + maxHeight = maxsize + } + + wRatio = maxWidth / w; + hRatio = maxHeight / h; + + if (maxWidth ==0 && maxHeight==0){ + Ratio = 1; + }else if (maxWidth==0){// + if (hRatio<1) Ratio = hRatio; + }else if (maxHeight==0){ + if (wRatio<1) Ratio = wRatio; + }else if (wRatio<1 || hRatio<1){ + Ratio = (wRatio<=hRatio?wRatio:hRatio); + } + /* + wRatio = maxWidth / w; + hRatio = maxHeight / h; + if (maxWidth ==0 && maxHeight==0){ + Ratio = 1; + }else if (maxWidth==0){// + if (hRatio<1) Ratio = hRatio; + }else if (maxHeight==0){ + if (wRatio<1) Ratio = wRatio; + }else if (wRatio<1 || hRatio<1){ + Ratio = (wRatio<=hRatio?wRatio:hRatio); + } + */ + if (Ratio<1){ + w = w * Ratio; + h = h * Ratio; + } + + return {"width":w,"height":h} +} +export function GetNewsize(maxsize,width,height) { + var hRatio; + var wRatio; + var Ratio = 1; + var w = width; + var h = height; + + var maxWidth; + var maxHeight; + + if(w>=h){ + maxWidth = maxsize + maxHeight = 0 + + }else{ + maxWidth = 0 + maxHeight = maxsize + } + + wRatio = maxWidth / w; + hRatio = maxHeight / h; + if (maxWidth ==0 && maxHeight==0){ + Ratio = 1; + }else if (maxWidth==0){// + if (hRatio<1) Ratio = hRatio; + }else if (maxHeight==0){ + if (wRatio<1) Ratio = wRatio; + }else if (wRatio<1 || hRatio<1){ + Ratio = (wRatio<=hRatio?wRatio:hRatio); + } + if (Ratio<1){ + w = w * Ratio; + h = h * Ratio; + } + return {"width":w,"height":h} +} +/*over、move、拖拽*/ +export function mouseOverHandle(e,chatUI){ + let clickBox = e.target + var direction = 0; + + var mouseDownX = e.clientX; + var mouseDownY = e.clientY; + var clickBoxTop = chatUI.offsetHeight - clickBox.offsetHeight + chatUI.offsetTop + var clickBoxBottom = clickBoxTop + 60 + var clickBoxLeft = chatUI.offsetLeft; + var clickBoxRight = chatUI.offsetWidth + clickBoxLeft + + if(clickBox.id=="chatInput" ){ + e=e||event; //兼容ie和其他浏览器的写法 + + if ( mouseDownY < clickBoxTop + 5){ + direction = 'top'; + } + + if(clickBox.id=="chatHeader" ){ + e=e||event; //兼容ie和其他浏览器的写法 + if ((mouseDownY > clickBoxTop && mouseDownY < clickBoxBottom) || (mouseDownX > clickBoxLeft && mouseDownX < clickBoxRight)){ + direction = 'move'; + } + } + + if(direction!=0){ + if(direction=="left" || direction=="right"){ + clickBox.style.cursor='w-resize' + }else if(direction=="top" || direction=="bottom"){ + clickBox.style.cursor='s-resize' + }else{ + clickBox.style.cursor="auto" + } + }else if(direction == "move"){ + clickBox.style.cursor="move" + }else{ + clickBox.style.cursor="auto" + } + } + +} +export function mouseMoveHandle(e,chatUI){ + let clickBox = e.target + var direction = 0; + clickBox.style.cursor="auto" + + var mouseDownX = e.clientX; + var mouseDownY = e.clientY; + var clickBoxTop = chatUI.offsetHeight - clickBox.offsetHeight + chatUI.offsetTop + var clickBoxBottom = clickBoxTop + 60 + var clickBoxLeft = chatUI.offsetLeft; + var clickBoxRight = chatUI.offsetWidth + clickBoxLeft + + if(clickBox.id=="chatInput" ){ + e=e||event; //兼容ie和其他浏览器的写法 + + if ( mouseDownY < clickBoxTop + 5){ + direction = 'top'; + } + } + if(clickBox.id=="chatHeader" ){ + e=e||event; //兼容ie和其他浏览器的写法 + if ((mouseDownY > clickBoxTop && mouseDownY < clickBoxBottom) || (mouseDownX > clickBoxLeft && mouseDownX < clickBoxRight)){ + direction = 'move'; + } + } + + if(direction!=0){ + if(direction=="left" || direction=="right"){ + clickBox.style.cursor='w-resize' + }else if(direction=="top" || direction=="bottom"){ + clickBox.style.cursor='s-resize' + }else if(direction == "move"){ + clickBox.style.cursor="move" + }else { + clickBox.style.cursor="auto" + } + }else{ + clickBox.style.cursor="auto" + } +} +export function mouseDrag(e,selfHeight,chatUI,changeHeight,setPostion){ + + let clickBox = e.target + var direction = 0; + let count = 0 + + var mouseDownX = e.clientX; + var mouseDownY = e.clientY; + var clickBoxTop = chatUI.offsetHeight - clickBox.offsetHeight + chatUI.offsetTop + var clickBoxBottom = clickBoxTop + 60 + var clickBoxLeft = chatUI.offsetLeft; + var clickBoxRight = chatUI.offsetWidth + clickBoxLeft + var ll = chatUI.offsetLeft + var tt = chatUI.offsetTop + let record=null; + let topHeight=0; + + var maxLeft = document.documentElement.clientWidth - chatUI.offsetWidth + var maxTop = document.documentElement.clientHeight - chatUI.offsetHeight + + if(clickBox.id=="chatInput" ){ + e=e||event; //兼容ie和其他浏览器的写法 + var clickBoxHeight = clickBox.offsetHeight; + var input_maxHeight = chatUI.offsetHeight - 60 - 40 + var record_maxHeight = chatUI.offsetHeight - 60 - selfHeight + record = document.getElementById('section') + topHeight = record.offsetHeight + + if ( mouseDownY < clickBoxTop + 10){ + direction = 'top'; + } + } + if(clickBox.id=="chatHeader" ){ + e=e||event; //兼容ie和其他浏览器的写法 + if ((mouseDownY > clickBoxTop && mouseDownY < clickBoxBottom) || (mouseDownX > clickBoxLeft && mouseDownX < clickBoxRight)){ + direction = 'move'; + } + } + document.onmousemove = function(e) { + e = e || event; //是要是使用原生js给我们提供的e回调参数,这存储了很多有用的信息 + + var yy = e.clientY; + var xx = e.clientX; + + if (clickBox.id=="chatInput" && direction==='top'){ + if(count==0 && setPostion && setPostion!=undefined) { + setPostion() + count++ + } + record.style.height = Math.min(record_maxHeight,(topHeight - mouseDownY + yy) < 40 ? 40:(topHeight - mouseDownY + yy)) + 'px'; + clickBox.style.height = Math.min(input_maxHeight, (clickBoxHeight + mouseDownY - yy) < selfHeight? selfHeight: (clickBoxHeight + mouseDownY - yy)) + 'px'; + if(changeHeight && changeHeight!=undefined) changeHeight(Math.min(record_maxHeight,(topHeight - mouseDownY + yy) < 20 ? 20:(topHeight - mouseDownY + yy))) + + } + if (clickBox.id=="chatHeader" && direction==='move'){ + + var l = xx - mouseDownX + ll + var t = yy - mouseDownY + tt + + chatUI.style.left = l <0 ? 0 : Math.min(l,maxLeft) +"px" + chatUI.style.top = t < 0 ? 0 : Math.min(t,maxTop)+"px" + + chatUI.style.transform='' + //chatUI.style.top = t < 0 ? 0 : Math.min(t,maxTop) + } + //return false; //这里为了避免抖动 + }; + document.onmouseup = function(e) { + document.onmousemove = null; + document.onmouseup = null; + e.target.style.cursor='auto' + }; + + if (e.preventDefault){ + e.preventDefault(); + } +} +/*base64转blob*/ +export function previewHtmlByBase64 (base64Str, fileType) { + if (base64Str) { + const myBlob = parseBase64ToBlob(base64Str, fileType) + return myBlob + } + } +export function parseBase64ToBlob (data, fileType) { + let bstr = window.atob(data) + let n = bstr.length + const u8arr = new Uint8Array(n) + while (n--) { + u8arr[n] = bstr.charCodeAt(n) + } + return new Blob([u8arr], { type: fileType }) +} +// 表情解码 +export function entitiestoUtf16 (strObj) { + const patt = /&#\d+;/g; + const arr = strObj.match(patt) || []; + + let H; + let L; + let code; + + for (let i = 0; i < arr.length; i += 1) { + code = arr[i]; + code = code.replace('&#', '').replace(';', ''); + // 高位 + H = Math.floor((code - 0x10000) / 0x400) + 0xD800; + // 低位 + L = ((code - 0x10000) % 0x400) + 0xDC00; + code = `&#${code};`; + const s = String.fromCharCode(H, L); + strObj = strObj.replace(code, s); + } + return strObj; + } +// 表情转义保存数据库 +export function utf16toEntities (str){ +const patt = /[\ud800-\udbff][\udc00-\udfff]/g; // 检测utf16字符正则 +str = str.replace(patt, (char) => { + let H; + let L; + let code; + let s; + + if (char.length === 2) { + H = char.charCodeAt(0); // 取出高位 + L = char.charCodeAt(1); // 取出低位 + code = (H - 0xD800) * 0x400 + 0x10000 + L - 0xDC00; // 转换算法 + s = `&#${code};`; + } else { + s = char; + } + + return s; +}); + +return str; +} +//补齐html标签 +export function closeHTML(str){ + var arrTags=["span","font","b","u","i","h1","h2","h3","h4","h5","h6","p","li","ul","table","div"]; + for(var i=0;i]+|)\\>","ig"); + var arrMatch=str.match(re); + if(arrMatch!=null) intOpen=arrMatch.length; + re=new RegExp("\\<\\/"+arrTags[i]+"\\>","ig"); + arrMatch=str.match(re); + if(arrMatch!=null) intClose=arrMatch.length; + for(var j=0;j"; + } + } + return str; +} +/*图片处理*/ +export const imageHandleUtil = { + //插入图片 + insertImage(img, range, reformMsg, resetCachedRange,resetLoadling){ + range.insertNode(img) + resetCachedRange && resetCachedRange(range) + range.collapse(false) + window.getSelection().removeAllRanges(); + window.getSelection().addRange(range); + reformMsg && reformMsg() + resetLoadling && resetLoadling() + }, + //图片调整大小、增加onclick事件 + changeImage(img, showPic, imgInputSize, imgMsgSize, imgPreviewSize){ + let w = img.width + let h =img.height + let inputSize = GetNewsize(imgInputSize,w,h) + let msgSize = GetNewsize(imgMsgSize,w,h) + let previewSize = GetNewsize(imgPreviewSize,w,h) + + img.style.width=inputSize.width+"px" + img.style.height=inputSize.height+"px" + + let realWidth = img.realWidth = w+"px" + let realHeight = img.realHeight = h+"px" + let msgWidth = img.msgWidth = msgSize.width+"px" + let msgHeight = img.msgHeight = msgSize.height+"px" + let previewidth = img.previewidth = previewSize.width+"px" + let previewHeight = img.previewHeight = previewSize.height+"px" + + img.onclick = function() { + let src = img.src + let param = { + src, realWidth, realHeight, msgWidth, msgHeight, previewidth, previewHeight + } + showPic(param); + } + }, + //粘贴图片 + pasteImages (params){ + let {srcArr, sig, el, range, reformMsg, showPic, resetCachedRange, resetLoadling, imgInputSize, imgMsgSize, imgPreviewSize} = params + var img=new Image(); + img.src=srcArr[sig].src + img.onload=function(){ + //console.log(srcArr[sig]) + //if(!srcArr[sig].style.width || srcArr[sig].style.width==undefined || srcArr[sig].style.width==''){ + if(srcArr[sig].src.indexOf('base64')!=-1){ + var dataarr = img.src.split(',') //分割为数组,分割到第一个逗号 + let mime = dataarr[0].match(/:(.*?);/)[1] //获取分割后的base64前缀中的类型 + let myBlob = parseBase64ToBlob(dataarr[1], mime); + srcArr[sig].src = URL.createObjectURL(myBlob); + } + imageHandleUtil.changeImage(srcArr[sig], showPic, imgInputSize, imgMsgSize, imgPreviewSize) + //} + if(sig{ + imageHandleUtil.changeImage(img, showPic, imgInputSize, imgMsgSize, imgPreviewSize) + imageHandleUtil.insertImage(img, range, reformMsg, resetCachedRange, resetLoadling) + } + } + }, + // canvas转dataURL:canvas对象、转换格式、图像品质 + canvasToDataURL(canvas, format, quality){ + return canvas.toDataURL(format||'image/*', quality||1.0); + }, + // DataURL转Blob对象 + dataURLToBlob(dataurl){ + var arr = dataurl.split(','); + var mime = arr[0].match(/:(.*?);/)[1]; + let blob = parseBase64ToBlob(arr[1], mime); + return blob + }, + // image转canvas:图片地址 + imageToCanvas(src, cb){ + var canvas = document.createElement('CANVAS'); + var ctx = canvas.getContext('2d'); + var img = new Image(); + + img.onload = function (){ + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0); + cb(canvas); + }; + img.setAttribute("crossOrigin",'anonymous') + img.src = src + }, + // image转Blob + imageToBlob(msgData, imgUpload){ + imageHandleUtil.imageToCanvas(msgData.message.content.content, function (canvas){ + let blob = imageHandleUtil.dataURLToBlob(imageHandleUtil.canvasToDataURL(canvas)); + imgUpload && imgUpload(blob, msgData) + }) + }, + uploadImage(msgData, imgUpload){ + imageHandleUtil.imageToBlob(msgData, imgUpload) + }, +} + +//拖动组件 +export const handleElement = { + handleDrag(e,imgRef){ + const o = imgRef.current + const s = o.style + const p = 'onmousemove' + //在jsx中需要用e.persist()此方法会从池中移除合成事件,允许用户代码保留对事件的引用,否则clientX会是null + let x = e.clientX - o.offsetLeft; + let y = e.clientY - o.offsetTop; + document[p] = function (e) { + s.left = e.clientX - x + 'px'; + s.top = e.clientY - y + 'px'; + } + + document.onmouseup = function () { + document[p] = null + } + }, + handleZoom(e, imgRef, scales, changeScales){ + let { style } = imgRef.current + if (e.nativeEvent.deltaY <= 0 && scales < 5 ) { + let a = scales+0.05 + style.transform = 'translate(-50%,-50%) scale('+a+')' + changeScales && changeScales(a) + } else if (e.nativeEvent.deltaY > 0) { + if(scales>0.5){ + let a = scales-0.05 + style.transform = 'translate(-50%,-50%) scale('+a+') ' + changeScales && changeScales(a) + } + } + }, + + handleStopDrag(e){ + e.preventDefault() + }, +} +export function downloadFile(data, name, suffix) { + if (window.navigator && window.navigator.msSaveBlob) { + window.navigator.msSaveBlob(new Blob([data]),name + (suffix ? ('.' + suffix) : '')); + }else{ + const url = window.URL.createObjectURL(new Blob([data])) + const link = document.createElement('a') + link.style.display = 'none' + link.href = url + // const fileName = parseTime(new Date()) + '-' + name + (suffix ? ('.' + suffix) : '') + const fileName = name + (suffix ? ('.' + suffix) : '') + link.setAttribute('download', fileName) + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + } + } +//加密 +export function doEncrypt(value) { + let encryptData = sm2.doEncrypt(value, `${REACT_APP_CUSTOMERSERVICE_PUBLICKEY}`, `${REACT_APP_CUSTOMERSERVICE_CIPHERCODE}`) + return '04' + encryptData; + } + //解密 + export function doDecrypt(value) { + let newValue = value.slice(2, value.length); + let decryptData = sm2.doDecrypt(newValue, `${REACT_APP_CUSTOMERSERVICE_PRIVATEKEY}`, `${REACT_APP_CUSTOMERSERVICE_CIPHERCODE}`) // 解密结果 + return decryptData; + } \ No newline at end of file diff --git a/src/services/customerservice.js b/src/services/customerservice.js new file mode 100644 index 0000000..e65295f --- /dev/null +++ b/src/services/customerservice.js @@ -0,0 +1,60 @@ +import { $post, $get } from '@/customerServiceHelpers/request'; +import request from '@/utils/request'; + +export const customerserviceAPI = { + //发送消息 + sendToCs(params) { + return request('/api/api/biz-customer-service/msg/sendToCs',{ + method:'POST', + data:{ + ...params, + } + }); + }, + //上传附件 + fileUpload(params){ + return $post('/api/doc/v1.0/files/upload?appCode=upload_cs&objectId=22&objectType='+params.objectType, { + params: params.multipartFiles, + headers: { + 'Content-Type': 'multipart/form-data' + }, + }); + }, + //下载附件 + getSecretKey(params) { + const downloadService = '/api/doc/api/data-service-document-center' + return $get(`${downloadService}` + '/outer/v1.0/files/getSecretKey', { params }); + }, + /** + * + * @param {object} params + * + * 物流订单详情附件下载 + */ + getDownload(params) { + const downloadService = '/api/doc/api/data-service-document-center' + return $post(`${downloadService}` + '/outer/v1.0/files/getDownloadBody', { params, responseType: 'arraybuffer' }); + }, + check(params) { + return request('/api/api/biz-customer-service/conversation/check',{ + method:'POST', + data:{ + ...params, + } + }); + }, + //评价 + estimate(params) { + return request('/api/api/biz-customer-service/outer/v1.0/eshopConversation/updateConversationById',{ + method:'POST', + data:{ + ...params, + } + }); + }, + //获取咨询项目 + list(params) { + let method = "get"; + return request('/api/biz-service-ebtp-tender/v1/participateProject/list?supplierNumber=' + params.supplierNumber, { method: method }) + }, +} \ No newline at end of file