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 0000000..6095106
Binary files /dev/null and b/src/pages/customerservice/support/conversation/image/emoji_black.png differ
diff --git a/src/pages/customerservice/support/conversation/image/emoji_light.png b/src/pages/customerservice/support/conversation/image/emoji_light.png
new file mode 100644
index 0000000..ebc0d2c
Binary files /dev/null and b/src/pages/customerservice/support/conversation/image/emoji_light.png differ
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 0000000..ac827b1
Binary files /dev/null and b/src/pages/customerservice/support/conversation/image/file_black.png differ
diff --git a/src/pages/customerservice/support/conversation/image/file_light.png b/src/pages/customerservice/support/conversation/image/file_light.png
new file mode 100644
index 0000000..204df98
Binary files /dev/null and b/src/pages/customerservice/support/conversation/image/file_light.png differ
diff --git a/src/pages/customerservice/support/conversation/image/pic_black.png b/src/pages/customerservice/support/conversation/image/pic_black.png
new file mode 100644
index 0000000..e14669b
Binary files /dev/null and b/src/pages/customerservice/support/conversation/image/pic_black.png differ
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 0000000..a3f3016
Binary files /dev/null and b/src/pages/customerservice/support/conversation/image/pic_light.png differ
diff --git a/src/pages/customerservice/support/conversation/image/server-avatar.jpg b/src/pages/customerservice/support/conversation/image/server-avatar.jpg
new file mode 100644
index 0000000..abb7ffe
Binary files /dev/null and b/src/pages/customerservice/support/conversation/image/server-avatar.jpg differ
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 0000000..bd61399
Binary files /dev/null and b/src/pages/customerservice/support/conversation/image/uploadFile.png differ
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 && }
+ {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