mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2026-03-17 19:23:32 +00:00
598 lines
17 KiB
Vue
598 lines
17 KiB
Vue
<!--知识库文档列表-->
|
||
<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>
|