mirror of https://github.com/jeecgboot/jeecg-boot
v3.8.1发布,上传前端代码
parent
3d414aaec8
commit
0148f45979
|
@ -28,3 +28,6 @@ VITE_APP_SUB_jeecg-app-1 = '//localhost:8092'
|
|||
|
||||
# 全局隐藏哪些布局。可选属性:sider,header,multi-tabs;多个用逗号隔开
|
||||
#VITE_GLOB_HIDE_LAYOUT_TYPES=sider,header,multi-tabs
|
||||
|
||||
# 在线文档编辑版本。可选属性:wps, offlineWps(离线版) ,onlyoffice
|
||||
VITE_GLOB_ONLINE_DOCUMENT_VERSION=wps
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# 是否启用mock
|
||||
VITE_USE_MOCK = false
|
||||
|
||||
# 后台接口父地址(必填)
|
||||
# 【Electron下需要与 VITE_GLOB_DOMAIN_URL 配置保持一致】
|
||||
VITE_GLOB_API_URL=https://api3.boot.jeecg.com
|
||||
|
||||
# 后台接口全路径地址(必填)
|
||||
VITE_GLOB_DOMAIN_URL=https://api3.boot.jeecg.com
|
||||
|
||||
# 接口父路径前缀
|
||||
VITE_GLOB_API_URL_PREFIX=
|
||||
|
||||
# 在线文档编辑版本。可选属性:wps, offlineWps(离线版), onlyoffice
|
||||
VITE_GLOB_ONLINE_DOCUMENT_VERSION=wps
|
||||
|
||||
# 全局隐藏哪些布局。可选属性:sider,header,multi-tabs;多个用逗号隔开
|
||||
#VITE_GLOB_HIDE_LAYOUT_TYPES=sider,header,multi-tabs
|
||||
|
||||
# -----------------------------------------
|
||||
# ------------ 以下参数不建议修改 ------------
|
||||
# -----------------------------------------
|
||||
|
||||
# 发布路径
|
||||
# 【election下只能是 . 开头的相对路径,建议为 ./ 】
|
||||
VITE_PUBLIC_PATH = ./
|
||||
|
||||
# 是否启用gzip或brotli压缩
|
||||
# 选项值: gzip | brotli | none
|
||||
# 如果需要多个可以使用“,”分隔
|
||||
# 【electron下由于是本地html文件访问,所以不需要压缩】
|
||||
VITE_BUILD_COMPRESS = 'none'
|
||||
|
||||
# 使用压缩时是否删除原始文件,默认为false
|
||||
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
|
||||
|
||||
# ※ 请勿修改此项 ※
|
||||
VITE_GLOB_RUN_PLATFORM=electron
|
|
@ -29,3 +29,6 @@ VITE_GLOB_API_URL_PREFIX=
|
|||
|
||||
# 全局隐藏哪些布局。可选属性:sider,header,multi-tabs;多个用逗号隔开
|
||||
#VITE_GLOB_HIDE_LAYOUT_TYPES=sider,header,multi-tabs
|
||||
|
||||
# 在线文档编辑版本。可选属性:wps, offlineWps(离线版), onlyoffice
|
||||
VITE_GLOB_ONLINE_DOCUMENT_VERSION=wps
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
shamefully-hoist=true
|
||||
strict-peer-dependencies=false
|
||||
|
||||
electron_mirror=https://npmmirror.com/mirrors/electron/
|
||||
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
|
||||
|
|
|
@ -19,15 +19,15 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
In any case, you must not make any such use of this software as to develop software which may be considered competitive with this software.
|
||||
|
||||
JeecgBoot 是由 北京国炬信息技术有限公司 发行的软件。 总部位于北京,地址:中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱:jeecgos@163.com
|
||||
JeecgBoot 是由 北京国炬信息技术有限公司 发行的软件。 总部位于北京,地址:中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱:jeecgos@163.com
|
||||
本软件受适用的国家软件著作权法(包括国际条约)和开源协议 双重保护许可。
|
||||
|
||||
|
||||
开源协议中文释意如下:
|
||||
1.JeecgBoot开源版本无任何限制,在遵循本开源协议条款下,允许商用使用,不会造成侵权行为。
|
||||
2.允许基于本平台软件开展业务系统开发。
|
||||
3.在任何情况下,您不得使用本软件开发可能被认为与本软件竞争的软件。
|
||||
|
||||
|
||||
最终解释权归:http://www.jeecg.com
|
||||
|
|
|
@ -35,6 +35,16 @@ function createConfig(params: CreateConfigParams) {
|
|||
|
||||
console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`);
|
||||
console.log(colors.gray(OUTPUT_DIR + '/' + colors.green(configFileName)) + '\n');
|
||||
|
||||
// update-begin--author:sunjianlei---date:20250423---for:【QQYUN-9685】构建 electron 桌面应用
|
||||
// 如果是 Electron 环境,还需要将配置文件写入到 JSON 文件中
|
||||
if (config.VITE_GLOB_RUN_PLATFORM === 'electron') {
|
||||
writeFileSync(getRootPath(`${OUTPUT_DIR}/electron/env.json`), JSON.stringify(config));
|
||||
console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - electron env file is build successfully:`);
|
||||
console.log(colors.gray(OUTPUT_DIR + '/' + colors.green('electron/env.json')) + '\n');
|
||||
}
|
||||
// update-end----author:sunjianlei---date:20250423---for:【QQYUN-9685】构建 electron 桌面应用
|
||||
|
||||
} catch (error) {
|
||||
console.log(colors.red('configuration file configuration file failed to package:\n' + error));
|
||||
}
|
||||
|
|
|
@ -49,6 +49,14 @@ export function wrapperEnv(envConf: Recordable): ViteEnv {
|
|||
* 获取当前环境下生效的配置文件名
|
||||
*/
|
||||
function getConfFiles() {
|
||||
|
||||
// update-begin--author:sunjianlei---date:20250411---for:【QQYUN-9685】构建 electron 桌面应用
|
||||
const {VITE_GLOB_RUN_PLATFORM} = process.env
|
||||
if (VITE_GLOB_RUN_PLATFORM === 'electron') {
|
||||
return ['.env', '.env.prod_electron'];
|
||||
}
|
||||
// update-end----author:sunjianlei---date:20250411---for:【QQYUN-9685】构建 electron 桌面应用
|
||||
|
||||
const script = process.env.npm_lifecycle_script;
|
||||
// update-begin--author:liaozhiyang---date:20240326---for:【QQYUN-8690】修正获取当前环境下的文件名
|
||||
const reg = new RegExp('NODE_ENV=([a-z_\\d]+)');
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
// import electron from 'vite-plugin-electron/simple'
|
||||
//
|
||||
// export function configElectronPlugin(_viteEnv: ViteEnv, isBuild: boolean) {
|
||||
// return electron({
|
||||
// main: {
|
||||
// // 主进程入口
|
||||
// entry: 'electron/main.ts',
|
||||
// vite: {
|
||||
// build: {
|
||||
// sourcemap: !isBuild,
|
||||
// outDir: 'dist/electron',
|
||||
// },
|
||||
// },
|
||||
// onstart: ({startup}) => {
|
||||
// // 开发热重载
|
||||
// startup()
|
||||
// },
|
||||
// },
|
||||
// preload: {
|
||||
// input: 'electron/preload/index.ts',
|
||||
// vite: {
|
||||
// build: {
|
||||
// sourcemap: !isBuild,
|
||||
// outDir: 'dist/electron/preload',
|
||||
// },
|
||||
// },
|
||||
// onstart: ({startup}) => {
|
||||
// // 开发热重载
|
||||
// startup()
|
||||
// },
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// }
|
|
@ -16,6 +16,8 @@ import { configVisualizerConfig } from './visualizer';
|
|||
import { configThemePlugin } from './theme';
|
||||
import { configSvgIconsPlugin } from './svgSprite';
|
||||
import { configQiankunMicroPlugin } from './qiankunMicro';
|
||||
// // electron plugin
|
||||
// import { configElectronPlugin } from "./electron";
|
||||
// //预编译加载插件(不支持vite3作废)
|
||||
// import OptimizationPersist from 'vite-plugin-optimize-persist';
|
||||
// import PkgConfig from 'vite-plugin-package-config';
|
||||
|
@ -68,6 +70,12 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, isQiankunM
|
|||
vitePlugins.push(...configQiankunMicroPlugin(viteEnv))
|
||||
}
|
||||
|
||||
// // electron plugin
|
||||
// const isElectron = viteEnv.VITE_GLOB_RUN_PLATFORM === 'electron';
|
||||
// if (isElectron) {
|
||||
// vitePlugins.push(configElectronPlugin(viteEnv, isBuild))
|
||||
// }
|
||||
|
||||
// The following plugins only work in the production environment
|
||||
if (isBuild) {
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
appId: 'com.jeecg.boot3'
|
||||
# 产品名称
|
||||
productName: 'jeecgboot'
|
||||
files:
|
||||
# 仅包含 dist 目录下所有文件
|
||||
- 'dist/**/*'
|
||||
# 特别排除 node_modules 目录
|
||||
- '!node_modules'
|
||||
directories:
|
||||
# 输出目录
|
||||
output: 'dist-electron'
|
||||
win:
|
||||
# win exe 程序图标
|
||||
icon: 'electron/icons/app.ico'
|
||||
artifactName: 'jeecgboot-setup-${version}.exe'
|
||||
# 安装包配置
|
||||
nsis:
|
||||
oneClick: false
|
||||
# 是否允许用户选择安装目录
|
||||
allowToChangeInstallationDirectory: true
|
||||
# 是否创建桌面快捷方式
|
||||
createDesktopShortcut: true
|
||||
# 安装程序的图标
|
||||
installerIcon: 'electron/icons/installer.ico'
|
|
@ -0,0 +1,35 @@
|
|||
# Electron桌面应用打包
|
||||
|
||||
- 1.安装依赖很慢,得10分钟左右
|
||||
- 2.electron桌面应用打包文档
|
||||
https://help.jeecg.com/ui/setup/electron-build
|
||||
- 3.临时注释掉electron功能
|
||||
注释代码:build/vite/plugin/electron.ts
|
||||
修改build/vite/plugin/index.ts,搜索`electron plugin`注释相关逻辑代码
|
||||
修改package.json删除相关依赖
|
||||
|
||||
```yaml
|
||||
{
|
||||
"main": "dist/electron/main.js",
|
||||
"scripts": {
|
||||
"electron:dev": "cross-env VITE_GLOB_RUN_PLATFORM=electron npm run dev",
|
||||
"electron:build-all": "npm run electron:build-web && npm run electron:build-app",
|
||||
"electron:build-web": "cross-env VITE_GLOB_RUN_PLATFORM=electron NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 vite build --mode prod_electron && cross-env VITE_GLOB_RUN_PLATFORM=electron esno ./build/script/postBuild.ts && esno ./build/script/copyChat.ts",
|
||||
"electron:build-app": "esno ./electron/script/buildBefore.ts && electron-builder && esno ./electron/script/buildAfter.ts",
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "35.1.4",
|
||||
"electron-builder": "^26.0.12",
|
||||
"vite-plugin-electron": "^0.29.0",
|
||||
},
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
# Electron桌面通知示例和代码位置
|
||||
|
||||
1. 代码位置:electron/utils/tray.ts
|
||||
2. 发送系统通知调用:sendDesktopNotice
|
||||
3. 开始托盘图标闪动调用:startBlink
|
||||
4. 停止托盘图标闪动调用:stopBlink
|
|
@ -0,0 +1,18 @@
|
|||
// 不能直接使用 process.env,会报错
|
||||
export const $ps = process;
|
||||
|
||||
export const isDev = !!$ps.env.VITE_DEV_SERVER_URL;
|
||||
|
||||
export const $env = getEnv();
|
||||
|
||||
function getEnv() {
|
||||
if (isDev) {
|
||||
return $ps.env;
|
||||
}
|
||||
// 非开发环境,从 JSON 文件中获取环境变量
|
||||
const env = require('./env.json');
|
||||
return {
|
||||
...$ps.env,
|
||||
...env,
|
||||
};
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 98 KiB |
Binary file not shown.
After Width: | Height: | Size: 116 KiB |
|
@ -0,0 +1,4 @@
|
|||
import {ipcMain} from 'electron'
|
||||
import {openInBrowser} from "../utils";
|
||||
|
||||
ipcMain.on('open-in-browser', (event, url) => openInBrowser(url));
|
|
@ -0,0 +1,56 @@
|
|||
import './ipc';
|
||||
|
||||
import { app, BrowserWindow, Menu } from 'electron';
|
||||
import { isDev } from './env';
|
||||
import { createMainWindow, createIndexWindow } from './utils/window';
|
||||
import { getAppInfo} from "./utils";
|
||||
|
||||
// 隐藏所有菜单
|
||||
Menu.setApplicationMenu(null);
|
||||
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
|
||||
function main() {
|
||||
mainWindow = createMainWindow();
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
// 非开发环境,只允许一个实例运行
|
||||
if (!isDev) {
|
||||
// 是否取得了单一实例锁
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
if (gotTheLock) {
|
||||
app.on('second-instance', () => {
|
||||
// 开启一个新的窗口
|
||||
createIndexWindow();
|
||||
});
|
||||
} else {
|
||||
// 没有取得单一实例锁,则退出应用
|
||||
app.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期管理
|
||||
app.whenReady().then(() => {
|
||||
// 获取应用信息
|
||||
const $appInfo = getAppInfo();
|
||||
if ($appInfo?.productName && $appInfo?.appId) {
|
||||
app.setName($appInfo.productName);
|
||||
app.setAppUserModelId($appInfo.appId);
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
main();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
import path from 'path'
|
||||
import {isDev} from "./env";
|
||||
|
||||
export const _PATHS = getPaths()
|
||||
|
||||
function getPaths() {
|
||||
const _root = __dirname;
|
||||
const publicRoot = path.join(_root, isDev ? '../../public' : '..');
|
||||
const preloadRoot = path.join(_root, 'preload')
|
||||
|
||||
return {
|
||||
electronRoot: _root,
|
||||
publicRoot,
|
||||
preloadRoot,
|
||||
|
||||
appIcon: path.join(_root, `icons/app.ico`).replace(/[\\/]dist[\\/]/, '/'),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import {contextBridge, ipcRenderer} from 'electron'
|
||||
|
||||
contextBridge.exposeInMainWorld('_ELECTRON_PRELOAD_UTILS_', {
|
||||
openInBrowser: (url: string) => ipcRenderer.send('open-in-browser', url),
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
console.log('build elctron is done.');
|
|
@ -0,0 +1,27 @@
|
|||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
const root = path.join(__dirname, '../../');
|
||||
const electronDistRoot = path.join(root, 'dist/electron');
|
||||
|
||||
let yamlName = 'electron-builder.yaml';
|
||||
const sourcePath = fs.readFileSync(path.join(root, yamlName), 'utf-8');
|
||||
|
||||
try {
|
||||
// 通过正则表达式匹配 appId 和 productName
|
||||
const appIdMatch = sourcePath.match(/appId:\s*['"]([^'"]+)['"]/);
|
||||
const productNameMatch = sourcePath.match(/productName:\s*['"]([^'"]+)['"]/);
|
||||
if (appIdMatch && productNameMatch) {
|
||||
const fileContent = `${appIdMatch[0]}\n${productNameMatch[0]}`;
|
||||
yamlName = 'env.yaml';
|
||||
const targetPath = path.join(electronDistRoot, yamlName);
|
||||
fs.writeFileSync(targetPath, fileContent, 'utf-8');
|
||||
console.log(`✨ write dist ${yamlName} successfully.`);
|
||||
} else {
|
||||
throw new Error('appId or productName not found');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.error(`请检查 ${yamlName} 是否存在,或者内容是否正确`);
|
||||
process.exit(1);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path'
|
||||
import {shell, dialog} from 'electron'
|
||||
import {_PATHS} from "../paths";
|
||||
import {isDev} from "../env";
|
||||
|
||||
// 通过浏览器打开链接
|
||||
export function openInBrowser(url: string) {
|
||||
return shell.openExternal(url);
|
||||
}
|
||||
|
||||
|
||||
export function getAppInfo(): any {
|
||||
try {
|
||||
const yamlPath = isDev ? path.join(_PATHS.publicRoot, '../electron-builder.yaml') : path.join(_PATHS.electronRoot, 'env.yaml');
|
||||
const yamlContent = fs.readFileSync(yamlPath, 'utf-8');
|
||||
// 通过正则表达式匹配 appId 和 productName
|
||||
const appIdMatch = yamlContent.match(/appId:\s*['"]([^'"]+)['"]/);
|
||||
const productNameMatch = yamlContent.match(/productName:\s*['"]([^'"]+)['"]/);
|
||||
const appId = appIdMatch ? appIdMatch[1] : '';
|
||||
const productName = productNameMatch ? productNameMatch[1] : '';
|
||||
return {appId, productName}
|
||||
} catch (e) {
|
||||
dialog.showMessageBoxSync(null, {
|
||||
type: 'error',
|
||||
title: '错误',
|
||||
message: '应用启动失败,请从官网下载最新版本安装包后重新安装!',
|
||||
});
|
||||
process.exit(-1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
// tray = 系统托盘
|
||||
import path from 'path';
|
||||
import {Tray, Menu, app, dialog, nativeImage, BrowserWindow, Notification} from 'electron';
|
||||
import {_PATHS} from '../paths';
|
||||
import {$env, isDev} from '../env';
|
||||
|
||||
const TrayIcons = {
|
||||
normal: nativeImage.createFromPath(path.join(_PATHS.publicRoot, 'logo.png')),
|
||||
empty: nativeImage.createEmpty(),
|
||||
};
|
||||
|
||||
// 创建托盘图标
|
||||
export function createTray(win: BrowserWindow) {
|
||||
const tray = new Tray(TrayIcons.normal);
|
||||
|
||||
const TrayUtils = useTray(tray, win);
|
||||
|
||||
tray.setToolTip($env.VITE_GLOB_APP_TITLE! + (isDev ? ' (开发环境)' : ''));
|
||||
|
||||
// 左键托盘图标显示主窗口
|
||||
tray.on('click', () => TrayUtils.showMainWindow());
|
||||
// 右键托盘图标显示托盘菜单
|
||||
tray.on('right-click', () => showTrayContextMenu());
|
||||
|
||||
function showTrayContextMenu() {
|
||||
const trayContextMenu = getTrayMenus(win, TrayUtils);
|
||||
// 弹出托盘菜单,不使用 setContextMenu 方法是因为要实时更新菜单内容
|
||||
tray.popUpContextMenu(trayContextMenu);
|
||||
}
|
||||
}
|
||||
|
||||
export function useTray(tray: Tray, win: BrowserWindow) {
|
||||
let isBlinking = false;
|
||||
let blinkTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
function showMainWindow() {
|
||||
win.show();
|
||||
}
|
||||
|
||||
// 开始闪动
|
||||
function startBlink() {
|
||||
isBlinking = true;
|
||||
tray.setImage(TrayIcons.empty);
|
||||
blinkTimer = setTimeout(() => {
|
||||
tray.setImage(TrayIcons.normal);
|
||||
setTimeout(() => {
|
||||
if (isBlinking) {
|
||||
startBlink();
|
||||
}
|
||||
}, 500);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// 结束闪动
|
||||
function stopBlink() {
|
||||
isBlinking = false;
|
||||
if (blinkTimer) {
|
||||
clearTimeout(blinkTimer);
|
||||
blinkTimer = null;
|
||||
}
|
||||
tray.setImage(TrayIcons.normal);
|
||||
}
|
||||
|
||||
// 发送桌面通知
|
||||
function sendDesktopNotice() {
|
||||
// 判断是否支持桌面通知
|
||||
if (!Notification.isSupported()) {
|
||||
// todo 实际开发中不需要提示,直接返回或者换一种提示方式
|
||||
dialog.showMessageBoxSync(win, {
|
||||
type: 'error',
|
||||
title: '错误',
|
||||
message: '当前系统不支持桌面通知',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const ins = new Notification({
|
||||
title: '通知标题',
|
||||
subtitle: '通知副标题',
|
||||
body: '通知内容第一行\n通知内容第二行',
|
||||
icon: TrayIcons.normal.resize({width: 32, height: 32}),
|
||||
});
|
||||
|
||||
ins.on('click', () => {
|
||||
dialog.showMessageBoxSync(win, {
|
||||
type: 'info',
|
||||
title: '提示',
|
||||
message: '通知被点击',
|
||||
});
|
||||
});
|
||||
|
||||
ins.show();
|
||||
}
|
||||
|
||||
return {
|
||||
showMainWindow,
|
||||
|
||||
startBlink,
|
||||
stopBlink,
|
||||
isBlinking: () => isBlinking,
|
||||
|
||||
sendDesktopNotice,
|
||||
};
|
||||
}
|
||||
|
||||
const MenuIcon = {
|
||||
exit: nativeImage
|
||||
.createFromDataURL(
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACJ0lEQVR4nH1TzWvUQBRP7fpxsWqVXsSLiAevRWhhN28msRJo981kay4WRBCF/QdEFJpbaUHw4kFBQTwUKX4gKh48KPiBBcGLJ1F0uzPZ7ibWXf0DIjObielS+mDIm/fxm9/85sWyBixN06E0CIaV3wB2XhC8puOWNZSG4Y7B+k2mi7Kl9l2n9rHnzvbWJoLRYn7r5jTViQjwzM8ynlC+AFyVgN2NU8G+Rnn6QETx3FfP223A/jeHfWqCsAUJ7Hlryh9Te0nYqiDsz9rE6VHVIABvNwEf/ADYk4OsZPeVFbwiCHtcZBVR9k4CJhJmDuUxwEVJ8H4fINOkC9Vjbeq/UTR1IgPturX3f93Z35+B7ddxgJL6dih/skF9zE9KCJ//5bDLpii1+npIuzolKTubC5gBxzarJo6vWWjrUP+etFlF+ds9lRFOXalN+NPEmxvRDS3KH34v8+PFIgNmTh0EahH+InGCwzoQEbYcuTMnlR8aYbaxGHFvRNiznssP6sA65UsxrdU1+hYnFhlpAGAkdvzlPLFu88mY8pcrVjCsxcqGapC2eYW249/tUH4xS4QaVQLeigi/YWJqPl4DlNRSrAwzSaoXIspeWUYrI9qXINglgT1qAt5JPG+kkNN5BSAJuyoJfhAVdmST4PlPBFASNs6rIgnspqC8HlF+SQAuRQTfKpYiEy6fwuIdP42P71T+t0l/TBKcE8AXm4DXBfB6w50+apgUhf4HZ5j+Z5+zNTAAAAAASUVORK5CYII='
|
||||
)
|
||||
.resize({
|
||||
width: 16,
|
||||
height: 16,
|
||||
}),
|
||||
};
|
||||
|
||||
// 设置托盘菜单
|
||||
function getTrayMenus(win: BrowserWindow, TrayUtils: ReturnType<typeof useTray>) {
|
||||
const {startBlink, stopBlink, sendDesktopNotice} = TrayUtils;
|
||||
const isBlinking = TrayUtils.isBlinking();
|
||||
|
||||
return Menu.buildFromTemplate([
|
||||
...(isDev
|
||||
? [
|
||||
{
|
||||
label: '开发工具',
|
||||
submenu: [
|
||||
{
|
||||
label: '以下菜单仅显示在开发环境',
|
||||
sublabel: '当前为开发环境',
|
||||
enabled: false,
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: '切换 DevTools',
|
||||
click: () => win.webContents.toggleDevTools(),
|
||||
},
|
||||
{
|
||||
label: `托盘图标${isBlinking ? '停止' : '开始'}闪烁`,
|
||||
sublabel: '模拟新消息提醒',
|
||||
click: () => (isBlinking ? stopBlink() : startBlink()),
|
||||
},
|
||||
{
|
||||
label: '发送桌面通知示例',
|
||||
click: () => sendDesktopNotice(),
|
||||
},
|
||||
],
|
||||
},
|
||||
{type: 'separator'},
|
||||
]
|
||||
: ([] as any)),
|
||||
{
|
||||
label: '显示主窗口',
|
||||
// 文件图标
|
||||
icon: TrayIcons.normal.resize({width: 16, height: 16}),
|
||||
click: () => win.show(),
|
||||
},
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: '退出',
|
||||
// base64图标
|
||||
icon: MenuIcon.exit,
|
||||
click: () => {
|
||||
// 弹出是否确认退出提示框
|
||||
const choice = dialog.showMessageBoxSync(win, {
|
||||
type: 'question',
|
||||
title: '提示',
|
||||
message: '确定要退出应用吗?',
|
||||
buttons: ['退出', '取消'],
|
||||
defaultId: 1,
|
||||
cancelId: 1,
|
||||
noLink: true,
|
||||
});
|
||||
// 用户选择了退出,直接 exit
|
||||
if (choice === 0) {
|
||||
// global.isQuitting = true;
|
||||
app.exit(0);
|
||||
}
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
import type {BrowserWindowConstructorOptions} from 'electron';
|
||||
import {BrowserWindow, dialog} from 'electron';
|
||||
import path from 'path';
|
||||
import {_PATHS} from '../paths';
|
||||
import {$env, isDev} from '../env';
|
||||
import {createTray} from './tray';
|
||||
|
||||
// 创建窗口
|
||||
export function createBrowserWindow(options?: BrowserWindowConstructorOptions) {
|
||||
const win = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
webPreferences: {
|
||||
preload: path.join(_PATHS.preloadRoot, 'index.js'),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
},
|
||||
// 应用图标
|
||||
icon: isDev ? _PATHS.appIcon : void 0,
|
||||
...options,
|
||||
});
|
||||
|
||||
// 设置窗口打开处理器
|
||||
win.webContents.setWindowOpenHandler(({url}) => {
|
||||
const win = createBrowserWindow();
|
||||
win.loadURL(url);
|
||||
// 阻止创建新窗口,因为已经被接管
|
||||
return {action: 'deny'};
|
||||
});
|
||||
|
||||
// 当 beforeunload 阻止窗口关闭时触发
|
||||
win.webContents.on('will-prevent-unload', () => {
|
||||
const choice = dialog.showMessageBoxSync(win, {
|
||||
type: 'question',
|
||||
title: '确认关闭吗?',
|
||||
message: '系统可能不会保存您所做的更改。',
|
||||
buttons: ['关闭', '取消'],
|
||||
defaultId: 1,
|
||||
cancelId: 1,
|
||||
noLink: true,
|
||||
});
|
||||
// 用户选择了关闭,直接销毁窗口
|
||||
if (choice === 0) {
|
||||
win.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
// 创建主窗口、系统托盘
|
||||
export function createMainWindow() {
|
||||
const win = createIndexWindow()
|
||||
|
||||
// 设置系统托盘图标
|
||||
createTray(win);
|
||||
|
||||
// 主窗口尝试关闭时,默认不直接退出应用,而是隐藏到托盘
|
||||
win.on('close', (event) => {
|
||||
event.preventDefault();
|
||||
win.hide();
|
||||
});
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
// 创建索引窗口
|
||||
export function createIndexWindow() {
|
||||
const win = createBrowserWindow({
|
||||
width: 1600,
|
||||
height: 1000,
|
||||
title: $env.VITE_GLOB_APP_TITLE!,
|
||||
});
|
||||
|
||||
// 开发环境加载Vite服务,生产加载打包文件
|
||||
if (isDev) {
|
||||
win.loadURL($env.VITE_DEV_SERVER_URL!)
|
||||
// 开发环境下,自动打开调试工具
|
||||
// win.webContents.openDevTools()
|
||||
} else {
|
||||
win.loadFile(path.join(_PATHS.publicRoot, 'index.html'));
|
||||
}
|
||||
|
||||
return win;
|
||||
}
|
|
@ -14,6 +14,7 @@ enum Api {
|
|||
getDictItems = '/sys/dict/getDictItems/',
|
||||
getTableList = '/sys/user/queryUserComponentData',
|
||||
getCategoryData = '/sys/category/loadAllData',
|
||||
refreshDragCache = '/drag/page/refreshCache',
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,3 +149,8 @@ export const getFileblob = (url, parameter) => {
|
|||
export const uploadMyFile = (url, data) => {
|
||||
return defHttp.uploadMyFile(url, data);
|
||||
};
|
||||
/**
|
||||
* 刷新仪表盘缓存
|
||||
* @param params
|
||||
*/
|
||||
export const refreshDragCache = () => defHttp.get({ url: Api.refreshDragCache }, { isTransformResponse: false });
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="64px" height="60.24px" viewBox="0 0 1088 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M972.8 742.4v192H115.2v-192H0V454.4h108.8v-192h864v192H1088v288h-115.2z m-108.8 83.2V371.2h-640v454.4h640zM659.2 96v108.8H428.8V96h230.4z m-256 582.4C358.4 678.4 320 640 320 595.2 320 550.4 358.4 512 403.2 512s83.2 38.4 83.2 83.2c0 44.8-38.4 83.2-83.2 83.2z m281.6 0c-44.8 0-83.2-38.4-83.2-83.2 0-44.8 38.4-83.2 83.2-83.2s83.2 38.4 83.2 83.2c-6.4 44.8-44.8 83.2-83.2 83.2z" /></svg>
|
After Width: | Height: | Size: 647 B |
|
@ -367,6 +367,13 @@
|
|||
margin-bottom: 24px;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240620---for:【TV360X-1420】校验时闪动
|
||||
|
||||
// 表单组件中间件样式
|
||||
.j-form-item-middleware {
|
||||
flex: 1;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
&.suffix-item {
|
||||
.ant-form-item-children {
|
||||
display: flex;
|
||||
|
@ -376,6 +383,12 @@
|
|||
margin-top: 4px;
|
||||
}
|
||||
|
||||
// 【QQYUN-12876】当紧凑型 suffix 时,表单组件中间件的宽度不占满
|
||||
&.suffix-compact .j-form-item-middleware {
|
||||
flex: unset;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.suffix {
|
||||
display: inline-flex;
|
||||
padding-left: 6px;
|
||||
|
|
|
@ -65,6 +65,7 @@ import JTreeSelect from './jeecg/components/JTreeSelect.vue';
|
|||
import JEllipsis from './jeecg/components/JEllipsis.vue';
|
||||
import JSelectUserByDept from './jeecg/components/JSelectUserByDept.vue';
|
||||
import JSelectUserByDepartment from './jeecg/components/JSelectUserByDepartment.vue';
|
||||
import JLinkTableCard from './jeecg/components/JLinkTableCard/JLinkTableCard.vue';
|
||||
import JUpload from './jeecg/components/JUpload/JUpload.vue';
|
||||
import JSearchSelect from './jeecg/components/JSearchSelect.vue';
|
||||
import JAddInput from './jeecg/components/JAddInput.vue';
|
||||
|
@ -128,6 +129,7 @@ componentMap.set('JImageUpload', JImageUpload);
|
|||
componentMap.set('JDictSelectTag', JDictSelectTag);
|
||||
componentMap.set('JSelectDept', JSelectDept);
|
||||
componentMap.set('JAreaSelect', JAreaSelect);
|
||||
componentMap.set('JLinkTableCard', JLinkTableCard);
|
||||
// componentMap.set(
|
||||
// 'JEditor',
|
||||
// createAsyncComponent(() => import('./jeecg/components/JEditor.vue'))
|
||||
|
|
|
@ -464,10 +464,17 @@
|
|||
}
|
||||
|
||||
function renderItem() {
|
||||
const { itemProps, slot, render, field, suffix, component } = props.schema;
|
||||
const { itemProps, slot, render, field, suffix, suffixCompact, component } = props.schema;
|
||||
const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
|
||||
const { colon } = props.formProps;
|
||||
|
||||
// update-begin--author:sunjianlei---date:20250613---for:itemProps 属性支持函数形式
|
||||
let getItemProps = itemProps;
|
||||
if (typeof getItemProps === 'function') {
|
||||
getItemProps = getItemProps(unref(getValues));
|
||||
}
|
||||
// update-end--author:sunjianlei---date:20250613---for:itemProps 属性支持函数形式
|
||||
|
||||
if (component === 'Divider') {
|
||||
return (
|
||||
<Col span={24}>
|
||||
|
@ -486,8 +493,8 @@
|
|||
<Form.Item
|
||||
name={field}
|
||||
colon={colon}
|
||||
class={{ 'suffix-item': showSuffix }}
|
||||
{...(itemProps as Recordable)}
|
||||
class={{ 'suffix-item': showSuffix, 'suffix-compact': showSuffix && suffixCompact }}
|
||||
{...(getItemProps as Recordable)}
|
||||
label={renderLabelHelpMessage()}
|
||||
rules={handleRules()}
|
||||
// update-begin--author:liaozhiyang---date:20240514---for:【issues/1244】标识了必填,但是必填标识没显示
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div :id="formItemId" style="flex: 1; width: 100%">
|
||||
<div :id="formItemId" class="j-form-item-middleware">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -89,6 +89,8 @@
|
|||
required: false,
|
||||
},
|
||||
style: propTypes.any,
|
||||
// 搜索时是否只搜索label
|
||||
onlySearchByLabel: propTypes.bool.def(false),
|
||||
},
|
||||
emits: ['options-change', 'change','update:value'],
|
||||
setup(props, { emit, refs }) {
|
||||
|
@ -202,6 +204,10 @@
|
|||
}
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20230914---for:【QQYUN-6514】 配置的时候,Y轴不能输入多个字段了,控制台报错
|
||||
if (props.onlySearchByLabel) {
|
||||
// 如果开启了只在 label 中搜索,就不继续往下搜索value了
|
||||
return false;
|
||||
}
|
||||
// 在 value 中搜索
|
||||
return (option.value || '').toString().toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
</a-button>
|
||||
</div>
|
||||
</a-upload>
|
||||
<a-modal :open="previewVisible" :footer="null" @cancel="handleCancel()">
|
||||
<a-modal :width="previewWidth" :open="previewVisible" :footer="null" @cancel="handleCancel()">
|
||||
<img alt="example" style="width: 100%" :src="previewImage" />
|
||||
</a-modal>
|
||||
</div>
|
||||
|
@ -38,7 +38,7 @@
|
|||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { getFileAccessHttpUrl, getHeaders, getRandom } from '/@/utils/common/compUtils';
|
||||
import { uploadUrl } from '/@/api/common/api';
|
||||
import { uploadUrl as systemUploadUrl } from '/@/api/common/api';
|
||||
import { getToken } from '/@/utils/auth';
|
||||
|
||||
const { createMessage, createErrorModal } = useMessage();
|
||||
|
@ -79,6 +79,15 @@
|
|||
required: false,
|
||||
default: 1,
|
||||
},
|
||||
uploadUrl: {
|
||||
type: String,
|
||||
default: systemUploadUrl,
|
||||
},
|
||||
previewWidth: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 520,
|
||||
},
|
||||
},
|
||||
emits: ['options-change', 'change', 'update:value'],
|
||||
setup(props, { emit, refs }) {
|
||||
|
@ -256,7 +265,6 @@
|
|||
multiple,
|
||||
headers,
|
||||
loading,
|
||||
uploadUrl,
|
||||
beforeUpload,
|
||||
uploadVisible,
|
||||
handlePreview,
|
||||
|
|
|
@ -0,0 +1,379 @@
|
|||
<template>
|
||||
<div ref="tableLinkCardRef">
|
||||
<div class="table-link-card">
|
||||
<div style="width: 100%; height: 100%">
|
||||
<div class="card-button" v-if="showButton">
|
||||
<a-button @click="handleAddRecord"><PlusOutlined />记 录</a-button>
|
||||
</div>
|
||||
|
||||
<a-row>
|
||||
<a-col :span="fixedSpan ? fixedSpan : itemSpan" v-for="(record, index) in selectRecords" :key="index">
|
||||
<div class="card-item" :class="{ 'disabled-chunk': detail == true }">
|
||||
<!-- -->
|
||||
<div class="card-item-left" :class="{ 'show-right-image': getImageSrc(record) }">
|
||||
<span class="card-delete" v-if="disabled == false">
|
||||
<minus-circle-filled @click="(e) => handleDeleteRecord(e, index)" />
|
||||
</span>
|
||||
<div class="card-inner">
|
||||
<div class="card-main-content">{{ getMainContent(record) }}</div>
|
||||
<div class="other-content">
|
||||
<a-row>
|
||||
<a-col :span="columnSpan" v-for="(col, cIndex) in realShowColumns" :key="cIndex">
|
||||
<span class="label ellipsis">{{ col.title }}</span>
|
||||
<span class="text ellipsis">{{ record[col.dataIndex] }}</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-item-image" v-if="getImageSrc(record)">
|
||||
<img v-if="getImageSrc(record)" :src="getImageSrc(record)" @error="handleImageError" />
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
<LinkTableListModal @register="registerListModal" :multi="multi" :id="popTableName" @success="addCard" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { PlusOutlined, MinusCircleFilled } from '@ant-design/icons-vue';
|
||||
import { computed, ref, watch, onMounted } from 'vue';
|
||||
import { useLinkTable } from './hooks/useLinkTable';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import placeholderImage from '/@/assets/images/placeholderImage.png';
|
||||
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent';
|
||||
|
||||
export default {
|
||||
name: 'JLinkTableCard',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
// value字段
|
||||
valueField: propTypes.string.def(''),
|
||||
// 文本字段
|
||||
textField: propTypes.string.def(''),
|
||||
// 关联表名
|
||||
tableName: propTypes.string.def(''),
|
||||
// 是否多选
|
||||
multi: propTypes.bool.def(false),
|
||||
value: propTypes.oneOfType([propTypes.string, propTypes.number]),
|
||||
// ["表单字段,表字典字段","表单字段,表字典字段"]
|
||||
linkFields: propTypes.array.def([]),
|
||||
//是否是禁用页面
|
||||
disabled: propTypes.bool.def(false),
|
||||
// 是否是detail页面
|
||||
detail: propTypes.bool.def(false),
|
||||
// 图片字段
|
||||
imageField: propTypes.string.def(''),
|
||||
},
|
||||
components: {
|
||||
PlusOutlined,
|
||||
MinusCircleFilled,
|
||||
LinkTableListModal: createAsyncComponent(() => import('./components/LinkTableListModal.vue'), { loading: true }),
|
||||
},
|
||||
emits: ['change', 'update:value'],
|
||||
setup(props, { emit }) {
|
||||
const popTableName = computed(() => {
|
||||
return props.tableName;
|
||||
});
|
||||
//注册model
|
||||
const [registerListModal, { openModal: openListModal }] = useModal();
|
||||
const selectValue = ref([]);
|
||||
const selectRecords = ref([]);
|
||||
const tableLinkCardRef = ref(null);
|
||||
const fixedSpan = ref(0);
|
||||
|
||||
const showButton = computed(() => {
|
||||
if (props.disabled == true) {
|
||||
return false;
|
||||
}
|
||||
if (props.multi === false) {
|
||||
//单选且有值
|
||||
if (selectRecords.value.length > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const {
|
||||
auths,
|
||||
otherColumns,
|
||||
realShowColumns,
|
||||
tableColumns,
|
||||
textFieldArray,
|
||||
transData,
|
||||
loadOne,
|
||||
compareData,
|
||||
formatData,
|
||||
initFormData,
|
||||
getImageSrc,
|
||||
showImage,
|
||||
} = useLinkTable(props);
|
||||
|
||||
const itemSpan = computed(() => {
|
||||
if (props.multi === true) {
|
||||
return 12;
|
||||
}
|
||||
return 24;
|
||||
});
|
||||
|
||||
const columnSpan = computed(() => {
|
||||
if (props.multi === true) {
|
||||
return 24;
|
||||
}
|
||||
return 12;
|
||||
});
|
||||
|
||||
function getMainContent(record) {
|
||||
if (record) {
|
||||
if (textFieldArray.value.length > 0) {
|
||||
let field = textFieldArray.value[0];
|
||||
return record[field];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prevent(e) {
|
||||
e?.stopPropagation();
|
||||
e?.preventDefault();
|
||||
}
|
||||
|
||||
|
||||
function handleAddRecord(e) {
|
||||
prevent(e);
|
||||
openListModal(true, {
|
||||
// update-begin--author:liaozhiyang---date:20240517---for:【TV360X-43】修复关联记录可以添加重复数据
|
||||
selectedRowKeys: selectRecords.value.map((item) => item.id),
|
||||
selectedRows: [...selectRecords.value],
|
||||
// update-end--author:liaozhiyang---date:20240517---for:【TV360X-43】修复关联记录可以添加重复数据
|
||||
});
|
||||
}
|
||||
|
||||
function addCard(data) {
|
||||
// update-begin--author:liaozhiyang---date:20240517---for:【TV360X-43】修复关联记录可以添加重复数据
|
||||
let arr = [];
|
||||
// update-end--author:liaozhiyang---date:20240517---for:【TV360X-43】修复关联记录可以添加重复数据
|
||||
for (let item of data) {
|
||||
let temp = { ...item };
|
||||
transData(temp);
|
||||
arr.push(temp);
|
||||
}
|
||||
selectRecords.value = arr;
|
||||
emitValue();
|
||||
}
|
||||
|
||||
function updateCardData(formData) {
|
||||
let arr = selectRecords.value;
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i].id === formData.id) {
|
||||
let temp = { ...formData };
|
||||
transData(temp);
|
||||
arr.splice(i, 1, temp);
|
||||
}
|
||||
}
|
||||
selectRecords.value = arr;
|
||||
emitValue();
|
||||
}
|
||||
|
||||
function handleDeleteRecord(e, index) {
|
||||
prevent(e);
|
||||
let temp = selectRecords.value;
|
||||
if (temp && temp.length > index) {
|
||||
temp.splice(index, 1);
|
||||
selectRecords.value = temp;
|
||||
}
|
||||
emitValue();
|
||||
}
|
||||
|
||||
function emitValue() {
|
||||
let arr = selectRecords.value;
|
||||
let values = [];
|
||||
let formData = {};
|
||||
let linkFieldArray = props.linkFields;
|
||||
if (arr.length > 0) {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
values.push(arr[i][props.valueField]);
|
||||
initFormData(formData, linkFieldArray, arr[i]);
|
||||
}
|
||||
} else {
|
||||
initFormData(formData, linkFieldArray);
|
||||
}
|
||||
let text = values.join(',');
|
||||
formatData(formData);
|
||||
emit('change', text, formData);
|
||||
emit('update:value', text);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
async (val) => {
|
||||
if (val) {
|
||||
let flag = compareData(selectRecords.value, val);
|
||||
if (flag === false) {
|
||||
let arr = await loadOne(val);
|
||||
selectRecords.value = arr;
|
||||
}
|
||||
//保证表单其他值回显成功
|
||||
if (props.linkFields && props.linkFields.length > 0) {
|
||||
emitValue();
|
||||
}
|
||||
} else {
|
||||
selectRecords.value = [];
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
// update-begin--author:liaozhiyang---date:20240522---for:【TV360X-281】分辨率小时关联记录文字被图片挤没了
|
||||
if (tableLinkCardRef.value.offsetWidth < 250) {
|
||||
fixedSpan.value = 24;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240522---for:【TV360X-281】分辨率小时关联记录文字被图片挤没了
|
||||
});
|
||||
// update-begin--author:liaozhiyang---date:20240529---for:【TV360X-389】下拉和卡片关联记录图裂开给个默认图片
|
||||
const handleImageError = (event) => {
|
||||
event.target.src = placeholderImage;
|
||||
};
|
||||
// update-end--author:liaozhiyang---date:20240529---for:【TV360X-389】下拉和卡片关联记录图裂开给个默认图片
|
||||
|
||||
return {
|
||||
popTableName,
|
||||
selectRecords,
|
||||
otherColumns,
|
||||
realShowColumns,
|
||||
showButton,
|
||||
selectValue,
|
||||
handleAddRecord,
|
||||
handleDeleteRecord,
|
||||
getMainContent,
|
||||
itemSpan,
|
||||
columnSpan,
|
||||
tableColumns,
|
||||
addCard,
|
||||
registerListModal,
|
||||
updateCardData,
|
||||
getImageSrc,
|
||||
showImage,
|
||||
auths,
|
||||
tableLinkCardRef,
|
||||
fixedSpan,
|
||||
placeholderImage,
|
||||
handleImageError,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.table-link-card {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
.card-button {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
.card-item {
|
||||
width: calc(100% - 10px);
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
margin: 0px 10px 10px 0px;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
box-shadow:
|
||||
rgb(0 0 0 / 12%) 0px 1px 4px 0px,
|
||||
rgb(0 0 0 / 12%) 0px 0px 2px 0px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
/* box-shadow: rgb(0 0 0 / 12%) 0px 4px 12px 0px, rgb(0 0 0 / 12%) 0px 0px 2px 0px;*/
|
||||
.card-item-left {
|
||||
.card-delete {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled-chunk {
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.card-item-image {
|
||||
width: 100px;
|
||||
padding-right: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.card-item-left {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
&.show-right-image {
|
||||
width: calc(100% - 100px);
|
||||
}
|
||||
.card-delete {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
font-size: 16px;
|
||||
color: #757575;
|
||||
line-height: 1em;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card-inner {
|
||||
flex: 1 1 0%;
|
||||
padding: 12px 16px;
|
||||
overflow: hidden;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.card-main-content {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: rgb(51, 51, 51);
|
||||
}
|
||||
|
||||
.other-content {
|
||||
.text {
|
||||
font-size: 12px !important;
|
||||
display: inline-block;
|
||||
width: 80%;
|
||||
}
|
||||
.label {
|
||||
max-width: 160px;
|
||||
color: rgb(158, 158, 158);
|
||||
padding-right: 0.7em;
|
||||
display: inline-block;
|
||||
}
|
||||
.ellipsis {
|
||||
overflow: hidden;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
text-overflow: ellipsis;
|
||||
/* vertical-align: top;*/
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,320 @@
|
|||
<template>
|
||||
<BasicModal
|
||||
@register="registerModal"
|
||||
:width="popModalFixedWidth"
|
||||
:dialogStyle="{ top: '70px' }"
|
||||
:bodyStyle="popBodyStyle"
|
||||
:title="modalTitle"
|
||||
wrapClassName="jeecg-online-pop-list-modal"
|
||||
>
|
||||
<template #footer>
|
||||
<a-button key="back" @click="handleCancel">关闭</a-button>
|
||||
<a-button :disabled="submitDisabled" key="submit" type="primary" @click="handleSubmit" :loading="submitLoading">确定</a-button>
|
||||
</template>
|
||||
|
||||
<BasicTable ref="tableRef" @register="registerTable" :rowSelection="rowSelection">
|
||||
<!-- update-begin-author:taoyan date:2023-7-11 for: issues/4992 online表单开发 字段控件类型是关联记录 新增的时候选择列表可以添加查询么 -->
|
||||
<template #tableTitle>
|
||||
<a-input-search v-model:value="searchText" @search="onSearch" placeholder="请输入关键词,按回车搜索" style="width: 240px" />
|
||||
</template>
|
||||
<!-- update-end-author:taoyan date:2023-7-11 for: issues/4992 online表单开发 字段控件类型是关联记录 新增的时候选择列表可以添加查询么 -->
|
||||
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)"> </TableAction>
|
||||
</template>
|
||||
|
||||
<template #fileSlot="{ text }">
|
||||
<span v-if="!text" style="font-size: 12px; font-style: italic">无文件</span>
|
||||
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download" size="small" @click="downloadRowFile(text)"> 下载 </a-button>
|
||||
</template>
|
||||
|
||||
<template #imgSlot="{ text }">
|
||||
<span v-if="!text" style="font-size: 12px; font-style: italic">无图片</span>
|
||||
<img v-else :src="getImgView(text)" alt="图片不存在" class="online-cell-image" @click="viewOnlineCellImage(text)" />
|
||||
</template>
|
||||
|
||||
<template #htmlSlot="{ text }">
|
||||
<div v-html="text"></div>
|
||||
</template>
|
||||
|
||||
<template #pcaSlot="{ text }">
|
||||
<div :title="getPcaText(text)">{{ getPcaText(text) }}</div>
|
||||
</template>
|
||||
|
||||
<template #dateSlot="{ text, column }">
|
||||
<span>{{ getFormatDate(text, column) }}</span>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch, ref, toRaw, computed, nextTick } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useTableColumns } from '@/views/super/online/cgform/hooks/auto/useTableColumns';
|
||||
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent';
|
||||
import { useFixedHeightModal } from '../hooks/useLinkTable';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LinkTableListModal',
|
||||
props: {
|
||||
/**可以是表名 可以是ID*/
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
multi: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
addAuth: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
BasicModal,
|
||||
BasicTable: createAsyncComponent(() => import('/@/components/Table/src/BasicTable.vue'), { loading: true, delay: 1000 }),
|
||||
TableAction: createAsyncComponent(() => import('/@/components/Table/src/components/TableAction.vue'), { loading: true, delay: 1000 }),
|
||||
},
|
||||
emits: ['success', 'register'],
|
||||
setup(props, { emit }) {
|
||||
const { createMessage: $message } = useMessage();
|
||||
const tableRef = ref(null);
|
||||
// 弹窗高度控制
|
||||
const { popModalFixedWidth, resetBodyStyle, popBodyStyle } = useFixedHeightModal();
|
||||
const searchText = ref('');
|
||||
const modalWidth = ref(800);
|
||||
//useModalInner
|
||||
const [registerModal, { closeModal }] = useModalInner((data) => {
|
||||
searchText.value = '';
|
||||
selectedRowKeys.value = data.selectedRowKeys;
|
||||
selectedRows.value = data.selectedRows;
|
||||
setTimeout(async () => {
|
||||
await setPagination({ current: 1 });
|
||||
await reload(); // 等待表格加载
|
||||
resetBodyStyle();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
function handleCancel() {
|
||||
closeModal();
|
||||
}
|
||||
const submitDisabled = computed(() => {
|
||||
const arr = selectedRowKeys.value;
|
||||
if (arr && arr.length > 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const submitLoading = ref(false);
|
||||
function handleSubmit() {
|
||||
submitLoading.value = true;
|
||||
let arr = toRaw(selectedRows.value);
|
||||
if (arr && arr.length > 0) {
|
||||
emit('success', arr);
|
||||
closeModal();
|
||||
}
|
||||
setTimeout(() => {
|
||||
submitLoading.value = false;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
//---------------------列表------------------------
|
||||
function queryTableData(params) {
|
||||
const url = '/online/cgform/api/getData/' + props.id;
|
||||
return defHttp.get({ url, params });
|
||||
}
|
||||
|
||||
function list(params) {
|
||||
params['column'] = 'id';
|
||||
return new Promise(async (resolve, _reject) => {
|
||||
const aa = await queryTableData(params);
|
||||
resolve(aa);
|
||||
});
|
||||
}
|
||||
|
||||
const onlineTableContext = {
|
||||
isPopList: true,
|
||||
reloadTable() {
|
||||
console.log('reloadTable');
|
||||
},
|
||||
isTree() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
const extConfigJson = ref<any>({});
|
||||
|
||||
// 处理 BasicTable 的配置
|
||||
const { columns, downloadRowFile, getImgView, getPcaText, getFormatDate, handleColumnResult, hrefComponent, viewOnlineCellImage } =
|
||||
useTableColumns(onlineTableContext, extConfigJson);
|
||||
|
||||
/**
|
||||
* 查询table列信息 及其他配置
|
||||
*/
|
||||
function getColumnList() {
|
||||
const url = '/online/cgform/api/getColumns/' + props.id;
|
||||
return new Promise((resolve, reject) => {
|
||||
defHttp.get({ url }, { isTransformResponse: false }).then((res) => {
|
||||
if (res.success) {
|
||||
resolve(res.result);
|
||||
} else {
|
||||
$message.warning(res.message);
|
||||
reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const modalTitle = ref('');
|
||||
watch(
|
||||
() => props.id,
|
||||
async () => {
|
||||
let columnResult: any = await getColumnList();
|
||||
handleColumnResult(columnResult);
|
||||
modalTitle.value = columnResult.description;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const { tableContext } = useListPage({
|
||||
designScope: 'process-design',
|
||||
pagination: true,
|
||||
tableProps: {
|
||||
title: '',
|
||||
api: list,
|
||||
clickToRowSelect: true,
|
||||
columns: columns,
|
||||
showTableSetting: false,
|
||||
immediate: false,
|
||||
//showIndexColumn: true,
|
||||
canResize: false,
|
||||
showActionColumn: false,
|
||||
actionColumn: {
|
||||
dataIndex: 'action',
|
||||
slots: { customRender: 'action' },
|
||||
},
|
||||
useSearchForm: false,
|
||||
beforeFetch: (params) => {
|
||||
return addQueryParams(params);
|
||||
},
|
||||
},
|
||||
});
|
||||
const [registerTable, { reload, setPagination }, { rowSelection, selectedRowKeys, selectedRows }] = tableContext;
|
||||
watch(
|
||||
() => props.multi,
|
||||
(val) => {
|
||||
if (val == true) {
|
||||
rowSelection.type = 'checkbox';
|
||||
} else {
|
||||
rowSelection.type = 'radio';
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleUpdate.bind(null, record),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function handleUpdate(record) {
|
||||
console.log('handleUpdate', record);
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
reload();
|
||||
}
|
||||
const eqConditonTypes = ['int', 'double', 'Date', 'Datetime', 'BigDecimal'];
|
||||
function addQueryParams(params) {
|
||||
let text = searchText.value;
|
||||
if (!text) {
|
||||
params['superQueryMatchType'] = 'or';
|
||||
params['superQueryParams'] = '';
|
||||
return params;
|
||||
}
|
||||
let arr = columns.value;
|
||||
let conditions: any[] = [];
|
||||
if (arr && arr.length > 0) {
|
||||
for (let item of arr) {
|
||||
if (item.dbType) {
|
||||
if (item.dbType == 'string') {
|
||||
conditions.push({ field: item.dataIndex, type: item.dbType.toLowerCase(), rule: 'like', val: text });
|
||||
} else if (item.dbType == 'Date') {
|
||||
if (text.length == '2020-10-10'.length) {
|
||||
conditions.push({ field: item.dataIndex, type: item.dbType.toLowerCase(), rule: 'eq', val: text });
|
||||
}
|
||||
} else if (item.dbType == 'Datetime') {
|
||||
if (text.length == '2020-10-10 10:10:10'.length) {
|
||||
conditions.push({ field: item.dataIndex, type: item.dbType.toLowerCase(), rule: 'eq', val: text });
|
||||
}
|
||||
} else if (eqConditonTypes.indexOf(item.dbType)) {
|
||||
conditions.push({ field: item.dataIndex, type: item.dbType.toLowerCase(), rule: 'eq', val: text });
|
||||
} else {
|
||||
//text blob不做处理
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
params['superQueryMatchType'] = 'or';
|
||||
params['superQueryParams'] = encodeURI(JSON.stringify(conditions));
|
||||
return params;
|
||||
}
|
||||
|
||||
// modal数据新增完成 直接关闭list,将新增的数据带回表单
|
||||
function handleDataSave(data) {
|
||||
console.log('handleDateSave', data);
|
||||
// update-begin--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录新增丢失
|
||||
let arr = [data, ...selectedRows.value];
|
||||
// update-end--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录新增丢失
|
||||
emit('success', arr);
|
||||
closeModal();
|
||||
//reload();
|
||||
}
|
||||
|
||||
return {
|
||||
registerModal,
|
||||
modalWidth,
|
||||
handleCancel,
|
||||
submitDisabled,
|
||||
submitLoading,
|
||||
handleSubmit,
|
||||
|
||||
registerTable,
|
||||
getTableAction,
|
||||
searchText,
|
||||
onSearch,
|
||||
|
||||
downloadRowFile,
|
||||
getImgView,
|
||||
getPcaText,
|
||||
getFormatDate,
|
||||
hrefComponent,
|
||||
viewOnlineCellImage,
|
||||
rowSelection,
|
||||
modalTitle,
|
||||
|
||||
reload,
|
||||
|
||||
popModalFixedWidth,
|
||||
popBodyStyle,
|
||||
handleDataSave,
|
||||
|
||||
tableRef,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,358 @@
|
|||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { ref, watchEffect, computed, reactive } from 'vue';
|
||||
import { pick } from 'lodash-es';
|
||||
import { filterMultiDictText } from '/@/utils/dict/JDictSelectUtil';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
|
||||
function queryTableData(tableName, params) {
|
||||
const url = '/online/cgform/api/getData/' + tableName;
|
||||
return defHttp.get({ url, params });
|
||||
}
|
||||
|
||||
function queryTableColumns(tableName, params) {
|
||||
const url = '/online/cgform/api/getColumns/' + tableName;
|
||||
return defHttp.get({ url, params });
|
||||
}
|
||||
|
||||
export function useLinkTable(props) {
|
||||
//TODO 目前只支持查询第一页的数据,可以输入关键字搜索
|
||||
const pageNo = ref('1');
|
||||
// 查询列
|
||||
const baseParam = ref<any>({});
|
||||
// 搜素条件
|
||||
const searchParam = ref<any>({});
|
||||
// 第一个文本列
|
||||
const mainContentField = ref('');
|
||||
//权限数据
|
||||
const auths = reactive({
|
||||
add: true,
|
||||
update: true,
|
||||
});
|
||||
|
||||
//显示列
|
||||
const textFieldArray = computed(() => {
|
||||
if (props.textField) {
|
||||
return props.textField.split(',');
|
||||
}
|
||||
return [];
|
||||
});
|
||||
const otherColumns = ref<any[]>([]);
|
||||
// 展示的列 配置的很多列,但是只展示三行
|
||||
const realShowColumns = computed(() => {
|
||||
const columns = otherColumns.value;
|
||||
if (props.multi == true) {
|
||||
return columns.slice(0, 3);
|
||||
} else {
|
||||
return columns.slice(0, 6);
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(async () => {
|
||||
const table = props.tableName;
|
||||
if (table) {
|
||||
const valueField = props.valueField || '';
|
||||
const textField = props.textField || '';
|
||||
const arr: any[] = [];
|
||||
if (valueField) {
|
||||
arr.push(valueField);
|
||||
}
|
||||
if (textField) {
|
||||
const temp = textField.split(',');
|
||||
mainContentField.value = temp[0];
|
||||
for (const field of temp) {
|
||||
arr.push(field);
|
||||
}
|
||||
}
|
||||
const imageField = props.imageField || '';
|
||||
if (imageField) {
|
||||
arr.push(imageField);
|
||||
}
|
||||
baseParam.value = {
|
||||
linkTableSelectFields: arr.join(','),
|
||||
};
|
||||
await resetTableColumns();
|
||||
await reloadTableLinkOptions();
|
||||
}
|
||||
});
|
||||
|
||||
const otherFields = computed(() => {
|
||||
const textField = props.textField || '';
|
||||
const others: any[] = [];
|
||||
let labelField = '';
|
||||
if (textField) {
|
||||
const temp = textField.split(',');
|
||||
labelField = temp[0];
|
||||
for (let i = 0; i < temp.length; i++) {
|
||||
if (i > 0) {
|
||||
others.push(temp[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
others,
|
||||
labelField,
|
||||
};
|
||||
});
|
||||
|
||||
// 选项
|
||||
const selectOptions = ref<any[]>([]);
|
||||
const tableColumns = ref<any[]>([]);
|
||||
const dictOptions = ref<any>({});
|
||||
|
||||
async function resetTableColumns() {
|
||||
const params = baseParam.value;
|
||||
const data = await queryTableColumns(props.tableName, params);
|
||||
tableColumns.value = data.columns;
|
||||
if (data.columns) {
|
||||
const imageField = props.imageField;
|
||||
const arr = data.columns.filter((c) => c.dataIndex != mainContentField.value && c.dataIndex != imageField);
|
||||
otherColumns.value = arr;
|
||||
}
|
||||
dictOptions.value = data.dictOptions;
|
||||
// 权限数据
|
||||
console.log('隐藏的按钮', data.hideColumns);
|
||||
if (data.hideColumns) {
|
||||
const hideCols = data.hideColumns;
|
||||
if (hideCols.indexOf('add') >= 0) {
|
||||
auths.add = false;
|
||||
} else {
|
||||
auths.add = true;
|
||||
}
|
||||
if (hideCols.indexOf('update') >= 0) {
|
||||
auths.update = false;
|
||||
} else {
|
||||
auths.update = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadTableLinkOptions() {
|
||||
const params = getLoadDataParams();
|
||||
const data = await queryTableData(props.tableName, params);
|
||||
const records = data.records;
|
||||
//tableTitle.value = data.head.tableTxt;
|
||||
const dataList: any[] = [];
|
||||
const { others, labelField } = otherFields.value;
|
||||
const imageField = props.imageField;
|
||||
if (records && records.length > 0) {
|
||||
for (const rd of records) {
|
||||
const temp = { ...rd };
|
||||
transData(temp);
|
||||
const result = Object.assign({}, pick(temp, others), { id: temp.id, label: temp[labelField], value: temp[props.valueField] });
|
||||
if (imageField) {
|
||||
result[imageField] = temp[imageField];
|
||||
}
|
||||
dataList.push(result);
|
||||
}
|
||||
}
|
||||
//添加一个空对象 为add操作占位
|
||||
// update-begin--author:liaozhiyang---date:20240607---for:【TV360X-1095】高级查询关联记录去掉编辑按钮及去掉记录按钮
|
||||
props.editBtnShow && dataList.push({});
|
||||
// update-end--author:liaozhiyang---date:20240607---for:【TV360X-1095】高级查询关联记录去掉编辑按钮及去掉记录按钮
|
||||
selectOptions.value = dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据简单翻译-字典
|
||||
* @param data
|
||||
*/
|
||||
function transData(data) {
|
||||
const columns = tableColumns.value;
|
||||
const dictInfo = dictOptions.value;
|
||||
for (const c of columns) {
|
||||
const { dataIndex, customRender } = c;
|
||||
if (data[dataIndex] || data[dataIndex] === 0) {
|
||||
if (customRender && customRender == dataIndex) {
|
||||
//这样的就是 字典数据了 可以直接翻译
|
||||
if (dictInfo[customRender]) {
|
||||
data[dataIndex] = filterMultiDictText(dictInfo[customRender], data[dataIndex]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 兼容后台翻译字段
|
||||
const dictText = data[dataIndex + '_dictText'];
|
||||
if (dictText) {
|
||||
data[dataIndex] = dictText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//获取加载数据的查询条件
|
||||
function getLoadDataParams() {
|
||||
const params = Object.assign({ pageSize: 100, pageNo: pageNo.value }, baseParam.value, searchParam.value);
|
||||
return params;
|
||||
}
|
||||
|
||||
//设置查询条件
|
||||
function addQueryParams(text) {
|
||||
if (!text) {
|
||||
searchParam.value = {};
|
||||
} else {
|
||||
const arr = textFieldArray.value;
|
||||
const params: any[] = [];
|
||||
const fields: any[] = [];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (i <= 1) {
|
||||
fields.push(arr[i]);
|
||||
params.push({ field: arr[i], rule: 'like', val: text });
|
||||
}
|
||||
}
|
||||
// params[arr[i]] = `*${text}*`
|
||||
// params['selectConditionFields'] = fields.join(',')
|
||||
// searchParam.value = params;
|
||||
params['superQueryMatchType'] = 'or';
|
||||
params['superQueryParams'] = encodeURI(JSON.stringify(params));
|
||||
searchParam.value = params;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOne(value) {
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
let valueFieldName = props.valueField;
|
||||
let params = {
|
||||
...baseParam.value,
|
||||
pageSize: 100,
|
||||
pageNo: pageNo.value,
|
||||
};
|
||||
params['superQueryMatchType'] = 'and';
|
||||
let valueCondition = [{ field: valueFieldName, rule: 'in', val: value }];
|
||||
params['superQueryParams'] = encodeURI(JSON.stringify(valueCondition));
|
||||
const data = await queryTableData(props.tableName, params);
|
||||
let records = data.records;
|
||||
//tableTitle.value = data.head.tableTxt;
|
||||
let dataList: any[] = [];
|
||||
if (records && records.length > 0) {
|
||||
for (let item of records) {
|
||||
let temp = { ...item };
|
||||
transData(temp);
|
||||
dataList.push(temp);
|
||||
}
|
||||
}
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* true:数据一致;false:数据不一致
|
||||
* @param arr
|
||||
* @param value
|
||||
*/
|
||||
function compareData(arr, value) {
|
||||
if (!arr || arr.length == 0) {
|
||||
return false;
|
||||
}
|
||||
const valueArray = value.split(',');
|
||||
if (valueArray.length != arr.length) {
|
||||
return false;
|
||||
}
|
||||
let flag = true;
|
||||
for (const item of arr) {
|
||||
const temp = item[props.valueField];
|
||||
if (valueArray.indexOf(temp) < 0) {
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
function formatData(formData) {
|
||||
Object.keys(formData).map((k) => {
|
||||
if (formData[k] instanceof Array) {
|
||||
formData[k] = formData[k].join(',');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initFormData(formData, linkFieldArray, record) {
|
||||
if (!record) {
|
||||
record = {};
|
||||
}
|
||||
if (linkFieldArray && linkFieldArray.length > 0) {
|
||||
for (const str of linkFieldArray) {
|
||||
const arr = str.split(',');
|
||||
//["表单字段,表字典字段"]
|
||||
const field = arr[0];
|
||||
const dictField = arr[1];
|
||||
if (!formData[field]) {
|
||||
const value = record[dictField] || '';
|
||||
formData[field] = [value];
|
||||
} else {
|
||||
formData[field].push(record[dictField]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取图片地址
|
||||
function getImageSrc(item) {
|
||||
if (props.imageField) {
|
||||
let url = item[props.imageField];
|
||||
// update-begin--author:liaozhiyang---date:20250517---for:【TV360X-38】关联记录空间,被关联数据优多个图片时,封面图片不展示
|
||||
if (typeof url === 'string') {
|
||||
// 有多张图时默认取第一张
|
||||
url = url.split(',')[0];
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20250517---for:【TV360X-38】关联记录空间,被关联数据优多个图片时,封面图片不展示
|
||||
return getFileAccessHttpUrl(url);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
const showImage = computed(() => {
|
||||
if (props.imageField) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
pageNo,
|
||||
otherColumns,
|
||||
realShowColumns,
|
||||
selectOptions,
|
||||
reloadTableLinkOptions,
|
||||
textFieldArray,
|
||||
addQueryParams,
|
||||
tableColumns,
|
||||
transData,
|
||||
mainContentField,
|
||||
loadOne,
|
||||
compareData,
|
||||
formatData,
|
||||
initFormData,
|
||||
getImageSrc,
|
||||
showImage,
|
||||
auths,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用固定高度的modal
|
||||
*/
|
||||
export function useFixedHeightModal() {
|
||||
const minWidth = 800;
|
||||
const popModalFixedWidth = ref(800);
|
||||
let tempWidth = window.innerWidth - 300;
|
||||
if (tempWidth < minWidth) {
|
||||
tempWidth = minWidth;
|
||||
}
|
||||
popModalFixedWidth.value = tempWidth;
|
||||
|
||||
// 弹窗高度控制
|
||||
const popBodyStyle = ref({});
|
||||
function resetBodyStyle() {
|
||||
const height = window.innerHeight - 210;
|
||||
popBodyStyle.value = {
|
||||
height: height + 'px',
|
||||
overflowY: 'auto',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
popModalFixedWidth,
|
||||
popBodyStyle,
|
||||
resetBodyStyle,
|
||||
};
|
||||
}
|
|
@ -15,6 +15,9 @@
|
|||
@search="loadData"
|
||||
@change="handleAsyncChange"
|
||||
@popupScroll="handlePopupScroll"
|
||||
:mode="multiple?'multiple':''"
|
||||
@select="handleSelect"
|
||||
@deselect="handleDeSelect"
|
||||
>
|
||||
<template #notFoundContent>
|
||||
<a-spin size="small" />
|
||||
|
@ -33,6 +36,9 @@
|
|||
:notFoundContent="loading ? undefined : null"
|
||||
:dropdownAlign="{overflow: {adjustY: adjustY }}"
|
||||
@change="handleChange"
|
||||
:mode="multiple?'multiple':''"
|
||||
@select="handleSelect"
|
||||
@deselect="handleDeSelect"
|
||||
>
|
||||
<template #notFoundContent>
|
||||
<a-spin v-if="loading" size="small" />
|
||||
|
@ -83,6 +89,13 @@
|
|||
default: ()=>{}
|
||||
},
|
||||
//update-end-author:taoyan date:2022-8-15 for: VUEN-1971 【online 专项测试】关联记录和他表字段 1
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
//是否为多选
|
||||
multiple:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//update-end---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
},
|
||||
emits: ['change', 'update:value'],
|
||||
setup(props, { emit, refs }) {
|
||||
|
@ -210,28 +223,55 @@
|
|||
if (!selectedAsyncValue || !selectedAsyncValue.key || selectedAsyncValue.key !== value) {
|
||||
defHttp.get({ url: `/sys/dict/loadDictItem/${dict}`, params: { key: value } }).then((res) => {
|
||||
if (res && res.length > 0) {
|
||||
let obj = {
|
||||
key: value,
|
||||
label: res,
|
||||
};
|
||||
if (props.value == value) {
|
||||
selectedAsyncValue.value = { ...obj };
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
//判断组件是否为多选
|
||||
if(props.multiple){
|
||||
if(value){
|
||||
let arr: any = [];
|
||||
//多选返回的是以逗号拼接的方式
|
||||
let values = value.toString().split(',');
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
let obj = {
|
||||
key: values[i],
|
||||
label: res[i],
|
||||
};
|
||||
arr.push(obj);
|
||||
selectedValue.value.push(obj.key);
|
||||
}
|
||||
selectedAsyncValue.value = arr;
|
||||
}
|
||||
} else {
|
||||
let obj = {
|
||||
key: value,
|
||||
label: res,
|
||||
};
|
||||
if (props.value == value) {
|
||||
selectedAsyncValue.value = { ...obj };
|
||||
}
|
||||
//update-begin-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online关联记录配置页面
|
||||
if(props.immediateChange == true){
|
||||
emit('change', props.value);
|
||||
}
|
||||
//update-end-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online关联记录配置页面
|
||||
//update-end---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
}
|
||||
//update-begin-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online关联记录配置页面
|
||||
if(props.immediateChange == true){
|
||||
emit('change', props.value);
|
||||
}
|
||||
//update-end-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online关联记录配置页面
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
selectedValue.value = value.toString();
|
||||
//update-begin-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online他表字段配置界面
|
||||
if(props.immediateChange == true){
|
||||
emit('change', value.toString());
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
if(!props.multiple){
|
||||
selectedValue.value = value.toString();
|
||||
//update-begin-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online他表字段配置界面
|
||||
if(props.immediateChange == true){
|
||||
emit('change', value.toString());
|
||||
}
|
||||
//update-end-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online他表字段配置界面
|
||||
}else{
|
||||
//多选的情况下需要转成数组
|
||||
selectedValue.value = value.toString().split(',');
|
||||
}
|
||||
//update-end-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online他表字段配置界面
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -306,35 +346,100 @@
|
|||
* 同步改变事件
|
||||
* */
|
||||
function handleChange(value) {
|
||||
selectedValue.value = value;
|
||||
callback();
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
//多选也会触发change事件,需要判断如果时多选不需要赋值
|
||||
if(!props.multiple){
|
||||
selectedValue.value = value;
|
||||
callback();
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
}
|
||||
/**
|
||||
* 异步改变事件
|
||||
* */
|
||||
function handleAsyncChange(selectedObj) {
|
||||
if (selectedObj) {
|
||||
selectedAsyncValue.value = selectedObj;
|
||||
selectedValue.value = selectedObj.key;
|
||||
} else {
|
||||
selectedAsyncValue.value = null;
|
||||
selectedValue.value = null;
|
||||
options.value = null;
|
||||
loadData('');
|
||||
}
|
||||
callback();
|
||||
// update-begin--author:liaozhiyang---date:20240524---for:【TV360X-426】下拉搜索设置了默认值,把查询条件删掉,再点击重置,没附上值
|
||||
// 点x清空时需要把loadSelectText设置true
|
||||
selectedObj ?? (loadSelectText.value = true);
|
||||
// update-end--author:liaozhiyang---date:20240524---for:【TV360X-426】下拉搜索设置了默认值,把查询条件删掉,再点击重置,没附上值
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
// 单选情况下使用change事件
|
||||
if(!props.multiple){
|
||||
if (selectedObj) {
|
||||
selectedAsyncValue.value = selectedObj;
|
||||
selectedValue.value = selectedObj.key;
|
||||
} else {
|
||||
selectedAsyncValue.value = null;
|
||||
selectedValue.value = null;
|
||||
options.value = null;
|
||||
loadData('');
|
||||
}
|
||||
callback();
|
||||
// update-begin--author:liaozhiyang---date:20240524---for:【TV360X-426】下拉搜索设置了默认值,把查询条件删掉,再点击重置,没附上值
|
||||
// 点x清空时需要把loadSelectText设置true
|
||||
selectedObj ?? (loadSelectText.value = true);
|
||||
// update-end--author:liaozhiyang---date:20240524---for:【TV360X-426】下拉搜索设置了默认值,把查询条件删掉,再点击重置,没附上值
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
}
|
||||
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
/**
|
||||
* 异步值选中事件
|
||||
* @param selectedObj
|
||||
*/
|
||||
function handleSelect(selectedObj){
|
||||
let key = selectedObj;
|
||||
if(props.async){
|
||||
key = selectedObj.key;
|
||||
}
|
||||
//多选情况下使用select事件
|
||||
if(props.multiple && key){
|
||||
//异步的时候才需要在selectedValue数组中添加值操作,同步的情况下直接走更新值操作
|
||||
if(props.async){
|
||||
selectedValue.value.push(key);
|
||||
}
|
||||
selectedObj ?? (loadSelectText.value = true);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步值取消选中事件
|
||||
* @param selectedObj
|
||||
*/
|
||||
function handleDeSelect(selectedObj){
|
||||
let key = selectedObj;
|
||||
if(props.async){
|
||||
key = selectedObj.key;
|
||||
}
|
||||
//多选情况下使用select事件
|
||||
if(props.multiple){
|
||||
//异步的时候才需要在selectedValue数组中删除值操作,同步的情况下直接走更新值操作
|
||||
if(props.async){
|
||||
let findIndex = selectedValue.value.findIndex(item => item === key);
|
||||
if(findIndex != -1){
|
||||
selectedValue.value.splice(findIndex,1);
|
||||
}
|
||||
}
|
||||
selectedObj ?? (loadSelectText.value = true);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
|
||||
/**
|
||||
*回调方法
|
||||
* */
|
||||
function callback() {
|
||||
loadSelectText.value = false;
|
||||
emit('change', unref(selectedValue));
|
||||
emit('update:value', unref(selectedValue));
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
//单选直接走更新值操作
|
||||
if(!props.multiple){
|
||||
emit('change', unref(selectedValue));
|
||||
emit('update:value', unref(selectedValue));
|
||||
} else {
|
||||
//多选需要把数组转成字符串
|
||||
emit('change', unref(selectedValue).join(","));
|
||||
emit('update:value', unref(selectedValue).join(","));
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
}
|
||||
/**
|
||||
* 过滤选中option
|
||||
|
@ -461,6 +566,8 @@
|
|||
handleAsyncChange,
|
||||
handleAsyncFocus,
|
||||
handlePopupScroll,
|
||||
handleSelect,
|
||||
handleDeSelect,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -71,6 +71,11 @@
|
|||
*/
|
||||
watch(selectValues, () => {
|
||||
if (selectValues) {
|
||||
// update-begin--author:liaozhiyang---date:20250616---for:【QQYUN-12869】通过部门选择用户组件,必填状态下选择用户后,点击重置后,会出校验信息
|
||||
if (props.value === undefined && selectValues.value?.length == 0) {
|
||||
return;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20250616---for:【QQYUN-12869】通过部门选择用户组件,必填状态下选择用户后,点击重置后,会出校验信息
|
||||
state.value = selectValues.value;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -28,8 +28,12 @@
|
|||
></a-select>
|
||||
</a-col>
|
||||
<a-col v-if="showButton" class="right">
|
||||
<a-button v-if="buttonIcon" :preIcon="buttonIcon" type="primary" @click="openModal(true)" :disabled="disabled">选择</a-button>
|
||||
<a-button v-else type="primary" @click="openModal(true)" :disabled="disabled">选择</a-button>
|
||||
<a-button v-if="buttonIcon" :preIcon="buttonIcon" type="primary" @click="openModal(true)" :disabled="disabled">
|
||||
{{ buttonText }}
|
||||
</a-button>
|
||||
<a-button v-else type="primary" @click="openModal(true)" :disabled="disabled">
|
||||
{{ buttonText }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
@ -46,6 +50,7 @@
|
|||
inheritAttrs: false,
|
||||
props: {
|
||||
showButton: propTypes.bool.def(true),
|
||||
buttonText: propTypes.string.def('选择'),
|
||||
disabled: propTypes.bool.def(false),
|
||||
placeholder: {
|
||||
type: String,
|
||||
|
|
|
@ -156,7 +156,9 @@ export interface FormSchema {
|
|||
// Required
|
||||
required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
suffix?: string | number | ((values: RenderCallbackParams) => string | number);
|
||||
suffix?: string | number | VueNode | ((values: RenderCallbackParams) => string | number | VueNode);
|
||||
// 【QQYUN-12876】是否是紧凑型 suffix(当组件宽度未占满时,可紧挨着组件右侧)
|
||||
suffixCompact?: boolean;
|
||||
|
||||
// Validation rules
|
||||
rules?: Rule[];
|
||||
|
@ -164,7 +166,7 @@ export interface FormSchema {
|
|||
rulesMessageJoinLabel?: boolean;
|
||||
|
||||
// Reference formModelItem
|
||||
itemProps?: Partial<FormItem>;
|
||||
itemProps?: Partial<FormItem> | ((renderCallbackParams: RenderCallbackParams) => Partial<FormItem>);
|
||||
|
||||
// col configuration outside formModelItem
|
||||
colProps?: Partial<ColEx>;
|
||||
|
|
|
@ -157,5 +157,6 @@ export type ComponentType =
|
|||
| 'linkRecordSelect'
|
||||
| 'RangeTime'
|
||||
| 'JRangeNumber'
|
||||
| 'JLinkTableCard'
|
||||
| 'JInputSelect';
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
:style="{ width }"
|
||||
@click="currentSelectClick"
|
||||
>
|
||||
<template #suffix v-if="allowClear && currentSelect">
|
||||
<CloseCircleFilled class="menu-current-close" @click.stop="clearCurrentSelect" />
|
||||
</template>
|
||||
<template #addonAfter>
|
||||
<span class="cursor-pointer px-2 py-1 flex items-center" v-if="isSvgMode && currentSelect">
|
||||
<SvgIcon :name="currentSelect" @click="currentSelectClick"/>
|
||||
|
@ -77,7 +80,7 @@
|
|||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import svgIcons from 'virtual:svg-icons-names';
|
||||
import IconList from "./IconList.vue";
|
||||
|
||||
import { CloseCircleFilled } from '@ant-design/icons-vue';
|
||||
// 没有使用别名引入,是因为WebStorm当前版本还不能正确识别,会报unused警告
|
||||
const AInput = Input;
|
||||
|
||||
|
@ -104,6 +107,7 @@
|
|||
mode: propTypes.oneOf<('svg' | 'iconify')[]>(['svg', 'iconify']).def('iconify'),
|
||||
disabled: propTypes.bool.def(false),
|
||||
clearSelect: propTypes.bool.def(false),
|
||||
allowClear: propTypes.bool.def(false),
|
||||
iconPrefixSave: propTypes.bool.def(true),
|
||||
});
|
||||
|
||||
|
@ -196,6 +200,12 @@
|
|||
iconOpen.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前选择图标
|
||||
*/
|
||||
function clearCurrentSelect(){
|
||||
currentSelect.value = '';
|
||||
}
|
||||
onMounted(()=>{
|
||||
//初始化加载图标
|
||||
initOtherIcon();
|
||||
|
@ -227,5 +237,16 @@
|
|||
height: 220px;
|
||||
}
|
||||
}
|
||||
//图标样式
|
||||
.menu-current-close {
|
||||
color: #cccccc;
|
||||
}
|
||||
}
|
||||
//图标样式兼容暗黑模式
|
||||
[data-theme='dark'] .@{prefix-cls} {
|
||||
.menu-current-close {
|
||||
color: #4f4f4f;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<div>
|
||||
<keep-alive>
|
||||
<component
|
||||
v-if="currentModal"
|
||||
v-bind="bindParams"
|
||||
:key="currentModal"
|
||||
:is="currentModal"
|
||||
@register="modalRegCache[currentModal].register"
|
||||
@reply="handReply"
|
||||
@selected="reloadPage"
|
||||
/>
|
||||
</keep-alive>
|
||||
<!-- 系统公告弹窗 -->
|
||||
<DynamicNotice ref="showDynamNotice" v-bind="bindParams" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted } from 'vue';
|
||||
import { useDragNotice } from '/@/hooks/web/useDragNotice';
|
||||
import DynamicNotice from '@/views/monitor/mynews/DynamicNotice.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JDragNotice',
|
||||
components: {
|
||||
DynamicNotice,
|
||||
},
|
||||
setup() {
|
||||
const {
|
||||
initDragWebSocket,
|
||||
currentModal,
|
||||
modalParams,
|
||||
modalRegCache,
|
||||
bindParams,
|
||||
reloadPage,
|
||||
} = useDragNotice();
|
||||
|
||||
onMounted(() => {
|
||||
initDragWebSocket();
|
||||
});
|
||||
|
||||
return {
|
||||
currentModal,
|
||||
modalParams,
|
||||
modalRegCache,
|
||||
bindParams,
|
||||
reloadPage,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
|
@ -13,6 +13,7 @@
|
|||
import { getTenantId, getToken } from '/@/utils/auth';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
import { uploadFile } from '@/api/common/api';
|
||||
import {$electron} from "@/electron";
|
||||
|
||||
type Lang = 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' | undefined;
|
||||
|
||||
|
@ -109,6 +110,13 @@
|
|||
function init() {
|
||||
const wrapEl = unref(wrapRef) as HTMLElement;
|
||||
if (!wrapEl) return;
|
||||
|
||||
// vditor组件本地化的路径配置【QQYUN-12053】
|
||||
let localCdn = '/resource/vditor@3.9.4';
|
||||
if ($electron.isElectron()) {
|
||||
localCdn = '.' + localCdn;
|
||||
}
|
||||
|
||||
const bindValue = { ...attrs, ...props };
|
||||
const insEditor = new Vditor(wrapEl, {
|
||||
theme: getDarkMode.value === 'dark' ? 'dark' : 'classic',
|
||||
|
@ -151,8 +159,8 @@
|
|||
],
|
||||
// update-end--author:liaozhiyang---date:20240520---for:【TV360X-146】Markdown组件去掉录音选项
|
||||
mode: 'sv',
|
||||
// cdn: 'https://cdn.jsdelivr.net/npm/vditor@3.9.6',
|
||||
cdn: 'https://unpkg.com/vditor@3.10.1',
|
||||
cdn: 'https://unpkg.com/vditor@3.10.8',
|
||||
//cdn: localCdn,
|
||||
fullscreen: {
|
||||
index: 520,
|
||||
},
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
{{ t('component.table.settingColumnShow') }}
|
||||
</Checkbox>
|
||||
|
||||
<Checkbox v-model:checked="checkIndex" @change="handleIndexCheckChange">
|
||||
<Checkbox :disabled="isTreeTable" v-model:checked="checkIndex" @change="handleIndexCheckChange">
|
||||
{{ t('component.table.settingIndexColumnShow') }}
|
||||
</Checkbox>
|
||||
|
||||
|
@ -143,6 +143,9 @@
|
|||
setup(props, { emit, attrs }) {
|
||||
const { t } = useI18n();
|
||||
const table = useTableContext();
|
||||
// update-begin--author:liaozhiyang---date:20250526---for:【issues/8301】树形表格序号列禁用
|
||||
const isTreeTable = computed(() => table.getBindValues.value.isTreeTable);
|
||||
// update-end--author:liaozhiyang---date:20250526---for:【issues/8301】树形表格序号列禁用
|
||||
const popoverVisible = ref(false);
|
||||
// update-begin--author:sunjianlei---date:20221101---for: 修复第一次进入时列表配置不能拖拽
|
||||
// nextTick(() => popoverVisible.value = false);
|
||||
|
@ -479,6 +482,7 @@
|
|||
defaultRowSelection,
|
||||
handleColumnFixed,
|
||||
getPopupContainer,
|
||||
isTreeTable,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -140,7 +140,12 @@ export function useCustomSelection(
|
|||
// 解决selectedRowKeys在页面调用处使用ref失效
|
||||
const value = unref(val);
|
||||
if (Array.isArray(value) && !sameArray(value, selectedKeys.value)) {
|
||||
setSelectedRowKeys(value);
|
||||
// update-begin--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录夸页数据丢失
|
||||
// 延迟是为了等watch selectedRows
|
||||
setTimeout(() => {
|
||||
setSelectedRowKeys(value);
|
||||
}, 0);
|
||||
// update-end--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录夸页数据丢失
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -149,7 +154,22 @@ export function useCustomSelection(
|
|||
}
|
||||
);
|
||||
// update-end--author:liaozhiyang---date:20240306---for:【QQYUN-8390】部门人员组件点击重置未清空(selectedRowKeys.value=[],watch没监听到加deep)
|
||||
|
||||
// update-begin--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录夸页数据丢失
|
||||
// 编辑时selectedRows可能会回填
|
||||
watch(
|
||||
() => unref(propsRef)?.rowSelection?.selectedRows,
|
||||
(val: string[]) => {
|
||||
const value: any = unref(val);
|
||||
if (Array.isArray(value) && !sameArray(value, selectedRows.value)) {
|
||||
selectedRows.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
// update-end--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录夸页数据丢失
|
||||
/**
|
||||
* 2024-03-06
|
||||
* liaozhiyang
|
||||
|
@ -581,7 +601,12 @@ export function useCustomSelection(
|
|||
// 通过 selectedKeys 同步 selectedRows
|
||||
function syncSelectedRows() {
|
||||
if (selectedKeys.value.length !== selectedRows.value.length) {
|
||||
setSelectedRowKeys(selectedKeys.value);
|
||||
// update-begin--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录夸页数据丢失
|
||||
// 延迟是为了等watch selectedRows
|
||||
setTimeout(() => {
|
||||
setSelectedRowKeys(selectedKeys.value);
|
||||
}, 0);
|
||||
// update-end--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录夸页数据丢失
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -146,25 +146,27 @@ export function useTableScroll(
|
|||
|
||||
bodyEl!.style.height = `${height}px`;
|
||||
// update-begin--author:liaozhiyang---date:20240609---for【issues/8374】分页始终显示在底部
|
||||
if (maxHeight === undefined) {
|
||||
if (unref(getPaginationInfo) && unref(getDataSourceRef).length) {
|
||||
const pageSize = unref(getPaginationInfo)?.pageSize;
|
||||
const current = unref(getPaginationInfo)?.current;
|
||||
const total = unref(getPaginationInfo)?.total;
|
||||
const tableBody = tableEl.querySelector('.ant-table-body') as HTMLElement;
|
||||
const tr = tableEl.querySelector('.ant-table-tbody')?.children ?? [];
|
||||
const lastrEl = tr[tr.length - 1] as HTMLElement;
|
||||
const trHeight = lastrEl.offsetHeight;
|
||||
const dataHeight = trHeight * pageSize;
|
||||
if (tableBody && lastrEl) {
|
||||
if (current === 1 && pageSize > unref(getDataSourceRef).length && total <= pageSize) {
|
||||
tableBody.style.height = `${height}px`;
|
||||
} else {
|
||||
tableBody.style.height = `${dataHeight < height ? dataHeight : height}px`;
|
||||
nextTick(() => {
|
||||
if (maxHeight === undefined) {
|
||||
if (unref(getPaginationInfo) && unref(getDataSourceRef).length) {
|
||||
const pageSize = unref(getPaginationInfo)?.pageSize;
|
||||
const current = unref(getPaginationInfo)?.current;
|
||||
const total = unref(getPaginationInfo)?.total;
|
||||
const tableBody = tableEl.querySelector('.ant-table-body') as HTMLElement;
|
||||
const tr = tableEl.querySelector('.ant-table-tbody')?.children ?? [];
|
||||
const lastrEl = tr[tr.length - 1] as HTMLElement;
|
||||
const trHeight = lastrEl.offsetHeight;
|
||||
const dataHeight = trHeight * pageSize;
|
||||
if (tableBody && lastrEl) {
|
||||
if (current === 1 && pageSize > unref(getDataSourceRef).length && total <= pageSize) {
|
||||
tableBody.style.height = `${height}px`;
|
||||
} else {
|
||||
tableBody.style.height = `${dataHeight < height ? dataHeight : height}px`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20240609---for【issues/8374】分页始终显示在底部
|
||||
}
|
||||
useWindowSizeFn(calcTableHeight, 280);
|
||||
|
|
|
@ -38,6 +38,8 @@ export interface TableRowSelection<T = any> extends ITableRowSelection {
|
|||
* @type Function
|
||||
*/
|
||||
onSelectInvert?: (selectedRows: string[] | number[]) => any;
|
||||
//【issues/8163】关联记录新增丢失
|
||||
selectedRows?: any[];
|
||||
}
|
||||
|
||||
export interface TableCustomRecord<T> {
|
||||
|
|
|
@ -293,3 +293,27 @@ html[data-theme='dark'] {
|
|||
margin-inline: 0;
|
||||
}
|
||||
// update-end--author:wangshuai---date:20240611---for:【TV360X-1070】一对多内嵌,为什么多这一块,不从头对齐
|
||||
|
||||
// 单行文本溢出省略号
|
||||
.ellipsis {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
// 两行省略
|
||||
.ellipsis-2 {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
word-break: break-all;
|
||||
}
|
||||
// 三行省略
|
||||
.ellipsis-3 {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import type {App} from "vue";
|
||||
import {router} from "@/router";
|
||||
import {useGlobSetting} from "@/hooks/setting";
|
||||
|
||||
const glob = useGlobSetting();
|
||||
|
||||
const _PRELOAD_UTILS = '_ELECTRON_PRELOAD_UTILS_';
|
||||
|
||||
export const $electron = {
|
||||
// 当前是否为Electron平台
|
||||
isElectron: () => glob.isElectronPlatform,
|
||||
|
||||
// 通过浏览器打开链接
|
||||
openInBrowser: bindUtils('openInBrowser') as (url: string) => void,
|
||||
|
||||
resolveRoutePath,
|
||||
}
|
||||
|
||||
function bindUtils(n: string) {
|
||||
const fn = window[_PRELOAD_UTILS]?.[n];
|
||||
if (typeof fn?.bind === 'function') {
|
||||
return fn.bind(null);
|
||||
}
|
||||
return () => console.warn(`Electron preload util ${n} is not a function`);
|
||||
}
|
||||
|
||||
// 解析路由路径
|
||||
function resolveRoutePath(path: string) {
|
||||
return window.location.origin + window.location.pathname + router.resolve(path).href;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置Electron
|
||||
*/
|
||||
export function setupElectron(_: App) {
|
||||
// 非Electron平台不执行
|
||||
if (!$electron.isElectron()) {
|
||||
return;
|
||||
}
|
||||
hookWindowOpen();
|
||||
}
|
||||
|
||||
function hookWindowOpen() {
|
||||
// 保存原生方法引用
|
||||
const originFunc = window.open;
|
||||
// 重写window.open方法
|
||||
window['open'] = function (url, windowName, windowFeatures) {
|
||||
url = typeof url === 'string' ? url.trim() : '';
|
||||
if (!url) {
|
||||
throw new Error('window.open: url is required');
|
||||
}
|
||||
// 判断是否以http或https开头
|
||||
if (/^https?:\/\//.test(url)) {
|
||||
// 判断是否为本地地址
|
||||
if (url.startsWith(window.location.origin) || url.startsWith(window['_CONFIG']['domianURL'])) {
|
||||
// 直接打开
|
||||
return originFunc(url, windowName, windowFeatures);
|
||||
}
|
||||
// 调用Electron进行外部浏览器打开
|
||||
return $electron.openInBrowser(url) as any;
|
||||
}
|
||||
// 自定义逻辑
|
||||
return originFunc(url, windowName, windowFeatures)
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
|
|||
VITE_GLOB_APP_OPEN_QIANKUN,
|
||||
VITE_GLOB_DOMAIN_URL,
|
||||
VITE_GLOB_ONLINE_VIEW_URL,
|
||||
VITE_GLOB_RUN_PLATFORM,
|
||||
|
||||
// 【JEECG作为乾坤子应用】
|
||||
VITE_GLOB_QIANKUN_MICRO_APP_NAME,
|
||||
|
@ -40,6 +41,8 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
|
|||
urlPrefix: VITE_GLOB_API_URL_PREFIX,
|
||||
uploadUrl: VITE_GLOB_DOMAIN_URL,
|
||||
viewUrl: VITE_GLOB_ONLINE_VIEW_URL,
|
||||
// 当前是否运行在 electron 平台
|
||||
isElectronPlatform: VITE_GLOB_RUN_PLATFORM === 'electron',
|
||||
|
||||
// 【JEECG作为乾坤子应用】是否以乾坤子应用模式启动
|
||||
isQiankunMicro: VITE_GLOB_QIANKUN_MICRO_APP_NAME != null && VITE_GLOB_QIANKUN_MICRO_APP_NAME !== '',
|
||||
|
|
|
@ -43,7 +43,9 @@ export function useRootSetting() {
|
|||
const getColorWeak = computed(() => appStore.getProjectConfig.colorWeak);
|
||||
|
||||
const getGrayMode = computed(() => appStore.getProjectConfig.grayMode);
|
||||
|
||||
// update-begin--author:liaozhiyang---date:20250407---for:【QQYUN-10952】AI助手支持通过设置来配置是否显示
|
||||
const getAiIconShow = computed(() => appStore.getProjectConfig.aiIconShow);
|
||||
// update-end--author:liaozhiyang---date:20250407---for:【QQYUN-10952】AI助手支持通过设置来配置是否显示
|
||||
const getLockTime = computed(() => appStore.getProjectConfig.lockTime);
|
||||
|
||||
const getShowDarkModeToggle = computed(() => appStore.getProjectConfig.showDarkModeToggle);
|
||||
|
@ -84,5 +86,6 @@ export function useRootSetting() {
|
|||
getDarkMode,
|
||||
setDarkMode,
|
||||
getShowDarkModeToggle,
|
||||
getAiIconShow,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
import { ref, nextTick, getCurrentInstance, watch } from 'vue';
|
||||
import { getToken } from '/@/utils/auth';
|
||||
import md5 from 'crypto-js/md5';
|
||||
import { connectWebSocket, onWebSocket } from '/@/hooks/web/useWebSocket';
|
||||
import { useGlobSetting } from '/@/hooks/setting';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { isUrl } from '@/utils/is';
|
||||
import { getQueryVariable, getUrlParams } from '@/utils';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useMessage } from '@/hooks/web/useMessage';
|
||||
const { createMessage } = useMessage();
|
||||
export function useDragNotice() {
|
||||
//*********************************websocket配置begin******************************************
|
||||
const glob = useGlobSetting();
|
||||
const { push, currentRoute } = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const instance: any = getCurrentInstance();
|
||||
// 初始化 WebSocket
|
||||
function initWebSocket() {
|
||||
const token = getToken();
|
||||
//将登录token生成一个短的标识
|
||||
const wsClientId = md5(token);
|
||||
// WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https
|
||||
const url = glob.domainUrl?.replace('https://', 'wss://').replace('http://', 'ws://') + '/dragChannelSocket/' + wsClientId;
|
||||
connectWebSocket(url);
|
||||
onWebSocket(onWebSocketMessage);
|
||||
}
|
||||
|
||||
async function onWebSocketMessage(data) {
|
||||
console.log('仪表盘监听按钮点击事件websocket', data);
|
||||
if (data?.CMD === 'drag') {
|
||||
//触发动作: url:路径 modal:弹窗
|
||||
const action = data.result.action;
|
||||
//弹窗类型: 点击按钮打开什么弹窗,根据type打开不同的弹窗
|
||||
const type = data.result.type;
|
||||
//url地址,可以是路由,也可以是外部链接
|
||||
let url = data.result.url;
|
||||
//弹窗参数或者url参数
|
||||
const record = data.result.records || {};
|
||||
console.log('仪表盘监听点击事件类型type', type);
|
||||
console.log('仪表盘监听点击事件动作action', action);
|
||||
console.log('仪表盘监听点击事件路径url', url);
|
||||
console.log('仪表盘监听点击事件参数', record);
|
||||
//1.路径的话,判断外部链接还是内部路由跳转
|
||||
if (action == 'url') {
|
||||
//常用下載特殊处理
|
||||
if (url == 'fileUrl') {
|
||||
url = record[url];
|
||||
}
|
||||
const urlParamsObj = getUrlParams(url);
|
||||
if (url.startsWith('http')) {
|
||||
window.open(url, '_blank');
|
||||
} else {
|
||||
push({ path: urlParamsObj.url, query: { ...urlParamsObj.params, ...record } });
|
||||
}
|
||||
} else {
|
||||
//2.弹窗方式打开项目组件
|
||||
switch (type) {
|
||||
case 'email':
|
||||
//邮箱查看弹窗
|
||||
handleOpenType('email', { record });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//*********************************websocket配置end******************************************
|
||||
|
||||
//*********************************打开弹窗修改,动态设置弹窗begin*******************************
|
||||
//当前表单弹窗
|
||||
const currentModal = ref<string | null>(null);
|
||||
//当前表单参数
|
||||
const modalParams = ref<Recordable>({});
|
||||
//表单注册缓存
|
||||
const modalRegCache = ref<Recordable>({});
|
||||
//组件绑定参数
|
||||
const bindParams = ref<Recordable>({});
|
||||
/**
|
||||
* 根据类型打开不同弹窗
|
||||
* @param type
|
||||
* @param params
|
||||
*/
|
||||
async function handleOpenType(type, params) {
|
||||
currentModal.value = null;
|
||||
modalParams.value = { ...params };
|
||||
switch (type) {
|
||||
case 'email':
|
||||
//邮件查看
|
||||
currentModal.value = 'EoaMailBoxInModal';
|
||||
break;
|
||||
default:
|
||||
currentModal.value = null;
|
||||
break;
|
||||
}
|
||||
//注册表单弹窗
|
||||
initModalRegister();
|
||||
await nextTick(() => {
|
||||
if (modalRegCache.value[currentModal.value!]?.isRegister) {
|
||||
console.log('已注冊,走缓存');
|
||||
modalRegCache.value[currentModal.value!].modalMethods.openModal(true, modalParams.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 初始化弹窗注册
|
||||
*/
|
||||
function initModalRegister() {
|
||||
//如果当前选择表单为null,就不处理
|
||||
if (!currentModal.value) {
|
||||
return;
|
||||
}
|
||||
//判断缓存中是否存在,不存在就走缓存逻辑
|
||||
if (!modalRegCache.value[currentModal.value]) {
|
||||
const [registerModal, modalMethods] = useModal();
|
||||
modalRegCache.value[currentModal.value] = {
|
||||
isRegister: false,
|
||||
register: bindRegisterModal(registerModal, modalMethods),
|
||||
modalMethods,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定注册弹窗
|
||||
* @param regFn
|
||||
* @param modalMethod
|
||||
*/
|
||||
function bindRegisterModal(regFn, modalMethod) {
|
||||
return async (...args) => {
|
||||
console.log('开始注册:', currentModal.value);
|
||||
await regFn(...args);
|
||||
console.log('注册完成:', currentModal.value);
|
||||
//打开弹窗
|
||||
modalMethod.openModal(true, modalParams.value);
|
||||
//设置缓存标识
|
||||
modalRegCache.value[currentModal.value!].isRegister = true;
|
||||
};
|
||||
}
|
||||
//*********************************打开弹窗修改,动态设置弹窗end******************************************
|
||||
//刷新页面
|
||||
function reloadPage() {
|
||||
const iframes: any = document.getElementsByClassName('jeecg-iframe-page__main');
|
||||
// 将 HTMLCollection 转换为数组
|
||||
const iframeArray = Array.from(iframes);
|
||||
if (currentRoute.value?.meta?.frameSrc && currentRoute.value?.meta?.frameSrc.indexOf('/drag/view?pageId=') >= 0) {
|
||||
const targetIframe: any = iframeArray.find((iframe: any) => iframe.src == currentRoute.value?.meta?.frameSrc);
|
||||
console.log('targetIframe', targetIframe);
|
||||
if (targetIframe) {
|
||||
targetIframe.contentWindow.postMessage({ reload: true }, '*');
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
initDragWebSocket: initWebSocket,
|
||||
handleOpenType,
|
||||
currentModal,
|
||||
modalParams,
|
||||
modalRegCache,
|
||||
bindParams,
|
||||
reloadPage,
|
||||
};
|
||||
}
|
|
@ -43,7 +43,7 @@ export function useGo(_router?: Router) {
|
|||
/**
|
||||
* @description: redo current page
|
||||
*/
|
||||
export const useRedo = (_router?: Router) => {
|
||||
export const useRedo = (_router?: Router, otherQuery?: Recordable) => {
|
||||
const { push, currentRoute } = _router || useRouter();
|
||||
const { query, params = {}, name, fullPath } = unref(currentRoute.value);
|
||||
function redo(): Promise<boolean> {
|
||||
|
@ -54,6 +54,11 @@ export const useRedo = (_router?: Router) => {
|
|||
}
|
||||
// update-begin--author:liaozhiyang---date:20231123---for:【QQYUN-7099】动态路由匹配右键重新加载404
|
||||
const tabStore = useMultipleTabStore();
|
||||
if (otherQuery && Object.keys(otherQuery).length > 0) {
|
||||
Object.keys(otherQuery).forEach((key) => {
|
||||
params[key] = otherQuery[key];
|
||||
});
|
||||
}
|
||||
if (name && Object.keys(params).length > 0) {
|
||||
tabStore.setRedirectPageParam({
|
||||
redirect_type: 'name',
|
||||
|
|
|
@ -13,6 +13,7 @@ enum TableActionEnum {
|
|||
CLOSE_RIGHT,
|
||||
CLOSE_OTHER,
|
||||
CLOSE_CURRENT,
|
||||
HOME_DESIGN,
|
||||
CLOSE,
|
||||
}
|
||||
|
||||
|
@ -66,6 +67,10 @@ export function useTabs(_router?: Router) {
|
|||
await tabStore.refreshPage(router);
|
||||
break;
|
||||
|
||||
case TableActionEnum.HOME_DESIGN:
|
||||
await tabStore.changeDesign(router);
|
||||
break;
|
||||
|
||||
case TableActionEnum.CLOSE_ALL:
|
||||
await tabStore.closeAllTab(router);
|
||||
break;
|
||||
|
@ -111,6 +116,7 @@ export function useTabs(_router?: Router) {
|
|||
|
||||
return {
|
||||
refreshPage: () => handleTabAction(TableActionEnum.REFRESH),
|
||||
changeDesign: () => handleTabAction(TableActionEnum.HOME_DESIGN),
|
||||
// update-begin--author:liaozhiyang---date:20240605---for:【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用
|
||||
closeAll: (tab) => handleTabAction(TableActionEnum.CLOSE_ALL, tab),
|
||||
closeLeft: (tab) => handleTabAction(TableActionEnum.CLOSE_LEFT, tab),
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
import { removeAuthCache, setAuthCache } from '/src/utils/auth';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
import { getRefPromise } from '/@/utils/index';
|
||||
import { refreshDragCache } from "@/api/common/api";
|
||||
|
||||
type MenuEvent = 'logout' | 'doc' | 'lock' | 'cache' | 'depart';
|
||||
const { createMessage } = useMessage();
|
||||
|
@ -122,6 +123,8 @@
|
|||
// 清除缓存
|
||||
async function clearCache() {
|
||||
const result = await refreshCache();
|
||||
const dragRes = await refreshDragCache();
|
||||
console.log('dragRes', dragRes);
|
||||
if (result.success) {
|
||||
const res = await queryAllDictItems();
|
||||
removeAuthCache(DB_DICT_DATA_KEY);
|
||||
|
|
|
@ -38,8 +38,8 @@
|
|||
<UserDropDown :theme="getHeaderTheme" />
|
||||
|
||||
<SettingDrawer v-if="getShowSetting" :class="`${prefixCls}-action__item`" />
|
||||
<!-- ai助手
|
||||
<Aide></Aide> -->
|
||||
<!-- ai助手 -->
|
||||
<Aide v-if="getAiIconShow"></Aide>
|
||||
</div>
|
||||
</Header>
|
||||
<LoginSelect ref="loginSelectRef" @success="loginSelectOk"></LoginSelect>
|
||||
|
@ -105,7 +105,7 @@
|
|||
const { prefixCls } = useDesign('layout-header');
|
||||
const userStore = useUserStore();
|
||||
const { getShowTopMenu, getShowHeaderTrigger, getSplit, getIsMixMode, getMenuWidth, getIsMixSidebar } = useMenuSetting();
|
||||
const { getUseErrorHandle, getShowSettingButton, getSettingButtonPosition } = useRootSetting();
|
||||
const { getUseErrorHandle, getShowSettingButton, getSettingButtonPosition, getAiIconShow } = useRootSetting();
|
||||
const { title } = useGlobSetting();
|
||||
|
||||
const {
|
||||
|
@ -215,7 +215,8 @@
|
|||
loginSelectOk,
|
||||
loginSelectRef,
|
||||
title,
|
||||
t
|
||||
t,
|
||||
getAiIconShow
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -46,6 +46,7 @@ export default defineComponent({
|
|||
getLockTime,
|
||||
getShowDarkModeToggle,
|
||||
getThemeColor,
|
||||
getAiIconShow,
|
||||
} = useRootSetting();
|
||||
|
||||
const { getOpenPageLoading, getBasicTransition, getEnableTransition, getOpenNProgress } = useTransitionSetting();
|
||||
|
@ -315,6 +316,8 @@ export default defineComponent({
|
|||
<SwitchItem title={t('layout.setting.grayMode')} event={HandlerEnum.GRAY_MODE} def={unref(getGrayMode)} />
|
||||
|
||||
<SwitchItem title={t('layout.setting.colorWeak')} event={HandlerEnum.COLOR_WEAK} def={unref(getColorWeak)} />
|
||||
|
||||
<SwitchItem title={t('layout.setting.aiIconSHow')} event={HandlerEnum.AI_ICON_SHOW} def={unref(getAiIconShow)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ export enum HandlerEnum {
|
|||
OPEN_PROGRESS,
|
||||
OPEN_PAGE_LOADING,
|
||||
OPEN_ROUTE_TRANSITION,
|
||||
AI_ICON_SHOW,
|
||||
}
|
||||
|
||||
// 标签页样式
|
||||
|
|
|
@ -199,7 +199,10 @@ export function handler(event: HandlerEnum, value: any): DeepPartial<ProjectConf
|
|||
case HandlerEnum.COLOR_WEAK:
|
||||
updateColorWeak(value);
|
||||
return { colorWeak: value };
|
||||
|
||||
// update-begin--author:liaozhiyang---date:20250407---for:【QQYUN-10952】AI助手支持通过设置来配置是否显示
|
||||
case HandlerEnum.AI_ICON_SHOW:
|
||||
return { aiIconShow: value };
|
||||
// update-end--author:liaozhiyang---date:20250407---for:【QQYUN-10952】AI助手支持通过设置来配置是否显示
|
||||
case HandlerEnum.SHOW_LOGO:
|
||||
return { showLogo: value };
|
||||
|
||||
|
|
|
@ -27,10 +27,9 @@
|
|||
<!-- <FullscreenOutlined /> -->
|
||||
<router-link to="/ai" class="ai-icon">
|
||||
<a-tooltip title="AI助手" placement="left">
|
||||
<!-- <svg t="1706259688149" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2056" width="17" height="17">-->
|
||||
<!-- <path d="M826.368 325.632c0-7.168 2.048-10.24 10.24-10.24h123.904c7.168 0 10.24 2.048 10.24 10.24v621.568c0 7.168-2.048 10.24-10.24 10.24h-122.88c-8.192 0-10.24-4.096-10.24-10.24l-1.024-621.568z m-8.192-178.176c0-50.176 35.84-79.872 79.872-79.872 48.128 0 79.872 32.768 79.872 79.872 0 52.224-33.792 79.872-81.92 79.872-46.08 1.024-77.824-27.648-77.824-79.872zM462.848 584.704C441.344 497.664 389.12 307.2 368.64 215.04h-2.048c-16.384 92.16-58.368 247.808-92.16 369.664h188.416zM243.712 712.704l-62.464 236.544c-2.048 7.168-4.096 8.192-12.288 8.192H54.272c-8.192 0-10.24-2.048-8.192-12.288l224.256-783.36c4.096-13.312 7.168-26.624 8.192-65.536 0-6.144 2.048-8.192 7.168-8.192H450.56c6.144 0 8.192 2.048 10.24 8.192l250.88 849.92c2.048 7.168 0 10.24-7.168 10.24H573.44c-7.168 0-10.24-2.048-12.288-7.168l-65.536-236.544c1.024 1.024-251.904 0-251.904 0z" fill="#333333" p-id="19816"></path>-->
|
||||
<!-- </svg>-->
|
||||
<svg t="1737024931936" class="icon" viewBox="0 0 3011 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4248" width="256" height="256"><path d="M2028.242824 558.561882h415.021176V445.620706h-346.352941V321.415529h346.352941V210.823529l-328.463059 10.842353a2367.969882 2367.969882 0 0 0-31.021176-127.09647c341.955765-4.397176 602.352941-13.492706 781.131294-27.286588l41.441882 124.265411-320.933647 14.095059v115.772235h307.802353v124.205177h-307.802353v112.941176h375.506824v127.096471h-375.506824v113.844706c0 30.117647-5.782588 56.982588-17.468235 80.474353a114.447059 114.447059 0 0 1-50.778353 53.187764c-22.287059 11.926588-41.261176 19.154824-56.922353 21.684706-15.36 2.770824-82.522353 5.300706-201.426824 7.529412a5065.065412 5065.065412 0 0 0-31.984941-142.155294c64.632471 4.999529 114.447059 7.529412 149.624471 7.529412 44.574118 0 66.861176-23.190588 66.861176-69.632v-72.463059h-415.081411v-127.096471zM1685.624471 956.717176a1296.865882 1296.865882 0 0 0-27.286589-133.662117 2187.745882 2187.745882 0 0 0 96.978824 3.794823c18.191059 0 32.587294-4.758588 43.248941-14.155294 10.661647-9.697882 17.468235-23.491765 20.239059-41.381647 3.132235-17.889882 6.023529-66.379294 8.432941-145.408 2.590118-79.390118 3.614118-179.621647 3.373177-300.754823h-109.206589c-3.734588 212.389647-24.455529 364.604235-62.102588 456.523294-37.345882 91.919059-77.161412 156.491294-119.567059 193.837176-28.551529-27.888941-59.632941-54.392471-93.123764-79.510588-142.757647 12.830118-265.456941 25.298824-368.037648 37.165176l-15.962352-126.132705c14.095059-0.602353 28.190118-1.385412 42.345411-2.349177V88.003765h377.374118v687.043764c12.227765-1.204706 24.154353-2.349176 35.779765-3.312941 50.838588-95.653647 77.040941-244.555294 78.607058-446.58447h-85.172705V200.944941h87.04c0.301176-47.706353 0.481882-100.412235 0.481882-158.117647h125.168941c0 58.368-0.301176 111.073882-0.963765 158.117647H1957.647059l-9.878588 525.613177c-1.566118 62.464-5.481412 104.688941-11.745883 126.614588-6.324706 22.287059-16.805647 41.441882-31.563294 57.404235-14.757647 16.022588-32.768 27.105882-54.091294 33.430588-21.082353 6.264471-75.896471 10.480941-164.743529 12.649412z m-321.837177-759.567058h-140.227765V327.077647h140.227765V197.150118z m-140.227765 359.604706h140.227765V426.767059h-140.227765v129.867294z m140.227765 229.616941v-129.92753h-140.227765v140.288l140.227765-10.36047zM963.764706 114.326588V843.294118h-155.286588V114.326588H963.764706zM716.679529 843.294118H547.297882l-54.573176-167.032471H227.689412L174.140235 843.294118H5.662118l266.842353-728.96753h182.512941L716.739765 843.294118zM455.981176 556.212706L373.157647 303.585882c-5.300706-16.022588-9.035294-37.165176-11.264-63.548235h-4.216471a269.010824 269.010824 0 0 1-12.709647 61.680941L261.662118 556.212706H455.981176z" fill="#1F1F1F" p-id="4249"></path></svg>
|
||||
<svg t="1706259688149" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2056" width="17" height="17">
|
||||
<path d="M826.368 325.632c0-7.168 2.048-10.24 10.24-10.24h123.904c7.168 0 10.24 2.048 10.24 10.24v621.568c0 7.168-2.048 10.24-10.24 10.24h-122.88c-8.192 0-10.24-4.096-10.24-10.24l-1.024-621.568z m-8.192-178.176c0-50.176 35.84-79.872 79.872-79.872 48.128 0 79.872 32.768 79.872 79.872 0 52.224-33.792 79.872-81.92 79.872-46.08 1.024-77.824-27.648-77.824-79.872zM462.848 584.704C441.344 497.664 389.12 307.2 368.64 215.04h-2.048c-16.384 92.16-58.368 247.808-92.16 369.664h188.416zM243.712 712.704l-62.464 236.544c-2.048 7.168-4.096 8.192-12.288 8.192H54.272c-8.192 0-10.24-2.048-8.192-12.288l224.256-783.36c4.096-13.312 7.168-26.624 8.192-65.536 0-6.144 2.048-8.192 7.168-8.192H450.56c6.144 0 8.192 2.048 10.24 8.192l250.88 849.92c2.048 7.168 0 10.24-7.168 10.24H573.44c-7.168 0-10.24-2.048-12.288-7.168l-65.536-236.544c1.024 1.024-251.904 0-251.904 0z" fill="#333333" p-id="19816"></path>
|
||||
</svg>
|
||||
</a-tooltip>
|
||||
</router-link>
|
||||
</div>
|
||||
|
@ -178,13 +177,10 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
width: 50px;
|
||||
padding: 0 4px;
|
||||
height: 50px;
|
||||
width: 36px;
|
||||
color: @text-color;
|
||||
text-align: center;
|
||||
border-left: 1px solid @border-color-base;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20241016---for:【issues/7345】标签样式切换到极简模式样式错乱
|
||||
|
|
|
@ -22,4 +22,5 @@ export enum MenuEventEnum {
|
|||
CLOSE_OTHER,
|
||||
CLOSE_ALL,
|
||||
SCALE,
|
||||
HOME_DESIGN,
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ export function useTabDropdown(tabContentProps: TabContentProps, getIsTabs: Comp
|
|||
const { t } = useI18n();
|
||||
const tabStore = useMultipleTabStore();
|
||||
const { currentRoute } = useRouter();
|
||||
const { refreshPage, closeAll, close, closeLeft, closeOther, closeRight } = useTabs();
|
||||
const { refreshPage, closeAll, close, closeLeft, closeOther, closeRight, changeDesign } = useTabs();
|
||||
|
||||
const getTargetTab = computed((): RouteLocationNormalized => {
|
||||
return unref(getIsTabs) ? tabContentProps.tabItem : unref(currentRoute);
|
||||
|
@ -71,6 +71,12 @@ export function useTabDropdown(tabContentProps: TabContentProps, getIsTabs: Comp
|
|||
event: MenuEventEnum.REFRESH_PAGE,
|
||||
text: t('layout.multipleTab.reload'),
|
||||
disabled: refreshDisabled,
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:setting-outlined',
|
||||
event: MenuEventEnum.HOME_DESIGN,
|
||||
text: t('layout.multipleTab.homeDesign'),
|
||||
disabled: path !== '/PortalView',
|
||||
divider: true,
|
||||
},
|
||||
// {
|
||||
|
@ -163,6 +169,12 @@ export function useTabDropdown(tabContentProps: TabContentProps, getIsTabs: Comp
|
|||
closeAll(state.current);
|
||||
// update-end--author:liaozhiyang---date:20240605---for:【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用
|
||||
break;
|
||||
// Close all
|
||||
case MenuEventEnum.HOME_DESIGN:
|
||||
// update-begin--author:liaozhiyang---date:20240605---for:【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用
|
||||
changeDesign();
|
||||
// update-end--author:liaozhiyang---date:20240605---for:【TV360X-732】非当前页右键关闭左侧、关闭右侧、关闭其它功能正常使用
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { getDropMenuList, handleMenuEvent, handleContextMenu };
|
||||
|
|
|
@ -8,7 +8,7 @@ export default {
|
|||
dropdownItemSwitchDepart: 'Switch Department',
|
||||
dropdownItemRefreshCache: 'Clean cache',
|
||||
dropdownItemSwitchAccount: 'Account Setting',
|
||||
|
||||
|
||||
tooltipErrorLog: 'Error log',
|
||||
tooltipLock: 'Lock screen',
|
||||
tooltipNotify: 'Notification',
|
||||
|
@ -33,6 +33,7 @@ export default {
|
|||
closeRight: 'Close Right',
|
||||
closeOther: 'Close Other',
|
||||
closeAll: 'Close All',
|
||||
homeDesign: 'Home Design',
|
||||
},
|
||||
setting: {
|
||||
// content mode
|
||||
|
|
|
@ -34,6 +34,7 @@ export default {
|
|||
closeRight: '关闭右侧',
|
||||
closeOther: '关闭其它',
|
||||
closeAll: '关闭全部',
|
||||
homeDesign: '设计模式',
|
||||
},
|
||||
setting: {
|
||||
// content mode
|
||||
|
@ -105,6 +106,7 @@ export default {
|
|||
fullContent: '全屏内容',
|
||||
grayMode: '灰色模式',
|
||||
colorWeak: '色弱模式',
|
||||
aiIconSHow: 'Ai图标显示',
|
||||
|
||||
progress: '顶部进度条',
|
||||
switchLoading: '切换loading',
|
||||
|
|
|
@ -14,6 +14,7 @@ import { setupRouterGuard } from '/@/router/guard';
|
|||
import { setupStore } from '/@/store';
|
||||
import { setupGlobDirectives } from '/@/directives';
|
||||
import { setupI18n } from '/@/locales/setupI18n';
|
||||
import { setupElectron } from "@/electron";
|
||||
import { registerGlobComp } from '/@/components/registerGlobComp';
|
||||
import { registerThirdComp } from '/@/settings/registerThirdComp';
|
||||
import { registerSuper } from '/@/views/super/registerSuper';
|
||||
|
@ -89,6 +90,9 @@ async function bootstrap(props?: MainAppProps) {
|
|||
// 注册第三方组件
|
||||
await registerThirdComp(app);
|
||||
|
||||
// 配置electron
|
||||
setupElectron(app)
|
||||
|
||||
// 当路由准备好时再执行挂载( https://next.router.vuejs.org/api/#isready)
|
||||
await router.isReady();
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import type { App } from 'vue';
|
||||
|
||||
import { $electron } from "@/electron";
|
||||
import { basicRoutes } from './routes';
|
||||
import {createRouter as createVueRouter, destroyRouter, router} from './router'
|
||||
|
||||
|
@ -18,10 +19,13 @@ getRouteNames(basicRoutes);
|
|||
*/
|
||||
export function createRouter() {
|
||||
let router = createVueRouter({
|
||||
routes: basicRoutes as unknown as RouteRecordRaw[],
|
||||
strict: true,
|
||||
scrollBehavior: () => ({left: 0, top: 0}),
|
||||
})
|
||||
routes: basicRoutes as unknown as RouteRecordRaw[],
|
||||
strict: true,
|
||||
scrollBehavior: () => ({left: 0, top: 0}),
|
||||
},
|
||||
// 如果是 Electron 环境,则使用 hash 路由
|
||||
$electron.isElectron(),
|
||||
)
|
||||
|
||||
// TODO 【QQYUN-4517】【表单设计器】记录分享路由守卫测试
|
||||
// @ts-ignore
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* 路由实例存储文件,请勿轻易添加其他代码,防止出现 HMR 或其他问题
|
||||
*/
|
||||
import type {Router, RouterHistory} from 'vue-router';
|
||||
import {createRouter as createVueRouter, createWebHistory, RouterOptions} from 'vue-router';
|
||||
import {createRouter as createVueRouter, createWebHistory, createWebHashHistory, RouterOptions} from 'vue-router';
|
||||
|
||||
export let router: Router = null as unknown as Router;
|
||||
|
||||
|
@ -15,9 +15,11 @@ let webHistory: Nullable<RouterHistory> = null;
|
|||
/**
|
||||
* 创建路由
|
||||
* @param options 参数
|
||||
* @param useHashHistory 是否使用 hash 路由,true使用,false不使用hash路由
|
||||
*/
|
||||
export function createRouter(options: Partial<RouterOptions>) {
|
||||
webHistory = createWebHistory(import.meta.env.VITE_PUBLIC_PATH);
|
||||
export function createRouter(options: Partial<RouterOptions>, useHashHistory = false) {
|
||||
const createFn = useHashHistory ? createWebHashHistory : createWebHistory;
|
||||
webHistory = createFn(import.meta.env.VITE_PUBLIC_PATH);
|
||||
// app router
|
||||
let router = createVueRouter({
|
||||
history: webHistory,
|
||||
|
|
|
@ -67,6 +67,9 @@ const setting: ProjectConfig = {
|
|||
// 是否显示底部信息 copyright
|
||||
showFooter: false,
|
||||
|
||||
// ai图标显示
|
||||
aiIconShow: false,
|
||||
|
||||
// 头部配置
|
||||
headerSetting: {
|
||||
// 背景色
|
||||
|
|
|
@ -16,6 +16,7 @@ import { MULTIPLE_TABS_KEY } from '/@/enums/cacheEnum';
|
|||
import projectSetting from '/@/settings/projectSetting';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import type { LocationQueryRaw, RouteParamsRaw } from 'vue-router';
|
||||
import { getMenus } from '/@/router/menus';
|
||||
|
||||
export interface MultipleTabState {
|
||||
cacheTabList: Set<string>;
|
||||
|
@ -65,6 +66,25 @@ const closeTabContainCurrentRoute = (router, pathList) => {
|
|||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* 2025-05-08
|
||||
* liaozhiyang
|
||||
* 【issues/8216】online生成的菜单sql 自动带上组件名称
|
||||
* */
|
||||
function getMatchingRoute(menus, path) {
|
||||
for (let i = 0, len = menus.length; i < len; i++) {
|
||||
const item = menus[i];
|
||||
if (item.path === path && !item.redirect && !item.paramPath) {
|
||||
return item;
|
||||
} else if (item.children?.length) {
|
||||
const result = getMatchingRoute(item.children, path);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const cacheTab = projectSetting.multiTabsSetting.cache;
|
||||
|
||||
|
@ -97,7 +117,7 @@ export const useMultipleTabStore = defineStore({
|
|||
*/
|
||||
async updateCacheTab() {
|
||||
const cacheMap: Set<string> = new Set();
|
||||
|
||||
const allMenus = await getMenus();
|
||||
for (const tab of this.tabList) {
|
||||
const item = getRawRoute(tab);
|
||||
// Ignore the cache
|
||||
|
@ -105,6 +125,19 @@ export const useMultipleTabStore = defineStore({
|
|||
if (!needCache) {
|
||||
continue;
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20240308---for:【QQYUN-12348】online生成的菜单sql 自动带上组件名称
|
||||
if (
|
||||
['OnlineAutoList', 'DefaultOnlineList', 'CgformErpList', 'OnlCgformInnerTableList', 'OnlCgformTabList', 'OnlCgReportList', 'GraphreportAutoChart', 'AutoDesformDataList'].includes(item.name as string) &&
|
||||
allMenus?.length
|
||||
) {
|
||||
const route = getMatchingRoute(allMenus, item.path);
|
||||
if (route?.meta?.keepAlive) {
|
||||
// 如果keepAlive为true,则添加到缓存中
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240308---for:【QQYUN-12348】online生成的菜单sql 自动带上组件名称
|
||||
const name = item.name as string;
|
||||
cacheMap.add(name);
|
||||
}
|
||||
|
@ -126,6 +159,22 @@ export const useMultipleTabStore = defineStore({
|
|||
const redo = useRedo(router);
|
||||
await redo();
|
||||
},
|
||||
/**
|
||||
* 修改设计模式
|
||||
* changeDesign
|
||||
*/
|
||||
async changeDesign(router: Router) {
|
||||
const { currentRoute } = router;
|
||||
const route = unref(currentRoute);
|
||||
const name = route.name;
|
||||
|
||||
const findTab = this.getCachedTabList.find((item) => item === name);
|
||||
if (findTab) {
|
||||
this.cacheTabList.delete(findTab);
|
||||
}
|
||||
const redo = useRedo(router, { isDesign: true });
|
||||
await redo();
|
||||
},
|
||||
clearCacheTabs(): void {
|
||||
this.cacheTabList = new Set();
|
||||
},
|
||||
|
@ -348,7 +397,7 @@ export const useMultipleTabStore = defineStore({
|
|||
this.clearCacheTabs();
|
||||
this.goToPage(router);
|
||||
},
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Close other tabs
|
||||
|
@ -413,6 +462,9 @@ export const useMultipleTabStore = defineStore({
|
|||
setRedirectPageParam(data) {
|
||||
this.redirectPageParam = data;
|
||||
},
|
||||
getRedirectPageParam() {
|
||||
return this.redirectPageParam;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -53,7 +53,8 @@ export function filterDictText(dictOptions, text) {
|
|||
let dictText = txt;
|
||||
for (let dictItem of dictOptions) {
|
||||
// update-begin--author:liaozhiyang---date:20240524---for:【TV360X-469】兼容数据null值防止报错
|
||||
if (dictItem == null) break;
|
||||
if (dictItem == null) continue;
|
||||
if (dictItem.value == null) continue;
|
||||
// update-end--author:liaozhiyang---date:20240524---for:【TV360X-469】兼容数据null值防止报错
|
||||
if (txt.toString() === dictItem.value.toString()) {
|
||||
dictText = dictItem.text || dictItem.title || dictItem.label;
|
||||
|
|
|
@ -25,7 +25,7 @@ export const getDictItemsByCode = (code) => {
|
|||
|
||||
};
|
||||
/**
|
||||
* Popup字典翻译方法
|
||||
* 从缓存中获取Pop字典配置
|
||||
* @param text
|
||||
* @param code
|
||||
*/
|
||||
|
|
|
@ -38,10 +38,15 @@ export function getAppEnvConfig() {
|
|||
VITE_GLOB_ONLINE_VIEW_URL,
|
||||
// 全局隐藏哪些布局,多个用逗号隔开
|
||||
VITE_GLOB_HIDE_LAYOUT_TYPES,
|
||||
// 当前运行在什么平台
|
||||
VITE_GLOB_RUN_PLATFORM,
|
||||
|
||||
// 【JEECG作为乾坤子应用】
|
||||
VITE_GLOB_QIANKUN_MICRO_APP_NAME,
|
||||
VITE_GLOB_QIANKUN_MICRO_APP_ENTRY,
|
||||
|
||||
//在线文档编辑版本。可选属性:wps, onlyoffice
|
||||
VITE_GLOB_ONLINE_DOCUMENT_VERSION,
|
||||
} = ENV;
|
||||
|
||||
// if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
|
||||
|
@ -62,10 +67,14 @@ export function getAppEnvConfig() {
|
|||
VITE_GLOB_DOMAIN_URL,
|
||||
VITE_GLOB_ONLINE_VIEW_URL,
|
||||
VITE_GLOB_HIDE_LAYOUT_TYPES,
|
||||
VITE_GLOB_RUN_PLATFORM,
|
||||
|
||||
// 【JEECG作为乾坤子应用】
|
||||
VITE_GLOB_QIANKUN_MICRO_APP_NAME,
|
||||
VITE_GLOB_QIANKUN_MICRO_APP_ENTRY,
|
||||
|
||||
//在线文档编辑版本。可选属性:wps, onlyoffice
|
||||
VITE_GLOB_ONLINE_DOCUMENT_VERSION
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -113,3 +122,14 @@ export function getHideLayoutTypes(): string[] {
|
|||
}
|
||||
return VITE_GLOB_HIDE_LAYOUT_TYPES.split(',');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线文档版本号
|
||||
*/
|
||||
export function getOnlineDocumentVersion(): string {
|
||||
const { VITE_GLOB_ONLINE_DOCUMENT_VERSION } = getAppEnvConfig();
|
||||
if (typeof VITE_GLOB_ONLINE_DOCUMENT_VERSION !== 'string') {
|
||||
return 'wps';
|
||||
}
|
||||
return VITE_GLOB_ONLINE_DOCUMENT_VERSION;
|
||||
}
|
||||
|
|
|
@ -99,6 +99,12 @@ const transform: AxiosTransform = {
|
|||
if(requestUrl!=null && (requestUrl.startsWith("http:") || requestUrl.startsWith("https:"))){
|
||||
isStartWithHttp = true;
|
||||
}
|
||||
// update-begin--author:sunjianlei---date:20250411---for:【QQYUN-9685】构建 electron 桌面应用
|
||||
if (!isStartWithHttp && requestUrl != null) {
|
||||
// 由于electron的url是file://开头的,所以需要判断一下
|
||||
isStartWithHttp = requestUrl.startsWith('file://');
|
||||
}
|
||||
// update-end----author:sunjianlei---date:20250411---for:【QQYUN-9685】构建 electron 桌面应用
|
||||
if (!isStartWithHttp && joinPrefix) {
|
||||
config.url = `${urlPrefix}${config.url}`;
|
||||
}
|
||||
|
|
|
@ -272,6 +272,12 @@ export const schemas: FormSchema[] = [
|
|||
span: 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'user4',
|
||||
component: 'JEllipsis',
|
||||
label: '选中用户',
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
field: 'role2',
|
||||
component: 'JSelectRole',
|
||||
|
|
|
@ -5,6 +5,7 @@ export enum Api {
|
|||
//知识库管理
|
||||
list = '/airag/app/list',
|
||||
save = '/airag/app/edit',
|
||||
release = '/airag/app/release',
|
||||
delete = '/airag/app/delete',
|
||||
queryById = '/airag/app/queryById',
|
||||
queryBathById = '/airag/knowledge/query/batch/byId',
|
||||
|
@ -44,6 +45,17 @@ export const saveApp = (params) => {
|
|||
return defHttp.put({ url: Api.save, params });
|
||||
};
|
||||
|
||||
// 发布应用
|
||||
export function releaseApp(appId: string, release = false) {
|
||||
return defHttp.post({
|
||||
url: Api.release,
|
||||
params: {
|
||||
id: appId,
|
||||
release: release,
|
||||
}
|
||||
}, {joinParamsToUrl: true});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除应用
|
||||
* @param params
|
||||
|
@ -77,5 +89,15 @@ export const queryFlowById = (params) => {
|
|||
* @param params
|
||||
*/
|
||||
export const promptGenerate = (params) => {
|
||||
return defHttp.get({ url: Api.promptGenerate, params,timeout: 5 * 60 * 1000 }, { isTransformResponse: false });
|
||||
return defHttp.post(
|
||||
{
|
||||
url: Api.promptGenerate+'?prompt='+ params.prompt,
|
||||
adapter: 'fetch',
|
||||
responseType: 'stream',
|
||||
timeout: 5 * 60 * 1000,
|
||||
},
|
||||
{
|
||||
isTransformResponse: false,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -42,7 +42,7 @@ export const formSchema: FormSchema[] = [
|
|||
label: '选择应用类型',
|
||||
field: 'type',
|
||||
component: 'Input',
|
||||
ifShow:({ values })=>{
|
||||
show:({ values })=>{
|
||||
return !values.id;
|
||||
},
|
||||
slot: 'typeSlot',
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
<!--知识库文档列表-->
|
||||
<template>
|
||||
<div class="p-2 knowledge">
|
||||
<div class="knowledge">
|
||||
<!--查询区域-->
|
||||
<div class="jeecg-basic-table-form-container">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
@keyup.enter.native="reload"
|
||||
@keyup.enter.native="searchQuery"
|
||||
:model="queryParam"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
style="background-color: #f7f8fc"
|
||||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :lg="6">
|
||||
<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 :lg="6">
|
||||
<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>
|
||||
|
@ -25,7 +25,7 @@
|
|||
<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="reload">查询</a-button>
|
||||
<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>
|
||||
|
@ -35,11 +35,10 @@
|
|||
</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" :bodyStyle="cardBodyStyle">
|
||||
<span style="line-height: 18px; font-weight: 500; color: #676f83; font-size: 12px">创建应用</span>
|
||||
<div class="add-knowledge-doc" @click="handleCreateApp">
|
||||
<Icon icon="ant-design:form-outlined" size="13"></Icon>
|
||||
<span>创建空白应用</span>
|
||||
<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>
|
||||
|
@ -49,7 +48,11 @@
|
|||
<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"> 创建者:{{ item.createBy }} </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">
|
||||
|
@ -69,25 +72,41 @@
|
|||
<Icon class="operation" icon="ant-design:youtube-outlined" size="20" color="#1F2329"></Icon>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<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="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 icon="ant-design:send-outlined"></Icon>
|
||||
<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 key="menu" @click.prevent.stop="handleSendClick(item,'menu')">
|
||||
<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>
|
||||
|
@ -109,6 +128,7 @@
|
|||
@change="handlePageChange"
|
||||
class="list-footer"
|
||||
size="small"
|
||||
:show-total="() => `共${total}条` "
|
||||
/>
|
||||
<!-- Ai新增弹窗 -->
|
||||
<AiAppModal @register="registerModal" @success="handleSuccess"></AiAppModal>
|
||||
|
@ -120,7 +140,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, reactive } from 'vue';
|
||||
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';
|
||||
|
@ -131,10 +151,12 @@
|
|||
import AiAppSettingModal from './components/AiAppSettingModal.vue';
|
||||
import AiAppSendModal from './components/AiAppSendModal.vue';
|
||||
import Icon from '@/components/Icon';
|
||||
import { appList, deleteApp } from './AiApp.api';
|
||||
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',
|
||||
|
@ -168,7 +190,7 @@
|
|||
const [registerModal, { openModal }] = useModal();
|
||||
const [registerSettingModal, { openModal: openAppModal }] = useModal();
|
||||
const [registerAiAppSendModal, { openModal: openAiAppSendModal }] = useModal();
|
||||
const { createMessage } = useMessage();
|
||||
const { createMessage, createConfirmSync } = useMessage();
|
||||
//查询参数
|
||||
const queryParam = reactive<any>({});
|
||||
//查询区域label宽度
|
||||
|
@ -185,7 +207,7 @@
|
|||
});
|
||||
//表单的ref
|
||||
const formRef = ref();
|
||||
|
||||
|
||||
reload();
|
||||
|
||||
/**
|
||||
|
@ -263,14 +285,27 @@
|
|||
/**
|
||||
* 演示
|
||||
*/
|
||||
function handleViewClick(id) {
|
||||
window.open('/ai/app/chat/' + id , '_blank');
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -280,22 +315,78 @@
|
|||
* @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,
|
||||
|
@ -320,6 +411,8 @@
|
|||
registerAiAppSendModal,
|
||||
searchReset,
|
||||
formRef,
|
||||
isShowMenu,
|
||||
searchQuery,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
@ -327,30 +420,38 @@
|
|||
|
||||
<style scoped lang="less">
|
||||
.knowledge {
|
||||
display: grid;
|
||||
height: 100%;
|
||||
height: calc(100vh - 115px);
|
||||
background: #f7f8fc;
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.add-knowledge-card {
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
display: inline-flex;
|
||||
font-size: 16px;
|
||||
height: 152px;
|
||||
width: calc(100% - 20px);
|
||||
background: #fcfcfd;
|
||||
border: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
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 {
|
||||
|
@ -360,7 +461,7 @@
|
|||
height: 152px;
|
||||
background: #fcfcfd;
|
||||
border: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px 4px #e6e6e6;
|
||||
transition: all 0.3s ease;
|
||||
.header-img {
|
||||
width: 40px;
|
||||
|
@ -396,10 +497,12 @@
|
|||
|
||||
.add-knowledge-card:hover,
|
||||
.knowledge-card:hover {
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0 6px 12px #d0d3d8;
|
||||
}
|
||||
|
||||
.knowledge-row {
|
||||
max-height: calc(100% - 100px);
|
||||
margin-top: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
@ -455,7 +558,7 @@
|
|||
|
||||
.card-footer-icon:hover {
|
||||
color: #000000;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
background-color: #e9ecf2;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div ref="chatContainerRef" class="chat-container" :style="chatContainerStyle">
|
||||
<template v-if="dataSource">
|
||||
<div class="leftArea" :class="[expand ? 'expand' : 'shrink']">
|
||||
<div v-if="isMultiSession" class="leftArea" :class="[expand ? 'expand' : 'shrink']">
|
||||
<div class="content">
|
||||
<slide v-if="uuid" :dataSource="dataSource" @save="handleSave" :prologue="prologue" :appData="appData" @click="handleChatClick"></slide>
|
||||
<slide :source="source" v-if="uuid" :dataSource="dataSource" @save="handleSave" :prologue="prologue" :appData="appData" @click="handleChatClick"></slide>
|
||||
</div>
|
||||
<div class="toggle-btn" @click="handleToggle">
|
||||
<span class="icon">
|
||||
|
@ -30,6 +30,7 @@
|
|||
@reload-message-title="reloadMessageTitle"
|
||||
:chatTitle="chatTitle"
|
||||
:quickCommandData="quickCommandData"
|
||||
:showAdvertising = "showAdvertising"
|
||||
></chat>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -46,6 +47,7 @@
|
|||
import { JEECG_CHAT_KEY } from '/@/enums/cacheEnum';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useAppInject } from "@/hooks/web/useAppInject";
|
||||
|
||||
const router = useRouter();
|
||||
const userId = useUserStore().getUserInfo?.id;
|
||||
|
@ -77,6 +79,8 @@
|
|||
const prologue = ref<string>('');
|
||||
//快捷指令
|
||||
const quickCommandData = ref<any>([]);
|
||||
//是否显示广告位
|
||||
const showAdvertising = ref<boolean>(false);
|
||||
|
||||
const priming = () => {
|
||||
dataSource.value = {
|
||||
|
@ -142,6 +146,13 @@
|
|||
);
|
||||
};
|
||||
|
||||
//是否为多会话模式
|
||||
const isMultiSession = ref<boolean>(true);
|
||||
//是否为手机
|
||||
const { getIsMobile } = useAppInject();
|
||||
//来源
|
||||
const source = ref<string>('');
|
||||
|
||||
/**
|
||||
* 初始化聊天信息
|
||||
* @param appId
|
||||
|
@ -184,6 +195,13 @@
|
|||
{ name: 'JEECG有哪些优势?', descr: "JEECG有哪些优势?" },
|
||||
{ name: 'JEECG可以做哪些事情?', descr: "JEECG可以做哪些事情?" },];
|
||||
}
|
||||
let query: any = router.currentRoute.value.query;
|
||||
source.value = query.source;
|
||||
if(query.source){
|
||||
showAdvertising.value = query.source === 'chatJs';
|
||||
}else{
|
||||
showAdvertising.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
@ -203,7 +221,7 @@
|
|||
await defHttp
|
||||
.get(
|
||||
{
|
||||
url: '/airag/app/queryById',
|
||||
url: '/airag/chat/init',
|
||||
params: { id: appId },
|
||||
},
|
||||
{ isTransformResponse: false }
|
||||
|
@ -220,6 +238,23 @@
|
|||
if (res.result && res.result.presetQuestion) {
|
||||
presetQuestion.value = res.result.presetQuestion;
|
||||
}
|
||||
if (res.result && res.result.metadata) {
|
||||
let metadata = JSON.parse(res.result.metadata);
|
||||
//判斷是否为手机模式
|
||||
if(!getIsMobile.value){
|
||||
//是否为多会话模式
|
||||
if((metadata.multiSession && metadata.multiSession === '1') || !metadata.multiSession) {
|
||||
isMultiSession.value = true;
|
||||
} else {
|
||||
isMultiSession.value = false;
|
||||
expand.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(getIsMobile.value){
|
||||
isMultiSession.value = false;
|
||||
expand.value = false;
|
||||
}
|
||||
} else {
|
||||
appData.value = {};
|
||||
}
|
||||
|
@ -281,7 +316,7 @@
|
|||
.chat-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
position: absolute;
|
||||
background: white;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
|
@ -339,7 +374,7 @@
|
|||
color: rgb(51, 54, 57);
|
||||
border: 1px solid rgb(239, 239, 245);
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 4px 0px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: 0 2px 4px 0px #e7e9ef;
|
||||
transform: translateX(50%) translateY(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
|
|
@ -17,19 +17,19 @@
|
|||
import AiChat from './AiChat.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
//aiChat的ref
|
||||
//aiChat的ref
|
||||
const aiChatRef = ref();
|
||||
//应用id
|
||||
//应用id
|
||||
const appId = ref<string>('');
|
||||
|
||||
//是否显示聊天
|
||||
//是否显示聊天
|
||||
const showChat = ref<any>(false);
|
||||
const router = useRouter();
|
||||
//判断是否为初始化
|
||||
//判断是否为初始化
|
||||
const isInit = ref<boolean>(false);
|
||||
|
||||
/**
|
||||
* chat图标点击事件
|
||||
* chat图标点击事件
|
||||
*/
|
||||
function chatClick() {
|
||||
showChat.value = !showChat.value;
|
||||
|
@ -67,7 +67,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0 4px 8px 0;
|
||||
box-shadow: #cccccc 0 4px 8px 0;
|
||||
}
|
||||
.footer-close-icon {
|
||||
color: #0a3069;
|
||||
|
|
|
@ -3,6 +3,13 @@
|
|||
<div class="content">
|
||||
<div class="header-title" v-if="type === 'view' && headerTitle">
|
||||
{{headerTitle}}
|
||||
<div v-if="showAdvertising" class="header-advertisint">
|
||||
AI客服由
|
||||
<a style="color: #4183c4;margin-left: 2px;margin-right: 2px" href="https://www.qiaoqiaoyun.com/aiCustomerService" target="_blank">
|
||||
敲敲云
|
||||
</a>
|
||||
提供
|
||||
</div>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div id="scrollRef" ref="scrollRef" class="scrollArea">
|
||||
|
@ -19,6 +26,8 @@
|
|||
:appData="appData"
|
||||
:presetQuestion="item.presetQuestion"
|
||||
:images = "item.images"
|
||||
:retrievalText="item.retrievalText"
|
||||
:referenceKnowledge="item.referenceKnowledge"
|
||||
@send="handleOutQuestion"
|
||||
></chatMessage>
|
||||
</div>
|
||||
|
@ -86,6 +95,7 @@
|
|||
autofocus
|
||||
:readonly="loading"
|
||||
style="border-color: #ffffff !important;box-shadow:none"
|
||||
@paste="paste"
|
||||
>
|
||||
</a-textarea>
|
||||
<a-button v-if="loading" type="primary" danger @click="handleStopChat" class="stopBtn">
|
||||
|
@ -122,7 +132,7 @@
|
|||
>
|
||||
<a-tooltip title="图片上传,支持jpg/jpeg/png">
|
||||
<a-button class="sendBtn" type="text">
|
||||
<Icon icon="ant-design:picture-outlined" style="color: rgba(15,21,40,0.8)"></Icon>
|
||||
<Icon icon="ant-design:picture-outlined" style="color: #3d4353"></Icon>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-upload>
|
||||
|
@ -178,21 +188,22 @@
|
|||
import { defHttp } from '@/utils/http/axios';
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import {getFileAccessHttpUrl, getHeaders} from "@/utils/common/compUtils";
|
||||
import { uploadUrl } from '/@/api/common/api';
|
||||
import { createImgPreview } from "@/components/Preview";
|
||||
|
||||
import { useAppInject } from "@/hooks/web/useAppInject";
|
||||
import { useGlobSetting } from "@/hooks/setting";
|
||||
|
||||
message.config({
|
||||
prefixCls: 'ai-chat-message',
|
||||
});
|
||||
|
||||
const props = defineProps(['uuid', 'prologue', 'formState', 'url', 'type','historyData','chatTitle','presetQuestion','quickCommandData']);
|
||||
|
||||
const props = defineProps(['uuid', 'prologue', 'formState', 'url', 'type','historyData','chatTitle','presetQuestion','quickCommandData','showAdvertising']);
|
||||
const emit = defineEmits(['save','reload-message-title']);
|
||||
const { scrollRef, scrollToBottom } = useScroll();
|
||||
const prompt = ref<string>('');
|
||||
const loading = ref<boolean>(false);
|
||||
const inputRef = ref<Ref | null>(null);
|
||||
const headerTitle = ref<string>(props.chatTitle);
|
||||
|
||||
|
||||
//聊天数据
|
||||
const chatData = ref<any>([]);
|
||||
//应用数据
|
||||
|
@ -202,15 +213,24 @@
|
|||
const topicId = ref<string>('');
|
||||
//请求id
|
||||
const requestId = ref<string>('');
|
||||
const { getIsMobile } = useAppInject();
|
||||
const conversationList = computed(() => chatData.value.filter((item) => item.inversion != 'user' && !!item.conversationOptions));
|
||||
const placeholder = computed(() => {
|
||||
return '来说点什么吧...(Shift + Enter = 换行)';
|
||||
if(getIsMobile.value){
|
||||
return '来说点什么吧...'
|
||||
} else {
|
||||
return '来说点什么吧...(Shift + Enter = 换行)';
|
||||
}
|
||||
});
|
||||
//token
|
||||
const headers = getHeaders();
|
||||
//文本域点击事件
|
||||
const textareaActive = ref<boolean>(false);
|
||||
|
||||
const globSetting = useGlobSetting();
|
||||
const baseUploadUrl = globSetting.uploadUrl;
|
||||
const uploadUrl = ref<string>(`${baseUploadUrl}/airag/chat/upload`);
|
||||
|
||||
function handleEnter(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
|
@ -269,6 +289,7 @@
|
|||
error: false,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
referenceKnowledge: [],
|
||||
});
|
||||
|
||||
scrollToBottom();
|
||||
|
@ -318,7 +339,7 @@
|
|||
dateTime: new Date().toLocaleString(),
|
||||
content: data,
|
||||
inversion: 'ai',
|
||||
error: false,
|
||||
error: true,
|
||||
loading: true,
|
||||
conversationOptions: null,
|
||||
requestOptions: null,
|
||||
|
@ -370,19 +391,27 @@
|
|||
|
||||
handleStop();
|
||||
|
||||
const knowList = ref<Recordable[]>([])
|
||||
|
||||
/**
|
||||
* 停止消息
|
||||
*/
|
||||
function handleStopChat() {
|
||||
if(requestId.value){
|
||||
//调用后端接口停止响应
|
||||
defHttp.get({
|
||||
url: '/airag/chat/stop/' + requestId.value,
|
||||
},{ isTransformResponse: false });
|
||||
//update-begin---author:wangshuai---date:2025-06-03---for:【issues/8338】AI应用聊天回复stop无效,仍会继续输出回复---
|
||||
const currentRequestId = requestId.value
|
||||
if(currentRequestId){
|
||||
try{
|
||||
//调用后端接口停止响应
|
||||
defHttp.get({
|
||||
url: '/airag/chat/stop/' + currentRequestId,
|
||||
},{ isTransformResponse: false });
|
||||
} finally {
|
||||
handleStop();
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-06-03---for:【issues/8338】AI应用聊天回复stop无效,仍会继续输出回复---
|
||||
}
|
||||
handleStop();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 读取文本
|
||||
* @param message
|
||||
|
@ -409,15 +438,16 @@
|
|||
};
|
||||
|
||||
if(headerTitle.value == '新建聊天'){
|
||||
headerTitle.value = message.length>5?message.substring(0,5):message
|
||||
headerTitle.value = message.length>10?truncateString(message,10):message
|
||||
}
|
||||
|
||||
emit("reload-message-title",message.length>5?message.substring(0,5):message)
|
||||
emit("reload-message-title",message.length>10?truncateString(message,10):message)
|
||||
}
|
||||
|
||||
uploadUrlList.value = [];
|
||||
fileInfoList.value = [];
|
||||
|
||||
knowList.value = [];
|
||||
|
||||
const readableStream = await defHttp.post(
|
||||
{
|
||||
url: props.url,
|
||||
|
@ -430,9 +460,18 @@
|
|||
isTransformResponse: false,
|
||||
}
|
||||
).catch((e)=>{
|
||||
updateChatFail(uuid, chatData.value.length - 1, "服务器错误,请稍后重试!");
|
||||
handleStop();
|
||||
return;
|
||||
//update-begin---author:wangshuai---date:2025-04-28---for:【QQYUN-12297】【AI】聊天,超时以后提示---
|
||||
if(e.code === 'ETIMEDOUT'){
|
||||
updateChatFail(uuid, chatData.value.length - 1, "当前用户较多,排队中,请稍候再次重试!");
|
||||
handleStop();
|
||||
return;
|
||||
}else{
|
||||
updateChatFail(uuid, chatData.value.length - 1, "服务器错误,请稍后重试!");
|
||||
handleStop();
|
||||
return;
|
||||
}
|
||||
console.error(e)
|
||||
//update-end---author:wangshuai---date:2025-04-28---for:【QQYUN-12297】【AI】聊天,超时以后提示---
|
||||
});
|
||||
const reader = readableStream.getReader();
|
||||
const decoder = new TextDecoder('UTF-8');
|
||||
|
@ -448,9 +487,9 @@
|
|||
let result = decoder.decode(value, { stream: true });
|
||||
result = buffer + result;
|
||||
const lines = result.split('\n\n');
|
||||
for (const line of lines) {
|
||||
for (let line of lines) {
|
||||
if (line.startsWith('data:')) {
|
||||
const content = line.replace('data:', '').trim();
|
||||
let content = line.replace('data:', '').trim();
|
||||
if(!content){
|
||||
continue;
|
||||
}
|
||||
|
@ -461,6 +500,9 @@
|
|||
buffer = "";
|
||||
try {
|
||||
//update-begin---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答---
|
||||
if(content.indexOf(":::card:::") !== -1){
|
||||
content = content.replace(/\s+/g, '');
|
||||
}
|
||||
let parse = JSON.parse(content);
|
||||
await renderText(parse,conversationId,text,options).then((res)=>{
|
||||
text = res.returnText;
|
||||
|
@ -482,6 +524,9 @@
|
|||
buffer = "";
|
||||
//update-begin---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答---
|
||||
try {
|
||||
if(line.indexOf(":::card:::") !== -1){
|
||||
line = line.replace(/\s+/g, '');
|
||||
}
|
||||
let parse = JSON.parse(line);
|
||||
await renderText(parse, conversationId, text, options).then((res) => {
|
||||
text = res.returnText;
|
||||
|
@ -523,24 +568,42 @@
|
|||
async function renderText(item,conversationId,text,options) {
|
||||
let returnText = "";
|
||||
if (item.event == 'MESSAGE') {
|
||||
text = text + item.data.message;
|
||||
returnText = text;
|
||||
let message = item.data.message;
|
||||
let messageText = "";
|
||||
//update-begin---author:wangshuai---date:2025-04-24---for:应该先判断是否包含card---
|
||||
if(message && message.indexOf("::card::") !== -1){
|
||||
messageText = message;
|
||||
} else {
|
||||
text = text + item.data.message;
|
||||
messageText = text;
|
||||
returnText = text;
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-04-24---for:应该先判断是否包含card---
|
||||
// 从消息中获取 requestId
|
||||
if (item.requestId) {
|
||||
requestId.value = item.requestId;
|
||||
}
|
||||
//更新聊天信息
|
||||
updateChat(uuid.value, chatData.value.length - 1, {
|
||||
dateTime: new Date().toLocaleString(),
|
||||
content: text,
|
||||
content: messageText,
|
||||
inversion: 'ai',
|
||||
error: false,
|
||||
loading: true,
|
||||
conversationOptions: { conversationId: conversationId, parentMessageId: topicId.value },
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
referenceKnowledge: knowList.value,
|
||||
});
|
||||
}
|
||||
if(item.event == 'INIT_REQUEST_ID'){
|
||||
if (item.requestId) {
|
||||
requestId.value = item.requestId;
|
||||
}
|
||||
}
|
||||
if (item.event == 'MESSAGE_END') {
|
||||
topicId.value = item.topicId;
|
||||
conversationId = item.conversationId;
|
||||
uuid.value = item.conversationId;
|
||||
requestId.value = item.requestId;
|
||||
handleStop();
|
||||
}
|
||||
if (item.event == 'FLOW_FINISHED') {
|
||||
|
@ -565,41 +628,60 @@
|
|||
|
||||
//update-begin---author:wangshuai---date:2025-03-21---for:【QQYUN-11495】【AI】实时展示当前思考进度---
|
||||
if(item.event === "NODE_STARTED"){
|
||||
let aiText = "";
|
||||
if(item.data.type === 'llm'){
|
||||
aiText = "正在构建响应内容";
|
||||
if(!item.data || item.data.type !== 'end'){
|
||||
let aiText = "";
|
||||
if(item.data.type === 'llm'){
|
||||
aiText = "正在构建响应内容";
|
||||
}
|
||||
if(item.data.type === 'knowledge'){
|
||||
aiText = "正在对知识库进行深度检索";
|
||||
}
|
||||
if(item.data.type === 'classifier'){
|
||||
aiText = "正在分类";
|
||||
}
|
||||
if(item.data.type === 'code'){
|
||||
aiText = "正在实施代码运行操作";
|
||||
}
|
||||
if(item.data.type === 'subflow'){
|
||||
aiText = "正在运行子流程";
|
||||
}
|
||||
if(item.data.type === 'enhanceJava'){
|
||||
aiText = "正在执行java增强";
|
||||
}
|
||||
if(item.data.type === 'http'){
|
||||
aiText = "正在发送http请求";
|
||||
}
|
||||
if(!text){
|
||||
//更新聊天信息
|
||||
updateChat(uuid.value, chatData.value.length - 1, {
|
||||
dateTime: new Date().toLocaleString(),
|
||||
retrievalText: aiText,
|
||||
text:"",
|
||||
inversion: 'ai',
|
||||
error: false,
|
||||
loading: true,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
referenceKnowledge: knowList.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
if(item.data.type === 'knowledge'){
|
||||
aiText = "正在对知识库进行深度检索";
|
||||
}
|
||||
if(item.data.type === 'classifier'){
|
||||
aiText = "正在分类";
|
||||
}
|
||||
if(item.data.type === 'code'){
|
||||
aiText = "正在实施代码运行操作";
|
||||
}
|
||||
if(item.data.type === 'subflow'){
|
||||
aiText = "正在运行子流程";
|
||||
}
|
||||
if(item.data.type === 'enhanceJava'){
|
||||
aiText = "正在执行java增强";
|
||||
}
|
||||
if(item.data.type === 'http'){
|
||||
aiText = "正在发送http请求";
|
||||
}
|
||||
//更新聊天信息
|
||||
updateChat(uuid.value, chatData.value.length - 1, {
|
||||
dateTime: new Date().toLocaleString(),
|
||||
content: aiText,
|
||||
inversion: 'ai',
|
||||
error: false,
|
||||
loading: true,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
});
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-03-21---for:【QQYUN-11495】【AI】实时展示当前思考进度---
|
||||
|
||||
else if (item.event === 'NODE_FINISHED') {
|
||||
if(!item.data || item.data.type !== 'end'){
|
||||
if(item.data.type === 'knowledge'){
|
||||
const id = item.data.id;
|
||||
const data = item.data.outputs[id + ".data"]
|
||||
knowList.value.push(data)
|
||||
//更新聊天信息
|
||||
updateChatSome(uuid.value, chatData.value.length - 1, {referenceKnowledge: knowList.value})
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!returnText){
|
||||
returnText = text;
|
||||
}
|
||||
return { returnText, conversationId };
|
||||
}
|
||||
|
||||
|
@ -607,7 +689,7 @@
|
|||
const uploadUrlList = ref<any>([]);
|
||||
//文件集合
|
||||
const fileInfoList = ref<any>([]);
|
||||
|
||||
|
||||
/**
|
||||
* 文件上传回调事件
|
||||
* @param info
|
||||
|
@ -615,8 +697,9 @@
|
|||
function handleChange(info) {
|
||||
let { fileList, file } = info;
|
||||
fileInfoList.value = fileList;
|
||||
if (file.status === 'error') {
|
||||
if (file.status === 'error' || (file.response && file.response.code == 500)) {
|
||||
message.error(file.response?.message || `${file.name} 上传失败,请查看服务端日志`);
|
||||
return;
|
||||
}
|
||||
if (file.status === 'done') {
|
||||
uploadUrlList.value.push(file.response.message);
|
||||
|
@ -625,7 +708,7 @@
|
|||
|
||||
/**
|
||||
* 获取图片地址
|
||||
*
|
||||
*
|
||||
* @param url
|
||||
*/
|
||||
function getImage(url) {
|
||||
|
@ -643,6 +726,10 @@
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if(uploadUrlList.value && uploadUrlList.value.length > 2){
|
||||
message.warning("最多只能上传三张!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -665,7 +752,85 @@
|
|||
let imageList = [getImage(url)];
|
||||
createImgPreview({ imageList: imageList, defaultWidth: 700, rememberState: true, onImgLoad });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 截取字符串
|
||||
* @param str
|
||||
* @param maxLength
|
||||
*/
|
||||
function truncateString(str, maxLength) {
|
||||
if (str.length <= maxLength){
|
||||
return str;
|
||||
}
|
||||
let chineseCount = 0;
|
||||
let englishCount = 0;
|
||||
let digitCount = 0;
|
||||
let result = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str[i];
|
||||
if (/[\u4e00-\u9fa5]/.test(char)) { // 判断是否为汉字
|
||||
chineseCount++;
|
||||
} else if (/[a-zA-Z]/.test(char)) { // 判断是否为英文字母
|
||||
englishCount++;
|
||||
} else if (/\d/.test(char)) { // 判断是否为数字
|
||||
digitCount++;
|
||||
}
|
||||
if (chineseCount + englishCount / 2 + digitCount / 2 > maxLength) {
|
||||
break;
|
||||
}
|
||||
result += char;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 粘贴事件
|
||||
* @param event
|
||||
*/
|
||||
function paste(event) {
|
||||
if(uploadUrlList.value && uploadUrlList.value.length > 2){
|
||||
message.warning("最多只能上传三张!");
|
||||
return;
|
||||
}
|
||||
const items = (event.clipboardData || window.clipboardData).items;
|
||||
if (!items || items.length === 0){
|
||||
//说明浏览器不支持复制图片
|
||||
message.error('当前浏览器不支持本地打开图片!');
|
||||
return;
|
||||
}
|
||||
let image = null;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].type.indexOf('image') !== -1) {
|
||||
image = items[i].getAsFile();
|
||||
handleUploadImage(image);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 粘贴图片
|
||||
* @param image
|
||||
*/
|
||||
async function handleUploadImage(image) {
|
||||
const isReturn = (fileInfo) => {
|
||||
try {
|
||||
if (fileInfo.code === 0) {
|
||||
let { message } = fileInfo;
|
||||
uploadUrlList.value.push(message);
|
||||
fileInfoList.value.push(image);
|
||||
} else if (fileInfo.code === 500 || fileInfo.code === 510) {
|
||||
message.error(fileInfo.message || `${image.name} 导入失败`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('导入的数据异常', error);
|
||||
message.error(`${image.name} 导入失败`);
|
||||
}
|
||||
};
|
||||
await defHttp.uploadFile({ url: "/airag/chat/upload" }, { file: image }, { success: isReturn });
|
||||
}
|
||||
|
||||
//监听开场白
|
||||
watch(
|
||||
() => props.prologue,
|
||||
|
@ -676,8 +841,8 @@
|
|||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
);
|
||||
|
||||
);
|
||||
|
||||
//监听开场白预制问题
|
||||
watch(
|
||||
() => props.presetQuestion,
|
||||
|
@ -698,7 +863,7 @@
|
|||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
|
||||
//监听历史信息
|
||||
watch(
|
||||
() => props.historyData,
|
||||
|
@ -840,6 +1005,14 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 30px;
|
||||
.header-advertisint{
|
||||
display:flex;
|
||||
margin-right: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.chat-textarea{
|
||||
display: flex;
|
||||
|
@ -850,7 +1023,7 @@
|
|||
border-width: 1px;
|
||||
flex-direction: column;
|
||||
transition: width 0.3s;
|
||||
border-color: rgba(68,83,130,0.2);
|
||||
border-color: #d2d7e5;
|
||||
.textarea-top{
|
||||
border-bottom: 1px solid #f0f0f5;
|
||||
padding: 12px 28px;
|
||||
|
@ -881,10 +1054,10 @@
|
|||
}
|
||||
}
|
||||
.chat-textarea:hover{
|
||||
border-color: rgba(59,130,246,0.5)
|
||||
border-color: #9dc1fb;
|
||||
}
|
||||
.textarea-active{
|
||||
border-color: rgba(59,130,246,0.5) !important;
|
||||
border-color: #98bdfa !important;
|
||||
}
|
||||
:deep(.ant-divider-vertical){
|
||||
margin: 0 2px;
|
||||
|
@ -899,7 +1072,7 @@
|
|||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px 4px #e6e6e6;
|
||||
margin-left: 44px;
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
@ -908,6 +1081,24 @@
|
|||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
//手机下的样式 平板不需要调整
|
||||
.footer{
|
||||
padding: 0;
|
||||
.bottomArea{
|
||||
.delBtn{
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.chatWrap{
|
||||
padding: 10px 10px 10px 0;
|
||||
}
|
||||
.main .chatContentArea{
|
||||
padding: 10px 0 0 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.ai-chat-modal{
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div class="chat" :class="[inversion === 'user' ? 'self' : 'chatgpt']">
|
||||
<div class="chat" :class="[inversion === 'user' ? 'self' : 'chatgpt']" v-if="getText || (props.presetQuestion && props.presetQuestion.length>0)">
|
||||
<div class="avatar">
|
||||
<img v-if="inversion === 'user'" :src="avatar()" />
|
||||
<img v-else :src="getAiImg()">
|
||||
<img v-else :src="getAiImg()" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<p class="date">
|
||||
|
@ -14,8 +14,24 @@
|
|||
<img :src="getImageUrl(item)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msgArea">
|
||||
<chatText :text="text" :inversion="inversion" :error="error" :loading="loading"></chatText>
|
||||
<div v-if="inversion === 'ai' && retrievalText && loading" class="retrieval">
|
||||
{{retrievalText}}
|
||||
</div>
|
||||
<div v-if="inversion === 'ai' && isCard" class="card">
|
||||
<a-row>
|
||||
<a-col :xl="6" :lg="8" :md="10" :sm="24" style="flex:1" v-for="item in getCardList()">
|
||||
<a-card class="ai-card" @click="aiCardHandleClick(item.linkUrl)">
|
||||
<div class="ai-card-title">{{item.productName}}</div>
|
||||
<div class="ai-card-img">
|
||||
<img :src="item.productImage">
|
||||
</div>
|
||||
<span class="ai-card-desc">{{item.descr}}</span>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div class="msgArea" v-if="!isCard">
|
||||
<chatText :text="text" :inversion="inversion" :error="error" :loading="loading" :referenceKnowledge="referenceKnowledge"></chatText>
|
||||
</div>
|
||||
<div v-if="presetQuestion" v-for="item in presetQuestion" class="question" @click="presetQuestionClick(item.descr)">
|
||||
<span>{{item.descr}}</span>
|
||||
|
@ -26,13 +42,31 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import chatText from './chatText.vue';
|
||||
import defaultAvatar from '/@/assets/images/ai/avatar.jpg';
|
||||
import defaultAvatar from "@/assets/images/ai/avatar.jpg";
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import defaultImg from '../img/ailogo.png';
|
||||
|
||||
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading','appData','presetQuestion','images']);
|
||||
|
||||
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading','appData','presetQuestion','images','retrievalText', 'referenceKnowledge']);
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
import { createImgPreview } from "@/components/Preview";
|
||||
import { computed } from "vue";
|
||||
|
||||
const getText = computed(()=>{
|
||||
let text = props.text || props.retrievalText;
|
||||
if(text){
|
||||
text = text.trim();
|
||||
}
|
||||
return text;
|
||||
})
|
||||
|
||||
const isCard = computed(() => {
|
||||
let text = props.text;
|
||||
if (text && text.indexOf('::card::') != -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const { userInfo } = useUserStore();
|
||||
const avatar = () => {
|
||||
return getFileAccessHttpUrl(userInfo?.avatar) || defaultAvatar;
|
||||
|
@ -44,7 +78,7 @@
|
|||
|
||||
/**
|
||||
* 预设问题点击事件
|
||||
*
|
||||
*
|
||||
*/
|
||||
function presetQuestionClick(descr) {
|
||||
emit("send",descr)
|
||||
|
@ -52,7 +86,7 @@
|
|||
|
||||
/**
|
||||
* 获取图片
|
||||
*
|
||||
*
|
||||
* @param item
|
||||
*/
|
||||
function getImageUrl(item) {
|
||||
|
@ -77,7 +111,28 @@
|
|||
let imageList = [getImageUrl(url)];
|
||||
createImgPreview({ imageList: imageList, defaultWidth: 700, rememberState: true, onImgLoad });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取卡片列表
|
||||
*/
|
||||
function getCardList() {
|
||||
let text = props.text;
|
||||
let card = text.replace('::card::', '').replace(/\s+/g, '');
|
||||
try {
|
||||
return JSON.parse(card);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ai卡片点击事件
|
||||
* @param url
|
||||
*/
|
||||
function aiCardHandleClick(url){
|
||||
window.open(url,'_blank');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -112,6 +167,7 @@
|
|||
}
|
||||
}
|
||||
.content {
|
||||
width: 90%;
|
||||
.date {
|
||||
color: #b4bbc4;
|
||||
font-size: 0.75rem;
|
||||
|
@ -134,14 +190,15 @@
|
|||
line-height: 1.25rem;
|
||||
cursor: pointer;
|
||||
border: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2px 4px #e6e6e6;
|
||||
}
|
||||
|
||||
|
||||
.images{
|
||||
margin-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: end;
|
||||
.image{
|
||||
width: 120px;
|
||||
height: 80px;
|
||||
|
@ -154,4 +211,76 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.retrieval,
|
||||
.card {
|
||||
background-color: #f4f6f8;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
border-radius: 0.375rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
}
|
||||
.retrieval:after{
|
||||
animation: blink 1s steps(5, start) infinite;
|
||||
color: #000;
|
||||
content: '_';
|
||||
font-weight: 700;
|
||||
margin-left: 3px;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
.card{
|
||||
width: 100%;
|
||||
background-color: unset;
|
||||
}
|
||||
.ai-card{
|
||||
width: 98%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
.ai-card-title{
|
||||
width: 100%;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0;
|
||||
white-space: pre-line;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-box-orient: vertical;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
text-align: left;
|
||||
color: #191919;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
.ai-card-img{
|
||||
margin-top: 10px;
|
||||
background-color: transparent;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
}
|
||||
.ai-card-desc{
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0;
|
||||
white-space: pre-line;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
text-overflow: ellipsis;
|
||||
text-align: left;
|
||||
color: #666f;
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.content{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,10 +1,26 @@
|
|||
<template>
|
||||
<div v-if="text != ''" class="textWrap" :class="[inversion === 'user' ? 'self' : 'chatgpt']" ref="textRef">
|
||||
<div v-if="inversion != 'user'">
|
||||
<div class="markdown-body" :class="{ 'markdown-body-generate': loading }" v-html="text" />
|
||||
<div v-if="inversion != 'user'" :style="{ width: getIsMobile? screenWidth : 'auto' }">
|
||||
<div class="markdown-body" :class="{ 'markdown-body-generate': loading }" :style="{color:error?'#FF4444 !important':''}" v-html="text" />
|
||||
<template v-if="showRefKnow">
|
||||
<a-divider orientation="left">引用</a-divider>
|
||||
<template v-for="(item, idx) of referenceKnowledge" :key="idx">
|
||||
<a-tooltip :title="item.substring(0, 800)">
|
||||
<a-tag >
|
||||
<a-space>
|
||||
<img :src="knowledgePng" width="16" height="16"/>
|
||||
<div style="max-width: 240px; overflow: hidden;white-space: nowrap;text-overflow: ellipsis;">
|
||||
{{ item }}
|
||||
</div>
|
||||
</a-space>
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else class="msg" v-text="text" />
|
||||
<div v-else class="msg" v-html="text" />
|
||||
</div>
|
||||
<ImageViewer v-if="amplifyImage" :imageUrl="imageUrl" @hide="pictureHide"></ImageViewer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -16,11 +32,21 @@
|
|||
import './style/github-markdown.less';
|
||||
import './style/highlight.less';
|
||||
import './style/style.less';
|
||||
import ImageViewer from '@/views/super/airag/aiapp/chat/components/ImageViewer.vue';
|
||||
import { useAppInject } from "@/hooks/web/useAppInject";
|
||||
import { useGlobSetting } from "@/hooks/setting";
|
||||
import knowledgePng from '../../aiknowledge/icon/knowledge.png'
|
||||
|
||||
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading']);
|
||||
/**
|
||||
* 屏幕宽度
|
||||
*/
|
||||
const screenWidth = ref<string>();
|
||||
const { getIsMobile } = useAppInject();
|
||||
|
||||
const props = defineProps(['dateTime', 'text', 'inversion', 'error', 'loading', 'referenceKnowledge']);
|
||||
const textRef = ref();
|
||||
const mdi = new MarkdownIt({
|
||||
html: false,
|
||||
html: true,
|
||||
linkify: true,
|
||||
highlight(code, language) {
|
||||
const validLang = !!(language && hljs.getLanguage(language));
|
||||
|
@ -36,11 +62,49 @@
|
|||
mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' });
|
||||
|
||||
const text = computed(() => {
|
||||
const value = props.text ?? '';
|
||||
if (props.inversion != 'user') return mdi.render(value);
|
||||
return value;
|
||||
let value = props.text ?? '';
|
||||
if (props.inversion != 'user'){
|
||||
value = replaceImageWith(value);
|
||||
value = replaceDomainUrl(value);
|
||||
return mdi.render(value);
|
||||
}
|
||||
return value.replace("\n","<br>");
|
||||
});
|
||||
|
||||
// 是否显示引用知识库
|
||||
const showRefKnow = computed(() => {
|
||||
const {loading, referenceKnowledge} = props
|
||||
if (loading) {
|
||||
return false;
|
||||
}
|
||||
return Array.isArray(referenceKnowledge) && referenceKnowledge.length > 0;
|
||||
})
|
||||
|
||||
//替换图片宽度
|
||||
const replaceImageWith = markdownContent => {
|
||||
// 支持图片设置width的写法 
|
||||
const regex = /!\[([^\]]*)\]\(([^)]+)=([0-9]+)\)/g;
|
||||
return markdownContent.replace(regex, (match, alt, src, width) => {
|
||||
let reg = /#\s*{\s*domainURL\s*}/g;
|
||||
src = src.replace(reg,domainUrl);
|
||||
return `<div><img src='${src}' alt='${alt}' width='${width}' /></div>`;
|
||||
});
|
||||
};
|
||||
const { domainUrl } = useGlobSetting();
|
||||
//替换domainURL
|
||||
const replaceDomainUrl = markdownContent => {
|
||||
const regex = /!\[([^\]]*)\]\(.*?#\s*{\s*domainURL\s*}.*?\)/g;
|
||||
return markdownContent.replace(regex, (match) => {
|
||||
let reg = /#\s*{\s*domainURL\s*}/g;
|
||||
return match.replace(reg,domainUrl);
|
||||
})
|
||||
}
|
||||
|
||||
//是否放大图片
|
||||
const amplifyImage = ref<boolean>(false);
|
||||
//图片地址
|
||||
const imageUrl = ref<string>('');
|
||||
|
||||
function highlightBlock(str: string, lang?: string) {
|
||||
return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">复制代码</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`;
|
||||
}
|
||||
|
@ -72,16 +136,74 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加图片点击事件
|
||||
*/
|
||||
function addImageClickEvent() {
|
||||
if (textRef.value) {
|
||||
const image = textRef.value.querySelectorAll('img');
|
||||
image.forEach((img) => {
|
||||
img.addEventListener('click', () => {
|
||||
imageUrl.value = img.src;
|
||||
amplifyImage.value = true;
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 移出图片点击事件
|
||||
*/
|
||||
function removeImageClickEvent(){
|
||||
if (textRef.value) {
|
||||
const image = textRef.value.querySelectorAll('img');
|
||||
image.forEach((img) => {
|
||||
img.removeEventListener('click', () => { })
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片隐藏
|
||||
*/
|
||||
function pictureHide(){
|
||||
amplifyImage.value = false;
|
||||
imageUrl.value = ""
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置markdown body整体宽度
|
||||
*/
|
||||
function setMarkdownBodyWidth() {
|
||||
//平板
|
||||
console.log("window.innerWidth::",window.innerWidth)
|
||||
if(window.innerWidth>600 && window.innerWidth<1024){
|
||||
screenWidth.value = window.innerWidth - 120 + 'px';
|
||||
}else if(window.innerWidth < 600){
|
||||
//手机
|
||||
screenWidth.value = window.innerWidth - 60 + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
addCopyEvents();
|
||||
addImageClickEvent();
|
||||
setMarkdownBodyWidth();
|
||||
window.addEventListener('resize', setMarkdownBodyWidth);
|
||||
});
|
||||
|
||||
onUpdated(() => {
|
||||
addCopyEvents();
|
||||
addImageClickEvent();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
removeCopyEvents();
|
||||
removeImageClickEvent();
|
||||
window.removeEventListener('resize', setMarkdownBodyWidth);
|
||||
});
|
||||
|
||||
function copyToClip(text: string) {
|
||||
|
@ -111,6 +233,18 @@
|
|||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: linear-gradient(135deg, #FF4444, #FF914D) !important;
|
||||
border-radius: 0.375rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.self {
|
||||
// background-color: #d2f9d1;
|
||||
background-color: @primary-color;
|
||||
|
@ -125,4 +259,11 @@
|
|||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
@media (max-width: 1024px) {
|
||||
//手机和平板下的样式
|
||||
.textWrap{
|
||||
margin-left: -40px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
<!--image放大封装-->
|
||||
<template>
|
||||
<div class="amplify-image">
|
||||
<div class="img-preview-content" @click="hideImageClick" @mousewheel="handlePicMousewheel">
|
||||
<img :src="imageUrl" ref="imageRef" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
//图片地址
|
||||
import {onMounted, ref, unref} from 'vue';
|
||||
const props = defineProps(['imageUrl']);
|
||||
const emit = defineEmits(['register', 'hide']);
|
||||
//图片的ref
|
||||
const imageRef = ref();
|
||||
//缩放级别
|
||||
const scale = ref<number>(1);
|
||||
|
||||
/**
|
||||
* 隐藏图片
|
||||
*/
|
||||
function hideImageClick() {
|
||||
scale.value = 1;
|
||||
emit('hide')
|
||||
}
|
||||
|
||||
/**
|
||||
* 鼠标滑轮滚动
|
||||
* @param event
|
||||
*/
|
||||
function handlePicMousewheel(event) {
|
||||
event.preventDefault();
|
||||
// 判断是放大还是缩小
|
||||
const delta = event.deltaY > 0 ? -1 : 1;
|
||||
const scaleStep = 0.1;
|
||||
// 更新缩放级别
|
||||
scale.value = scale.value + delta * scaleStep
|
||||
imageRef.value.style.transform = `scale(${unref(scale)})`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.amplify-image{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
.img-preview-content{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
touch-action: none;
|
||||
-webkit-user-drag: none;
|
||||
img{
|
||||
transition: transform 0.3s;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
-webkit-background-size: cover;
|
||||
-moz-background-size: cover;
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -2,38 +2,40 @@
|
|||
(function () {
|
||||
let widgetInstance = null;
|
||||
const defaultConfig = {
|
||||
// 支持'top-left'左上, 'top-right'右上, 'bottom-left'左下, 'bottom-right'右下
|
||||
// 支持'top-left'左上, 'top-right'右上, 'bottom-left'左下, 'bottom-right'右下
|
||||
iconPosition: 'bottom-right',
|
||||
//图标的大小
|
||||
iconSize: '30px',
|
||||
//图标的颜色
|
||||
//图标的大小
|
||||
iconSize: '45px',
|
||||
//图标的颜色
|
||||
iconColor: '#155eef',
|
||||
//必填不允许修改
|
||||
//必填不允许修改
|
||||
appId: '',
|
||||
//聊天弹窗的宽度
|
||||
//聊天弹窗的宽度
|
||||
chatWidth: '800px',
|
||||
//聊天弹窗的高度
|
||||
//聊天弹窗的高度
|
||||
chatHeight: '700px',
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建ai图标
|
||||
* 创建ai图标
|
||||
* @param config
|
||||
*/
|
||||
function createAiChat(config) {
|
||||
// 单例模式,确保只存在一个实例
|
||||
// 单例模式,确保只存在一个实例
|
||||
if (widgetInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 合并配置
|
||||
// 合并配置
|
||||
const finalConfig = { ...defaultConfig, ...config };
|
||||
|
||||
if (!finalConfig.appId) {
|
||||
console.error('appId为空!');
|
||||
console.error('appId为空!');
|
||||
return;
|
||||
}
|
||||
// 创建容器
|
||||
let body = document.body;
|
||||
body.style.margin = "0";
|
||||
// 创建容器
|
||||
const container = document.createElement('div');
|
||||
container.style.cssText = `
|
||||
position: fixed;
|
||||
|
@ -41,39 +43,50 @@
|
|||
${getPositionStyles(finalConfig.iconPosition)}
|
||||
cursor: pointer;
|
||||
`;
|
||||
// 创建图标
|
||||
// 创建图标
|
||||
const icon = document.createElement('div');
|
||||
icon.style.cssText = `
|
||||
width: ${finalConfig.iconSize};
|
||||
height: ${finalConfig.iconSize};
|
||||
background-color: ${finalConfig.iconColor};
|
||||
border-radius: 50%;
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0 4px 8px 0;
|
||||
padding: 12px;
|
||||
box-shadow: #cccccc 0 4px 8px 0;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
icon.innerHTML =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 1024 1024" class="iconify iconify--ant-design"><path fill="currentColor" d="M573 421c-23.1 0-41 17.9-41 40s17.9 40 41 40c21.1 0 39-17.9 39-40s-17.9-40-39-40m-280 0c-23.1 0-41 17.9-41 40s17.9 40 41 40c21.1 0 39-17.9 39-40s-17.9-40-39-40"></path><path fill="currentColor" d="M894 345c-48.1-66-115.3-110.1-189-130v.1c-17.1-19-36.4-36.5-58-52.1c-163.7-119-393.5-82.7-513 81c-96.3 133-92.2 311.9 6 439l.8 132.6c0 3.2.5 6.4 1.5 9.4c5.3 16.9 23.3 26.2 40.1 20.9L309 806c33.5 11.9 68.1 18.7 102.5 20.6l-.5.4c89.1 64.9 205.9 84.4 313 49l127.1 41.4c3.2 1 6.5 1.6 9.9 1.6c17.7 0 32-14.3 32-32V753c88.1-119.6 90.4-284.9 1-408M323 735l-12-5l-99 31l-1-104l-8-9c-84.6-103.2-90.2-251.9-11-361c96.4-132.2 281.2-161.4 413-66c132.2 96.1 161.5 280.6 66 412c-80.1 109.9-223.5 150.5-348 102m505-17l-8 10l1 104l-98-33l-12 5c-56 20.8-115.7 22.5-171 7l-.2-.1C613.7 788.2 680.7 742.2 729 676c76.4-105.3 88.8-237.6 44.4-350.4l.6.4c23 16.5 44.1 37.1 62 62c72.6 99.6 68.5 235.2-8 330"></path><path fill="currentColor" d="M433 421c-23.1 0-41 17.9-41 40s17.9 40 41 40c21.1 0 39-17.9 39-40s-17.9-40-39-40"></path></svg>';
|
||||
|
||||
// 创建iframe容器
|
||||
// 创建iframe容器
|
||||
const iframeContainer = document.createElement('div');
|
||||
let right = finalConfig.chatWidth === '100%' ? '0' : '10px';
|
||||
let bottom = finalConfig.chatHeight === '100%' ? '0' : '10px';
|
||||
let chatWidth = finalConfig.chatWidth;
|
||||
let chatHeight = finalConfig.chatHeight;
|
||||
if(isMobileDevice()){
|
||||
chatWidth = "100%";
|
||||
chatHeight = "100%";
|
||||
right = '0';
|
||||
bottom = '0';
|
||||
}
|
||||
iframeContainer.style.cssText = `
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
width: ${finalConfig.chatWidth} !important;
|
||||
height: ${finalConfig.chatHeight} !important;
|
||||
position: fixed;
|
||||
right: ${right};
|
||||
bottom: ${bottom};
|
||||
width: ${chatWidth} !important;
|
||||
height: ${chatHeight} !important;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 20px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 0 20px #cccccc;
|
||||
display: none;
|
||||
z-index: 10000;
|
||||
`;
|
||||
|
||||
// 创建iframe
|
||||
// 创建iframe
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.style.cssText = `
|
||||
width: 100%;
|
||||
|
@ -83,15 +96,23 @@
|
|||
`;
|
||||
|
||||
iframe.id = 'ai-app-chat-document';
|
||||
iframe.src = getIframeSrc(finalConfig) + '/ai/app/chat/' + finalConfig.appId;
|
||||
// 创建关闭按钮
|
||||
//update-begin---author:wangshuai---date:2025-04-25---for:【QQYUN-12159】【AI 广告位】让需要自建AI知识库的用户知道如何通过敲敲云搭建自己的AI知识库---
|
||||
iframe.src = getIframeSrc(finalConfig) + '/ai/app/chat/' + finalConfig.appId + "?source=chatJs";
|
||||
//update-end---author:wangshuai---date:2025-04-25---for:【QQYUN-12159】【AI 广告位】让需要自建AI知识库的用户知道如何通过敲敲云搭建自己的AI知识库---
|
||||
let iconRight = finalConfig.chatWidth === '100%'?'0':'-6px';
|
||||
let iconTop = finalConfig.chatWidth === '100%'?'0':'-9px';
|
||||
if(isMobileDevice()){
|
||||
iconRight = '2px';
|
||||
iconTop = '2px';
|
||||
}
|
||||
// 创建关闭按钮
|
||||
const closeBtn = document.createElement('div');
|
||||
closeBtn.innerHTML =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" width="1em" height="1em" viewBox="0 0 1024 1024" class="iconify iconify--ant-design"><path fill="currentColor" fill-rule="evenodd" d="M799.855 166.312c.023.007.043.018.084.059l57.69 57.69c.041.041.052.06.059.084a.1.1 0 0 1 0 .069c-.007.023-.018.042-.059.083L569.926 512l287.703 287.703c.041.04.052.06.059.083a.12.12 0 0 1 0 .07c-.007.022-.018.042-.059.083l-57.69 57.69c-.041.041-.06.052-.084.059a.1.1 0 0 1-.069 0c-.023-.007-.042-.018-.083-.059L512 569.926L224.297 857.629c-.04.041-.06.052-.083.059a.12.12 0 0 1-.07 0c-.022-.007-.042-.018-.083-.059l-57.69-57.69c-.041-.041-.052-.06-.059-.084a.1.1 0 0 1 0-.069c.007-.023.018-.042.059-.083L454.073 512L166.371 224.297c-.041-.04-.052-.06-.059-.083a.12.12 0 0 1 0-.07c.007-.022.018-.042.059-.083l57.69-57.69c.041-.041.06-.052.084-.059a.1.1 0 0 1 .069 0c.023.007.042.018.083.059L512 454.073l287.703-287.702c.04-.041.06-.052.083-.059a.12.12 0 0 1 .07 0Z"></path></svg>';
|
||||
closeBtn.style.cssText = `
|
||||
position: absolute;
|
||||
right: -3px;
|
||||
top: -10px;
|
||||
margin-top: ${iconTop};
|
||||
right: ${iconRight};
|
||||
cursor: pointer;
|
||||
background: white;
|
||||
width: 25px;
|
||||
|
@ -100,17 +121,17 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 2px 5px #cccccc;
|
||||
`;
|
||||
|
||||
// 组装元素
|
||||
// 组装元素
|
||||
iframeContainer.appendChild(closeBtn);
|
||||
iframeContainer.appendChild(iframe);
|
||||
document.body.appendChild(iframeContainer);
|
||||
container.appendChild(icon);
|
||||
document.body.appendChild(container);
|
||||
|
||||
// 事件监听
|
||||
// 事件监听
|
||||
icon.addEventListener('click', () => {
|
||||
iframeContainer.style.display = 'block';
|
||||
});
|
||||
|
@ -119,7 +140,7 @@
|
|||
iframeContainer.style.display = 'none';
|
||||
});
|
||||
|
||||
// 保存实例引用
|
||||
// 保存实例引用
|
||||
widgetInstance = {
|
||||
remove: () => {
|
||||
container.remove();
|
||||
|
@ -129,7 +150,7 @@
|
|||
}
|
||||
|
||||
/**
|
||||
* 获取位置信息
|
||||
* 获取位置信息
|
||||
*
|
||||
* @param position
|
||||
* @returns {*|string}
|
||||
|
@ -145,7 +166,7 @@
|
|||
}
|
||||
|
||||
/**
|
||||
* 获取src地址
|
||||
* 获取src地址
|
||||
*/
|
||||
function getIframeSrc(finalConfig) {
|
||||
const specificScript = document.getElementById("e7e007dd52f67fe36365eff636bbffbd");
|
||||
|
@ -154,6 +175,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
// 暴露全局方法
|
||||
/**
|
||||
* 判断是否为手机
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isMobileDevice() {
|
||||
return /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
// 暴露全局方法
|
||||
window.createAiChat = createAiChat;
|
||||
})();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="presetQuestion-wrap">
|
||||
<svg
|
||||
<!-- <svg
|
||||
v-if="btnShow"
|
||||
class="leftBtn"
|
||||
:class="leftBtnStatus"
|
||||
|
@ -16,7 +16,7 @@
|
|||
fill="currentColor"
|
||||
p-id="5071"
|
||||
></path>
|
||||
</svg>
|
||||
</svg>-->
|
||||
<div class="content">
|
||||
<ul ref="ulElemRef">
|
||||
<li v-for="(item, index) in data" :key="index" class="item" @click="handleQuestion(item.descr)">
|
||||
|
@ -28,7 +28,7 @@
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<svg
|
||||
<!-- <svg
|
||||
v-if="btnShow"
|
||||
class="rightBtn"
|
||||
:class="rightBtnStatus"
|
||||
|
@ -44,7 +44,7 @@
|
|||
fill="currentColor"
|
||||
p-id="5071"
|
||||
></path>
|
||||
</svg>
|
||||
</svg>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -135,7 +135,8 @@ import {ref, onMounted, onBeforeUnmount, watch} from 'vue';
|
|||
display: flex;
|
||||
margin-bottom: 0;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
/* 隐藏所有滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
|
@ -143,8 +144,15 @@ import {ref, onMounted, onBeforeUnmount, watch} from 'vue';
|
|||
width: 0;
|
||||
}
|
||||
}
|
||||
ul:hover {
|
||||
&::-webkit-scrollbar {
|
||||
display: block;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
}
|
||||
}
|
||||
.item {
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #e6e6e6;
|
||||
border-radius: 16px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
|
|
|
@ -71,6 +71,13 @@
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="left-footer" v-if="source!='chatJs'">
|
||||
AI客服由
|
||||
<a style="color: #4183c4;margin-left: 2px;margin-right: 2px" href="https://www.qiaoqiaoyun.com/aiCustomerService" target="_blank">
|
||||
敲敲云
|
||||
</a>
|
||||
提供
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -80,7 +87,7 @@
|
|||
import { defHttp } from '@/utils/http/axios';
|
||||
import { getFileAccessHttpUrl } from '@/utils/common/compUtils';
|
||||
import defaultImg from '../img/ailogo.png';
|
||||
const props = defineProps(['dataSource', 'appData']);
|
||||
const props = defineProps(['dataSource', 'appData','source']);
|
||||
const emit = defineEmits(['save', 'click', 'reloadRight', 'prologue']);
|
||||
const inputRef = ref(null);
|
||||
const router = useRouter();
|
||||
|
@ -149,8 +156,7 @@
|
|||
emit('click', props.dataSource.history[0].title, findIndex);
|
||||
} else {
|
||||
// 删没了(删除了最后一个)
|
||||
props.dataSource.active = null;
|
||||
emit('click', "", -1);
|
||||
handleCreate();
|
||||
}
|
||||
}
|
||||
//update-begin---author:wangshuai---date:2025-03-12---for:【QQYUN-11560】新建聊天内容为空,无法删除---
|
||||
|
@ -216,6 +222,7 @@
|
|||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
margin-bottom: 20px;
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
|
@ -285,6 +292,9 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
&.ant-input {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
@ -316,4 +326,13 @@
|
|||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
.left-footer{
|
||||
display:flex;
|
||||
margin-right: 20px;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: 50px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -38,10 +38,10 @@ html.dark {
|
|||
--color-canvas-subtle: #161b22;
|
||||
--color-border-default: #30363d;
|
||||
--color-border-muted: #21262d;
|
||||
--color-neutral-muted: rgba(110, 118, 129, 0.4);
|
||||
--color-neutral-muted: #bfc4cc;
|
||||
--color-accent-fg: #58a6ff;
|
||||
--color-accent-emphasis: #1f6feb;
|
||||
--color-attention-subtle: rgba(187, 128, 9, 0.15);
|
||||
--color-attention-subtle: #ece6d9;
|
||||
--color-danger-fg: #f85149;
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ html {
|
|||
--color-canvas-subtle: #f6f8fa;
|
||||
--color-border-default: #d0d7de;
|
||||
--color-border-muted: hsla(210, 18%, 87%, 1);
|
||||
--color-neutral-muted: rgba(175, 184, 193, 0.2);
|
||||
--color-neutral-muted: #e7ebf2;
|
||||
--color-accent-fg: #0969da;
|
||||
--color-accent-emphasis: #0969da;
|
||||
--color-attention-subtle: #fff8c5;
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<div class="flex text-status" v-if="item.metadata && item.metadata.length>0">
|
||||
<span class="tag-input">输入</span>
|
||||
<div v-for="(metaItem, index) in item.metadata">
|
||||
<a-tag color="rgba(87,104,161,0.08)" class="tags-meadata">
|
||||
<a-tag color="#f2f3f8" class="tags-meadata">
|
||||
<span v-if="index<3" class="tag-text">{{ metaItem.field }}</span>
|
||||
</a-tag>
|
||||
</div>
|
||||
|
@ -126,7 +126,7 @@
|
|||
};
|
||||
|
||||
/**
|
||||
* 加载知识库
|
||||
* 加载AI流程
|
||||
*/
|
||||
function loadFlowData() {
|
||||
let params = {
|
||||
|
@ -135,7 +135,7 @@
|
|||
column: 'createTime',
|
||||
order: 'desc',
|
||||
name: searchText.value,
|
||||
status:'enable'
|
||||
status: 'enable,release'
|
||||
};
|
||||
getAiFlowList(params).then((res) =>{
|
||||
if(res){
|
||||
|
@ -313,11 +313,11 @@
|
|||
white-space: nowrap;
|
||||
height: 20px;
|
||||
font-size: 12px;
|
||||
color: rgba(15, 21, 40,0.82);
|
||||
color: #3a3f4f;
|
||||
}
|
||||
.tag-input{
|
||||
align-self: center;
|
||||
color: rgba(55,67,106,0.7);
|
||||
color: #707a97;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
<div class="p-2">
|
||||
<BasicModal destroyOnClose @register="registerModal" :canFullscreen="false" width="600px" :title="title" @ok="handleOk" @cancel="handleCancel">
|
||||
<div class="flex header">
|
||||
<span>所选知识库必须使用相同的 Embedding 模型</span>
|
||||
<a-input
|
||||
@pressEnter="loadKnowledgeData"
|
||||
class="header-search"
|
||||
|
@ -19,7 +18,7 @@
|
|||
<img class="checkbox-img" :src="knowledge" />
|
||||
<span class="checkbox-name">{{ item.name }}</span>
|
||||
</div>
|
||||
<a-checkbox v-model:checked="item.checked" @click.stop class="quantum-checker"> </a-checkbox>
|
||||
<a-checkbox v-model:checked="item.checked" @click.stop class="quantum-checker" @change="(e)=>handleChange(e,item)"> </a-checkbox>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
@ -91,6 +90,7 @@
|
|||
* 保存
|
||||
*/
|
||||
async function handleOk() {
|
||||
console.log("知识库确定选中的值",knowledgeData.value);
|
||||
emit('success', knowledgeIds.value, knowledgeData.value);
|
||||
handleCancel();
|
||||
}
|
||||
|
@ -103,16 +103,17 @@
|
|||
}
|
||||
|
||||
//复选框选中事件
|
||||
const handleSelect = (item) => {
|
||||
function handleSelect(item){
|
||||
let id = item.id;
|
||||
const target = appKnowledgeOption.value.find((item) => item.id === id);
|
||||
if (target) {
|
||||
target.checked = !target.checked;
|
||||
}
|
||||
//存放选中的知识库的id
|
||||
if (knowledgeIds.value.length == 0) {
|
||||
if (!knowledgeIds.value || knowledgeIds.value.length == 0) {
|
||||
knowledgeIds.value.push(id);
|
||||
knowledgeData.value.push(item);
|
||||
console.log("知识库勾选或取消勾选复选框的值",knowledgeData.value);
|
||||
return;
|
||||
}
|
||||
let findIndex = knowledgeIds.value.findIndex((item) => item === id);
|
||||
|
@ -123,7 +124,8 @@
|
|||
knowledgeIds.value.splice(findIndex, 1);
|
||||
knowledgeData.value.splice(findIndex, 1);
|
||||
}
|
||||
};
|
||||
console.log("知识库勾选或取消勾选复选框的值",knowledgeData.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载知识库
|
||||
|
@ -176,6 +178,25 @@
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 复选框选中事件
|
||||
*
|
||||
* @param e
|
||||
* @param item
|
||||
*/
|
||||
function handleChange(e, item) {
|
||||
if (e.target.checked) {
|
||||
knowledgeIds.value.push(item.id);
|
||||
knowledgeData.value.push(item);
|
||||
} else {
|
||||
let findIndex = knowledgeIds.value.findIndex((val) => val === item.id);
|
||||
if (findIndex != -1) {
|
||||
knowledgeIds.value.splice(findIndex, 1);
|
||||
knowledgeData.value.splice(findIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
registerModal,
|
||||
title,
|
||||
|
@ -193,6 +214,7 @@
|
|||
searchText,
|
||||
loadKnowledgeData,
|
||||
handleClearClick,
|
||||
handleChange,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
@ -222,7 +244,7 @@
|
|||
.list-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 260px;
|
||||
right: 10px;
|
||||
}
|
||||
.checkbox-card {
|
||||
margin-bottom: 10px;
|
||||
|
|
|
@ -109,24 +109,80 @@
|
|||
handleCancel();
|
||||
}
|
||||
|
||||
//update-begin---author:wangshuai---date:2025-04-01---for:【QQYUN-11796】【AI】提示词生成器,改成异步---
|
||||
/**
|
||||
* 生成
|
||||
*/
|
||||
function generatedPrompt() {
|
||||
async function generatedPrompt() {
|
||||
content.value = '';
|
||||
loading.value = true;
|
||||
promptGenerate({ prompt: prompt.value })
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
content.value = res.result;
|
||||
let readableStream = await promptGenerate({ prompt: prompt.value }).catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
const reader = readableStream.getReader();
|
||||
const decoder = new TextDecoder('UTF-8');
|
||||
let buffer = '';
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
let result = decoder.decode(value, { stream: true });
|
||||
const lines = result.split('\n\n');
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data:')) {
|
||||
const content = line.replace('data:', '').trim();
|
||||
if(!content){
|
||||
continue;
|
||||
}
|
||||
if(!content.endsWith('}')){
|
||||
buffer = buffer + line;
|
||||
continue;
|
||||
}
|
||||
buffer = "";
|
||||
renderText(content)
|
||||
} else {
|
||||
if(!line) {
|
||||
continue;
|
||||
}
|
||||
if(!line.endsWith('}')) {
|
||||
buffer = buffer + line;
|
||||
continue;
|
||||
}
|
||||
buffer = "";
|
||||
renderText(line)
|
||||
}
|
||||
}
|
||||
loading.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染文本
|
||||
*
|
||||
* @param item
|
||||
*/
|
||||
function renderText(item) {
|
||||
try {
|
||||
let parse = JSON.parse(item);
|
||||
if (parse.event == 'MESSAGE') {
|
||||
content.value += parse.data.message;
|
||||
if(loading.value){
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
if (parse.event == 'MESSAGE_END') {
|
||||
loading.value = false;
|
||||
}
|
||||
if (parse.event == 'ERROR') {
|
||||
content.value = parse.data.message?parse.data.message:'生成失败,请稍后重试!'
|
||||
loading.value = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error parsing update:', error);
|
||||
}
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-04-01---for:【QQYUN-11796】【AI】提示词生成器,改成异步---
|
||||
|
||||
/**
|
||||
* 指令点击事件
|
||||
*/
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
<template>
|
||||
<div class="p-2">
|
||||
<BasicModal destroyOnClose @register="registerModal" :canFullscreen="false" width="800px" :title="title" @ok="handleOk" @cancel="handleCancel">
|
||||
<template #title>
|
||||
<span style="display: flex">
|
||||
{{title}}
|
||||
<a-tooltip title="AI应用文档">
|
||||
<a style="color: unset" href="https://help.jeecg.com/aigc/guide/app" target="_blank">
|
||||
<Icon style="position:relative;left:2px;top:1px" icon="ant-design:question-circle-outlined"></Icon>
|
||||
</a>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<BasicForm @register="registerForm">
|
||||
<template #typeSlot="{ model, field }">
|
||||
<a-radio-group v-model:value="type" style="display: flex">
|
||||
<a-radio-group v-model:value="model[field]" style="display: flex">
|
||||
<a-card
|
||||
v-for="item in appTypeOption"
|
||||
style="margin-right: 10px; cursor: pointer; width: 100%"
|
||||
@click="handleTypeClick(item.value)"
|
||||
:style="type === item.value ? { borderColor: '#3370ff' } : {}"
|
||||
@click="model[field] = item.value"
|
||||
:style="model[field] === item.value ? { borderColor: '#3370ff' } : {}"
|
||||
>
|
||||
<a-radio :value="item.value">
|
||||
<div class="type-title">{{ item.title }}</div>
|
||||
|
@ -23,7 +33,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, unref } from 'vue';
|
||||
import { ref, unref, computed } from 'vue';
|
||||
import BasicModal from '@/components/Modal/src/BasicModal.vue';
|
||||
import { useModal, useModalInner } from '@/components/Modal';
|
||||
|
||||
|
@ -42,15 +52,13 @@
|
|||
},
|
||||
emits: ['success', 'register'],
|
||||
setup(props, { emit }) {
|
||||
const title = ref<string>('创建应用');
|
||||
|
||||
//保存或修改
|
||||
const isUpdate = ref<boolean>(false);
|
||||
|
||||
const title = computed<string>(() => isUpdate.value ? '修改应用' : '创建应用');
|
||||
|
||||
//app类型
|
||||
const appTypeOption = ref<any>([]);
|
||||
//应用类型
|
||||
const type = ref<string>('chatSimple');
|
||||
|
||||
//表单配置
|
||||
const [registerForm, { validate, resetFields, setFieldsValue }] = useForm({
|
||||
|
@ -63,7 +71,6 @@
|
|||
//注册modal
|
||||
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
|
||||
await resetFields();
|
||||
type.value = 'chatSimple';
|
||||
//update-begin---author:wangshuai---date:2025-03-11---for: 【QQYUN-11324】8.修改弹窗head---
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
|
@ -71,6 +78,10 @@
|
|||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
} else {
|
||||
await setFieldsValue({
|
||||
type: 'chatSimple',
|
||||
})
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-03-11---for:【QQYUN-11324】8.修改弹窗head---
|
||||
setModalProps({ minHeight: 500, bodyStyle: { padding: '10px' } });
|
||||
|
@ -83,7 +94,6 @@
|
|||
try {
|
||||
let values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
values.type = type.value;
|
||||
let result = await saveApp(values);
|
||||
if (result) {
|
||||
//关闭弹窗
|
||||
|
@ -128,13 +138,6 @@
|
|||
closeModal();
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用类型点击事件
|
||||
*/
|
||||
function handleTypeClick(val) {
|
||||
type.value = val;
|
||||
}
|
||||
|
||||
return {
|
||||
registerModal,
|
||||
registerForm,
|
||||
|
@ -142,8 +145,6 @@
|
|||
handleOk,
|
||||
handleCancel,
|
||||
appTypeOption,
|
||||
type,
|
||||
handleTypeClick,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -6,13 +6,20 @@
|
|||
<div style="display: flex">
|
||||
<img :src="getImage()" class="header-img"/>
|
||||
<div class="header-name">{{formState.name}}</div>
|
||||
<a-tooltip title="编辑">
|
||||
<a-tooltip v-if="!isRelease" title="编辑">
|
||||
<Icon icon="ant-design:edit-outlined" style="margin-left: 4px;cursor: pointer" color="#354052" size="20" @click="handleEdit"></Icon>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div>应用编排</div>
|
||||
<div>
|
||||
应用编排
|
||||
<a-tooltip title="AI应用文档">
|
||||
<a style="color: unset" href="https://help.jeecg.com/aigc/guide/app" target="_blank">
|
||||
<Icon style="position:relative;left:2px;top:1px" icon="ant-design:question-circle-outlined"></Icon>
|
||||
</a>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div style="display: flex">
|
||||
<a-button @click="handleOk" style="margin-right: 30px" type="primary">保存</a-button>
|
||||
<a-button v-if="!isRelease" @click="handleOk" style="margin-right: 30px" type="primary">保存</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -35,7 +42,7 @@
|
|||
<template #label>
|
||||
<div style="display: flex;justify-content: space-between;width: 100%;">
|
||||
<span>关联流程</span>
|
||||
<span @click="handleAddFlowClick" class="knowledge-txt">
|
||||
<span v-if="!isRelease" @click="handleAddFlowClick" class="knowledge-txt">
|
||||
<Icon icon="ant-design:plus-outlined" size="13" style="margin-right: 2px"></Icon>添加
|
||||
</span>
|
||||
</div>
|
||||
|
@ -49,14 +56,14 @@
|
|||
<div class="flex text-status" v-if="flowData.metadata && flowData.metadata.length>0">
|
||||
<span class="tag-input">输入</span>
|
||||
<div v-for="(metaItem, index) in flowData.metadata">
|
||||
<a-tag color="rgba(87,104,161,0.08)" class="tags-meadata">
|
||||
<a-tag color="#f2f3f8" class="tags-meadata">
|
||||
<span v-if="index<5" class="tag-text">{{ metaItem.field }}</span>
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Icon @click="handleDeleteFlow" icon="ant-design:close-outlined" size="20" class="knowledge-icon"></Icon>
|
||||
<Icon v-if="!isRelease" @click="handleDeleteFlow" icon="ant-design:close-outlined" size="20" class="knowledge-icon"></Icon>
|
||||
</div>
|
||||
</a-card>
|
||||
<div v-else class="data-empty-text">
|
||||
|
@ -71,7 +78,7 @@
|
|||
<template #label>
|
||||
<div class="prompt-title-padding item-title space-between">
|
||||
<span>提示词</span>
|
||||
<a-button size="middle" @click="generatedPrompt" ghost>
|
||||
<a-button v-if="!isRelease" size="middle" @click="generatedPrompt" ghost>
|
||||
<span style="align-items: center;display:flex">
|
||||
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M18.9839 1.85931C19.1612 1.38023 19.8388 1.38023 20.0161 1.85931L20.5021 3.17278C20.5578 3.3234 20.6766 3.44216 20.8272 3.49789L22.1407 3.98392C22.6198 4.1612 22.6198 4.8388 22.1407 5.01608L20.8272 5.50211C20.6766 5.55784 20.5578 5.6766 20.5021 5.82722L20.0161 7.14069C19.8388 7.61977 19.1612 7.61977 18.9839 7.14069L18.4979 5.82722C18.4422 5.6766 18.3234 5.55784 18.1728 5.50211L16.8593 5.01608C16.3802 4.8388 16.3802 4.1612 16.8593 3.98392L18.1728 3.49789C18.3234 3.44216 18.4422 3.3234 18.4979 3.17278L18.9839 1.85931zM13.5482 4.07793C13.0164 2.64069 10.9836 2.64069 10.4518 4.07793L8.99368 8.01834C8.82648 8.47021 8.47021 8.82648 8.01834 8.99368L4.07793 10.4518C2.64069 10.9836 2.64069 13.0164 4.07793 13.5482L8.01834 15.0063C8.47021 15.1735 8.82648 15.5298 8.99368 15.9817L10.4518 19.9221C10.9836 21.3593 13.0164 21.3593 13.5482 19.9221L15.0063 15.9817C15.1735 15.5298 15.5298 15.1735 15.9817 15.0063L19.9221 13.5482C21.3593 13.0164 21.3593 10.9836 19.9221 10.4518L15.9817 8.99368C15.5298 8.82648 15.1735 8.47021 15.0063 8.01834L13.5482 4.07793zM5.01608 16.8593C4.8388 16.3802 4.1612 16.3802 3.98392 16.8593L3.49789 18.1728C3.44216 18.3234 3.3234 18.4422 3.17278 18.4979L1.85931 18.9839C1.38023 19.1612 1.38023 19.8388 1.85931 20.0161L3.17278 20.5021C3.3234 20.5578 3.44216 20.6766 3.49789 20.8272L3.98392 22.1407C4.1612 22.6198 4.8388 22.6198 5.01608 22.1407L5.50211 20.8272C5.55784 20.6766 5.6766 20.5578 5.82722 20.5021L7.14069 20.0161C7.61977 19.8388 7.61977 19.1612 7.14069 18.9839L5.82722 18.4979C5.6766 18.4422 5.55784 18.3234 5.50211 18.1728L5.01608 16.8593z"></path></svg>
|
||||
<span style="margin-left: 4px">生成</span>
|
||||
|
@ -79,7 +86,7 @@
|
|||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<a-textarea :rows="8" v-model:value="formState.prompt" placeholder="请输入提示词"/>
|
||||
<a-textarea :disabled="isRelease" :rows="8" v-model:value="formState.prompt" placeholder="请输入提示词"/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</a-col>
|
||||
|
@ -90,7 +97,7 @@
|
|||
<div class="prompt-title-padding item-title">开场白</div>
|
||||
</template>
|
||||
<div class="prologue-chunk-edit">
|
||||
<j-markdown-editor :height="166" v-model:value="formState.prologue" @change="prologueTextAreaBlur" :preview="{ mode: 'view', action: [] }"></j-markdown-editor>
|
||||
<j-markdown-editor :height="166" v-model:value="formState.prologue" :disabled="isRelease" @change="prologueTextAreaBlur" :preview="{ mode: 'view', action: [] }"></j-markdown-editor>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
@ -101,7 +108,7 @@
|
|||
<template #label>
|
||||
<div class="prompt-title-padding item-title space-between">
|
||||
<div class="item-title">预设问题</div>
|
||||
<a-tooltip title="添加预设问题">
|
||||
<a-tooltip v-if="!isRelease" title="添加预设问题">
|
||||
<Icon icon="ant-design:plus-outlined" size="13" style="margin-right: 16px;cursor: pointer" @click="presetQuestionAddClick"></Icon>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
|
@ -110,9 +117,9 @@
|
|||
<draggable :disabled="disabledDrag" item-key="key" v-model="presetQuestionList" @end="presetQuestionEnd">
|
||||
<template #item="{ element:item }">
|
||||
<div style="display: flex;width: 100%;margin-top: 10px">
|
||||
<Icon icon="ant-design:holder-outlined" size="20"></Icon>
|
||||
<a-input placeholder="输入预设问题" v-model:value="item.descr" style="margin-left: 10px;" @blur="onBlur(item)" @focus="onFocus(item)" @change="questionChange"></a-input>
|
||||
<Icon style="cursor: pointer;margin-left: 10px" icon="ant-design:delete-outlined" @click="deleteQuestionClick(item.key)"></Icon>
|
||||
<Icon v-if="!isRelease" icon="ant-design:holder-outlined" size="20"></Icon>
|
||||
<a-input :disabled="isRelease" placeholder="输入预设问题" v-model:value="item.descr" style="margin-left: 10px;" @blur="onBlur(item)" @focus="onFocus(item)" @change="questionChange"></a-input>
|
||||
<Icon v-if="!isRelease" style="cursor: pointer;margin-left: 10px" icon="ant-design:delete-outlined" @click="deleteQuestionClick(item.key)"></Icon>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
@ -129,7 +136,7 @@
|
|||
<template #label>
|
||||
<div class="prompt-title-padding item-title space-between">
|
||||
<div class="item-title">快捷指令</div>
|
||||
<a-tooltip title="添加快捷指令">
|
||||
<a-tooltip v-if="!isRelease" title="添加快捷指令">
|
||||
<Icon icon="ant-design:plus-outlined" size="13" style="margin-right: 16px;cursor: pointer" @click="quickCommandAddClick"></Icon>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
|
@ -143,7 +150,7 @@
|
|||
<svg v-else width="14px" height="14px" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M18.9839 1.85931C19.1612 1.38023 19.8388 1.38023 20.0161 1.85931L20.5021 3.17278C20.5578 3.3234 20.6766 3.44216 20.8272 3.49789L22.1407 3.98392C22.6198 4.1612 22.6198 4.8388 22.1407 5.01608L20.8272 5.50211C20.6766 5.55784 20.5578 5.6766 20.5021 5.82722L20.0161 7.14069C19.8388 7.61977 19.1612 7.61977 18.9839 7.14069L18.4979 5.82722C18.4422 5.6766 18.3234 5.55784 18.1728 5.50211L16.8593 5.01608C16.3802 4.8388 16.3802 4.1612 16.8593 3.98392L18.1728 3.49789C18.3234 3.44216 18.4422 3.3234 18.4979 3.17278L18.9839 1.85931zM13.5482 4.07793C13.0164 2.64069 10.9836 2.64069 10.4518 4.07793L8.99368 8.01834C8.82648 8.47021 8.47021 8.82648 8.01834 8.99368L4.07793 10.4518C2.64069 10.9836 2.64069 13.0164 4.07793 13.5482L8.01834 15.0063C8.47021 15.1735 8.82648 15.5298 8.99368 15.9817L10.4518 19.9221C10.9836 21.3593 13.0164 21.3593 13.5482 19.9221L15.0063 15.9817C15.1735 15.5298 15.5298 15.1735 15.9817 15.0063L19.9221 13.5482C21.3593 13.0164 21.3593 10.9836 19.9221 10.4518L15.9817 8.99368C15.5298 8.82648 15.1735 8.47021 15.0063 8.01834L13.5482 4.07793zM5.01608 16.8593C4.8388 16.3802 4.1612 16.3802 3.98392 16.8593L3.49789 18.1728C3.44216 18.3234 3.3234 18.4422 3.17278 18.4979L1.85931 18.9839C1.38023 19.1612 1.38023 19.8388 1.85931 20.0161L3.17278 20.5021C3.3234 20.5578 3.44216 20.6766 3.49789 20.8272L3.98392 22.1407C4.1612 22.6198 4.8388 22.6198 5.01608 22.1407L5.50211 20.8272C5.55784 20.6766 5.6766 20.5578 5.82722 20.5021L7.14069 20.0161C7.61977 19.8388 7.61977 19.1612 7.14069 18.9839L5.82722 18.4979C5.6766 18.4422 5.55784 18.3234 5.50211 18.1728L5.01608 16.8593z"></path></svg>
|
||||
<div style="max-width: 400px;margin-left: 4px" class="ellipsis">{{item.name}}</div>
|
||||
</div>
|
||||
<div style="align-items: center" class="quick-command-icon">
|
||||
<div v-if="!isRelease" style="align-items: center" class="quick-command-icon">
|
||||
<a-tooltip title="编辑">
|
||||
<Icon style="cursor: pointer;margin-left: 10px" icon="ant-design:edit-outlined" @click="editCommandClick(item)"></Icon>
|
||||
</a-tooltip>
|
||||
|
@ -167,13 +174,14 @@
|
|||
<template #label>
|
||||
<div style="display: flex;justify-content: space-between;width: 100%;margin-right: 2px">
|
||||
<div class="item-title">AI模型</div>
|
||||
<div @click="handleParamSettingClick('model')" class="knowledge-txt">
|
||||
<div v-if="!isRelease" @click="handleParamSettingClick('model')" class="knowledge-txt">
|
||||
<Icon icon="ant-design:setting-outlined" size="13" style="margin-right: 2px"></Icon>参数配置
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<JDictSelectTag
|
||||
v-model:value="formState.modelId"
|
||||
:disabled="isRelease"
|
||||
placeholder="请选择AI模型"
|
||||
dict-code="airag_model where model_type = 'LLM',name,id"
|
||||
style="width: 100%;"
|
||||
|
@ -193,7 +201,7 @@
|
|||
<template #label>
|
||||
<div style="display: flex; justify-content: space-between; width: 100%;margin-left: 2px;">
|
||||
<div class="item-title">知识库</div>
|
||||
<div>
|
||||
<div v-if="!isRelease">
|
||||
<span @click="handleParamSettingClick('knowledge')" class="knowledge-txt">
|
||||
<Icon icon="ant-design:setting-outlined" size="13" style="margin-right: 2px"></Icon>参数配置
|
||||
</span>
|
||||
|
@ -209,9 +217,10 @@
|
|||
<div style="display: flex; width: 100%; justify-content: space-between">
|
||||
<div>
|
||||
<img class="knowledge-img" :src="knowledge" />
|
||||
<span class="knowledge-name">{{ item.name }}</span>
|
||||
<span class="knowledge-name" style="color: #e03e2d;text-decoration: line-through" v-if="item.type">{{ item.name }}</span>
|
||||
<span class="knowledge-name" v-else>{{ item.name }}</span>
|
||||
</div>
|
||||
<Icon @click="handleDeleteKnowledge(item.id)" icon="ant-design:close-outlined" size="20" class="knowledge-icon"></Icon>
|
||||
<Icon v-if="!isRelease" @click="handleDeleteKnowledge(item.id)" icon="ant-design:close-outlined" size="20" class="knowledge-icon"></Icon>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
@ -228,10 +237,23 @@
|
|||
<template #label>
|
||||
<div style="margin-left: 2px">历史聊天记录</div>
|
||||
</template>
|
||||
<a-input-number v-model:value="formState.msgNum"></a-input-number>
|
||||
<a-input-number :disabled="isRelease" v-model:value="formState.msgNum"></a-input-number>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="24" class="mt-10">
|
||||
<div class="prologue-chunk">
|
||||
<div style="margin-left: 2px">个性化设置</div>
|
||||
<a-row>
|
||||
<a-form-item :labelCol="labelCol" :wrapperCol="wrapperCol" v-bind="validateInfos.multiSession">
|
||||
<div style="display: flex;margin-top: 10px">
|
||||
<div style="margin-left: 2px">多会话模式:</div>
|
||||
<a-switch :disabled="isRelease" v-model:checked="multiSessionChecked" checked-children="开" un-checked-children="关" @change="handleMultiSessionChange"></a-switch>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-col>
|
||||
|
@ -284,7 +306,6 @@
|
|||
import draggable from 'vuedraggable';
|
||||
import { useMessage } from "@/hooks/web/useMessage";
|
||||
import defaultFlowImg from "@/assets/images/ai/aiflow.png";
|
||||
|
||||
export default {
|
||||
name: 'AiAppSettingModal',
|
||||
components: {
|
||||
|
@ -358,10 +379,15 @@
|
|||
//快捷指令
|
||||
const quickCommand = ref<any>('');
|
||||
const { createMessage } = useMessage();
|
||||
//多会话模式选中状态
|
||||
const multiSessionChecked = ref<boolean>(true);
|
||||
// 是否已发布
|
||||
const isRelease = ref<boolean>(false);
|
||||
//注册modal
|
||||
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
|
||||
appId.value = data.id;
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
isRelease.value = data?.status === 'release';
|
||||
clearParam();
|
||||
if (isUpdate.value) {
|
||||
setTimeout(() => {
|
||||
|
@ -371,14 +397,16 @@
|
|||
//新增成功之后需要有id
|
||||
queryById({ id: data.id }).then((res) => {
|
||||
if (res.success) {
|
||||
resetFields();
|
||||
//赋值
|
||||
Object.assign(formState, res.result);
|
||||
formState.prompt = AiAppJson.prompt;
|
||||
formState.prologue = AiAppJson.prologue;
|
||||
formState.presetQuestion = JSON.stringify(AiAppJson.presetQuestion);
|
||||
prologue.value = AiAppJson.prologue;
|
||||
formState.prompt = cloneDeep(AiAppJson.prompt);
|
||||
formState.prologue = cloneDeep(AiAppJson.prologue);
|
||||
formState.presetQuestion = JSON.stringify(cloneDeep(AiAppJson.presetQuestion));
|
||||
formState.msgNum = 1;
|
||||
prologue.value = cloneDeep(AiAppJson.prologue);
|
||||
presetQuestion.value = formState.presetQuestion;
|
||||
presetQuestionList.value = AiAppJson.presetQuestion;
|
||||
presetQuestionList.value = cloneDeep(AiAppJson.presetQuestion);
|
||||
addRules(res.result.type)
|
||||
}
|
||||
});
|
||||
|
@ -403,6 +431,7 @@
|
|||
setModalProps({ confirmLoading: true });
|
||||
formState.knowledgeIds = knowledgeIds.value;
|
||||
await saveApp(formState);
|
||||
emit('success')
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
|
@ -457,7 +486,9 @@
|
|||
*/
|
||||
function handleSuccess(knowledgeId, knowledgeData) {
|
||||
knowledgeIds.value = cloneDeep(knowledgeId.join(','));
|
||||
console.log("知识库id",knowledgeIds.value);
|
||||
knowledgeDataList.value = cloneDeep(knowledgeData);
|
||||
console.log("知识库的数据",knowledgeDataList.value);
|
||||
formState.knowledgeIds = knowledgeIds.value;
|
||||
}
|
||||
|
||||
|
@ -481,10 +512,30 @@
|
|||
*/
|
||||
function getKnowledgeDataList(ids) {
|
||||
queryKnowledgeBathById({ ids: ids }).then((res) => {
|
||||
if (res.success) {
|
||||
knowledgeDataList.value = res.result;
|
||||
//update-begin---author:wangshuai---date:2025-04-24---for:【QQYUN-12133】【AI】应用关联的知识库呗删除后,再次进入应用看不到已删除的知识库,并且无法清理掉知识库。---
|
||||
if (res.success && res.result) {
|
||||
let result = res.result;
|
||||
let idArray = ids.split(",");
|
||||
let arr = [];
|
||||
for (const id of idArray) {
|
||||
let filter = result.filter((item) => item.id === id);
|
||||
if(filter && filter.length > 0) {
|
||||
arr.push({ id: id, name: filter[0].name});
|
||||
} else {
|
||||
arr.push({ name: '该知识库已被删除', id: id,type: 'delete' })
|
||||
}
|
||||
}
|
||||
knowledgeDataList.value = arr;
|
||||
knowledgeIds.value = ids;
|
||||
} else {
|
||||
let arr = [];
|
||||
for (const id of ids) {
|
||||
arr.push({ name: '该知识库已被删除', id: id})
|
||||
}
|
||||
knowledgeDataList.value = arr;
|
||||
knowledgeIds.value = ids;
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-04-24---for:【QQYUN-12133】【AI】应用关联的知识库呗删除后,再次进入应用看不到已删除的知识库,并且无法清理掉知识库。---
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -497,7 +548,7 @@
|
|||
|
||||
/**
|
||||
* 关闭弹窗触发列表刷新
|
||||
*
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
function visibleChange(value) {
|
||||
|
@ -508,7 +559,7 @@
|
|||
|
||||
/**
|
||||
* 添加检验
|
||||
*
|
||||
*
|
||||
* @param type
|
||||
*/
|
||||
function addRules(type){
|
||||
|
@ -535,13 +586,13 @@
|
|||
|
||||
/**
|
||||
* 参数配置确定回调事件
|
||||
*
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
function handleParamsSettingOk(value){
|
||||
metadata.value = value;
|
||||
Object.assign(metadata.value,value)
|
||||
if(value) {
|
||||
formState.metadata = JSON.stringify(value);
|
||||
formState.metadata = JSON.stringify(metadata.value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -620,16 +671,16 @@
|
|||
|
||||
/**
|
||||
* 应用编辑回调事件
|
||||
*
|
||||
*
|
||||
* @param values
|
||||
*/
|
||||
function handelEditSuccess(values) {
|
||||
formState.icon = values.icon ? values.icon:'';
|
||||
formState.name = values.name ? values.name:'';
|
||||
}
|
||||
|
||||
|
||||
//=========== begin预设问题 ===========================
|
||||
|
||||
|
||||
// 编辑状态不允许拖动
|
||||
const disabledDrag = computed(()=>{
|
||||
let list = presetQuestionList.value;
|
||||
|
@ -641,7 +692,7 @@
|
|||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* 预设问题拖拽
|
||||
*/
|
||||
|
@ -652,7 +703,7 @@
|
|||
|
||||
/**
|
||||
* 预设问题添加
|
||||
*
|
||||
*
|
||||
* @param e
|
||||
*/
|
||||
function presetQuestionAddClick(e){
|
||||
|
@ -662,11 +713,11 @@
|
|||
}
|
||||
const length = presetQuestionList.value.length;
|
||||
presetQuestionList.value.push({key: length + 1, sort: length + 1, descr: ''})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 预设问题删除
|
||||
*
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
function deleteQuestionClick(key){
|
||||
|
@ -693,7 +744,7 @@
|
|||
|
||||
/**
|
||||
* 预设问题值改变事件
|
||||
*
|
||||
*
|
||||
*/
|
||||
function questionChange() {
|
||||
if(presetQuestionList.value && presetQuestionList.value.length>0){
|
||||
|
@ -704,9 +755,9 @@
|
|||
formState.presetQuestion = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=========== end预设问题 ===========================
|
||||
|
||||
|
||||
/**
|
||||
* 清除参数
|
||||
*/
|
||||
|
@ -717,9 +768,10 @@
|
|||
flowId.value = '';
|
||||
flowData.value = null;
|
||||
presetQuestion.value = '';
|
||||
presetQuestionList.value = [];
|
||||
presetQuestionList.value = [{ key:1, sort: 1, descr: '' }];
|
||||
quickCommandList.value = [];
|
||||
quickCommand.value = '';
|
||||
multiSessionChecked.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -735,13 +787,24 @@
|
|||
data.msgNum = data.msgNum ? data.msgNum : 1;
|
||||
if(data.metadata){
|
||||
metadata.value = JSON.parse(data.metadata);
|
||||
if(metadata.value?.multiSession){
|
||||
multiSessionChecked.value = metadata.value.multiSession === '1';
|
||||
}else{
|
||||
multiSessionChecked.value = "1";
|
||||
}
|
||||
}
|
||||
if(data.presetQuestion){
|
||||
presetQuestion.value = data.presetQuestion;
|
||||
presetQuestionList.value = JSON.parse(data.presetQuestion);
|
||||
}
|
||||
if(data.quickCommand){
|
||||
quickCommandList.value = JSON.parse(data.quickCommand);
|
||||
//update-begin---author:wangshuai---date:2025-04-08---for:【QQYUN-11939】ai应用 快捷指令 修改保存以后,再次打开还是原来的---
|
||||
let parse = JSON.parse(data.quickCommand);
|
||||
for (let i = 0; i < parse.length; i++) {
|
||||
parse[i].key = (i+1).toString();
|
||||
}
|
||||
quickCommandList.value = parse;
|
||||
//update-end---author:wangshuai---date:2025-04-08---for:【QQYUN-11939】ai应用 快捷指令 修改保存以后,再次打开还是原来的---
|
||||
}
|
||||
//赋值
|
||||
Object.assign(formState, data);
|
||||
|
@ -765,14 +828,14 @@
|
|||
|
||||
/**
|
||||
* 提示词回调
|
||||
*
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
function handleAiAppPromptOk(value) {
|
||||
formState.prompt = value;
|
||||
}
|
||||
//============= end 提示词 ================================
|
||||
|
||||
|
||||
//=============== begin 快捷指令 ============================
|
||||
function quickCommandEnd() {
|
||||
quickCommand.value = JSON.stringify(quickCommandList.value);
|
||||
|
@ -780,7 +843,7 @@
|
|||
}
|
||||
|
||||
/**
|
||||
* 快捷指令新增呢个==点击事件
|
||||
* 快捷指令新增点击事件
|
||||
*/
|
||||
function quickCommandAddClick(){
|
||||
if(quickCommandList.value && quickCommandList.value.length > 4){
|
||||
|
@ -788,8 +851,8 @@
|
|||
return;
|
||||
}
|
||||
aiAppCommandModalOpen(true,{})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷指令编辑点击事件
|
||||
* @param item
|
||||
|
@ -799,18 +862,21 @@
|
|||
isUpdate: true,
|
||||
record: item
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷指令添加回调事件
|
||||
* @param value
|
||||
*/
|
||||
function handleAiAppCommandOk(value){
|
||||
quickCommandList.value.push({ key: quickCommandList.value.length + 1, ...value });
|
||||
//update-begin---author:wangshuai---date:2025-04-08---for:【QQYUN-11939】ai应用 快捷指令 修改保存以后,再次打开还是原来的---
|
||||
value.key = (quickCommandList.value.length + 1).toString();
|
||||
//update-end---author:wangshuai---date:2025-04-08---for:【QQYUN-11939】ai应用 快捷指令 修改保存以后,再次打开还是原来的---
|
||||
quickCommandList.value.unshift({...value });
|
||||
quickCommand.value = JSON.stringify(quickCommandList.value);
|
||||
formState.quickCommand = quickCommand.value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 快捷指令更新回调事件
|
||||
* @param value
|
||||
|
@ -819,6 +885,8 @@
|
|||
let findIndex = quickCommandList.value.findIndex(item => item.key === value.key);
|
||||
if(findIndex>-1){
|
||||
quickCommandList.value[findIndex] = value;
|
||||
quickCommand.value = JSON.stringify(quickCommandList.value);
|
||||
formState.quickCommand = quickCommand.value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -835,10 +903,23 @@
|
|||
}
|
||||
}
|
||||
//=============== end 快捷指令 ============================
|
||||
|
||||
|
||||
/**
|
||||
* 复选框相中时的回调
|
||||
*/
|
||||
function handleMultiSessionChange(checked){
|
||||
if(checked){
|
||||
metadata.value.multiSession = "1";
|
||||
}else{
|
||||
metadata.value.multiSession = "0";
|
||||
}
|
||||
formState.metadata = JSON.stringify(metadata.value);
|
||||
}
|
||||
|
||||
return {
|
||||
registerModal,
|
||||
title,
|
||||
isRelease,
|
||||
handleOk,
|
||||
handleCancel,
|
||||
appTypeOption,
|
||||
|
@ -895,6 +976,8 @@
|
|||
quickCommand,
|
||||
getFlowImage,
|
||||
metadata,
|
||||
multiSessionChecked,
|
||||
handleMultiSessionChange,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
@ -997,7 +1080,7 @@
|
|||
align-content: center;
|
||||
}
|
||||
.prompt-back{
|
||||
background-color: rgba(238,244,255,1);
|
||||
background-color: #eef4ff;
|
||||
border-radius: 12px;
|
||||
padding: 2px;
|
||||
border: 1px solid #77B2F8;
|
||||
|
@ -1019,8 +1102,8 @@
|
|||
border-radius: 12px;
|
||||
padding: 2px 10px 2px 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.prologue-chunk-edit{
|
||||
background-color: #f2f4f7;
|
||||
border-radius: 12px;
|
||||
|
@ -1033,7 +1116,7 @@
|
|||
:deep(.ant-form-item-label){
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
:deep(.ant-form-item-required){
|
||||
margin-left: 4px !important;
|
||||
}
|
||||
|
@ -1052,7 +1135,7 @@
|
|||
}
|
||||
:deep(.vditor){
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
:deep(.vditor-sv){
|
||||
font-size: 14px;
|
||||
}
|
||||
|
@ -1100,7 +1183,7 @@
|
|||
}
|
||||
}
|
||||
.data-empty-text{
|
||||
color: rgba(32,41,69,0.6);
|
||||
color: #757c8f;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.flow-icon{
|
||||
|
@ -1123,11 +1206,11 @@
|
|||
white-space: nowrap;
|
||||
height: 20px;
|
||||
font-size: 12px;
|
||||
color: rgba(15, 21, 40,0.82);
|
||||
color: #3a3f4f;
|
||||
}
|
||||
.tag-input{
|
||||
align-self: center;
|
||||
color: rgba(55,67,106,0.7);
|
||||
color: #737c97;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
|
|
|
@ -13,6 +13,7 @@ enum Api {
|
|||
knowledgeDocList = '/airag/knowledge/doc/list',
|
||||
knowledgeEditDoc = '/airag/knowledge/doc/edit',
|
||||
knowledgeDeleteBatchDoc = '/airag/knowledge/doc/deleteBatch',
|
||||
knowledgeDeleteAllDoc = '/airag/knowledge/doc/deleteAll',
|
||||
knowledgeRebuildDoc = '/airag/knowledge/doc/rebuild',
|
||||
knowledgeEmbeddingHitTest = '/airag/knowledge/embedding/hitTest',
|
||||
}
|
||||
|
@ -114,6 +115,18 @@ export const knowledgeDeleteBatchDoc = (params, handleSuccess) => {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量删除文档
|
||||
*
|
||||
* @param params
|
||||
* @param handleSuccess
|
||||
*/
|
||||
export const knowledgeDeleteAllDoc = (knowId: string, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.knowledgeDeleteAllDoc, params: {knowId} }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 命中测试
|
||||
* @param params
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import {knowledgeDeleteAllDoc} from "./AiKnowledgeBase.api";
|
||||
import {useMessage} from "@/hooks/web/useMessage";
|
||||
|
||||
const {createConfirmSync} = useMessage();
|
||||
|
||||
// 清空文档
|
||||
export async function doDeleteAllDoc(knowledgeId: string, reload: () => void) {
|
||||
const flag = await createConfirmSync({
|
||||
title: '清空文档',
|
||||
content: () => (
|
||||
<p>
|
||||
<span>确定要清空所有文档吗?</span>
|
||||
<br/>
|
||||
<span style="color: #ee0000;">
|
||||
此操作会删除所有已录入的文档,并且不能恢复,请谨慎操作
|
||||
</span>
|
||||
</p>
|
||||
),
|
||||
});
|
||||
if (!flag) {
|
||||
return;
|
||||
}
|
||||
knowledgeDeleteAllDoc(knowledgeId, reload)
|
||||
}
|
|
@ -45,7 +45,7 @@
|
|||
<img class="header-img" src="./icon/knowledge.png" />
|
||||
<div class="header-text">
|
||||
<span class="header-text-top header-name ellipsis" :title="item.name"> {{ item.name }} </span>
|
||||
<span class="header-text-top"> 创建者:{{ item.createBy }} </span>
|
||||
<span class="header-text-top"> 创建者:{{ item.createBy_dictText || item.createBy }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -75,6 +75,10 @@
|
|||
<Icon class="pointer" icon="ant-design:delete-outlined" size="16"></Icon>
|
||||
删除
|
||||
</a-menu-item>
|
||||
<a-menu-item key="clear" @click.prevent.stop="onDeleteAllDoc(item)">
|
||||
<Icon icon="ant-design:delete-outlined" size="16"></Icon>
|
||||
清空文档
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
|
@ -93,6 +97,7 @@
|
|||
@change="handlePageChange"
|
||||
class="list-footer"
|
||||
size="small"
|
||||
:show-total="() => `共${total}条` "
|
||||
/>
|
||||
<!--添加知识库弹窗-->
|
||||
<KnowledgeBaseModal @register="registerModal" @success="reload"></KnowledgeBaseModal>
|
||||
|
@ -105,6 +110,7 @@
|
|||
import { reactive, ref } from 'vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { deleteModel, list, rebuild } from './AiKnowledgeBase.api';
|
||||
import { doDeleteAllDoc } from "./AiKnowledgeBase.api.util";
|
||||
import { Pagination } from 'ant-design-vue';
|
||||
import JInput from '@/components/Form/src/jeecg/components/JInput.vue';
|
||||
import KnowledgeBaseModal from './components/AiKnowledgeBaseModal.vue';
|
||||
|
@ -221,13 +227,26 @@
|
|||
* @param item
|
||||
*/
|
||||
async function handleDelete(item) {
|
||||
if(knowledgeList.value.length == 1 && pageNo.value > 1) {
|
||||
pageNo.value = pageNo.value - 1;
|
||||
}
|
||||
await deleteModel({ id: item.id, name: item.name }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空文档
|
||||
* @param item
|
||||
*/
|
||||
async function onDeleteAllDoc(item) {
|
||||
pageNo.value = 1;
|
||||
return doDeleteAllDoc(item.id, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*/
|
||||
function searchQuery() {
|
||||
pageNo.value = 1;
|
||||
reload();
|
||||
}
|
||||
|
||||
|
@ -237,6 +256,7 @@
|
|||
function searchReset() {
|
||||
formRef.value.resetFields();
|
||||
queryParam.createBy = '';
|
||||
pageNo.value = 1;
|
||||
//刷新数据
|
||||
reload();
|
||||
}
|
||||
|
@ -279,6 +299,7 @@
|
|||
total,
|
||||
handlePageChange,
|
||||
handleDelete,
|
||||
onDeleteAllDoc,
|
||||
searchQuery,
|
||||
searchReset,
|
||||
queryParam,
|
||||
|
@ -389,7 +410,7 @@
|
|||
margin-bottom: 20px;
|
||||
background: #fcfcfd;
|
||||
border: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 4px #e6e6e6;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 10px;
|
||||
display: inline-flex;
|
||||
|
@ -414,7 +435,7 @@
|
|||
}
|
||||
|
||||
.add-knowledge-card:hover {
|
||||
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
||||
box-shadow: 0 6px 12px #d0d3d8;
|
||||
}
|
||||
|
||||
.knowledge-card {
|
||||
|
@ -424,11 +445,11 @@
|
|||
border-radius: 10px;
|
||||
background: #fcfcfd;
|
||||
border: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 4px #e6e6e6;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.knowledge-card:hover {
|
||||
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
||||
box-shadow: 0 6px 12px #d0d3d8;
|
||||
.knowledge-btn {
|
||||
display: block;
|
||||
}
|
||||
|
@ -459,7 +480,7 @@
|
|||
}
|
||||
.model-icon:hover{
|
||||
color: #000000;
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
background-color: #e9ecf2;
|
||||
border: none;
|
||||
}
|
||||
.ant-dropdown-link{
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
<template>
|
||||
<div class="p-2">
|
||||
<BasicModal destroyOnClose @register="registerModal" :canFullscreen="false" width="600px" :title="title" @ok="handleOk" @cancel="handleCancel">
|
||||
<template #title>
|
||||
<span style="display: flex">
|
||||
{{title}}
|
||||
<a-tooltip title="AI知识库文档">
|
||||
<a style="color: unset" href="https://help.jeecg.com/aigc/guide/knowledge" target="_blank">
|
||||
<Icon style="position:relative;left:2px;top:1px" icon="ant-design:question-circle-outlined"></Icon>
|
||||
</a>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<BasicForm @register="registerForm"></BasicForm>
|
||||
</BasicModal>
|
||||
</div>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
import BasicForm from '@/components/Form/src/BasicForm.vue';
|
||||
import { MarkdownViewer } from '@/components/Markdown';
|
||||
import { useGlobSetting } from "@/hooks/setting";
|
||||
|
||||
export default {
|
||||
name: 'AiTextDescModal',
|
||||
|
@ -37,10 +38,32 @@
|
|||
//注册modal
|
||||
const [registerModal, { closeModal, setModalProps }] = useModalInner(async (data) => {
|
||||
hitTextDescData.value.source = 'score' + ' ' + data.score.toFixed(2);
|
||||
//替换图片宽度
|
||||
data.content = replaceImageWith(data.content);
|
||||
//替换图片domainUrl
|
||||
data.content = replaceDomainUrl(data.content);
|
||||
hitTextDescData.value.content = data.content;
|
||||
setModalProps({ header: '300px' })
|
||||
});
|
||||
|
||||
const { domainUrl } = useGlobSetting();
|
||||
const replaceImageWith = markdownContent => {
|
||||
// 支持图片设置width的写法 
|
||||
const regex = /!\[([^\]]*)\]\(([^)]+)=([0-9]+)\)/g;
|
||||
return markdownContent.replace(regex, (match, alt, src, width) => {
|
||||
let reg = /#\s*{\s*domainURL\s*}/g;
|
||||
src = src.replace(reg,domainUrl);
|
||||
return `<img src='${src}' alt='${alt}' width='${width}' />`;
|
||||
});
|
||||
};
|
||||
|
||||
//替换domainURL
|
||||
const replaceDomainUrl = markdownContent => {
|
||||
const regex = /!\[([^\]]*)\]\(.*?#\s*{\s*domainURL\s*}.*?\)/g;
|
||||
return markdownContent.replace(regex, (match) => {
|
||||
let reg = /#\s*{\s*domainURL\s*}/g;
|
||||
return match.replace(reg,domainUrl);
|
||||
})
|
||||
}
|
||||
return {
|
||||
registerModal,
|
||||
hitTextDescData
|
||||
|
|
|
@ -16,18 +16,46 @@
|
|||
</a-layout-sider>
|
||||
<a-layout-content :style="contentStyle">
|
||||
<div v-if="selectedKey === 'document'">
|
||||
<a-input v-model:value="searchText" placeholder="请输入文档名称,回车搜索" class="search-title" @pressEnter="reload"/>
|
||||
<div class="search-header" style="text-align: left;">
|
||||
<a-space align="center" wrap>
|
||||
<a-input
|
||||
class="search-input"
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入文档名称,回车搜索"
|
||||
@pressEnter="searchTextEnter"
|
||||
/>
|
||||
<template v-if="selectedRows.length > 0">
|
||||
<div>
|
||||
<span>已进入多选模式,当前选中</span>
|
||||
<a style="margin: 0 4px;"> {{ selectedRows.length }} </a>
|
||||
<span>条文档</span>
|
||||
</div>
|
||||
<div>
|
||||
<a @click="onClearSelected">清空选择</a>
|
||||
<a-divider type="vertical"/>
|
||||
<a @click="onDeleteBatch">批量删除</a>
|
||||
</div>
|
||||
</template>
|
||||
</a-space>
|
||||
</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" :bodyStyle="cardBodyStyle">
|
||||
<span style="line-height: 18px;font-weight: 500;color:#676f83;font-size: 12px">创建文档</span>
|
||||
<span style="line-height: 18px;font-weight: 500;color:#676f83;font-size: 12px">
|
||||
创建文档
|
||||
<a-tooltip title="知识库文档">
|
||||
<a style="color: unset" href="https://help.jeecg.com/aigc/guide/knowledge#4-%E7%9F%A5%E8%AF%86%E5%BA%93%E6%96%87%E6%A1%A3" target="_blank">
|
||||
<Icon style="position:relative;top:1px" icon="ant-design:question-circle-outlined" size="14"></Icon>
|
||||
</a>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<div class="add-knowledge-doc" @click="handleCreateText">
|
||||
<Icon icon="ant-design:form-outlined" size="13"></Icon><span>手动录入</span>
|
||||
</div>
|
||||
<div class="add-knowledge-doc" @click="handleCreateUpload">
|
||||
<Icon icon="ant-design:cloud-upload-outlined" size="13"></Icon><span>文件上传</span>
|
||||
</div>
|
||||
<div class="add-knowledge-doc" @click="handleCreateUploadLibrary">
|
||||
<div class="add-knowledge-doc">
|
||||
<a-upload
|
||||
accept=".zip"
|
||||
name="file"
|
||||
|
@ -37,15 +65,36 @@
|
|||
:beforeUpload="beforeUpload"
|
||||
:action="uploadUrl"
|
||||
@change="handleUploadChange"
|
||||
style="width: 100%;"
|
||||
>
|
||||
<Icon style="margin-left: 0" icon="ant-design:project-outlined" size="13"></Icon>
|
||||
<span>文档库上传</span>
|
||||
<div style="display: flex;width: 100%">
|
||||
<Icon style="margin-left: 0;color:#676f83" icon="ant-design:project-outlined" size="13"></Icon>
|
||||
<span style="color:#676f83;font-size: 12px">文档库上传</span>
|
||||
</div>
|
||||
</a-upload>
|
||||
</div>
|
||||
<a-dropdown placement="bottomRight" :trigger="['click']">
|
||||
<div class="ant-dropdown-link pointer operation" @click.prevent.stop>
|
||||
<Icon icon="ant-design:ellipsis-outlined" size="16"></Icon>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="delete" @click="onDeleteAll">
|
||||
<Icon icon="ant-design:delete-outlined" size="16"></Icon>
|
||||
清空文档
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :xxl="4" :xl="6" :lg="6" :md="6" :sm="12" :xs="24" v-for="item in knowledgeDocDataList">
|
||||
<a-card class="knowledge-card pointer" @click="handleEdit(item)">
|
||||
<a-card :class="['knowledge-card','pointer', {
|
||||
checked: item.__checked,
|
||||
}]" @click="handleEdit(item)">
|
||||
<div class="knowledge-checkbox">
|
||||
<a-checkbox v-model:checked="item.__checked" @click.stop=""/>
|
||||
</div>
|
||||
<div class="knowledge-header">
|
||||
<div class="header-text flex">
|
||||
<Icon v-if="item.type==='text'" icon="ant-design:file-text-outlined" size="32" color="#00a7d0"></Icon>
|
||||
|
@ -77,6 +126,12 @@
|
|||
<img src="../icon/draft.png" style="width: 16px;height: 16px" />
|
||||
<span class="ml-2">草稿</span>
|
||||
</div>
|
||||
<a-tooltip v-else-if="item.status==='failed'" :title="getDocFailedReason(item)">
|
||||
<div class="card-text-status">
|
||||
<Icon icon="ant-design:close-circle-outlined" size="16" color="#FF4D4F"></Icon>
|
||||
<span class="ml-2">失败</span>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-dropdown placement="bottomRight" :trigger="['click']">
|
||||
<div class="ant-dropdown-link pointer operation" @click.prevent.stop>
|
||||
|
@ -114,6 +169,7 @@
|
|||
@change="handlePageChange"
|
||||
class="list-footer"
|
||||
size="small"
|
||||
:show-total="() => `共${total}条` "
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -186,6 +242,7 @@
|
|||
</div>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
<Loading tip="上传中,请稍后" :loading="uploadLoading"></Loading>
|
||||
</BasicModal>
|
||||
|
||||
<!-- 手工录入文本 -->
|
||||
|
@ -195,11 +252,12 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { onBeforeMount, reactive, ref, unref, h } from 'vue';
|
||||
<script lang="tsx">
|
||||
import { onBeforeMount, computed, ref, unref, h } from 'vue';
|
||||
import BasicModal from '@/components/Modal/src/BasicModal.vue';
|
||||
import { useModal, useModalInner } from '@/components/Modal';
|
||||
import { knowledgeDocList, knowledgeDeleteBatchDoc, knowledgeRebuildDoc, knowledgeEmbeddingHitTest } from '../AiKnowledgeBase.api';
|
||||
import { knowledgeDocList, knowledgeDeleteBatchDoc, knowledgeDeleteAllDoc, knowledgeRebuildDoc, knowledgeEmbeddingHitTest } from '../AiKnowledgeBase.api';
|
||||
import { doDeleteAllDoc } from '../AiKnowledgeBase.api.util';
|
||||
import { ActionItem, BasicTable, TableAction } from '@/components/Table';
|
||||
import { useListPage } from '@/hooks/system/useListPage';
|
||||
import AiragKnowledgeDocTextModal from './AiragKnowledgeDocTextModal.vue';
|
||||
|
@ -212,6 +270,7 @@
|
|||
import defaultImg from '/@/assets/images/header.jpg';
|
||||
import Icon from "@/components/Icon";
|
||||
import { useGlobSetting } from '/@/hooks/setting';
|
||||
import Loading from '@/components/Loading/src/Loading.vue';
|
||||
|
||||
export default {
|
||||
name: 'AiragKnowledgeDocListModal',
|
||||
|
@ -225,6 +284,7 @@
|
|||
BasicModal,
|
||||
AiragKnowledgeDocTextModal,
|
||||
AiTextDescModal,
|
||||
Loading,
|
||||
},
|
||||
emits: ['success', 'register'],
|
||||
setup(props, { emit }) {
|
||||
|
@ -274,7 +334,9 @@
|
|||
const globSetting = useGlobSetting();
|
||||
//上传路径
|
||||
const uploadUrl = ref<string>(globSetting.domainUrl+"/airag/knowledge/doc/import/zip");
|
||||
|
||||
//上传加载
|
||||
const uploadLoading = ref<boolean>(false);
|
||||
|
||||
//菜单项
|
||||
const menuItems = ref<any>([
|
||||
{
|
||||
|
@ -290,6 +352,10 @@
|
|||
title: '命中测试',
|
||||
},
|
||||
]);
|
||||
|
||||
// 当前选中的行
|
||||
const selectedRows = computed(() => knowledgeDocDataList.value.filter(item => item.__checked))
|
||||
|
||||
//注册modal
|
||||
const [docTextRegister, { openModal: docTextOpenModal }] = useModal();
|
||||
const [docTextDescRegister, { openModal: docTextDescOpenModal }] = useModal();
|
||||
|
@ -329,8 +395,8 @@
|
|||
},
|
||||
spin: true,
|
||||
});
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
const { createMessage, createConfirmSync } = useMessage();
|
||||
|
||||
/**
|
||||
* 手工录入文本
|
||||
|
@ -343,6 +409,7 @@
|
|||
* 文件上传
|
||||
*/
|
||||
function handleCreateUpload() {
|
||||
console.log("11111111111")
|
||||
docTextOpenModal(true, { knowledgeId: knowledgeId.value, type: "file" });
|
||||
}
|
||||
|
||||
|
@ -357,6 +424,13 @@
|
|||
* 编辑
|
||||
*/
|
||||
function handleEdit(record) {
|
||||
// 判断如果有选中的行,则说明是批量操作模式
|
||||
if (selectedRows.value.length > 0) {
|
||||
record.__checked = !record.__checked;
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (record.type === 'text' || record.type === 'file') {
|
||||
docTextOpenModal(true, {
|
||||
record,
|
||||
|
@ -376,11 +450,28 @@
|
|||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
if(knowledgeDocDataList.value.length == 1 && pageNo.value > 1) {
|
||||
pageNo.value = pageNo.value - 1;
|
||||
}
|
||||
knowledgeDeleteBatchDoc({ ids: id }, reload);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getDocFailedReason(doc) {
|
||||
let metadata = doc?.metadata;
|
||||
if (!metadata) {
|
||||
return '构建失败,原因未知';
|
||||
}
|
||||
try {
|
||||
metadata = JSON.parse(metadata);
|
||||
return metadata?.failedReason || '构建失败,原因未知';
|
||||
} catch (e) {
|
||||
console.log('getDocFailedReason', e);
|
||||
return '构建失败,原因未知';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量化
|
||||
*
|
||||
|
@ -394,11 +485,9 @@
|
|||
* 文档新增和编辑成功回调
|
||||
*/
|
||||
function handleSuccess() {
|
||||
if(!timer.value){
|
||||
reload();
|
||||
}
|
||||
clearInterval(timer.value);
|
||||
timer.value = null;
|
||||
reload();
|
||||
triggeringTimer();
|
||||
}
|
||||
|
||||
|
@ -421,7 +510,7 @@
|
|||
pageNo.value = 1;
|
||||
pageSize.value = 10;
|
||||
searchText.value = "";
|
||||
|
||||
|
||||
reload();
|
||||
});
|
||||
} else {
|
||||
|
@ -511,7 +600,10 @@
|
|||
}
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-03-21---for:【QQYUN-11636】向量化功能改成异步---
|
||||
knowledgeDocDataList.value = res.result.records;
|
||||
knowledgeDocDataList.value = res.result.records.map((item)=>{
|
||||
item.__checked = false;
|
||||
return item;
|
||||
});
|
||||
total.value = res.result.total;
|
||||
} else {
|
||||
knowledgeDocDataList.value = [];
|
||||
|
@ -519,7 +611,7 @@
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 分页改变事件
|
||||
* @param page
|
||||
|
@ -548,10 +640,11 @@
|
|||
*/
|
||||
function beforeUpload(file) {
|
||||
let fileType = file.type;
|
||||
if (fileType !== 'application/x-zip-compressed') {
|
||||
if (fileType !== 'application/zip' && fileType !== 'application/x-zip-compressed') {
|
||||
createMessage.warning('请上传zip文件');
|
||||
return false;
|
||||
}
|
||||
uploadLoading.value = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -561,24 +654,63 @@
|
|||
*/
|
||||
function handleUploadChange(info) {
|
||||
let { file } = info;
|
||||
if (file.status === 'error') {
|
||||
createMessage.error(file.response.message ||`${file.name} 上传失败.`);
|
||||
if (file.status === 'error' || (file.response && file.response.code == 500)) {
|
||||
createMessage.error(file.response?.message ||`${file.name} 上传失败,请查看服务端日志`);
|
||||
uploadLoading.value = false;
|
||||
return;
|
||||
}
|
||||
if (file.status === 'done') {
|
||||
|
||||
if(!file.response.success){
|
||||
createMessage.warning(file.response.message);
|
||||
uploadLoading.value = false;
|
||||
return;
|
||||
}
|
||||
uploadLoading.value = false;
|
||||
createMessage.success(file.response.message);
|
||||
handleSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
function onClearSelected() {
|
||||
knowledgeDocDataList.value.forEach(item => {
|
||||
item.__checked = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 清空文档
|
||||
async function onDeleteAll() {
|
||||
pageNo.value = 1;
|
||||
doDeleteAllDoc(knowledgeId.value, reload);
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
async function onDeleteBatch() {
|
||||
const flag = await createConfirmSync({ title: '批量删除', content: `确定要删除这 ${selectedRows.value.length} 条数据吗?` });
|
||||
if (!flag) {
|
||||
return;
|
||||
}
|
||||
const ids = selectedRows.value.map(item => item.id)
|
||||
let number = knowledgeDocDataList.value.length - ids.length;
|
||||
if(number == 0 && pageNo.value > 1) {
|
||||
pageNo.value = pageNo.value - 1;
|
||||
}
|
||||
knowledgeDeleteBatchDoc({ ids }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回车搜索
|
||||
*/
|
||||
function searchTextEnter(){
|
||||
pageNo.value = 1;
|
||||
reload();
|
||||
}
|
||||
|
||||
onBeforeMount(()=>{
|
||||
clearInterval(timer.value);
|
||||
timer.value = null;
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
registerModal,
|
||||
title,
|
||||
|
@ -607,6 +739,7 @@
|
|||
knowledgeDocDataList,
|
||||
handleEdit,
|
||||
handleDelete,
|
||||
getDocFailedReason,
|
||||
handleVectorization,
|
||||
pageNo,
|
||||
pageSize,
|
||||
|
@ -623,6 +756,12 @@
|
|||
uploadUrl,
|
||||
handleUploadChange,
|
||||
knowledgeId,
|
||||
uploadLoading,
|
||||
selectedRows,
|
||||
onClearSelected,
|
||||
onDeleteAll,
|
||||
onDeleteBatch,
|
||||
searchTextEnter,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
@ -703,7 +842,7 @@
|
|||
border-radius: 10px;
|
||||
background: #fcfcfd;
|
||||
border: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 4px #e6e6e6;
|
||||
transition: all 0.3s ease;
|
||||
.card-title {
|
||||
justify-content: space-between;
|
||||
|
@ -715,12 +854,12 @@
|
|||
}
|
||||
}
|
||||
.hit-card:hover {
|
||||
box-shadow: 0 6px 12px rgba(0,0,0,0.15) !important;
|
||||
box-shadow: 0 6px 12px #d0d3d8 !important;
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.card-description {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
|
@ -734,16 +873,16 @@
|
|||
font-size: 12px;
|
||||
color: #676F83;
|
||||
}
|
||||
|
||||
|
||||
.card-title-tag {
|
||||
color: #477dee;
|
||||
}
|
||||
|
||||
|
||||
.knowledge-row {
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
.add-knowledge-card {
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
|
@ -753,14 +892,14 @@
|
|||
width: calc(100% - 20px);
|
||||
background: #fcfcfd;
|
||||
border: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 4px #e6e6e6;
|
||||
transition: all 0.3s ease;
|
||||
.add-knowledge-card-icon {
|
||||
padding: 8px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.knowledge-card {
|
||||
border-radius: 10px;
|
||||
margin-right: 20px;
|
||||
|
@ -768,8 +907,29 @@
|
|||
height: 166px;
|
||||
background: #fcfcfd;
|
||||
border: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 4px #e6e6e6;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.knowledge-checkbox {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover, &.checked {
|
||||
.knowledge-checkbox {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&.checked {
|
||||
border: 1px solid @primary-color;
|
||||
}
|
||||
|
||||
.knowledge-header {
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
|
@ -786,7 +946,7 @@
|
|||
margin-left: 4px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
|
||||
.header-text {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
@ -801,34 +961,37 @@
|
|||
}
|
||||
|
||||
.add-knowledge-card:hover,.knowledge-card:hover{
|
||||
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
||||
box-shadow: 0 6px 12px #d0d3d8;
|
||||
}
|
||||
|
||||
|
||||
.ellipsis {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
width: calc(100% - 30px);
|
||||
}
|
||||
|
||||
|
||||
:deep(.ant-card .ant-card-body) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
|
||||
.card-text{
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
margin-top: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-title{
|
||||
width: 200px;
|
||||
|
||||
.search-header {
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
margin-left: 20px;
|
||||
margin-left: 26px;
|
||||
|
||||
.search-input {
|
||||
width: 240px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.operation{
|
||||
border: none;
|
||||
margin-top: 10px;
|
||||
|
@ -838,13 +1001,13 @@
|
|||
right: 4px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.knowledge-card:hover{
|
||||
|
||||
.add-knowledge-card:hover, .knowledge-card:hover{
|
||||
.operation{
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.add-knowledge-doc{
|
||||
margin-top: 6px;
|
||||
color:#6F6F83;
|
||||
|
@ -867,7 +1030,7 @@
|
|||
}
|
||||
.operation:hover{
|
||||
color: #000000;
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
background-color: #e9ecf2;
|
||||
border: none;
|
||||
}
|
||||
.ant-dropdown-link{
|
||||
|
@ -892,6 +1055,11 @@
|
|||
.ml-2{
|
||||
margin-left: 2px;
|
||||
}
|
||||
.add-knowledge-doc {
|
||||
:deep(.ant-upload) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.airag-knowledge-doc .scroll-container {
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
</li>
|
||||
<li class="flex mr-14 mt-6">
|
||||
<span class="label">创建者</span>
|
||||
<span class="described">{{ item.createBy }}</span>
|
||||
<span class="described">{{ item.createBy_dictText || item.createBy }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -93,6 +93,7 @@
|
|||
@change="handlePageChange"
|
||||
class="list-footer"
|
||||
size="small"
|
||||
:show-total="() => `共${total}条` "
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -218,6 +219,9 @@
|
|||
* @param item
|
||||
*/
|
||||
async function handleDeleteClick(item) {
|
||||
if(modalList.value.length == 1 && pageNo.value > 1) {
|
||||
pageNo.value = pageNo.value - 1;
|
||||
}
|
||||
await deleteModel({ id: item.id, name: item.name }, reload);
|
||||
}
|
||||
|
||||
|
@ -225,6 +229,7 @@
|
|||
* 查询
|
||||
*/
|
||||
function searchQuery() {
|
||||
pageNo.value = 1;
|
||||
reload();
|
||||
}
|
||||
|
||||
|
@ -350,14 +355,14 @@
|
|||
margin-bottom: 20px;
|
||||
background: #fcfcfd;
|
||||
border: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 4px #e6e6e6;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 10px;
|
||||
height: 152px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.model-card:hover {
|
||||
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
||||
box-shadow: 0 6px 12px #d0d3d8;
|
||||
.model-btn {
|
||||
display: flex;
|
||||
}
|
||||
|
@ -385,7 +390,7 @@
|
|||
margin-bottom: 20px;
|
||||
background: #fcfcfd;
|
||||
border: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 4px #e6e6e6;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 10px;
|
||||
display: inline-flex;
|
||||
|
@ -410,7 +415,7 @@
|
|||
}
|
||||
|
||||
.add-knowledge-card:hover {
|
||||
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
||||
box-shadow: 0 6px 12px #d0d3d8;
|
||||
}
|
||||
.model-icon{
|
||||
background-color: unset;
|
||||
|
@ -419,7 +424,7 @@
|
|||
}
|
||||
.model-icon:hover{
|
||||
color: #000000;
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
background-color: #e9ecf2;
|
||||
border: none;
|
||||
}
|
||||
.ant-dropdown-link{
|
||||
|
|
|
@ -3,7 +3,14 @@
|
|||
<div class="modal">
|
||||
<div class="header">
|
||||
<span class="header-title">
|
||||
<span v-if="dataIndex ==='list' || dataIndex ==='add'" :class="dataIndex === 'list' ? '' : 'add-header-title pointer'" @click="goToList">选择供应商</span>
|
||||
<span v-if="dataIndex ==='list' || dataIndex ==='add'" :class="dataIndex === 'list' ? '' : 'add-header-title pointer'" @click="goToList">
|
||||
选择供应商
|
||||
<a-tooltip title="供应商文档" v-if="dataIndex ==='list'">
|
||||
<a style="color: #333333" href="https://help.jeecg.com/aigc/guide/model/#2-%E4%BE%9B%E5%BA%94%E5%95%86%E9%80%89%E6%8B%A9" target="_blank">
|
||||
<Icon style="position:relative;left: -2px;top:1px" icon="ant-design:question-circle-outlined"></Icon>
|
||||
</a>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<span v-if="dataIndex === 'add'" class="add-header-title"> > </span>
|
||||
<span v-if="dataIndex === 'add'" style="color: #1f2329">添加 {{ providerName }}</span>
|
||||
</span>
|
||||
|
@ -27,7 +34,17 @@
|
|||
</a-row>
|
||||
</div>
|
||||
<a-tabs v-model:activeKey="activeKey" v-if="dataIndex === 'add' || dataIndex === 'edit'">
|
||||
<a-tab-pane :key="1" tab="基础信息">
|
||||
<a-tab-pane :key="1">
|
||||
<template #tab>
|
||||
<span style="display: flex">
|
||||
基础信息
|
||||
<a-tooltip title="基础信息文档">
|
||||
<a @click.stop style="color: unset" href="https://help.jeecg.com/aigc/guide/model/#31-%E5%A1%AB%E5%86%99%E5%9F%BA%E7%A1%80%E4%BF%A1%E6%81%AF" target="_blank">
|
||||
<Icon style="position:relative;left:2px;top:1px" icon="ant-design:question-circle-outlined"></Icon>
|
||||
</a>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<div class="model-content">
|
||||
<BasicForm @register="registerForm">
|
||||
<template #modelType="{ model, field }">
|
||||
|
@ -62,13 +79,24 @@
|
|||
</BasicForm>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="2" tab="高级配置" v-if="modelParamsShow">
|
||||
<a-tab-pane :key="2" v-if="modelParamsShow">
|
||||
<template #tab>
|
||||
<span style="display: flex">
|
||||
高级配置
|
||||
<a-tooltip title="高级配置文档">
|
||||
<a @click.stop style="color: unset" href="https://help.jeecg.com/aigc/guide/model/#32-%E9%85%8D%E7%BD%AE%E9%AB%98%E7%BA%A7%E5%8F%82%E6%95%B0" target="_blank">
|
||||
<Icon style="position:relative;left:2px;top:1px" icon="ant-design:question-circle-outlined"></Icon>
|
||||
</a>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<AiModelSeniorForm ref="modelParamsRef" :modelParams="modelParams"></AiModelSeniorForm>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
</div>
|
||||
<template v-if="dataIndex === 'add' || dataIndex === 'edit'" #footer>
|
||||
<a-button @click="test" :loading="testLoading">测试</a-button>
|
||||
<a-button @click="cancel">关闭</a-button>
|
||||
<a-button @click="save" type="primary">保存</a-button>
|
||||
</template>
|
||||
|
@ -87,9 +115,10 @@
|
|||
import BasicForm from '@/components/Form/src/BasicForm.vue';
|
||||
import { useForm } from '@/components/Form';
|
||||
import { formSchema, imageList } from '../model.data';
|
||||
import { editModel, queryById, saveModel } from '../model.api';
|
||||
import { editModel, queryById, saveModel, testConn } from '../model.api';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import AiModelSeniorForm from './AiModelSeniorForm.vue';
|
||||
import { cloneDeep } from "lodash-es";
|
||||
export default {
|
||||
name: 'AddModelModal',
|
||||
components: {
|
||||
|
@ -128,6 +157,8 @@
|
|||
const modelParamsShow = ref<boolean>(false);
|
||||
//模型参数ref
|
||||
const modelParamsRef = ref();
|
||||
//测试按钮loading状态
|
||||
const testLoading = ref<boolean>(false);
|
||||
|
||||
const getImage = (name) => {
|
||||
return imageList.value[name];
|
||||
|
@ -206,7 +237,7 @@
|
|||
*/
|
||||
function initModelTypeOption() {
|
||||
initDictOptions('model_type').then((data) => {
|
||||
modelTypeOption.value = data;
|
||||
modelTypeOption.value = cloneDeep(data);
|
||||
//update-begin---author:wangshuai---date:2025-03-04---for: 解决页面tab刷新一次就多一个全部类型的选项---
|
||||
if(data[0].value != 'all'){
|
||||
modelTypeOption.value.unshift({
|
||||
|
@ -301,6 +332,38 @@
|
|||
closeModal();
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试连接
|
||||
*/
|
||||
async function test() {
|
||||
try {
|
||||
testLoading.value = true;
|
||||
let values = await validate();
|
||||
let credential = {
|
||||
apiKey: values.apiKey,
|
||||
secretKey: values.secretKey,
|
||||
};
|
||||
if (modelParamsRef.value) {
|
||||
let modelParams = modelParamsRef.value.emitChange();
|
||||
if (modelParams) {
|
||||
values.modelParams = JSON.stringify(modelParams);
|
||||
}
|
||||
}
|
||||
values.credential = JSON.stringify(credential);
|
||||
if (!values.provider) {
|
||||
values.provider = modelData.value.value;
|
||||
}
|
||||
//测试
|
||||
await testConn(values);
|
||||
} catch (e) {
|
||||
if (e.hasOwnProperty('errorFields')) {
|
||||
activeKey.value = 1;
|
||||
}
|
||||
} finally {
|
||||
testLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型类型选择事件
|
||||
* @param value
|
||||
|
@ -364,6 +427,8 @@
|
|||
modelParamsRef,
|
||||
filterOption,
|
||||
getTitle,
|
||||
test,
|
||||
testLoading,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
],
|
||||
"type": ["LLM", "EMBED"],
|
||||
"baseUrl": "https://api.openai.com/v1/",
|
||||
"LLMDefaultValue": "gpt-3.5-turbo",
|
||||
"LLMDefaultValue": "gpt-4o-mini",
|
||||
"EMBEDDefaultValue": "text-embedding-ada-002"
|
||||
},
|
||||
{
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue