This commit is contained in:
@zuopngfei 2025-10-23 18:28:19 +08:00
parent d7772a23b0
commit 884870d9b7
15 changed files with 966 additions and 282 deletions

3
components.d.ts vendored
View File

@ -8,6 +8,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
ChatRecord: typeof import('./src/components/ChatRecord/index.vue')['default']
copy: typeof import('./src/components/UserCard copy/index.vue')['default']
ElAside: typeof import('element-plus/es')['ElAside']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
@ -66,6 +67,8 @@ declare module 'vue' {
UserKeyWord: typeof import('./src/components/KeyWords/userKeyWord.vue')['default']
UserMessage: typeof import('./src/components/UserMessage/index.vue')['default']
Welcome: typeof import('./src/components/Welcome/index.vue')['default']
WxChatRecord: typeof import('./src/components/wxChatRecord/index.vue')['default']
WxUserCard: typeof import('./src/components/WxUserCard/index.vue')['default']
}
export interface ComponentCustomProperties {
vInfiniteScroll: typeof import('element-plus/es')['ElInfiniteScroll']

View File

@ -5,7 +5,7 @@
<link rel="icon" type="image" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>小程序聊天</title>
<script src="//at.alicdn.com/t/c/font_4781615_as5g6ws9ae.js"></script>
<script src="//at.alicdn.com/t/c/font_4781615_6z0ukqz2smj.js"></script>
</head>
<body>
<div id="app"></div>

View File

@ -18,7 +18,6 @@
"lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix"
},
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@element-plus/icons-vue": "^2.1.0",
"axios": "^1.4.0",
"bigdecimal": "^0.6.1",

View File

