2025-06-25 16:04:02 +08:00

598 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--知识库文档列表-->
<template>
<div class="knowledge">
<!--查询区域-->
<div class="jeecg-basic-table-form-container">
<a-form
ref="formRef"
@keyup.enter.native="searchQuery"
:model="queryParam"
:label-col="labelCol"
:wrapper-col="wrapperCol"
style="background-color: #f7f8fc"
>
<a-row :gutter="24">
<a-col :xl="7" :lg="7" :md="8" :sm="24">
<a-form-item name="name" label="应用名称">
<JInput v-model:value="queryParam.name" placeholder="请输入应用名称" />
</a-form-item>
</a-col>
<a-col :xl="7" :lg="7" :md="8" :sm="24">
<a-form-item name="type" label="应用类型">
<j-dict-select-tag v-model:value="queryParam.type" dict-code="ai_app_type" placeholder="请选择应用类型" />
</a-form-item>
</a-col>
<a-col :xl="6" :lg="7" :md="8" :sm="24">
<span style="float: left; overflow: hidden" class="table-page-search-submitButtons">
<a-col :lg="6">
<a-button type="primary" preIcon="ant-design:search-outlined" @click="searchQuery">查询</a-button>
<a-button type="primary" preIcon="ant-design:reload-outlined" @click="searchReset" style="margin-left: 8px">重置</a-button>
</a-col>
</span>
</a-col>
</a-row>
</a-form>
</div>
<a-row :span="24" class="knowledge-row">
<a-col :xxl="4" :xl="6" :lg="6" :md="6" :sm="12" :xs="24">
<a-card class="add-knowledge-card" @click="handleCreateApp">
<div class="flex">
<Icon icon="ant-design:plus-outlined" class="add-knowledge-card-icon" size="20"></Icon>
<span class="add-knowledge-card-title">创建应用</span>
</div>
</a-card>
</a-col>
<a-col :xxl="4" :xl="6" :lg="6" :md="6" :sm="12" :xs="24" v-for="item in knowledgeAppDataList">
<a-card class="knowledge-card pointer" @click="handleEditClick(item)">
<div class="flex">
<img class="header-img" :src="getImage(item.icon)" />
<div class="header-text">
<span class="header-text-top header-name ellipsis"> {{ item.name }} </span>
<span class="header-text-top header-create ellipsis">
<a-tag v-if="item.status === 'release'" color="green">已发布</a-tag>
<a-tag v-if="item.status === 'disable'">已禁用</a-tag>
<span>创建者{{ item.createBy_dictText || item.createBy }}</span>
</span>
</div>
</div>
<div class="header-tag">
<a-tag color="#EBF1FF" style="margin-right: 0" v-if="item.type === 'chatSimple'">
<span style="color: #3370ff">简单配置</span>
</a-tag>
<a-tag color="#FDF6EC" style="margin-right: 0" v-if="item.type === 'chatFLow'">
<span style="color: #e6a343">高级编排</span>
</a-tag>
</div>
<div class="card-description">
<span>{{ item.descr || '暂无描述' }}</span>
</div>
<div class="card-footer">
<a-tooltip title="演示">
<div class="card-footer-icon" @click.prevent.stop="handleViewClick(item.id)">
<Icon class="operation" icon="ant-design:youtube-outlined" size="20" color="#1F2329"></Icon>
</div>
</a-tooltip>
<template v-if="item.status !== 'release'">
<a-divider type="vertical" style="float: left" />
<a-tooltip title="删除">
<div class="card-footer-icon" @click.prevent.stop="handleDeleteClick(item)">
<Icon icon="ant-design:delete-outlined" class="operation" size="18" color="#1F2329"></Icon>
</div>
</a-tooltip>
</template>
<a-divider type="vertical" style="float: left" />
<a-tooltip title="发布">
<a-dropdown class="card-footer-icon" placement="bottomRight" :trigger="['click']">
<div @click.prevent.stop>
<Icon style="position: relative;top: 1px" icon="ant-design:send-outlined" size="16" color="#1F2329"></Icon>
</div>
<template #overlay>
<a-menu>
<template v-if="item.status === 'enable'">
<a-menu-item key="release" @click.prevent.stop="handleSendClick(item,'release')">
<Icon icon="lineicons:rocket-5" size="16"></Icon>
发布
</a-menu-item>
<a-menu-divider/>
</template>
<template v-else-if="item.status === 'release'">
<a-menu-item key="un-release" @click.prevent.stop="handleSendClick(item,'un-release')">
<Icon icon="tabler:rocket-off" size="16"></Icon>
取消发布
</a-menu-item>
<a-menu-divider/>
</template>
<a-menu-item key="web" @click.prevent.stop="handleSendClick(item,'web')">
<Icon icon="ant-design:dribbble-outlined" size="16"></Icon>
嵌入网站
</a-menu-item>
<a-menu-item v-if="isShowMenu" key="menu" @click.prevent.stop="handleSendClick(item,'menu')">
<Icon icon="ant-design:menu-outlined" size="16"></Icon> 配置菜单
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</div>
</a-card>
</a-col>
</a-row>
<Pagination
v-if="knowledgeAppDataList.length > 0"
:current="pageNo"
:page-size="pageSize"
:page-size-options="pageSizeOptions"
:total="total"
:showQuickJumper="true"
:showSizeChanger="true"
@change="handlePageChange"
class="list-footer"
size="small"
:show-total="() => `${total}` "
/>
<!-- Ai新增弹窗 -->
<AiAppModal @register="registerModal" @success="handleSuccess"></AiAppModal>
<!-- Ai设置弹窗 -->
<AiAppSettingModal @register="registerSettingModal" @success="reload"></AiAppSettingModal>
<!-- 发布弹窗 -->
<AiAppSendModal @register="registerAiAppSendModal"/>
</div>
</template>
<script lang="ts">
import { ref, reactive, onMounted } from 'vue';
import BasicModal from '@/components/Modal/src/BasicModal.vue';
import { useModal, useModalInner } from '@/components/Modal';
import { LoadingOutlined } from '@ant-design/icons-vue';
import { Avatar, Modal, Pagination } from 'ant-design-vue';
import { getFileAccessHttpUrl } from '@/utils/common/compUtils';
import defaultImg from './img/ailogo.png';
import AiAppModal from './components/AiAppModal.vue';
import AiAppSettingModal from './components/AiAppSettingModal.vue';
import AiAppSendModal from './components/AiAppSendModal.vue';
import Icon from '@/components/Icon';
import { $electron } from "@/electron";
import { appList, deleteApp, releaseApp } from './AiApp.api';
import { useMessage } from '@/hooks/web/useMessage';
import JInput from '@/components/Form/src/jeecg/components/JInput.vue';
import JDictSelectTag from '@/components/Form/src/jeecg/components/JDictSelectTag.vue';
import { useRouter } from "vue-router";
export default {
name: 'AiAppList',
components: {
JDictSelectTag,
JInput,
AiAppSendModal,
Icon,
Pagination,
Avatar,
LoadingOutlined,
BasicModal,
AiAppModal,
AiAppSettingModal,
},
emits: ['success', 'register'],
setup(props, { emit }) {
/**
* 创建应用的集合
*/
const knowledgeAppDataList = ref<any>([]);
//当前页数
const pageNo = ref<number>(1);
//每页条数
const pageSize = ref<number>(10);
//总条数
const total = ref<number>(0);
//可选择的页数
const pageSizeOptions = ref<any>(['10', '20', '30']);
//注册modal
const [registerModal, { openModal }] = useModal();
const [registerSettingModal, { openModal: openAppModal }] = useModal();
const [registerAiAppSendModal, { openModal: openAiAppSendModal }] = useModal();
const { createMessage, createConfirmSync } = useMessage();
//查询参数
const queryParam = reactive<any>({});
//查询区域label宽度
const labelCol = reactive({
xs: 24,
sm: 4,
xl: 6,
xxl: 6,
});
//查询区域组件宽度
const wrapperCol = reactive({
xs: 24,
sm: 20,
});
//表单的ref
const formRef = ref();
reload();
/**
* 加载数据
*/
function reload() {
let params = {
pageNo: pageNo.value,
pageSize: pageSize.value,
column: 'createTime',
order: 'desc',
};
Object.assign(params, queryParam);
appList(params).then((res) => {
if (res.success) {
knowledgeAppDataList.value = res.result.records;
total.value = res.result.total;
} else {
knowledgeAppDataList.value = [];
total.value = 0;
}
});
}
/**
* 创建应用
*/
function handleCreateApp() {
openModal(true, {});
}
/**
* 分页改变事件
* @param page
* @param current
*/
function handlePageChange(page, current) {
pageNo.value = page;
pageSize.value = current;
reload();
}
/**
* 成功
*/
function handleSuccess(id) {
reload();
//打开编辑弹窗
openAppModal(true, {
isUpdate: false,
id: id,
});
}
/**
* 获取图片
* @param url
*/
function getImage(url) {
return url ? getFileAccessHttpUrl(url) : defaultImg;
}
/**
* 编辑
* @param item
*/
function handleEditClick(item) {
console.log('item:::', item);
openAppModal(true, {
isUpdate: true,
...item,
});
}
/**
* 演示
*/
function handleViewClick(id: string) {
let url = '/ai/app/chat/' + id;
// update-begin--author:sunjianlei---date:20250411---for【QQYUN-9685】构建 electron 桌面应用
if ($electron.isElectron()) {
url = $electron.resolveRoutePath(url);
window.open(url, '_blank', 'width=1200,height=800');
return
}
// update-end----author:sunjianlei---date:20250411---for【QQYUN-9685】构建 electron 桌面应用
window.open(url, '_blank');
}
/**
* 删除
*/
function handleDeleteClick(item) {
if(knowledgeAppDataList.value.length == 1 && pageNo.value > 1) {
pageNo.value = pageNo.value - 1;
}
deleteApp({ id: item.id, name: item.name }, reload);
}
/**
* 发布点击事件
* @param item 数据
* @param type 类别
*/
function handleSendClick(item,type) {
if (type === 'release' || type === 'un-release') {
return onRelease(item);
}
openAiAppSendModal(true,{
type: type,
data: item
})
}
async function onRelease(item) {
const toRelease = item.status === 'enable';
let flag = await createConfirmSync({
title: toRelease ? '发布应用' : '取消发布应用',
content: toRelease ? '确定要发布应用吗?发布后将不允许修改应用。' : '确定要取消发布应用吗?',
okText: '确定',
cancelText: '取消',
});
if (!flag) {
return
}
doRelease(item, item.status === 'enable');
}
/**
* 发布
*/
async function doRelease(item, release: boolean) {
let success: boolean = await releaseApp(item.id, release);
if (success) {
// 发布成功
if (release) {
item.status = 'release'
} else {
item.status = 'enable'
}
}
}
/**
* 重置
*/
function searchReset() {
pageNo.value = 1;
formRef.value.resetFields();
queryParam.name = '';
//刷新数据
reload();
}
/**
* 查询
*/
function searchQuery(){
pageNo.value = 1;
//刷新数据
reload();
}
const router = useRouter();
//是否显示菜单配置选项
const isShowMenu = ref<boolean>(false);
onMounted((()=>{
let fullPath = router.currentRoute.value.fullPath;
console.log(fullPath)
if(fullPath === '/myapps/ai/app'){
isShowMenu.value = false;
} else {
isShowMenu.value = true;
}
}))
return {
handleCreateApp,
knowledgeAppDataList,
pageNo,
pageSize,
total,
pageSizeOptions,
handlePageChange,
cardBodyStyle: { textAlign: 'left', width: '100%' },
registerModal,
handleSuccess,
getImage,
handleEditClick,
handleViewClick,
handleDeleteClick,
registerSettingModal,
reload,
queryParam,
labelCol,
wrapperCol,
handleSendClick,
registerAiAppSendModal,
searchReset,
formRef,
isShowMenu,
searchQuery,
};
},
};
</script>
<style scoped lang="less">
.knowledge {
height: calc(100vh - 115px);
background: #f7f8fc;
padding: 24px;
overflow-y: auto;
}
.add-knowledge-card {
margin-bottom: 20px;
background: #fcfcfd;
border: 1px solid #f0f0f0;
box-shadow: 0 2px 4px #e6e6e6;
transition: all 0.3s ease;
border-radius: 10px;
display: inline-flex;
justify-content: center;
align-items: center;
font-size: 16px;
cursor: pointer;
height: 152px;
width: calc(100% - 20px);
.add-knowledge-card-icon {
padding: 8px;
color: #1f2329;
background-color: #f5f6f7;
margin-right: 12px;
}
.add-knowledge-card-title {
font-size: 16px;
color:#1f2329;
font-weight: 400;
align-self: center;
}
}
.knowledge-card {
border-radius: 10px;
margin-right: 20px;
margin-bottom: 20px;
height: 152px;
background: #fcfcfd;
border: 1px solid #f0f0f0;
box-shadow: 0 2px 4px #e6e6e6;
transition: all 0.3s ease;
.header-img {
width: 40px;
height: 40px;
border-radius: 0.5rem;
}
.header-text {
margin-left: 5px;
position: relative;
font-size: 14px;
display: grid;
width: calc(100% - 100px);
.header-name {
font-weight: bold;
color: #354052;
}
.header-create {
font-size: 12px;
color: #646a73;
}
}
.header-tag {
position: absolute;
right: 4px;
top: 6px;
}
}
.add-knowledge-card,
.knowledge-card {
transition: box-shadow 0.3s ease;
}
.add-knowledge-card:hover,
.knowledge-card:hover {
box-shadow: 0 6px 12px #d0d3d8;
}
.knowledge-row {
max-height: calc(100% - 100px);
margin-top: 20px;
overflow-y: auto;
}
.add-knowledge-doc {
margin-top: 6px;
color: #6f6f83;
font-size: 13px;
width: 100%;
cursor: pointer;
display: flex;
span {
margin-left: 4px;
line-height: 28px;
}
}
.add-knowledge-doc:hover {
background: #c8ceda33;
}
.card-description {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
height: 4.5em;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.5;
margin-top: 10px;
text-align: left;
font-size: 12px;
color: #676f83;
}
.card-footer {
position: absolute;
bottom: 8px;
left: 0;
min-height: 30px;
padding: 0 16px;
width: 100%;
align-items: center;
display: flex;
}
.card-footer-icon {
font-size: 14px;
height: 24px;
padding: 0 7px;
border-radius: 4px;
text-align: center;
align-content: center;
float: left;
width: 36px;
}
.card-footer-icon:hover {
color: #000000;
background-color: #e9ecf2;
border: none;
}
.operation {
position: relative;
top: 2px;
}
.list-footer {
text-align: right;
margin-top: 5px;
}
:deep(.ant-card .ant-card-body) {
padding: 16px;
}
.ellipsis{
overflow: hidden;
text-wrap: nowrap;
text-overflow: ellipsis;
}
.jeecg-basic-table-form-container {
padding: 0;
:deep(.ant-form) {
background-color: transparent;
}
.table-page-search-submitButtons {
display: block;
margin-bottom: 24px;
white-space: nowrap;
}
}
</style>
<style lang="less">
.airag-knowledge-doc .scroll-container {
padding: 0 !important;
}
</style>