Merge remote-tracking branch 'yudao/develop'

pull/125/head
puhui999 2024-11-18 17:32:38 +08:00
commit 7017561242
4 changed files with 105 additions and 63 deletions

View File

@ -30,11 +30,11 @@
支持 Spring Boot、Spring Cloud 两种架构:
① Spring Boot 单体架构:<https://github.com/YunaiV/ruoyi-vue-pro>
① Spring Boot 单体架构:<https://doc.iocoder.cn>
![架构图](/.image/common/ruoyi-vue-pro-architecture.png)
② Spring Cloud 微服务架构:<https://github.com/YunaiV/yudao-cloud>
② Spring Cloud 微服务架构:<https://cloud.iocoder.cn>
![架构图](/.image/common/yudao-cloud-architecture.png)

View File

@ -1,20 +1,35 @@
<template>
<!-- 聊天虚拟列表 -->
<z-paging ref="pagingRef" v-model="messageList" use-chat-record-mode use-virtual-list
cell-height-mode="dynamic" default-page-size="20" :auto-clean-list-when-reload="false"
safe-area-inset-bottom bottom-bg-color="#f8f8f8" :back-to-top-style="backToTopStyle"
:auto-show-back-to-top="showNewMessageTip" @backToTopClick="onBackToTopClick"
@scrolltoupper="onScrollToUpper" @query="queryList">
<z-paging
ref="pagingRef"
v-model="messageList"
use-chat-record-mode
use-virtual-list
cell-height-mode="dynamic"
default-page-size="20"
:auto-clean-list-when-reload="false"
safe-area-inset-bottom
bottom-bg-color="#f8f8f8"
:back-to-top-style="backToTopStyle"
:auto-show-back-to-top="showNewMessageTip"
@backToTopClick="onBackToTopClick"
@scrolltoupper="onScrollToUpper"
@query="queryList"
>
<template #top>
<!-- 撑一下顶部导航 -->
<view :style="{ height: sys_navBar + 'px' }"></view>
</template>
<!-- style="transform: scaleY(-1)"必须写否则会导致列表倒置 -->
<!-- 注意不要直接在chat-item组件标签上设置style因为在微信小程序中是无效的请包一层view -->
<template #cell="{item,index}">
<template #cell="{ item, index }">
<view style="transform: scaleY(-1)">
<!-- 消息渲染 -->
<MessageListItem :message="item" :message-index="index" :message-list="messageList"></MessageListItem>
<MessageListItem
:message="item"
:message-index="index"
:message-list="messageList"
></MessageListItem>
</view>
</template>
<!-- 底部聊天输入框 -->
@ -41,13 +56,13 @@
const showNewMessageTip = ref(false); //
const refreshMessage = ref(false); //
const backToTopStyle = reactive({
'width': '100px',
width: '100px',
'background-color': '#fff',
'border-radius': '30px',
'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.1)',
'display': 'flex',
'justifyContent': 'center',
'alignItems': 'center',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}); //
const queryParams = reactive({
no: 1, //
@ -106,6 +121,7 @@
onScrollToUpper();
}
};
/** 滚动到最新消息 */
const onBackToTopClick = (event) => {
event(false); //

View File

@ -1,17 +1,35 @@
<template>
<s-layout class="chat-wrap" :title="!isReconnecting ? '连接客服成功' : '会话重连中'" navbar="inner">
<s-layout
class="chat-wrap"
:title="!isReconnecting ? '连接客服成功' : '会话重连中'"
navbar="inner"
>
<!-- 覆盖头部导航栏背景颜色 -->
<div class="page-bg" :style="{ height: sys_navBar + 'px' }"></div>
<!-- 聊天区域 -->
<MessageList ref="messageListRef">
<template #bottom>
<message-input v-model="chat.msg" @on-tools="onTools" @send-message="onSendMessage"></message-input>
<message-input
v-model="chat.msg"
@on-tools="onTools"
@send-message="onSendMessage"
></message-input>
</template>
</MessageList>
<!-- 聊天工具 -->
<tools-popup :show-tools="chat.showTools" :tools-mode="chat.toolsMode" @close="handleToolsClose"
@on-emoji="onEmoji" @image-select="onSelect" @on-show-select="onShowSelect">
<message-input v-model="chat.msg" @on-tools="onTools" @send-message="onSendMessage"></message-input>
<tools-popup
:show-tools="chat.showTools"
:tools-mode="chat.toolsMode"
@close="handleToolsClose"
@on-emoji="onEmoji"
@image-select="onSelect"
@on-show-select="onShowSelect"
>
<message-input
v-model="chat.msg"
@on-tools="onTools"
@send-message="onSendMessage"
></message-input>
</tools-popup>
<!-- 商品订单选择 -->
<SelectPopup
@ -30,7 +48,10 @@
import ToolsPopup from '@/pages/chat/components/toolsPopup.vue';
import MessageInput from '@/pages/chat/components/messageInput.vue';
import SelectPopup from '@/pages/chat/components/select-popup.vue';
import { KeFuMessageContentTypeEnum, WebSocketMessageTypeConstants } from '@/pages/chat/util/constants';
import {
KeFuMessageContentTypeEnum,
WebSocketMessageTypeConstants,
} from '@/pages/chat/util/constants';
import FileApi from '@/sheep/api/infra/file';
import KeFuApi from '@/sheep/api/promotion/kefu';
import { useWebSocket } from '@/sheep/hooks/useWebSocket';
@ -105,7 +126,7 @@
const res = await FileApi.uploadFile(data.tempFiles[0].path);
msg = {
contentType: KeFuMessageContentTypeEnum.IMAGE,
content: JSON.stringify({picUrl: res.data}),
content: JSON.stringify({ picUrl: res.data }),
};
break;
case 'goods':
@ -135,8 +156,7 @@
//======================= end =======================
const { options } = useWebSocket({
//
onConnected: async () => {
},
onConnected: async () => {},
//
onMessage: async (data) => {
const type = data.type;
@ -161,7 +181,6 @@
<style scoped lang="scss">
.chat-wrap {
.page-bg {
width: 100%;
position: absolute;

View File

@ -1,4 +1,4 @@
import dayjs from "dayjs";
import dayjs from 'dayjs';
/**
* 将一个整数转换为分数保留两位小数
@ -6,10 +6,10 @@ import dayjs from "dayjs";
* @return {number} 分数
*/
export const formatToFraction = (num) => {
if (typeof num === 'undefined') return 0
const parsedNumber = typeof num === 'string' ? parseFloat(num) : num
return parseFloat((parsedNumber / 100).toFixed(2))
}
if (typeof num === 'undefined') return 0;
const parsedNumber = typeof num === 'string' ? parseFloat(num) : num;
return parseFloat((parsedNumber / 100).toFixed(2));
};
/**
* 将一个数转换为 1.00 这样
@ -19,26 +19,26 @@ export const formatToFraction = (num) => {
* @return {string} 分数
*/
export const floatToFixed2 = (num) => {
let str = '0.00'
let str = '0.00';
if (typeof num === 'undefined') {
return str
return str;
}
const f = formatToFraction(num)
const decimalPart = f.toString().split('.')[1]
const len = decimalPart ? decimalPart.length : 0
const f = formatToFraction(num);
const decimalPart = f.toString().split('.')[1];
const len = decimalPart ? decimalPart.length : 0;
switch (len) {
case 0:
str = f.toString() + '.00'
break
str = f.toString() + '.00';
break;
case 1:
str = f.toString() + '.0'
break
str = f.toString() + '.0';
break;
case 2:
str = f.toString()
break
str = f.toString();
break;
}
return str
}
return str;
};
/**
* 将一个分数转换为整数
@ -47,11 +47,11 @@ export const floatToFixed2 = (num) => {
* @return {number} 整数
*/
export const convertToInteger = (num) => {
if (typeof num === 'undefined') return 0
const parsedNumber = typeof num === 'string' ? parseFloat(num) : num
if (typeof num === 'undefined') return 0;
const parsedNumber = typeof num === 'string' ? parseFloat(num) : num;
// TODO 分转元后还有小数则四舍五入
return Math.round(parsedNumber * 100)
}
return Math.round(parsedNumber * 100);
};
/**
* 时间日期转换
@ -64,16 +64,16 @@ export const convertToInteger = (num) => {
* @description format 季度 + 星期 + 几周"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
* @returns {string} 返回拼接后的时间字符串
*/
export function formatDate(date, format= 'YYYY-MM-DD HH:mm:ss') {
export function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
// 日期不存在,则返回空
if (!date) {
return ''
return '';
}
// 日期存在,则进行格式化
if (format === undefined) {
format = 'YYYY-MM-DD HH:mm:ss'
format = 'YYYY-MM-DD HH:mm:ss';
}
return dayjs(date).format(format)
return dayjs(date).format(format);
}
/**
@ -85,16 +85,22 @@ export function formatDate(date, format= 'YYYY-MM-DD HH:mm:ss') {
* @param {*} children 孩子节点字段 默认 'children'
* @param {*} rootId 根Id 默认 0
*/
export function handleTree(data, id = 'id', parentId = 'parentId', children = 'children', rootId = 0) {
export function handleTree(
data,
id = 'id',
parentId = 'parentId',
children = 'children',
rootId = 0,
) {
// 对源数据深度克隆
const cloneData = JSON.parse(JSON.stringify(data))
const cloneData = JSON.parse(JSON.stringify(data));
// 循环所有项
const treeData = cloneData.filter(father => {
let branchArr = cloneData.filter(child => {
const treeData = cloneData.filter((father) => {
let branchArr = cloneData.filter((child) => {
//返回每一项的子级数组
return father[id] === child[parentId]
return father[id] === child[parentId];
});
branchArr.length > 0 ? father.children = branchArr : '';
branchArr.length > 0 ? (father.children = branchArr) : '';
//返回第一层
return father[parentId] === rootId;
});
@ -120,17 +126,18 @@ export function resetPagination(pagination) {
* @param source 源对象
*/
export const copyValueToTarget = (target, source) => {
const newObj = Object.assign({}, target, source)
const newObj = Object.assign({}, target, source);
// 删除多余属性
Object.keys(newObj).forEach((key) => {
// 如果不是target中的属性则删除
if (Object.keys(target).indexOf(key) === -1) {
delete newObj[key]
delete newObj[key];
}
})
});
// 更新目标对象值
Object.assign(target, newObj)
}
Object.assign(target, newObj);
};
/**
* 解析 JSON 字符串
*
@ -138,9 +145,9 @@ export const copyValueToTarget = (target, source) => {
*/
export function jsonParse(str) {
try {
return JSON.parse(str)
return JSON.parse(str);
} catch (e) {
console.error(`str[${str}] 不是一个 JSON 字符串`)
return ''
console.error(`str[${str}] 不是一个 JSON 字符串`);
return '';
}
}