@zuopngfei bf6b7f5fcb sdw
2025-11-05 18:37:01 +08:00

833 lines
28 KiB
Vue

<template>
<div class="group-container">
<div class="search">
<el-button type="primary" @click="handleAdd">添加微信小程序</el-button>
</div>
<div class="table-card">
<div class="table-card-center">
<div class="table-box">
<el-table :data="robotCards" style="width: 100%" max-height="calc(100vh - 285px)"
@selection-change="handleSelectionChange" row-key="user_id">
<template #empty>
<span v-if="tableLoading">加载中...</span>
<span v-if="!tableLoading && query.robot_id">暂无数据</span>
<span v-if="!query.robot_id && !tableLoading" style="font-weight: bold;">请选择一个小程序</span>
</template>
<el-table-column type="selection" width="55" :reserve-selection="true" />
<el-table-column prop="avatar" label="头像" align="center">
<template #default="scoped">
<el-image style="width: 30px;display: block;margin: auto;border-radius: 4px;"
:src="scoped.row.avatar" />
</template>
</el-table-column>
<el-table-column prop="name" label="小程序名称" />
<el-table-column prop="app_id" label="小程序ID" />
<el-table-column prop="app_secret" label="AppSecret" />
<el-table-column prop="template_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>
<el-table-column prop="" label="操作" width="100" align="center" fixed="right">
<template #default="scoped">
<div class="mht-operations">
<!-- <el-tooltip class="box-item" effect="dark" content="客户详情" placement="bottom">
<el-icon @click="toDetail(scoped.row)">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-xiangqingye-43"></use>
</svg>
</el-icon>
</el-tooltip>
<el-tooltip class="box-item" effect="dark" content="发送消息" placement="bottom">
<el-icon @click="handleSend(scoped.row)">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-fasong"></use>
</svg>
</el-icon>
</el-tooltip> -->
<el-tooltip class="box-item" effect="dark" content="获取二维码" placement="bottom">
<!-- <el-icon class="el-icon-primary" @click="handleEdit(scoped.row)">
<Edit />
</el-icon> -->
<el-icon @click="handleqr(scoped.row)">
<FullScreen />
</el-icon>
</el-tooltip>
<el-tooltip class="box-item" effect="dark" content="编辑" placement="bottom">
<el-icon class="el-icon-primary" @click="handleEdit(scoped.row)">
<Edit />
</el-icon>
</el-tooltip>
<el-tooltip class="box-item" effect="dark" content="详情" placement="bottom">
<el-icon class="el-icon-primary" @click="handleDetail(scoped.row)">
<ChatDotRound />
</el-icon>
</el-tooltip>
<el-tooltip class="box-item" effect="dark" content="删除" placement="bottom">
<el-icon class="el-icon-danger" @click="handeleDelete(scoped.row)">
<Delete />
</el-icon>
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination-box">
<el-pagination v-model:current-page="query.page" v-model:page-size="query.page_size"
:page-sizes="[20, 30, 40, 50]" :size="query.page_size" background
layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</div>
</div>
</div>
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑小程序' : '添加小程序'" width="600px" align-center
:before-close="handleClose">
<div class="dialog-box">
<el-form ref="ruleFormRef" :model="ruleForm" :rules="rules">
<el-form-item label="小程序名称" prop="name">
<el-input v-model="ruleForm.name" placeholder="请输入小程序名称" size="large" />
</el-form-item>
<el-form-item label="小程序ID" prop="app_id">
<el-input v-model="ruleForm.app_id" placeholder="请输入小程序ID" size="large" />
</el-form-item>
<el-form-item label="AppSecret" prop="app_secret">
<el-input v-model="ruleForm.app_secret" placeholder="请输入AppSecret" size="large" type="password" show-password />
</el-form-item>
<el-form-item label="模板ID" prop="template_id">
<el-input v-model="ruleForm.template_id" placeholder="请输入模板ID" size="large" />
</el-form-item>
<el-form-item label="头像" prop="avatar">
<Upload v-model="ruleForm.avatar" type="image" accept="image/*" :action="robotHost"
@change="changeFile" @success="uploadSuccess" @error="uploadError"></Upload>
</el-form-item>
<el-form-item label="小程序描述" prop="description">
<el-input type="textarea" row="3" v-model="ruleForm.description" placeholder="请输入小程序描述"
size="large" />
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitRobot" :class="{ 'no-click': mhtLoading }">
<el-icon v-if="mhtLoading" class="mht-loading" style="margin-right: 4px;">
<Loading />
</el-icon>
<el-icon v-else style="margin-right: 4px;">
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-baocun"></use>
</svg></el-icon>
{{ isEdit ? '保 存' : '添 加' }}
</el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="dialogLog" title="小程序日志" width="1000px" align-center :before-close="handleClose">
<div class="dialog-box">
<el-table :data="logList" style="width: 100%" max-height="500px" :row-class-name="rowClassname">
<template #empty>
<span v-if="tableLoading">加载中...</span>
<span v-if="!tableLoading">暂无数据</span>
</template>
<el-table-column prop="created_at" label="监听时间" align="center" width="160">
</el-table-column>
<el-table-column prop="remark" label="监听结果" align="center" header-align="center" />
</el-table>
<div class="pagination-box">
<el-pagination v-model:current-page="logQuery.page" v-model:page-size="logQuery.page_size"
:page-sizes="[10, 20, 30, 40, 50]" :size="logQuery.page_size" background
layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="dialogLog = false">
关闭
</el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="qrDialog" title="小程序二维码" width="500px" align-center :before-close="handleClose">
<div class="dialog-box" style="text-align: center;">
<img :src="qrCode" style="width: 200px;" alt="">
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="qrDialog = false">
关闭
</el-button>
</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';
const userRobotStore = robotStore()
const router = useRouter()
const robotCards = ref([])
const dialogVisible = ref(false)
const loading = ref(false)
const ruleFormRef = ref()
const ruleForm = ref({})
const isEdit = ref(false)
const newRobotId = ref('')
let robotCopy = {}
const rules = ref({
name: [
{
required: true,
message: '请输入小程序名称',
trigger: 'change',
}
],
description: [{
required: true,
message: '请输入小程序描述',
trigger: 'change',
}],
app_id: [{
required: true,
message: '请输入小程序ID',
trigger: 'change',
}],
app_secret: [{
required: true,
message: '请输入AppSecret',
trigger: 'change',
}],
avatar: [{
required: true,
message: '请上传小程序头像',
trigger: 'change',
}],
app_secret: [{
required: true,
message: '请上传小程序头像',
trigger: 'change',
}],
template_id: [{
required: true,
message: '请输入模板ID',
trigger: 'change',
}]
})
const query = reactive({
page: 1,
page_size: 30
})
let robtoTotal = 0
const mpTable = ref([])
const getTable = async () => {
const res = await appList(query)
robotCards.value = res.list
total.value = res.total
}
const handleAdd = () => {
if (robotCards.value.length > 100) {
ElNotification({
title: '提示',
message: '最多添加100个小程序',
type: 'warning',
duration: 2000
})
return
}
isEdit.value = false
dialogVisible.value = true
nextTick(() => {
ruleForm.value = {}
ruleFormRef.value.resetFields()
})
}
let editItem = {}
let editIndex = 0
const handleEdit = (item) => {
isEdit.value = true
// editItem = item
// editIndex = index
ruleForm.value = JSON.parse(JSON.stringify(item))
dialogVisible.value = true
}
const mhtLoading = ref(false)
const submitRobot = async () => {
mhtLoading.value = true
await ruleFormRef.value.validate(async (valid, fields) => {
if (valid) {
if (isEdit.value) {
try {
const res = await editRobot(ruleForm.value.id, {
description: ruleForm.value.description,
name: ruleForm.value.name,
app_id: ruleForm.value.app_id,
app_secret: ruleForm.value.app_secret,
avatar: ruleForm.value.avatar,
template_id: ruleForm.value.template_id
})
// editItem = ruleForm.value
// robotCards.value[editIndex] = editItem
// getTable()
dialogVisible.value = false
mhtLoading.value = false
getTable()
ElNotification({
type: 'success',
message: '操作成功'
})
} catch {
mhtLoading.value = false
}
} else {
try {
const res = await addApp(ruleForm.value)
robotCards.value.push({
...ruleForm.value,
})
// getTable()
dialogVisible.value = false
mhtLoading.value = false
getTable()
ElNotification({
type: 'success',
message: '操作成功'
})
} catch {
mhtLoading.value = false
}
}
} else {
mhtLoading.value = false
}
})
}
const handeleDelete = (item, index) => {
ElMessageBox.confirm(
'确认删除该小程序吗?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async () => {
ElNotification({
message: '删除中,请稍等',
type: 'success',
duration: 2000
})
await deleteRobot(item.id)
// getGroups()
robotCards.value.splice(index, 1)
})
.catch(() => {
})
}
const handleRobot = (row) => {
// newRobotId.value = row.id
userRobotStore.robotInfo = row
router.push('/dashboard')
}
const logQuery = reactive({
page: 1,
page_size: 10
})
const total = ref(0)
const dialogLog = ref(false)
const logList = ref([])
let robot_data = ''
const tableLoading = ref(false)
const openLog = async (row) => {
dialogLog.value = true
robot_data = row
tableLoading.value = true
const res = await robotLog({
robot_id: row.id,
...logQuery
})
tableLoading.value = false
logList.value = res.list
total.value = res.total
}
const handleSizeChange = (e) => {
logQuery.page = 1
logQuery.page_size = e
openLog(robot_data)
}
const handleCurrentChange = (e) => {
logQuery.page = e
openLog(robot_data)
}
const rowClassname = (e) => {
if (e.row.status == 1) {
return 'row-success'
} else {
return 'row-danger'
}
}
const xjLoading = ref(false)
const setMonitor = async (e) => {
if (xjLoading.value == true) return
try {
xjLoading.value = true
const res = await robot_monitor({
robot_id: e.id
})
e.status_text = res.status_text
e.status = res.status
xjLoading.value = false
ElNotification({
type: 'success',
message: '监听成功'
})
} catch (e) {
xjLoading.value = false
}
}
const robotHost = import.meta.env.VITE_APP_BASE_API + 'admin/upload/image'
const disabledBtn = ref(true)
const changeFile = () => {
disabledBtn.value = true
}
const uploadSuccess = (e) => {
ruleForm.value.avatar = import.meta.env.VITE_APP_BASE_API_img + e.preview_image_url
disabledBtn.value = false
}
const handleDetail = (row) => {
router.push({ path: '/chat', query: { app_id: row.app_id, app_secret: row.app_secret, template_id: row.template_id } })
}
const qrCode = ref('')
const qrDialog = ref(false)
const handleqr = (row) => {
getQr({
"app_id": row.app_id,
"app_secret": row.app_secret,
"path": "pages/contact/index"
}).then((res => {
qrDialog.value = true
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
robotCards.value = []
getTable()
// newRobotId.value = userRobotStore.robotInfo.id ? userRobotStore.robotInfo.id : ''
})
</script>
<style></style>
<style lang="scss" scoped>
.group-container {
height: 100%;
padding: 20px;
background-color: #fff;
.infinite-list {
height: 100%;
padding: var(--el-main-padding);
padding-right: 0;
}
.el-card {
:deep(.el-card__body) {
height: 100%;
}
.robot-title {
overflow: hidden;
margin-bottom: 10px;
.robot-head {
float: left;
width: 64px;
height: 64px;
border-radius: 10px;
}
.robot-name-box {
float: left;
padding-left: 12px;
width: calc(100% - 66px);
label {
display: block;
line-height: 16px;
margin-bottom: 6px;
font-size: 14px;
}
}
}
.name {
font-size: 15px;
font-weight: bold;
margin-top: 4px;
}
.wx_name {
display: block;
color: var(--el-text-color-regular);
}
.description {
width: 86%;
font-size: 14px;
line-height: 20px;
display: -webkit-box;
-webkit-box-orient: vertical;
/* 设置垂直排列 */
-webkit-line-clamp: 2;
/* 设置行数 */
overflow: hidden;
/* 设置超出省略 */
text-overflow: ellipsis;
/* 设置省略以...结尾 */
color: var(--el-text-color-secondary);
}
.success {
position: absolute;
right: 20px;
bottom: 21px;
font-size: 12px;
color: var(--el-color-success);
&::after {
position: absolute;
left: -15px;
top: 3px;
content: '';
width: 8px;
height: 8px;
border-radius: 8px;
background-color: var(--el-color-success);
}
}
.danger {
position: absolute;
right: 20px;
bottom: 21px;
font-size: 12px;
color: var(--el-color-danger);
&::after {
position: absolute;
left: -15px;
top: 3px;
content: '';
width: 8px;
height: 8px;
border-radius: 8px;
background-color: var(--el-color-danger);
}
}
&:hover {
transform: translateY(-6px);
background-color: var(--el-color-primary-light-7);
}
.icon-robot {
font-size: 66px;
}
.act-icon {
position: absolute;
font-size: 40px;
right: -2px;
bottom: -8px;
opacity: 0;
}
}
.active-robot {
.act-icon {
opacity: 0.6;
}
}
.add-robot {
background-image: url('@/assets/images/add_robot_bg.png');
background-color: var(--el-color-primary-light-5);
background-size: 100% auto;
&:hover {
transform: translateY(-10px);
}
.add-box {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: var(--el-color-primary);
.el-icon {
position: relative;
top: -1px;
margin-right: 3px;
}
}
}
.edit-icon {
position: absolute;
right: 15px;
top: 14px;
// display: none;
height: 30px;
width: 30px;
text-align: center;
line-height: 30px;
border-radius: 8px;
transition: all 0.1s;
i {
position: relative;
left: 2px;
top: 3px;
color: var(--el-color-primary);
// color: #fff;
// transition: all 0.1s;
// transform: rotate(90deg);
}
}
}
.popover-list {
li {
height: 32px;
line-height: 32px;
padding-left: 10px;
// border-radius: 6px;
cursor: pointer;
// color: var(--el-color-primary);
.el-icon {
position: relative;
top: 2px;
margin-right: 4px;
}
&:hover {
background-color: var(--el-color-primary-light-9);
}
}
.rizhi-icon {
color: var(--el-color-warning);
}
.edit-icon {
color: var(--el-color-primary);
}
.delete-icon {
color: var(--el-color-danger);
}
}
</style>