@ -123,7 +123,7 @@ export const deleteAgent = (data: any) => {
export const keyWordArr = (data: any) => {
return request({
url: `api/group/keywords`,
url: `admin/app/keywords`,
method: 'get',
params: data
})
@ -131,7 +131,7 @@ export const keyWordArr = (data: any) => {
export const deleteKeyword = (id) => {
return request({
url: `api/group/keyword/${id}`,
url: `admin/app/keyword/${id}`,
method: 'delete'
})
}

View File

@ -0,0 +1,49 @@
// 用户管理
import request from '@/utils/request'
// 意图管理
export const keywords = (data: any) => {
return request({
url: `api/group/keywords`,
method: 'get',
params: data
})
}
export const addKeywords = (data: any) => {
return request({
url: `api/group/keyword`,
method: 'post',
data
})
}
export const addKeywordsMaterial = (data: any, id) => {
return request({
url: `api/group/keyword/material/${id}`,
method: 'post',
data
})
}
export const keywordsMaterials = (data: any) => {
return request({
url: `api/group/keyword/materials`,
method: 'get',
params: data
})
}
export const upKeyword = (data: any, id) => {
return request({
url: `api/group/keyword/${id}`,
method: 'put',
data
})
}
export const deleteKeyword = (id) => {
return request({
url: `api/group/keyword/${id}`,
method: 'delete'
})
}

View File

@ -0,0 +1,49 @@
// 用户管理
import request from '@/utils/request'
// 意图管理
export const keywords = (data: any) => {
return request({
url: `api/contact/keywords`,
method: 'get',
params: data
})
}
export const addKeywords = (data: any) => {
return request({
url: `api/contact/keyword`,
method: 'post',
data
})
}
export const addKeywordsMaterial = (data: any, id) => {
return request({
url: `api/contact/keyword/material/${id}`,
method: 'post',
data
})
}
export const keywordsMaterials = (data: any) => {
return request({
url: `api/contact/keyword/materials`,
method: 'get',
params: data
})
}
export const upKeyword = (data: any, id) => {
return request({
url: `api/contact/keyword/${id}`,
method: 'put',
data
})
}
export const deleteKeyword = (id) => {
return request({
url: `api/contact/keyword/${id}`,
method: 'delete'
})
}

View File

@ -7,7 +7,7 @@ import request from '@/utils/request'
export const keywords = (data: any) => {
return request({
url: `api/group/keywords`,
url: `admin/app/keywords`,
method: 'get',
params: data
})
@ -15,35 +15,35 @@ export const keywords = (data: any) => {
export const addKeywords = (data: any) => {
return request({
url: `api/group/keyword`,
url: `admin/app/keyword`,
method: 'post',
data
})
}
export const addKeywordsMaterial = (data: any, id) => {
return request({
url: `api/group/keyword/material/${id}`,
url: `admin/app/keyword/material/${id}`,
method: 'post',
data
})
}
export const keywordsMaterials = (data: any) => {
return request({
url: `api/group/keyword/materials`,
url: `admin/app/keyword/materials`,
method: 'get',
params: data
})
}
export const upKeyword = (data: any, id) => {
return request({
url: `api/group/keyword/${id}`,
url: `admin/app/keyword/${id}`,
method: 'put',
data
})
}
export const deleteKeyword = (id) => {
return request({
url: `api/group/keyword/${id}`,
url: `admin/app/keyword/${id}`,
method: 'delete'
})
}

View File

@ -210,8 +210,7 @@ const save = async () => {
} else {
const resKeys = await addKeywords({
"keyword": ruleForm.value.keyword,
"robot_id": userRobotStore.robotInfo.id,
"room_id": props.groupInfo.room_id
"app_id": props.groupInfo.app_id,
})
key_id = resKeys.id
}

View File

@ -340,7 +340,6 @@ import { addMaterial, updataMaterial, deletaMaterial } from '@/api/material'
import { userList } from '@/api/user'
import { minis, voices } from '@/api/selfMiniVoice'
import Upload from '@/components/Upload/index.vue'
import AMapLoader from '@amap/amap-jsapi-loader'
import robotStore from '@/store/modules/robotStore'
import { uploadProps } from 'element-plus'
import V3Emoji from "vue3-emoji";
@ -604,117 +603,7 @@ onMounted(async () => {
getCards()
}
if (ruleForm.value.type == '6') {
AMapLoader.load({
key: '6c581ef31373ac8659ca4db49c6f1032',
version: '2.0',
//
plugins: ["AMap.Geocoder", "AMap.AutoComplete", 'AMap.Geolocation', 'AMap.PlaceSearch', 'AMap.Marker'],
})
.then((AMap) => {
map = new AMap.Map('mapContainer', {
viewMode: '2D',
zoom: 13,
// mapStyle: 'amap://styles/grey', //
center: ['121.475164', '31.228816'],
})
if (marker) {
map.remove(marker);
}
if (isEdit) {
map.setFitView();
marker = new AMap.Marker({
map: map,
// icon: "//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png",
position: [ruleForm.value.content.longitude, ruleForm.value.content.latitude],
// offset: new AMap.Pixel(-13, -30)
});
map.setFitView();
map.add(marker);
}
// AMap.plugin('AMap.AutoComplete', function () {
// var autoOptions = {
// city: '010',
// input: 'tipinput', //inputid
// outPutDirAuto: true,
// }
// var AutoComplete = new AMap.AutoComplete(autoOptions);
// AutoComplete.search(function (status, result) {
// // result
// })
// })
//
AutoComplete = new AMap.AutoComplete(autoOptions);
var placeSearch = new AMap.PlaceSearch({
map: map
}); //
AutoComplete.on("select", select);//
function select(e) {
placeSearch.setCity(e.poi.adcode);
placeSearch.search(e.poi.name); //
ruleForm.value.content.address = e.poi.name
ruleForm.value.content.detailed_address = e.poi.district + e.poi.address
ruleForm.value.content.longitude = e.poi.location.lng
ruleForm.value.content.latitude = e.poi.location.lat
}
//click
map.on('click', function (e) {
map.value = e.lnglat.getLng() + ',' + e.lnglat.getLat()
// '', e.lnglat;
//
ruleForm.value.content.longitude = e.lnglat.lng;
ruleForm.value.content.latitude = e.lnglat.lat;
//
if (marker) {
map.remove(marker);
}
//
// setMapMarker();
marker = new AMap.Marker({
map: map,
// icon: "//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png",
position: [ruleForm.value.content.longitude, ruleForm.value.content.latitude],
// offset: new AMap.Pixel(-13, -30)
});
// regeoCode();
geocoder = new AMap.Geocoder({
// city: "010", //
radius: 1000 //500
});
let lnglat = [e.lnglat.lng, e.lnglat.lat];
geocoder.getAddress(lnglat, function (status, result) {
if (status === 'complete' && result.regeocode) {
let address = result.regeocode.formattedAddress;
ruleForm.value.content.address = result.regeocode.addressComponent.province + result.regeocode.addressComponent.district
ruleForm.value.content.detailed_address = result.regeocode.addressComponent.township + result.regeocode.addressComponent.street + result.regeocode.addressComponent.streetNumber + result.regeocode.addressComponent.neighborhood
// ''address;
let obj = {
ParkLongitude: e.lnglat.lng + '',
ParkLatitude: e.lnglat.lat + '',
address: address
}
// emit("clickChild", obj);
} else {
// log.error('')
}
});
});
// isTrue.value=true
})
.catch((e) => {
})
}
})
onUnmounted(() => {

View File

@ -359,18 +359,13 @@
</template>
<!-- 转人工 -->
<template v-if="ruleForm.type == 11 && isKeyWords && isHs">
<!-- <template v-if="ruleForm.type == 11 && isKeyWords && isHs">
<el-form-item label="选择通知群">
<el-select-v2 v-model="ruleForm.content.group.group_id" popper-class="costom-select"
:options="groupArr" placeholder="请选择群" style="width: 500px" :props="v2props2" collapse-tags
collapse-tags-tooltip filterable>
<!-- <template #default="{ item }">
<div class="costom-select-item">
<el-image :src="item.avatar"></el-image>
<span>{{ item.nickname }}</span>
</div>
</template> -->
</el-select-v2>
</el-form-item>
@ -403,7 +398,7 @@
<el-input type="textarea" v-model="ruleForm.content.auto_reply" placeholder="输入内容" />
<V3Emoji @click-emoji="onEmojiSelect3" :recent="true"></V3Emoji>
</el-form-item>
</template>
</template> -->
<el-form-item v-if="isHs" label="间隔时间s" prop="interval_seconds">
<el-input-number v-model="ruleForm.interval_seconds" :min="1" />
</el-form-item>
@ -486,7 +481,6 @@ import { sendMessages } from '@/api/message'
import { minis, voices } from '@/api/selfMiniVoice'
import { userList } from '@/api/user'
import { materialList } from '@/api/material'
import AMapLoader from '@amap/amap-jsapi-loader'
import { groupMemberList, groupList } from '@/api/group'
import MaterialLibrary from '@/views/materialLibrary/index.vue'
import V3Emoji from "vue3-emoji";
@ -495,43 +489,46 @@ const materialTypes = ref([{
label: '文本',
value: 1,
url: 'send_text_message'
}, {
},
{
label: '图片',
value: 2,
url: 'send_picture_message'
}, {
label: '语音',
value: 3,
url: 'send_voice_message'
}, {
label: '视频',
value: 4,
url: 'send_video_message'
}, {
label: '小程序',
value: 5,
url: 'send_mini_program_message'
}, {
label: '位置',
value: 6,
url: 'send_location_message'
}, {
label: '链接',
value: 7,
url: 'send_link_message'
}, {
label: 'GIF图片',
value: 8,
url: 'send_gif_message'
}, {
label: '名片',
value: 9,
url: 'send_card_message'
}, {
label: '文件',
value: 10,
url: 'send_file_message'
}])
},
//{
// label: '',
// value: 3,
// url: 'send_voice_message'
// }, {
// label: '',
// value: 4,
// url: 'send_video_message'
// }, {
// label: '',
// value: 5,
// url: 'send_mini_program_message'
// }, {
// label: '',
// value: 6,
// url: 'send_location_message'
// }, {
// label: '',
// value: 7,
// url: 'send_link_message'
// }, {
// label: 'GIF',
// value: 8,
// url: 'send_gif_message'
// }, {
// label: '',
// value: 9,
// url: 'send_card_message'
// }, {
// label: '',
// value: 10,
// url: 'send_file_message'
// }
])
let sendUrl = 'send_text_message'
const robotHost = computed(() => {
return userRobotStore.robotInfo.server_ip
@ -977,23 +974,23 @@ let marker = null
let geocoder = null
onMounted(() => {
if (props.sendGroup == 'sendGroup' ) {
if (!props.allUserIds) {
materialTypes.value.push({
label: '@消息',
value: 11,
url: 'send_group_at_message'
})
}
// if (props.sendGroup == 'sendGroup' ) {
// if (!props.allUserIds) {
// materialTypes.value.push({
// label: '@',
// value: 11,
// url: 'send_group_at_message'
// })
// }
} else {
if (props.isKeyWords) {
materialTypes.value.push({
label: '转人工',
value: 11
})
}
}
// } else {
// if (props.isKeyWords) {
// materialTypes.value.push({
// label: '',
// value: 11
// })
// }
// }
if(props.editData){
ruleForm.value = props.editData
@ -1004,98 +1001,7 @@ onMounted(() => {
}
}
AMapLoader.load({
key: '6c581ef31373ac8659ca4db49c6f1032',
version: '2.0',
//
plugins: ["AMap.Geocoder", "AMap.AutoComplete", 'AMap.Geolocation', 'AMap.PlaceSearch'],
})
.then((AMap) => {
map = new AMap.Map('container', {
viewMode: '2D',
zoom: 13,
// mapStyle: 'amap://styles/grey', //
center: ['121.475164', '31.228816'],
})
// AMap.plugin('AMap.AutoComplete', function () {
// var autoOptions = {
// city: '010',
// input: 'tipinput', //inputid
// outPutDirAuto: true,
// }
// var AutoComplete = new AMap.AutoComplete(autoOptions);
// AutoComplete.search(function (status, result) {
// // result
// })
// })
//
AutoComplete = new AMap.AutoComplete(autoOptions);
var placeSearch = new AMap.PlaceSearch({
map: map
}); //
AutoComplete.on("select", select);//
function select(e) {
placeSearch.setCity(e.poi.adcode);
placeSearch.search(e.poi.name); //
ruleForm.value.content.address = e.poi.name
ruleForm.value.content.detailed_address = e.poi.district + e.poi.address
ruleForm.value.content.longitude = e.poi.location.lng
ruleForm.value.content.latitude = e.poi.location.lat
}
//click
map.on('click', function (e) {
map.value = e.lnglat.getLng() + ',' + e.lnglat.getLat()
ruleForm.value.content.longitude = e.lnglat.lng;
ruleForm.value.content.latitude = e.lnglat.lat;
//
if (marker) {
map.remove(marker);
}
//
// setMapMarker();
marker = new AMap.Marker({
map: map,
// icon: "//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png",
position: [ruleForm.value.content.longitude, ruleForm.value.content.latitude],
// offset: new AMap.Pixel(-13, -30)
});
// regeoCode();
geocoder = new AMap.Geocoder({
// city: "010", //
radius: 1000 //500
});
let lnglat = [e.lnglat.lng, e.lnglat.lat];
geocoder.getAddress(lnglat, function (status, result) {
if (status === 'complete' && result.regeocode) {
let address = result.regeocode.formattedAddress;
ruleForm.value.content.address = result.regeocode.addressComponent.province + result.regeocode.addressComponent.district
ruleForm.value.content.detailed_address = result.regeocode.addressComponent.township + result.regeocode.addressComponent.street + result.regeocode.addressComponent.streetNumber + result.regeocode.addressComponent.neighborhood
// ''address;
let obj = {
ParkLongitude: e.lnglat.lng + '',
ParkLatitude: e.lnglat.lat + '',
address: address
}
// emit("clickChild", obj);
} else {
// log.error('')
}
});
});
// isTrue.value=true
})
.catch((e) => {
})
})
onUnmounted(() => {
map && map.destroy();

View File

@ -0,0 +1,193 @@
<template>
<div style="width: 100%;">
<!-- <el-input v-model="centent" suffix-icon="Search" @input="inputSearch" placeholder="搜索"></el-input> -->
<ul v-infinite-scroll="load" class="infinite-list" style="overflow: auto" :style="'height:' + height" :infinite-scroll-immediate="false" infinite-scroll-distance="5">
<li v-for="item in cardlist" :key="item.sender_id" class="infinite-list-item" :class="{ active: item.active }"
@click="handleItem(item)">
<div class="avatar-container">
<el-image class="user-avatar" :src="item.sender_avatar"></el-image>
<span v-if="item.unread_count && item.unread_count > 0" class="unread-badge">{{ item.unread_count > 99 ? '99+' : item.unread_count }}</span>
</div>
<div class="name-block">
<span class="nickname">{{ item.sender_name }}</span>
<span v-if="item.send_time" class="send-time">{{ item.send_time }}</span>
</div>
<span class="sex">
<!-- <el-icon v-if="item.sex == '男'" style="color: rgb(121.3, 187.1, 255);">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-nan"></use>
</svg>
</el-icon>
<el-icon v-else style="color: rgb(248, 152.1, 152.1);">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-nv"></use>
</svg>
</el-icon> -->
<!-- <el-icon v-if="item.active" class="check"><Select /></el-icon> -->
</span>
</li>
</ul>
</div>
</template>
<script setup>
import { ref, onMounted, reactive } from 'vue'
const props = defineProps({
cardlist: {
type: Array,
default: []
},
multiple: {
type: Boolean,
default: false
},
height: {
type: String,
default: '300px'
}
})
const centent = ref('')
const emits = defineEmits(["update:modelValue", 'load', 'input', 'change'])
const load = () => {
emits('load')
}
let timer = null
const inputSearch = (e) => {
// if (timer != null) {
clearTimeout(timer)
// timer = null
// }
timer = setTimeout(() => {
emits('input', e)
}, 500)
}
const handleItem = (row) => {
if (props.multiple) {
if (!row.active) {
row.active = true
} else {
row.active = false
}
let arr = []
props.cardlist.forEach((el) => {
if (el.active === true) {
arr.push(el.sender_id)
}
})
setTimeout(() => {
emits('change', arr)
emits("update:modelValue", arr)
})
} else {
//
props.cardlist.forEach(el => { el.active = false })
// true
row.active = true
//
if (row.unread_count) row.unread_count = 0
// emitcardlistactive
emits('change', row.sender_id)
emits("update:modelValue", row.sender_id)
}
}
onMounted(() => {
})
</script>
<style lang="scss" scoped>
.infinite-list {
list-style: none;
}
.infinite-list .infinite-list-item {
display: flex;
height: 46px;
// margin: 10px 0;
padding: 0 10px;
line-height: 22px;
cursor: pointer;
position: relative;
&:hover {
background-color: var(--el-color-primary-light-9);
}
.avatar-container {
position: relative;
display: inline-block;
}
.user-avatar {
height: 32px;
width: 32px;
border-radius: 4px;
vertical-align: middle;
margin-top: 6px;
}
.unread-badge {
position: absolute;
top: 2px;
right: -6px;
background-color: #ff4d4f;
color: white;
border-radius: 10px;
padding: 2px 6px;
font-size: 10px;
font-weight: bold;
min-width: 16px;
height: 16px;
line-height: 7px;
text-align: center;
border: 2px solid white;
box-sizing: border-box;
}
.nickname {
margin: 0 8px 0 0px;
font-size: 14px ;
}
.name-block {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 8px;
}
.send-time {
font-size: 12px;
color: #999;
margin-top: 2px;
}
.sex {
.el-icon {
font-size: 18px;
transform: translateY(5px);
}
}
.check {
position: absolute;
right: 12px;
color: var(--el-color-primary);
top: 20%;
}
}
.active {
background-color: var(--el-color-primary-light-7);
}
.infinite-list .infinite-list-item+.list-item {
margin-top: 10px;
}
</style>

View File

@ -0,0 +1,442 @@
<template>
<div v-for="(element, index) in list">
<div class="materia-element" :class="{ robotSend : element.sender_id != sendeInfo.userInfo.user_id }">
<!-- <el-image v-if="element.sender_id == sendeInfo.robotInfo.wx_id" class="tobot-image" :src="sendeInfo.robotInfo.head_url"></el-image>
<template v-else> -->
<el-image v-if="sendeInfo.userInfo.user_id != element.sender_id" class="tobot-image" :src="robot_avatar"></el-image>
<el-image v-if="sendeInfo.userInfo.user_id == element.sender_id" class="tobot-image" :src="sendeInfo.userInfo.user_avatar"></el-image>
<!-- <span class="tobot-image text-logo" v-else> {{ element.sender_name.charAt(0) }} </span> -->
<!-- </template> -->
<div class="materia-info">
<!-- <span class="robot-name">{{ element.sender_name }}</span> -->
<!-- <span v-if="element.sender_id != sendeInfo.userInfo.user_id" class="robot-name">{{ sendeInfo.robotInfo.name }}</span>
<span v-if="element.sender_id == sendeInfo.userInfo.user_id" class="robot-name">{{ sendeInfo.userInfo.nickname }}</span> -->
<div class="text" v-if="element.msg_type == 1">
<pre>{{ element.content.messages }}</pre>
</div>
<div class="image" v-if="element.msg_type == 2">
<el-image :src="element.content.messages" fit="cover"
:preview-src-list="[element.content.messages]" preview-teleported>
<template #placeholder>
<div class="image-slot">Loading<span class="dot">...</span></div>
</template>
</el-image>
</div>
<div class="time">{{ element.send_time }}
<el-icon v-if="element._failed" class="retry" @click.stop="emits('retry', element._id)">
<svg class="icon" aria-hidden="true"><use xlink:href="#icon-jinggao"></use></svg>
</el-icon>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import mihoutai from '@/assets/images/mihoutai.png'
import addess_img from '@/assets/images/addess.png'
import user_avatar from '@/assets/images/user_avatar.png'
import robot_avatar from '@/assets/images/robot.png'
const emits = defineEmits(["update:modelValue", "editMateria", "retry"])
const props = defineProps({
msgList: {
type: Array,
default: []
},
sendeInfo: {
type: Object,
default: {}
},
msgType: {
type: String,
default: 'user'
}
})
const keyWordsText = ref(props.keyWords)
const list = ref(props.msgList)
watch(() => props.msgList, (val) => {
list.value = val
})
onMounted(() => {
})
</script>
<style lang="scss" scoped>
.materia-element {
overflow: hidden;
margin-bottom: 20px;
.tobot-image {
float: left;
width: 36px;
height: 36px;
margin-right: 15px;
border-radius: 4px;
}
.text-logo{
background-color: var(--el-color-primary);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
}
.materia-info {
width: 300px;
float: left;
.robot-name {
display: block;
font-size: 12px;
color: var(--el-text-color-secondary);
margin-bottom: 6px;
.edit {
color: var(--el-color-primary);
margin-left: 6px;
transform: translateY(3px);
cursor: pointer;
font-size: 16px;
}
.remove {
color: var(--el-color-danger);
margin-left: 6px;
transform: translateY(3px);
cursor: pointer;
font-size: 16px;
}
}
.text {
display: inline-block;
line-height: 22px;
background-color: #fff;
border-radius: 8px;
padding: 6px 12px;
font-size: 14px;
position: relative;
pre {
white-space: break-spaces;
}
&::before {
position: absolute;
width: 0;
height: 0;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-right: 10px solid #fff;
content: '';
top: 10px;
left: -8px;
}
}
.image {
height: auto;
.el-image {
width: 120px;
height: auto;
border-radius: 6px;
}
}
.gif {
.el-image {
width: 120px;
height: auto;
}
}
.audio {
width: 100px;
.el-icon {
font-size: 20px;
transform: translateY(5px);
}
}
.video {
video {
width: 180px;
height: auto;
}
}
.mini {
height: 95px;
width: 220px;
background-color: #fff;
padding: 0;
.card-user {
overflow: hidden;
padding: 10px;
}
.el-image {
height: 50px;
width: 50px;
border-radius: 4px;
vertical-align: middle;
float: left;
}
.name {
font-size: 15px;
margin-left: 5px;
transform: translateY(7px);
display: inline-block;
float: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
p {
height: 20px;
line-height: 22px;
border-top: 1px solid var(--el-border-color);
font-size: 12px;
color: var(--el-text-color-secondary);
padding-left: 10px;
}
}
.chengxu {
height: 103px;
.el-image {
height: 30px;
width: 30px;
margin-right: 5px;
}
.name {
transform: translateY(0px);
color: var(--el-text-color-secondary);
font-size: 12px;
line-height: 30px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.cg-text {
display: block;
padding-left: 10px;
padding-right: 10px;
line-height: 20px;
margin-bottom: 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
p {
line-height: 25px;
}
}
.address {
padding: 0;
// overflow: hidden;
width: 220px;
h4 {
line-height: 28px;
font-size: 14px;
margin-bottom: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
position: relative;
z-index: 0;
font-weight: normal;
padding: 0 10px;
margin-top: 5px;
}
p {
font-size: 12px;
color: var(--el-text-color-secondary);
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
position: relative;
z-index: 0;
padding: 0 10px;
}
.el-image {
height: 86px;
width: 100%;
position: relative;
z-index: 0;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
top: 5px;
}
}
.link {
width: 220px;
height: 100px;
.el-image {
height: 46px;
width: 46px;
float: right;
// transform: translateY(-17px);
margin-right: 0;
}
div {
// margin-top: 13px;
height: 50px;
h4 {
font-size: 14px;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
p {
color: var(--el-text-color-secondary);
height: 48px;
font-size: 12px;
float: left;
width: calc(100% - 55px);
line-height: normal;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
/* 防止文本换行 */
display: -webkit-box;
-webkit-line-clamp: 3; //
-webkit-box-orient: vertical;
}
}
}
.file {
width: 220px;
height: 75px;
.file-box {
float: left;
width: calc(100% - 50px);
}
.el-image {
width: 46px;
height: 46px;
float: right;
margin-right: 0;
transform: translateY(10px);
}
.el-icon {
float: right;
font-size: 40px;
color: var(--el-color-primary);
transform: translateY(12px);
}
label {
display: block;
line-height: 30px;
transform: translateY(2px);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 6px;
}
span {
display: inline-block;
font-size: 12px;
color: var(--el-text-color-secondary);
line-height: normal;
transform: translateY(-8px);
}
}
.interval-seconds {
font-size: 12px;
color: var(--el-text-color-secondary);
margin-top: 10px;
}
.time{
font-size: 12px;
color: var(--el-text-color-secondary);
margin-top: 10px;
}
}
}
.robotSend {
.tobot-image {
float: right;
margin-right: 0;
margin-left: 15px;
}
.materia-info {
text-align: right;
float: right;
.robot-name{
text-align: right;
}
.text {
display: inline-block;
text-align: left;
background-color: var(--el-color-primary);
color: #fff;
&::before {
position: absolute;
width: 0;
height: 0;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-left: 10px solid var(--el-color-primary);
border-right: 0;
content: '';
top: 10px;
right: -8px;
left: auto;
}
}
}
}
.image-slot {
height: 100%;
background-color: #F0F2F5;
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-secondary);
}
</style>

View File

@ -1,14 +1,14 @@
<template>
<div class="chat-page">
<div class="left-panel">
<UserCard @load="onLoadUserList" :cardlist="userList" height="calc(100vh - 120px)" @change="onSelectUser" />
<WxUserCard @load="onLoadUserList" :cardlist="userList" height="calc(100vh - 120px)" @change="onSelectUser" />
</div>
<div class="right-panel">
<div :class="['chat-body', { 'chat-body--loading': isLoadingMessages }]" ref="chatBody" @scroll="handleScroll">
<!-- 加载更多提示 -->
<div v-if="loadingMore" class="loading-more">加载中...</div>
<ChatRecord :msgList="messages" :sendeInfo="sendeInfo" msgType="user" @retry="handleRetry" />
<WxChatRecord :msgList="messages" :sendeInfo="sendeInfo" msgType="user" @retry="handleRetry" />
</div>
<div class="chat-footer">
@ -35,8 +35,8 @@
<script setup>
import { ref, reactive, onMounted, nextTick, watch, onUnmounted } from 'vue'
import ChatRecord from '@/components/ChatRecord/index.vue'
import UserCard from '@/components/UserCard/index.vue'
import WxChatRecord from '@/components/ChatRecord/index.vue'
import WxUserCard from '@/components/UserCard/index.vue'
import UserMessage from '@/components/UserMessage/index.vue'
import mihoutai from '@/assets/images/mihoutai.png'
import V3Emoji from "vue3-emoji";

View File

@ -434,7 +434,7 @@ const changeFile = () => {
disabledBtn.value = true
}
const uploadSuccess = (e) => {
ruleForm.value.avatar = import.meta.env.VITE_APP_BASE_API + e.preview_image_url
ruleForm.value.avatar = import.meta.env.VITE_APP_BASE_API_img + e.preview_image_url
disabledBtn.value = false
}

View File

@ -24,7 +24,13 @@
<el-table-column prop="name" label="小程序名称" />
<el-table-column prop="app_id" label="小程序ID" />
<el-table-column prop="description" label="描述" />
<el-table-column label="意图关键字" align="center">
<template #default="scoped">
<div class="mht-operations">
<el-link type="primary" :underline="false" @click="setKeys(scoped.row)">查看</el-link>
</div>
</template>
</el-table-column>
<el-table-column prop="created_at" label="添加时间" align="center"
min-width="160"></el-table-column>
@ -168,12 +174,81 @@
</div>
</template>
</el-dialog>
<el-dialog v-model="dialogKeywords" title="意图识别" width="800px" align-center destroy-on-close>
<div class="dialog-box">
<KeyWords :groupInfo="groupInfo" :keywordInfo="keywordInfo" @hideKey="hideKey" />
</div>
<!-- <template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitNewGroup" :class="{ 'no-click': createLoading }">
<el-icon v-if="createLoading" class="mht-loading" style="margin-right: 4px;">
<Loading />
</el-icon>
</el-button>
</div>
</template> -->
</el-dialog>
<el-dialog v-model="dialogKeys" title="查看意图识别列表" width="1000px" align-center :before-close="handleClose">
<div class="dialog-box">
<el-input v-model="keyQuery.keyword" style="width: 230px;" placeholder="输入关键字">
<template #append>
<el-button icon="Search" @click="searchKeys" />
</template>
</el-input>
<el-button type="primary" icon="Plus" @click="addKey" style="margin-left: 12px;">添加关键字</el-button>
<el-table :data="keyWordList" style="width: 100%; margin-top: 20px;" max-height="500px">
<template #empty>
<span v-if="tableLoading2">加载中...</span>
<span v-if="!tableLoading2">暂无数据</span>
</template>
<el-table-column prop="keyword" label="意图关键字" align="left" width="150" />
<el-table-column prop="material_type_count" label="素材类型数量" align="center">
</el-table-column>
<el-table-column prop="" label="操作" width="100" align="center">
<template #default="scoped">
<div class="mht-operations">
<el-tooltip class="box-item" effect="dark" content="编辑" placement="bottom">
<el-icon @click="editKey(scoped.row)">
<Edit />
</el-icon>
</el-tooltip>
<el-tooltip class="box-item" effect="dark" content="复制" placement="bottom">
<el-icon @click="handleCopy(scoped.row)">
<DocumentCopy />
</el-icon>
</el-tooltip>
<el-tooltip class="box-item" effect="dark" content="删除" placement="bottom">
<el-icon @click="deleteKey(scoped.row)" class="el-icon-danger">
<Delete />
</el-icon>
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination-box">
<el-pagination v-model:current-page="keyQuery.page" v-model:page-size="keyQuery.page_size"
:page-sizes="[20, 30, 40, 50]" :size="keyQuery.page_size" background
layout="total, sizes, prev, pager, next, jumper" :total="keyWordTotal"
@size-change="keySizeChange" @current-change="keyCurrentChange" />
</div>
</div>
<!-- <template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="dialogLog = false">
关闭
</el-button>
</div>
</template> -->
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted, reactive, nextTick } from 'vue'
import { appList, addApp, deleteRobot, editRobot, robotLog, robot_monitor, getQr } from '@/api/miniProgram'
import { groupList, syncGroup, groupMemberList, syncGroupMember, sendTopic, deleteGroup, createGroup, keyWordArr, deleteKeyword } from '@/api/group'
import { ElNotification } from 'element-plus';
import robotStore from '@/store/modules/robotStore'
import { useRouter } from 'vue-router';
@ -413,7 +488,7 @@ const changeFile = () => {
disabledBtn.value = true
}
const uploadSuccess = (e) => {
ruleForm.value.avatar = import.meta.env.VITE_APP_BASE_API + e.preview_image_url
ruleForm.value.avatar = import.meta.env.VITE_APP_BASE_API_img + e.preview_image_url
disabledBtn.value = false
}
@ -434,6 +509,86 @@ const handleqr = (row) => {
qrCode.value = `data:image/png;base64,${res.data}`
}))
}
//
const dialogKeys = ref(false)
const keywordInfo = ref({})
const dialogKeywords = ref(false)
const groupInfo = ref({})
const setKeys = (row) => {
groupInfo.value = row
dialogKeys.value = true
keyWordList.value = []
getKeyWords()
}
const keyQuery = reactive({
page: 1,
page_size: 20,
keyword: ''
})
const keyWordList = ref([])
const keyWordTotal = ref(0)
const tableLoading2 = ref(false)
const getKeyWords = async () => {
tableLoading2.value = true
const res = await keyWordArr({
app_id: groupInfo.value.app_id,
...keyQuery
})
tableLoading2.value = false
keyWordList.value = res.list
keyWordTotal.value = res.total
}
const keySizeChange = (e) => {
keyQuery.page = 1
keyQuery.page_size = e
getKeyWords()
}
const keyCurrentChange = (e) => {
keyQuery.page = e
getKeyWords()
}
const addKey = () => {
keywordInfo.value = {}
dialogKeywords.value = true
}
const hideKey = () => {
dialogKeywords.value = false
keyQuery.page = 1
getKeyWords()
}
const searchKeys = () => {
keyQuery.page = 1
getKeyWords()
}
const editKey = (row) => {
keywordInfo.value = row
dialogKeywords.value = true
}
const deleteKey = (row) => {
ElMessageBox.confirm(
'确认删除该关键字吗?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async () => {
await deleteKeyword(row.id)
getKeyWords()
})
.catch(() => {
})
}
// const handleqr = (row)
onMounted(() => {
query.page = 1