@@ -61,6 +61,7 @@
|
||||
"vue": "catalog:",
|
||||
"vue-dompurify-html": "catalog:",
|
||||
"vue-router": "catalog:",
|
||||
"vue3-signature": "catalog:"
|
||||
"vue3-signature": "catalog:",
|
||||
"vuedraggable": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 495 KiB |
@@ -6,6 +6,7 @@ import { requestClient } from '#/api/request';
|
||||
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
const accessStore = useAccessStore();
|
||||
|
||||
export namespace AiMindmapApi {
|
||||
// AI 思维导图
|
||||
export interface MindMap {
|
||||
@@ -19,7 +20,7 @@ export namespace AiMindmapApi {
|
||||
}
|
||||
|
||||
// AI 思维导图生成
|
||||
export interface AiMindMapGenerateReq {
|
||||
export interface AiMindMapGenerateReqVO {
|
||||
prompt: string;
|
||||
}
|
||||
}
|
||||
@@ -32,7 +33,7 @@ export function generateMindMap({
|
||||
ctrl,
|
||||
}: {
|
||||
ctrl: AbortController;
|
||||
data: AiMindmapApi.AiMindMapGenerateReq;
|
||||
data: AiMindmapApi.AiMindMapGenerateReqVO;
|
||||
onClose?: (...args: any[]) => void;
|
||||
onError?: (...args: any[]) => void;
|
||||
onMessage?: (res: any) => void;
|
||||
@@ -53,12 +54,12 @@ export function generateMindMap({
|
||||
});
|
||||
}
|
||||
|
||||
// 查询思维导图分页
|
||||
/** 查询思维导图分页 */
|
||||
export function getMindMapPage(params: any) {
|
||||
return requestClient.get(`/ai/mind-map/page`, { params });
|
||||
}
|
||||
|
||||
// 删除思维导图
|
||||
/** 删除思维导图 */
|
||||
export function deleteMindMap(id: number) {
|
||||
return requestClient.delete(`/ai/mind-map/delete?id=${id}`);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export namespace AiModelApiKeyApi {
|
||||
}
|
||||
}
|
||||
|
||||
// 查询 API 密钥分页
|
||||
/** 查询 API 密钥分页 */
|
||||
export function getApiKeyPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<AiModelApiKeyApi.ApiKey>>(
|
||||
'/ai/api-key/page',
|
||||
@@ -21,28 +21,29 @@ export function getApiKeyPage(params: PageParam) {
|
||||
);
|
||||
}
|
||||
|
||||
// 获得 API 密钥列表
|
||||
/** 获得 API 密钥列表 */
|
||||
export function getApiKeySimpleList() {
|
||||
return requestClient.get<AiModelApiKeyApi.ApiKey[]>(
|
||||
'/ai/api-key/simple-list',
|
||||
);
|
||||
}
|
||||
|
||||
// 查询 API 密钥详情
|
||||
/** 查询 API 密钥详情 */
|
||||
export function getApiKey(id: number) {
|
||||
return requestClient.get<AiModelApiKeyApi.ApiKey>(`/ai/api-key/get?id=${id}`);
|
||||
}
|
||||
// 新增 API 密钥
|
||||
|
||||
/** 新增 API 密钥 */
|
||||
export function createApiKey(data: AiModelApiKeyApi.ApiKey) {
|
||||
return requestClient.post('/ai/api-key/create', data);
|
||||
}
|
||||
|
||||
// 修改 API 密钥
|
||||
/** 修改 API 密钥 */
|
||||
export function updateApiKey(data: AiModelApiKeyApi.ApiKey) {
|
||||
return requestClient.put('/ai/api-key/update', data);
|
||||
}
|
||||
|
||||
// 删除 API 密钥
|
||||
/** 删除 API 密钥 */
|
||||
export function deleteApiKey(id: number) {
|
||||
return requestClient.delete(`/ai/api-key/delete?id=${id}`);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export namespace AiModelChatRoleApi {
|
||||
}
|
||||
|
||||
// AI 聊天角色 分页请求
|
||||
export interface ChatRolePageReq {
|
||||
export interface ChatRolePageReqVO {
|
||||
name?: string; // 角色名称
|
||||
category?: string; // 角色类别
|
||||
publicStatus: boolean; // 是否公开
|
||||
@@ -29,7 +29,7 @@ export namespace AiModelChatRoleApi {
|
||||
}
|
||||
}
|
||||
|
||||
// 查询聊天角色分页
|
||||
/** 查询聊天角色分页 */
|
||||
export function getChatRolePage(params: PageParam) {
|
||||
return requestClient.get<PageResult<AiModelChatRoleApi.ChatRole>>(
|
||||
'/ai/chat-role/page',
|
||||
@@ -37,49 +37,49 @@ export function getChatRolePage(params: PageParam) {
|
||||
);
|
||||
}
|
||||
|
||||
// 查询聊天角色详情
|
||||
/** 查询聊天角色详情 */
|
||||
export function getChatRole(id: number) {
|
||||
return requestClient.get<AiModelChatRoleApi.ChatRole>(
|
||||
`/ai/chat-role/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
// 新增聊天角色
|
||||
|
||||
/** 新增聊天角色 */
|
||||
export function createChatRole(data: AiModelChatRoleApi.ChatRole) {
|
||||
return requestClient.post('/ai/chat-role/create', data);
|
||||
}
|
||||
|
||||
// 修改聊天角色
|
||||
/** 修改聊天角色 */
|
||||
export function updateChatRole(data: AiModelChatRoleApi.ChatRole) {
|
||||
return requestClient.put('/ai/chat-role/update', data);
|
||||
}
|
||||
|
||||
// 删除聊天角色
|
||||
/** 删除聊天角色 */
|
||||
export function deleteChatRole(id: number) {
|
||||
return requestClient.delete(`/ai/chat-role/delete?id=${id}`);
|
||||
}
|
||||
|
||||
// ======= chat 聊天
|
||||
// 获取 my role
|
||||
export function getMyPage(params: AiModelChatRoleApi.ChatRolePageReq) {
|
||||
/** 获取 my role */
|
||||
export function getMyPage(params: AiModelChatRoleApi.ChatRolePageReqVO) {
|
||||
return requestClient.get('/ai/chat-role/my-page', { params });
|
||||
}
|
||||
|
||||
// 获取角色分类
|
||||
/** 获取角色分类 */
|
||||
export function getCategoryList() {
|
||||
return requestClient.get('/ai/chat-role/category-list');
|
||||
}
|
||||
|
||||
// 创建角色
|
||||
/** 创建角色 */
|
||||
export function createMy(data: AiModelChatRoleApi.ChatRole) {
|
||||
return requestClient.post('/ai/chat-role/create-my', data);
|
||||
}
|
||||
|
||||
// 更新角色
|
||||
/** 更新角色 */
|
||||
export function updateMy(data: AiModelChatRoleApi.ChatRole) {
|
||||
return requestClient.put('/ai/chat-role/update', data);
|
||||
}
|
||||
|
||||
// 删除角色 my
|
||||
/** 删除角色 my */
|
||||
export function deleteMy(id: number) {
|
||||
return requestClient.delete(`/ai/chat-role/delete-my?id=${id}`);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export namespace AiModelModelApi {
|
||||
}
|
||||
}
|
||||
|
||||
// 查询模型分页
|
||||
/** 查询模型分页 */
|
||||
export function getModelPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<AiModelModelApi.Model>>(
|
||||
'/ai/model/page',
|
||||
@@ -26,7 +26,7 @@ export function getModelPage(params: PageParam) {
|
||||
);
|
||||
}
|
||||
|
||||
// 获得模型列表
|
||||
/** 获得模型列表 */
|
||||
export function getModelSimpleList(type?: number) {
|
||||
return requestClient.get<AiModelModelApi.Model[]>('/ai/model/simple-list', {
|
||||
params: {
|
||||
@@ -35,21 +35,22 @@ export function getModelSimpleList(type?: number) {
|
||||
});
|
||||
}
|
||||
|
||||
// 查询模型详情
|
||||
/** 查询模型详情 */
|
||||
export function getModel(id: number) {
|
||||
return requestClient.get<AiModelModelApi.Model>(`/ai/model/get?id=${id}`);
|
||||
}
|
||||
// 新增模型
|
||||
|
||||
/** 新增模型 */
|
||||
export function createModel(data: AiModelModelApi.Model) {
|
||||
return requestClient.post('/ai/model/create', data);
|
||||
}
|
||||
|
||||
// 修改模型
|
||||
/** 修改模型 */
|
||||
export function updateModel(data: AiModelModelApi.Model) {
|
||||
return requestClient.put('/ai/model/update', data);
|
||||
}
|
||||
|
||||
// 删除模型
|
||||
/** 删除模型 */
|
||||
export function deleteModel(id: number) {
|
||||
return requestClient.delete(`/ai/model/delete?id=${id}`);
|
||||
}
|
||||
|
||||
@@ -11,33 +11,34 @@ export namespace AiModelToolApi {
|
||||
}
|
||||
}
|
||||
|
||||
// 查询工具分页
|
||||
/** 查询工具分页 */
|
||||
export function getToolPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<AiModelToolApi.Tool>>('/ai/tool/page', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 查询工具详情
|
||||
/** 查询工具详情 */
|
||||
export function getTool(id: number) {
|
||||
return requestClient.get<AiModelToolApi.Tool>(`/ai/tool/get?id=${id}`);
|
||||
}
|
||||
// 新增工具
|
||||
|
||||
/** 新增工具 */
|
||||
export function createTool(data: AiModelToolApi.Tool) {
|
||||
return requestClient.post('/ai/tool/create', data);
|
||||
}
|
||||
|
||||
// 修改工具
|
||||
/** 修改工具 */
|
||||
export function updateTool(data: AiModelToolApi.Tool) {
|
||||
return requestClient.put('/ai/tool/update', data);
|
||||
}
|
||||
|
||||
// 删除工具
|
||||
/** 删除工具 */
|
||||
export function deleteTool(id: number) {
|
||||
return requestClient.delete(`/ai/tool/delete?id=${id}`);
|
||||
}
|
||||
|
||||
// 获取工具简单列表
|
||||
/** 获取工具简单列表 */
|
||||
export function getToolSimpleList() {
|
||||
return requestClient.get<AiModelToolApi.Tool[]>('/ai/tool/simple-list');
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ import { requestClient } from '#/api/request';
|
||||
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
const accessStore = useAccessStore();
|
||||
|
||||
export namespace AiWriteApi {
|
||||
export interface Write {
|
||||
id?: number;
|
||||
type: AiWriteTypeEnum.REPLY | AiWriteTypeEnum.WRITING; // 1:撰写 2:回复
|
||||
prompt: string; // 写作内容提示 1。撰写 2回复
|
||||
originalContent: string; // 原文
|
||||
@@ -26,29 +28,12 @@ export namespace AiWriteApi {
|
||||
createTime?: Date; // 创建时间
|
||||
}
|
||||
|
||||
export interface AiWritePageReq extends PageParam {
|
||||
export interface AiWritePageReqVO extends PageParam {
|
||||
userId?: number; // 用户编号
|
||||
type?: AiWriteTypeEnum; // 写作类型
|
||||
platform?: string; // 平台
|
||||
createTime?: [string, string]; // 创建时间
|
||||
}
|
||||
|
||||
export interface AiWriteResp {
|
||||
id: number;
|
||||
userId: number;
|
||||
type: number;
|
||||
platform: string;
|
||||
model: string;
|
||||
prompt: string;
|
||||
generatedContent: string;
|
||||
originalContent: string;
|
||||
length: number;
|
||||
format: number;
|
||||
tone: number;
|
||||
language: number;
|
||||
errorMessage: string;
|
||||
createTime: string;
|
||||
}
|
||||
}
|
||||
|
||||
export function writeStream({
|
||||
@@ -80,15 +65,14 @@ export function writeStream({
|
||||
});
|
||||
}
|
||||
|
||||
// 获取写作列表
|
||||
export function getWritePage(params: any) {
|
||||
return requestClient.get<PageResult<AiWriteApi.AiWritePageReq>>(
|
||||
`/ai/write/page`,
|
||||
{ params },
|
||||
);
|
||||
/** 获取写作列表 */
|
||||
export function getWritePage(params: AiWriteApi.AiWritePageReqVO) {
|
||||
return requestClient.get<PageResult<AiWriteApi.Write>>(`/ai/write/page`, {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除音乐
|
||||
/** 删除写作记录 */
|
||||
export function deleteWrite(id: number) {
|
||||
return requestClient.delete(`/ai/write/delete`, { params: { id } });
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export namespace BpmCategoryApi {
|
||||
code: string;
|
||||
status: number;
|
||||
description?: string;
|
||||
sort: number; // 分类排序
|
||||
sort: number;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import type { BpmModelApi } from '#/api/bpm/model';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace BpmProcessDefinitionApi {
|
||||
/** 流程定义 */
|
||||
export interface ProcessDefinition {
|
||||
id: string;
|
||||
key?: string;
|
||||
version: number;
|
||||
name: string;
|
||||
category: string;
|
||||
description: string;
|
||||
deploymentTime: number;
|
||||
suspensionState: number;
|
||||
modelType: number;
|
||||
modelId: string;
|
||||
formType?: number;
|
||||
formId?: number;
|
||||
formName?: string;
|
||||
formCustomCreatePath?: string;
|
||||
bpmnXml?: string;
|
||||
simpleModel?: string;
|
||||
formFields?: string[];
|
||||
icon?: string;
|
||||
startUsers?: BpmModelApi.UserInfo[];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { requestClient } from '#/api/request';
|
||||
export namespace BpmFormApi {
|
||||
/** 流程表单 */
|
||||
export interface Form {
|
||||
id?: number | undefined;
|
||||
id?: number;
|
||||
name: string;
|
||||
conf: string;
|
||||
fields: string[];
|
||||
@@ -23,7 +23,7 @@ export async function getFormPage(params: PageParam) {
|
||||
}
|
||||
|
||||
/** 获取表单详情 */
|
||||
export async function getFormDetail(id: number) {
|
||||
export async function getForm(id: number) {
|
||||
return requestClient.get<BpmFormApi.Form>(`/bpm/form/get?id=${id}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ export interface ModelCategoryInfo {
|
||||
}
|
||||
|
||||
/** 获取流程模型列表 */
|
||||
export async function getModelList(name: string | undefined) {
|
||||
export async function getModelList(name?: string) {
|
||||
return requestClient.get<BpmModelApi.Model[]>('/bpm/model/list', {
|
||||
params: { name },
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ export namespace BpmProcessInstanceApi {
|
||||
export interface Task {
|
||||
id: number;
|
||||
name: string;
|
||||
assigneeUser?: User;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
@@ -51,6 +52,7 @@ export namespace BpmProcessInstanceApi {
|
||||
export interface ProcessInstance {
|
||||
businessKey: string;
|
||||
category: string;
|
||||
categoryName?: string;
|
||||
createTime: string;
|
||||
endTime: string;
|
||||
fields: string[];
|
||||
@@ -64,6 +66,10 @@ export namespace BpmProcessInstanceApi {
|
||||
startTime?: Date;
|
||||
startUser?: User;
|
||||
status: number;
|
||||
summary?: {
|
||||
key: string;
|
||||
value: string;
|
||||
}[];
|
||||
tasks?: BpmProcessInstanceApi.Task[];
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ export namespace BpmTaskApi {
|
||||
status: number; // 监听器状态
|
||||
event: string; // 监听事件
|
||||
valueType: string; // 监听器值类型
|
||||
processInstance?: BpmProcessInstanceApi.ProcessInstance; // 流程实例
|
||||
}
|
||||
|
||||
// 流程任务
|
||||
|
||||
@@ -20,9 +20,9 @@ export namespace ErpFinancePaymentApi {
|
||||
export interface FinancePayment {
|
||||
id?: number; // 付款单编号
|
||||
no: string; // 付款单号
|
||||
supplierId: number; // 供应商编号
|
||||
supplierId?: number; // 供应商编号
|
||||
supplierName?: string; // 供应商名称
|
||||
paymentTime: Date; // 付款时间
|
||||
paymentTime?: Date; // 付款时间
|
||||
totalPrice: number; // 合计金额,单位:元
|
||||
discountPrice: number; // 优惠金额
|
||||
paymentPrice: number; // 实际付款金额
|
||||
|
||||
@@ -22,6 +22,7 @@ export namespace IoTOtaTaskRecordApi {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @AI:这里应该拿到 IoTOtaTaskRecordApi 里
|
||||
/** IoT OTA 升级任务记录 */
|
||||
export interface OtaTaskRecord {
|
||||
id?: number;
|
||||
@@ -95,7 +96,7 @@ export function getOtaTaskRecordStatusStatistics(
|
||||
taskId?: number,
|
||||
) {
|
||||
return requestClient.get<Record<string, number>>(
|
||||
'/iot/ota/task/record/status-statistics',
|
||||
'/iot/ota/task/record/get-status-statistics',
|
||||
{ params: { firmwareId, taskId } },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,12 +5,18 @@ import { requestClient } from '#/api/request';
|
||||
export namespace MallDiyPageApi {
|
||||
/** 装修页面 */
|
||||
export interface DiyPage {
|
||||
id?: number; // 页面编号
|
||||
templateId?: number; // 模板编号
|
||||
name: string; // 页面名称
|
||||
remark: string; // 备注
|
||||
previewPicUrls: string[]; // 预览图片地址数组
|
||||
property: string; // 页面属性
|
||||
/** 页面编号 */
|
||||
id?: number;
|
||||
/** 模板编号 */
|
||||
templateId?: number;
|
||||
/** 页面名称 */
|
||||
name: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
/** 预览图片地址数组 */
|
||||
previewPicUrls: string[];
|
||||
/** 页面属性 */
|
||||
property: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +52,7 @@ export function deleteDiyPage(id: number) {
|
||||
|
||||
/** 获得装修页面属性 */
|
||||
export function getDiyPageProperty(id: number) {
|
||||
return requestClient.get<string>(`/promotion/diy-page/get-property?id=${id}`);
|
||||
return requestClient.get(`/promotion/diy-page/get-property?id=${id}`);
|
||||
}
|
||||
|
||||
/** 更新装修页面属性 */
|
||||
|
||||
@@ -7,18 +7,26 @@ import { requestClient } from '#/api/request';
|
||||
export namespace MallDiyTemplateApi {
|
||||
/** 装修模板 */
|
||||
export interface DiyTemplate {
|
||||
id?: number; // 模板编号
|
||||
name: string; // 模板名称
|
||||
used: boolean; // 是否使用
|
||||
usedTime?: Date; // 使用时间
|
||||
remark: string; // 备注
|
||||
previewPicUrls: string[]; // 预览图片地址数组
|
||||
property: string; // 模板属性
|
||||
/** 模板编号 */
|
||||
id?: number;
|
||||
/** 模板名称 */
|
||||
name: string;
|
||||
/** 是否使用 */
|
||||
used: boolean;
|
||||
/** 使用时间 */
|
||||
usedTime?: Date;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
/** 预览图片地址数组 */
|
||||
previewPicUrls: string[];
|
||||
/** 模板属性 */
|
||||
property: string;
|
||||
}
|
||||
|
||||
/** 装修模板属性(包含页面列表) */
|
||||
export interface DiyTemplateProperty extends DiyTemplate {
|
||||
pages: MallDiyPageApi.DiyPage[]; // 页面列表
|
||||
/** 页面列表 */
|
||||
pages: MallDiyPageApi.DiyPage[];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,31 +76,23 @@ export function getTradeStatisticsSummary() {
|
||||
}
|
||||
|
||||
/** 获得交易状况统计 */
|
||||
export function getTradeStatisticsAnalyse(
|
||||
params: MallTradeStatisticsApi.TradeTrendReq,
|
||||
) {
|
||||
export function getTradeStatisticsAnalyse(params: any) {
|
||||
return requestClient.get<
|
||||
DataComparisonRespVO<MallTradeStatisticsApi.TradeTrendSummary>
|
||||
>('/statistics/trade/analyse', { params: formatDateParam(params) });
|
||||
>('/statistics/trade/analyse', { params });
|
||||
}
|
||||
|
||||
/** 获得交易状况明细 */
|
||||
export function getTradeStatisticsList(
|
||||
params: MallTradeStatisticsApi.TradeTrendReq,
|
||||
) {
|
||||
export function getTradeStatisticsList(params: any) {
|
||||
return requestClient.get<MallTradeStatisticsApi.TradeTrendSummary[]>(
|
||||
'/statistics/trade/list',
|
||||
{ params: formatDateParam(params) },
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 导出交易状况明细 */
|
||||
export function exportTradeStatisticsExcel(
|
||||
params: MallTradeStatisticsApi.TradeTrendReq,
|
||||
) {
|
||||
return requestClient.download('/statistics/trade/export-excel', {
|
||||
params: formatDateParam(params),
|
||||
});
|
||||
export function exportTradeStatisticsExcel(params: any) {
|
||||
return requestClient.download('/statistics/trade/export-excel', { params });
|
||||
}
|
||||
|
||||
/** 获得交易订单数量 */
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import type { PageParam } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace MpStatisticsApi {
|
||||
/** 统计查询参数 */
|
||||
export interface StatisticsQuery extends PageParam {
|
||||
export interface StatisticsQuery {
|
||||
accountId: number;
|
||||
beginDate: string;
|
||||
endDate: string;
|
||||
date: Date[];
|
||||
}
|
||||
|
||||
/** 消息发送概况数据 */
|
||||
|
||||
BIN
apps/web-antd/src/assets/imgs/diy/app-nav-bar-mp.png
Normal file
BIN
apps/web-antd/src/assets/imgs/diy/app-nav-bar-mp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
apps/web-antd/src/assets/imgs/diy/statusBar.png
Normal file
BIN
apps/web-antd/src/assets/imgs/diy/statusBar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
@@ -35,6 +35,7 @@ async function bootstrap(namespace: string) {
|
||||
// });
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(VueDOMPurifyHTML);
|
||||
|
||||
// 注册v-loading指令
|
||||
registerLoadingDirective(app, {
|
||||
@@ -61,10 +62,6 @@ async function bootstrap(namespace: string) {
|
||||
// formCreate
|
||||
setupFormCreate(app);
|
||||
|
||||
// vue-dompurify-html
|
||||
// TODO @dhb52:VueDOMPurifyHTML 是不是不用引入哈?
|
||||
app.use(VueDOMPurifyHTML);
|
||||
|
||||
// 配置Motion插件
|
||||
const { MotionPlugin } = await import('@vben/plugins/motion');
|
||||
app.use(MotionPlugin);
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import './theme/index.scss';
|
||||
import 'bpmn-js/dist/assets/diagram-js.css';
|
||||
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
|
||||
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
|
||||
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
|
||||
// TODO @puhui999:样式问题:设计器那,位置不太对;
|
||||
|
||||
export { default as MyProcessDesigner } from './designer';
|
||||
// TODO @puhui999:流程发起时,预览相关的,需要使用;
|
||||
export { default as MyProcessViewer } from './designer/index2';
|
||||
export { default as MyProcessPenal } from './penal';
|
||||
|
||||
// TODO @puhui999:【有个迁移的打印】【新增】流程打印,由 [@Lesan](https://gitee.com/LesanOuO) 贡献 [#816](https://gitee.com/yudaocode/yudao-ui-admin-vue3/pulls/816/)、[#1418](https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1418/)、[#817](https://gitee.com/yudaocode/yudao-ui-admin-vue3/pulls/817/)、[#1419](https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1419/)、[#1424](https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1424)、[#819](https://gitee.com/yudaocode/yudao-ui-admin-vue3/pulls/819)、[#821](https://gitee.com/yudaocode/yudao-ui-admin-vue3/pulls/821/)
|
||||
@@ -1,82 +1,197 @@
|
||||
<script lang="tsx">
|
||||
import type { DescriptionsProps } from 'ant-design-vue';
|
||||
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions';
|
||||
|
||||
import type { PropType } from 'vue';
|
||||
import type { CSSProperties, PropType, Slots } from 'vue';
|
||||
|
||||
import type { DescriptionItemSchema, DescriptionsOptions } from './typing';
|
||||
import type { DescriptionItemSchema, DescriptionProps } from './typing';
|
||||
|
||||
import { defineComponent } from 'vue';
|
||||
import { computed, defineComponent, ref, unref, useAttrs } from 'vue';
|
||||
|
||||
import { get } from '@vben/utils';
|
||||
import { get, getNestedValue, isFunction } from '@vben/utils';
|
||||
|
||||
import { Descriptions, DescriptionsItem } from 'ant-design-vue';
|
||||
import { Card, Descriptions } from 'ant-design-vue';
|
||||
|
||||
/** 对 Descriptions 进行二次封装 */
|
||||
const Description = defineComponent({
|
||||
name: 'Descriptions',
|
||||
props: {
|
||||
data: {
|
||||
type: Object as PropType<Record<string, any>>,
|
||||
default: () => ({}),
|
||||
},
|
||||
schema: {
|
||||
type: Array as PropType<DescriptionItemSchema[]>,
|
||||
default: () => [],
|
||||
},
|
||||
// Descriptions 原生 props
|
||||
componentProps: {
|
||||
type: Object as PropType<DescriptionsProps>,
|
||||
default: () => ({}),
|
||||
const props = {
|
||||
bordered: { default: true, type: Boolean },
|
||||
column: {
|
||||
default: () => {
|
||||
return { lg: 3, md: 3, sm: 2, xl: 3, xs: 1, xxl: 4 };
|
||||
},
|
||||
type: [Number, Object],
|
||||
},
|
||||
data: { type: Object },
|
||||
schema: {
|
||||
default: () => [],
|
||||
type: Array as PropType<DescriptionItemSchema[]>,
|
||||
},
|
||||
size: {
|
||||
default: 'small',
|
||||
type: String,
|
||||
validator: (v: string) =>
|
||||
['default', 'middle', 'small', undefined].includes(v),
|
||||
},
|
||||
title: { default: '', type: String },
|
||||
useCard: { default: true, type: Boolean },
|
||||
};
|
||||
|
||||
setup(props: DescriptionsOptions) {
|
||||
// TODO @xingyu:每个 field 的 slot 的考虑
|
||||
// TODO @xingyu:from 5.0:extra: () => getSlot(slots, 'extra')
|
||||
/** 过滤掉不需要展示的 */
|
||||
const shouldShowItem = (item: DescriptionItemSchema) => {
|
||||
if (item.hidden === undefined) return true;
|
||||
return typeof item.hidden === 'function'
|
||||
? !item.hidden(props.data)
|
||||
: !item.hidden;
|
||||
};
|
||||
/** 渲染内容 */
|
||||
const renderContent = (item: DescriptionItemSchema) => {
|
||||
if (item.content) {
|
||||
return typeof item.content === 'function'
|
||||
? item.content(props.data)
|
||||
: item.content;
|
||||
function getSlot(slots: Slots, slot: string, data?: any) {
|
||||
if (!slots || !Reflect.has(slots, slot)) {
|
||||
return null;
|
||||
}
|
||||
if (!isFunction(slots[slot])) {
|
||||
console.error(`${slot} is not a function!`);
|
||||
return null;
|
||||
}
|
||||
const slotFn = slots[slot];
|
||||
if (!slotFn) return null;
|
||||
return slotFn({ data });
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Description',
|
||||
props,
|
||||
setup(props, { slots }) {
|
||||
const propsRef = ref<null | Partial<DescriptionProps>>(null);
|
||||
|
||||
const prefixCls = 'description';
|
||||
const attrs = useAttrs();
|
||||
|
||||
// Custom title component: get title
|
||||
const getMergeProps = computed(() => {
|
||||
return {
|
||||
...props,
|
||||
...(unref(propsRef) as any),
|
||||
} as DescriptionProps;
|
||||
});
|
||||
|
||||
const getProps = computed(() => {
|
||||
const opt = {
|
||||
...unref(getMergeProps),
|
||||
title: undefined,
|
||||
};
|
||||
return opt as DescriptionProps;
|
||||
});
|
||||
|
||||
const useWrapper = computed(() => !!unref(getMergeProps).title);
|
||||
|
||||
const getDescriptionsProps = computed(() => {
|
||||
return { ...unref(attrs), ...unref(getProps) } as DescriptionsProps;
|
||||
});
|
||||
|
||||
// 防止换行
|
||||
function renderLabel({
|
||||
label,
|
||||
labelMinWidth,
|
||||
labelStyle,
|
||||
}: DescriptionItemSchema) {
|
||||
if (!labelStyle && !labelMinWidth) {
|
||||
return label;
|
||||
}
|
||||
return item.field ? get(props.data, item.field) : null;
|
||||
};
|
||||
|
||||
return () => (
|
||||
<Descriptions
|
||||
{...props}
|
||||
bordered={props.componentProps?.bordered}
|
||||
colon={props.componentProps?.colon}
|
||||
column={props.componentProps?.column}
|
||||
extra={props.componentProps?.extra}
|
||||
layout={props.componentProps?.layout}
|
||||
size={props.componentProps?.size}
|
||||
title={props.componentProps?.title}
|
||||
>
|
||||
{props.schema?.filter(shouldShowItem).map((item) => (
|
||||
<DescriptionsItem
|
||||
contentStyle={item.contentStyle}
|
||||
key={item.field || String(item.label)}
|
||||
label={item.label}
|
||||
labelStyle={item.labelStyle}
|
||||
span={item.span}
|
||||
>
|
||||
{renderContent(item)}
|
||||
</DescriptionsItem>
|
||||
))}
|
||||
</Descriptions>
|
||||
);
|
||||
const labelStyles: CSSProperties = {
|
||||
...labelStyle,
|
||||
minWidth: `${labelMinWidth}px `,
|
||||
};
|
||||
return <div style={labelStyles}>{label}</div>;
|
||||
}
|
||||
|
||||
function renderItem() {
|
||||
const { data, schema } = unref(getProps);
|
||||
return unref(schema)
|
||||
.map((item) => {
|
||||
const { contentMinWidth, field, render, show, span } = item;
|
||||
|
||||
if (show && isFunction(show) && !show(data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getContent() {
|
||||
const _data = unref(getProps)?.data;
|
||||
if (!_data) {
|
||||
return null;
|
||||
}
|
||||
const getField = field.includes('.')
|
||||
? (getNestedValue(_data, field) ?? get(_data, field))
|
||||
: get(_data, field);
|
||||
// if (
|
||||
// getField &&
|
||||
// !Object.prototype.hasOwnProperty.call(toRefs(_data), field)
|
||||
// ) {
|
||||
// return isFunction(render) ? render('', _data) : (getField ?? '');
|
||||
// }
|
||||
return isFunction(render)
|
||||
? render(getField, _data)
|
||||
: (getField ?? '');
|
||||
}
|
||||
|
||||
const width = contentMinWidth;
|
||||
return (
|
||||
<Descriptions.Item
|
||||
key={field}
|
||||
label={renderLabel(item)}
|
||||
span={span}
|
||||
>
|
||||
{() => {
|
||||
if (item.slot) {
|
||||
// TODO @xingyu:这里要 inline 掉么?
|
||||
const slotContent = getSlot(slots, item.slot, data);
|
||||
return slotContent;
|
||||
}
|
||||
if (!contentMinWidth) {
|
||||
return getContent();
|
||||
}
|
||||
const style: CSSProperties = {
|
||||
minWidth: `${width}px`,
|
||||
};
|
||||
return <div style={style}>{getContent()}</div>;
|
||||
}}
|
||||
</Descriptions.Item>
|
||||
);
|
||||
})
|
||||
.filter((item) => !!item);
|
||||
}
|
||||
|
||||
function renderDesc() {
|
||||
return (
|
||||
<Descriptions
|
||||
class={`${prefixCls}`}
|
||||
{...(unref(getDescriptionsProps) as any)}
|
||||
>
|
||||
{renderItem()}
|
||||
</Descriptions>
|
||||
);
|
||||
}
|
||||
|
||||
function renderCard() {
|
||||
const content = props.useCard ? renderDesc() : <div>{renderDesc()}</div>;
|
||||
// Reduce the dom level
|
||||
if (!props.useCard) {
|
||||
return content;
|
||||
}
|
||||
|
||||
const { title } = unref(getMergeProps);
|
||||
const extraSlot = getSlot(slots, 'extra');
|
||||
|
||||
return (
|
||||
<Card
|
||||
bodyStyle={{ padding: '8px 0' }}
|
||||
headStyle={{
|
||||
padding: '8px 16px',
|
||||
fontSize: '14px',
|
||||
minHeight: '24px',
|
||||
}}
|
||||
style={{ margin: 0 }}
|
||||
title={title}
|
||||
>
|
||||
{{
|
||||
default: () => content,
|
||||
extra: () => extraSlot && <div>{extraSlot}</div>,
|
||||
}}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return () => (unref(useWrapper) ? renderCard() : renderDesc());
|
||||
},
|
||||
});
|
||||
|
||||
// TODO @xingyu:from 5.0:emits: ['register'] 事件
|
||||
export default Description;
|
||||
</script>
|
||||
|
||||
@@ -1,27 +1,56 @@
|
||||
import type { DescriptionsProps } from 'ant-design-vue';
|
||||
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions';
|
||||
import type { JSX } from 'vue/jsx-runtime';
|
||||
|
||||
import type { CSSProperties, VNode } from 'vue';
|
||||
|
||||
// TODO @xingyu:【content】这个纠结下;1)vben2.0 是 render;https://doc.vvbin.cn/components/desc.html#usage 2)
|
||||
// TODO @xingyu:vben2.0 还有 sapn【done】、labelMinWidth、contentMinWidth
|
||||
// TODO @xingyu:【hidden】这个纠结下;1)vben2.0 是 show;
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
export interface DescriptionItemSchema {
|
||||
label: string | VNode; // 内容的描述
|
||||
field?: string; // 对应 data 中的字段名
|
||||
content?: ((data: any) => string | VNode) | string | VNode; // 自定义需要展示的内容,比如说 dict-tag
|
||||
span?: number; // 包含列的数量
|
||||
labelStyle?: CSSProperties; // 自定义标签样式
|
||||
contentStyle?: CSSProperties; // 自定义内容样式
|
||||
hidden?: ((data: any) => boolean) | boolean; // 是否显示
|
||||
labelMinWidth?: number;
|
||||
contentMinWidth?: number;
|
||||
// 自定义标签样式
|
||||
labelStyle?: CSSProperties;
|
||||
// 对应 data 中的字段名
|
||||
field: string;
|
||||
// 内容的描述
|
||||
label: JSX.Element | string | VNode;
|
||||
// 包含列的数量
|
||||
span?: number;
|
||||
// 是否显示
|
||||
show?: (...arg: any) => boolean;
|
||||
// 插槽名称
|
||||
slot?: string;
|
||||
// 自定义需要展示的内容
|
||||
render?: (
|
||||
val: any,
|
||||
data?: Recordable<any>,
|
||||
) => Element | JSX.Element | number | string | undefined | VNode;
|
||||
}
|
||||
|
||||
// TODO @xingyu:vben2.0 还有 title【done】、bordered【done】d、useCollapse、collapseOptions
|
||||
// TODO @xingyu:from 5.0:bordered 默认为 true
|
||||
// TODO @xingyu:from 5.0:column 默认为 lg: 3, md: 3, sm: 2, xl: 3, xs: 1, xxl: 4
|
||||
// TODO @xingyu:from 5.0:size 默认为 small;有 'default', 'middle', 'small', undefined
|
||||
// TODO @xingyu:from 5.0:useCollapse 默认为 true
|
||||
export interface DescriptionsOptions {
|
||||
data?: Record<string, any>; // 数据
|
||||
schema?: DescriptionItemSchema[]; // 描述项配置
|
||||
componentProps?: DescriptionsProps; // antd Descriptions 组件参数
|
||||
export interface DescriptionProps extends DescriptionsProps {
|
||||
// 是否包含卡片组件
|
||||
useCard?: boolean;
|
||||
// 描述项配置
|
||||
schema: DescriptionItemSchema[];
|
||||
// 数据
|
||||
data: Recordable<any>;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 是否包含边框
|
||||
bordered?: boolean;
|
||||
// 列数
|
||||
column?:
|
||||
| number
|
||||
| {
|
||||
lg: number;
|
||||
md: number;
|
||||
sm: number;
|
||||
xl: number;
|
||||
xs: number;
|
||||
xxl: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DescInstance {
|
||||
setDescProps(descProps: Partial<DescriptionProps>): void;
|
||||
}
|
||||
|
||||
@@ -1,71 +1,31 @@
|
||||
import type { DescriptionsOptions } from './typing';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import { defineComponent, h, isReactive, reactive, watch } from 'vue';
|
||||
import type { DescInstance, DescriptionProps } from './typing';
|
||||
|
||||
import { h, reactive } from 'vue';
|
||||
|
||||
import Description from './description.vue';
|
||||
|
||||
/** 描述列表 api 定义 */
|
||||
class DescriptionApi {
|
||||
private state = reactive<Record<string, any>>({});
|
||||
export function useDescription(options?: Partial<DescriptionProps>) {
|
||||
const propsState = reactive<Partial<DescriptionProps>>(options || {});
|
||||
|
||||
constructor(options: DescriptionsOptions) {
|
||||
this.state = { ...options };
|
||||
}
|
||||
const api: DescInstance = {
|
||||
setDescProps: (descProps: Partial<DescriptionProps>): void => {
|
||||
Object.assign(propsState, descProps);
|
||||
},
|
||||
};
|
||||
|
||||
getState(): DescriptionsOptions {
|
||||
return this.state as DescriptionsOptions;
|
||||
}
|
||||
|
||||
// TODO @xingyu:【setState】纠结下:1)vben2.0 是 data https://doc.vvbin.cn/components/desc.html#usage;
|
||||
setState(newState: Partial<DescriptionsOptions>) {
|
||||
this.state = { ...this.state, ...newState };
|
||||
}
|
||||
}
|
||||
|
||||
export type ExtendedDescriptionApi = DescriptionApi;
|
||||
|
||||
export function useDescription(options: DescriptionsOptions) {
|
||||
const IS_REACTIVE = isReactive(options);
|
||||
const api = new DescriptionApi(options);
|
||||
// 扩展 API
|
||||
const extendedApi: ExtendedDescriptionApi = api as never;
|
||||
const Desc = defineComponent({
|
||||
// 创建一个包装组件,将 propsState 合并到 props 中
|
||||
const DescriptionWrapper: Component = {
|
||||
name: 'UseDescription',
|
||||
inheritAttrs: false,
|
||||
setup(_, { attrs, slots }) {
|
||||
// 合并props和attrs到state
|
||||
api.setState({ ...attrs });
|
||||
|
||||
return () =>
|
||||
h(
|
||||
Description,
|
||||
{
|
||||
...api.getState(),
|
||||
...attrs,
|
||||
},
|
||||
slots,
|
||||
);
|
||||
setup(_props, { attrs, slots }) {
|
||||
return () => {
|
||||
// @ts-ignore - 避免类型实例化过深
|
||||
return h(Description, { ...propsState, ...attrs }, slots);
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 响应式支持
|
||||
if (IS_REACTIVE) {
|
||||
watch(
|
||||
() => options.schema,
|
||||
(newSchema) => {
|
||||
api.setState({ schema: newSchema });
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => options.data,
|
||||
(newData) => {
|
||||
api.setState({ data: newData });
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
}
|
||||
|
||||
return [Desc, extendedApi] as const;
|
||||
return [DescriptionWrapper, api] as const;
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ export function useApiSelect(option: ApiSelectProps) {
|
||||
onUpdate:value={onUpdateModelValue as any}
|
||||
value={modelValue as any}
|
||||
{...restAttrs}
|
||||
// TODO: remote 对等实现
|
||||
// TODO @xingyu remote 对等实现, 还是说没作用
|
||||
// remote={props.remote}
|
||||
{...(props.remote && { remoteMethod })}
|
||||
>
|
||||
@@ -223,7 +223,7 @@ export function useApiSelect(option: ApiSelectProps) {
|
||||
onUpdate:value={onUpdateModelValue as any}
|
||||
value={modelValue as any}
|
||||
{...restAttrs}
|
||||
// TODO: @dhb52 remote 对等实现, 还是说没作用
|
||||
// TODO: @xingyu remote 对等实现, 还是说没作用
|
||||
// remote={props.remote}
|
||||
{...(props.remote && { remoteMethod })}
|
||||
>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { SystemDictTypeApi } from '#/api/system/dict/type';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { buildUUID, cloneDeep } from '@vben/utils';
|
||||
|
||||
import * as DictDataApi from '#/api/system/dict/type';
|
||||
import { getSimpleDictTypeList } from '#/api/system/dict/type';
|
||||
import {
|
||||
localeProps,
|
||||
makeRequiredRule,
|
||||
@@ -18,12 +20,12 @@ export function useDictSelectRule() {
|
||||
const rules = cloneDeep(selectRule);
|
||||
const dictOptions = ref<{ label: string; value: string }[]>([]); // 字典类型下拉数据
|
||||
onMounted(async () => {
|
||||
const data = await DictDataApi.getSimpleDictTypeList();
|
||||
const data = await getSimpleDictTypeList();
|
||||
if (!data || data.length === 0) {
|
||||
return;
|
||||
}
|
||||
dictOptions.value =
|
||||
data?.map((item: DictDataApi.SystemDictTypeApi.DictType) => ({
|
||||
data?.map((item: SystemDictTypeApi.DictType) => ({
|
||||
label: item.name,
|
||||
value: item.type,
|
||||
})) ?? [];
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { Rule } from '@form-create/ant-design-vue';
|
||||
|
||||
/** 数据字典 Select 选择器组件 Props 类型 */
|
||||
export interface DictSelectProps {
|
||||
dictType: string; // 字典类型
|
||||
valueType?: 'bool' | 'int' | 'str'; // 字典值类型 TODO @芋艿:'boolean' | 'number' | 'string';需要和 vue3 一起统一!
|
||||
valueType?: 'bool' | 'int' | 'str'; // 字典值类型
|
||||
selectType?: 'checkbox' | 'radio' | 'select'; // 选择器类型,下拉框 select、多选框 checkbox、单选框 radio
|
||||
formCreateInject?: any;
|
||||
}
|
||||
@@ -22,25 +20,6 @@ export interface Menu {
|
||||
list: MenuItem[];
|
||||
}
|
||||
|
||||
export type MenuList = Array<Menu>;
|
||||
|
||||
// TODO @dhb52:MenuList、Menu、MenuItem、DragRule 这几个,是不是没用到呀?
|
||||
// 拖拽组件的规则
|
||||
export interface DragRule {
|
||||
icon: string;
|
||||
name: string;
|
||||
label: string;
|
||||
children?: string;
|
||||
inside?: true;
|
||||
drag?: string | true;
|
||||
dragBtn?: false;
|
||||
mask?: false;
|
||||
|
||||
rule(): Rule;
|
||||
|
||||
props(v: any, v1: any): Rule[];
|
||||
}
|
||||
|
||||
/** 通用 API 下拉组件 Props 类型 */
|
||||
export interface ApiSelectProps {
|
||||
name: string; // 组件名称
|
||||
|
||||
@@ -10,4 +10,5 @@ export const ACTION_ICON = {
|
||||
MORE: 'lucide:ellipsis-vertical',
|
||||
VIEW: 'lucide:eye',
|
||||
COPY: 'lucide:copy',
|
||||
CLOSE: 'lucide:x',
|
||||
};
|
||||
|
||||
@@ -241,7 +241,7 @@ function handleUploadSuccess(res: any, file: File) {
|
||||
// 处理上传错误
|
||||
function handleUploadError(error: any) {
|
||||
console.error('上传错误:', error);
|
||||
message.error($t('ui.upload.uploadError'));
|
||||
message.error('上传错误!!!');
|
||||
// 上传失败时减少计数器
|
||||
uploadNumber.value = Math.max(0, uploadNumber.value - 1);
|
||||
}
|
||||
|
||||
@@ -91,10 +91,12 @@ const components = [
|
||||
FileUpload,
|
||||
];
|
||||
|
||||
// 参考 https://www.form-create.com/v3/ant-design-vue/auto-import 文档
|
||||
export function setupFormCreate(app: App) {
|
||||
components.forEach((component) => {
|
||||
app.component(component.name as string, component);
|
||||
});
|
||||
// TODO @xingyu:这里为啥 app.component('AMessage', message); 看官方是没有的;
|
||||
app.component('AMessage', message);
|
||||
formCreate.use(install);
|
||||
app.use(formCreate);
|
||||
|
||||
@@ -83,7 +83,7 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'console/workflow/create',
|
||||
path: String.raw`workflow/create/:id(\d+)/:type(update|create)`,
|
||||
component: () => import('#/views/ai/workflow/form/index.vue'),
|
||||
name: 'AiWorkflowCreate',
|
||||
meta: {
|
||||
|
||||
@@ -51,7 +51,7 @@ const routes: RouteRecordRaw[] = [
|
||||
name: 'BpmFormEditor',
|
||||
component: () => import('#/views/bpm/form/designer/index.vue'),
|
||||
meta: {
|
||||
title: '编辑流程表单',
|
||||
title: '设计流程表单',
|
||||
activePath: '/bpm/manager/form',
|
||||
},
|
||||
props: (route) => {
|
||||
|
||||
@@ -31,6 +31,16 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () =>
|
||||
import('#/views/iot/device/device/modules/detail/index.vue'),
|
||||
},
|
||||
{
|
||||
path: 'ota/firmware/detail/:id',
|
||||
name: 'IoTOtaFirmwareDetail',
|
||||
meta: {
|
||||
title: '固件详情',
|
||||
activePath: '/iot/ota',
|
||||
},
|
||||
component: () =>
|
||||
import('#/views/iot/ota/modules/firmware-detail/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -18,7 +18,7 @@ const routes: RouteRecordRaw[] = [
|
||||
title: '商品添加',
|
||||
activePath: '/mall/product/spu',
|
||||
},
|
||||
component: () => import('#/views/mall/product/spu/modules/form.vue'),
|
||||
component: () => import('#/views/mall/product/spu/form/index.vue'),
|
||||
},
|
||||
{
|
||||
path: String.raw`spu/edit/:id(\d+)`,
|
||||
@@ -27,16 +27,16 @@ const routes: RouteRecordRaw[] = [
|
||||
title: '商品编辑',
|
||||
activePath: '/mall/product/spu',
|
||||
},
|
||||
component: () => import('#/views/mall/product/spu/modules/form.vue'),
|
||||
component: () => import('#/views/mall/product/spu/form/index.vue'),
|
||||
},
|
||||
{
|
||||
path: String.raw`spu/detail/:id(\d+)`,
|
||||
name: 'ProductSpuDetail',
|
||||
meta: {
|
||||
title: '商品详情',
|
||||
activePath: '/crm/business',
|
||||
activePath: '/mall/product/spu',
|
||||
},
|
||||
component: () => import('#/views/mall/product/spu/modules/detail.vue'),
|
||||
component: () => import('#/views/mall/product/spu/form/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -71,6 +71,40 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/diy',
|
||||
name: 'DiyCenter',
|
||||
meta: {
|
||||
title: '营销中心',
|
||||
icon: 'lucide:shopping-bag',
|
||||
keepAlive: true,
|
||||
hideInMenu: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: String.raw`template/decorate/:id(\d+)`,
|
||||
name: 'DiyTemplateDecorate',
|
||||
meta: {
|
||||
title: '模板装修',
|
||||
activePath: '/mall/promotion/diy-template/diy-template',
|
||||
},
|
||||
component: () =>
|
||||
import('#/views/mall/promotion/diy/template/decorate/index.vue'),
|
||||
},
|
||||
{
|
||||
path: 'page/decorate/:id',
|
||||
name: 'DiyPageDecorate',
|
||||
meta: {
|
||||
title: '页面装修',
|
||||
noCache: false,
|
||||
hidden: true,
|
||||
activePath: '/mall/promotion/diy-template/diy-page',
|
||||
},
|
||||
component: () =>
|
||||
import('#/views/mall/promotion/diy/page/decorate/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
|
||||
@@ -197,7 +197,7 @@ async function handleSubmit(values: Recordable<any>) {
|
||||
await smsResetPassword({ mobile, code, password });
|
||||
message.success($t('authentication.resetPasswordSuccess'));
|
||||
// 重置成功后跳转到首页
|
||||
router.push('/');
|
||||
await router.push('/');
|
||||
} catch (error) {
|
||||
console.error('重置密码失败:', error);
|
||||
} finally {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { authorize, getAuthorize } from '#/api/system/oauth2/open';
|
||||
|
||||
defineOptions({ name: 'SSOLogin' });
|
||||
|
||||
const { query } = useRoute(); // 路由参数
|
||||
const { query } = useRoute();
|
||||
|
||||
const client = ref({
|
||||
name: '',
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
getChatConversationMyList,
|
||||
updateChatConversationMy,
|
||||
} from '#/api/ai/chat/conversation';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import RoleRepository from '../role/RoleRepository.vue';
|
||||
|
||||
@@ -201,7 +202,8 @@ async function updateConversationTitle(
|
||||
if (
|
||||
filterConversationList.length > 0 &&
|
||||
filterConversationList[0] && // tip:避免切换对话
|
||||
activeConversationId.value === filterConversationList[0].id
|
||||
activeConversationId.value ===
|
||||
(filterConversationList[0].id as number)
|
||||
) {
|
||||
emits('onConversationClick', filterConversationList[0]);
|
||||
}
|
||||
@@ -249,7 +251,7 @@ async function handleClearConversation() {
|
||||
try {
|
||||
await confirm('确认后对话会全部清空,置顶的对话除外。');
|
||||
await deleteChatConversationMyByUnpinned();
|
||||
message.success('操作成功!');
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
// 清空 对话 和 对话内容
|
||||
activeConversationId.value = null;
|
||||
// 获取 对话列表
|
||||
@@ -305,7 +307,7 @@ onMounted(async () => {
|
||||
<template>
|
||||
<Layout.Sider
|
||||
width="280px"
|
||||
class="conversation-container relative flex h-full flex-col justify-between overflow-hidden p-4"
|
||||
class="relative flex h-full flex-col justify-between overflow-hidden p-4"
|
||||
>
|
||||
<Drawer />
|
||||
<!-- 左顶部:对话 -->
|
||||
@@ -328,7 +330,7 @@ onMounted(async () => {
|
||||
</Input>
|
||||
|
||||
<!-- 左中间:对话列表 -->
|
||||
<div class="conversation-list mt-2 flex-1 overflow-auto">
|
||||
<div class="mt-2 flex-1 overflow-auto">
|
||||
<!-- 情况一:加载中 -->
|
||||
<Empty v-if="loading" description="." v-loading="loading" />
|
||||
|
||||
@@ -336,11 +338,10 @@ onMounted(async () => {
|
||||
<div
|
||||
v-for="conversationKey in Object.keys(conversationMap)"
|
||||
:key="conversationKey"
|
||||
class=""
|
||||
>
|
||||
<div
|
||||
v-if="conversationMap[conversationKey].length > 0"
|
||||
class="conversation-item classify-title pt-2"
|
||||
class="classify-title pt-2"
|
||||
>
|
||||
<b class="mx-1">
|
||||
{{ conversationKey }}
|
||||
@@ -353,24 +354,24 @@ onMounted(async () => {
|
||||
@click="handleConversationClick(conversation.id)"
|
||||
@mouseover="hoverConversationId = conversation.id"
|
||||
@mouseout="hoverConversationId = null"
|
||||
class="conversation-item mt-1"
|
||||
class="mt-1"
|
||||
>
|
||||
<div
|
||||
class="conversation flex cursor-pointer flex-row items-center justify-between rounded-lg px-2 leading-10"
|
||||
class="flex cursor-pointer flex-row items-center justify-between rounded-lg px-2 leading-10"
|
||||
:class="[
|
||||
conversation.id === activeConversationId
|
||||
? 'bg-primary-200'
|
||||
? 'bg-success-600'
|
||||
: '',
|
||||
]"
|
||||
>
|
||||
<div class="title-wrapper flex items-center">
|
||||
<div class="flex items-center">
|
||||
<Avatar
|
||||
v-if="conversation.roleAvatar"
|
||||
:src="conversation.roleAvatar"
|
||||
/>
|
||||
<SvgGptIcon v-else class="size-8" />
|
||||
<span
|
||||
class="max-w-36 overflow-hidden text-ellipsis whitespace-nowrap p-2 text-sm font-normal text-gray-600"
|
||||
class="max-w-32 overflow-hidden text-ellipsis whitespace-nowrap p-2 text-sm font-normal"
|
||||
>
|
||||
{{ conversation.title }}
|
||||
</span>
|
||||
@@ -378,7 +379,7 @@ onMounted(async () => {
|
||||
|
||||
<div
|
||||
v-show="hoverConversationId === conversation.id"
|
||||
class="button-wrapper relative right-0.5 flex items-center text-gray-400"
|
||||
class="relative right-0.5 flex items-center text-gray-400"
|
||||
>
|
||||
<Button
|
||||
class="mr-0 px-1"
|
||||
|
||||
@@ -17,7 +17,7 @@ import Form from '../../../../model/chatRole/modules/form.vue';
|
||||
import RoleCategoryList from './RoleCategoryList.vue';
|
||||
import RoleList from './RoleList.vue';
|
||||
|
||||
const router = useRouter(); // 路由对象
|
||||
const router = useRouter();
|
||||
const [Drawer] = useVbenDrawer({
|
||||
title: '角色管理',
|
||||
footer: false,
|
||||
@@ -55,7 +55,7 @@ async function handleTabsClick(tab: any) {
|
||||
|
||||
/** 获取 my role 我的角色 */
|
||||
async function getMyRole(append?: boolean) {
|
||||
const params: AiModelChatRoleApi.ChatRolePageReq = {
|
||||
const params: AiModelChatRoleApi.ChatRolePageReqVO = {
|
||||
...myRoleParams,
|
||||
name: search.value,
|
||||
publicStatus: false,
|
||||
@@ -70,7 +70,7 @@ async function getMyRole(append?: boolean) {
|
||||
|
||||
/** 获取 public role 公共角色 */
|
||||
async function getPublicRole(append?: boolean) {
|
||||
const params: AiModelChatRoleApi.ChatRolePageReq = {
|
||||
const params: AiModelChatRoleApi.ChatRolePageReqVO = {
|
||||
...publicRoleParams,
|
||||
category: activeCategory.value === '全部' ? '' : activeCategory.value,
|
||||
name: search.value,
|
||||
|
||||
@@ -28,7 +28,7 @@ import MessageNewConversation from './components/message/MessageNewConversation.
|
||||
/** AI 聊天对话 列表 */
|
||||
defineOptions({ name: 'AiChat' });
|
||||
|
||||
const route = useRoute(); // 路由
|
||||
const route = useRoute();
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: ConversationUpdateForm,
|
||||
destroyOnClose: true,
|
||||
@@ -428,7 +428,7 @@ async function textRoll() {
|
||||
// 设置状态
|
||||
textRoleRunning.value = true;
|
||||
receiveMessageDisplayedText.value = '';
|
||||
const task = async () => {
|
||||
async function task() {
|
||||
// 调整速度
|
||||
const diff =
|
||||
(receiveMessageFullText.value.length -
|
||||
@@ -472,7 +472,7 @@ async function textRoll() {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
let timer = setTimeout(task, textSpeed.value);
|
||||
} catch {}
|
||||
}
|
||||
@@ -503,7 +503,7 @@ onMounted(async () => {
|
||||
<!-- 左侧:对话列表 -->
|
||||
<ConversationList
|
||||
class="!bg-card"
|
||||
:active-id="activeConversationId as any"
|
||||
:active-id="activeConversationId"
|
||||
ref="conversationListRef"
|
||||
@on-conversation-create="handleConversationCreateSuccess"
|
||||
@on-conversation-click="handleConversationClick"
|
||||
@@ -578,7 +578,7 @@ onMounted(async () => {
|
||||
class="border-border my-5 mb-5 mt-2 flex flex-col rounded-xl border px-2 py-2.5"
|
||||
>
|
||||
<textarea
|
||||
class="box-border h-24 resize-none overflow-auto border-none px-0 py-1 focus:outline-none"
|
||||
class="box-border h-24 resize-none overflow-auto rounded-md px-0 py-1 focus:outline-none"
|
||||
v-model="prompt"
|
||||
@keydown="handleSendByKeydown"
|
||||
@input="handlePromptInput"
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 关联数据 */
|
||||
let userList: SystemUserApi.User[] = [];
|
||||
getSimpleUserList().then((data) => (userList = data));
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchemaConversation(): VbenFormSchema[] {
|
||||
return [
|
||||
@@ -13,11 +18,19 @@ export function useGridFormSchemaConversation(): VbenFormSchema[] {
|
||||
fieldName: 'userId',
|
||||
label: '用户编号',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入用户编号',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'title',
|
||||
label: '聊天标题',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入聊天标题',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'createTime',
|
||||
@@ -49,7 +62,13 @@ export function useGridColumnsConversation(): VxeTableGridOptions['columns'] {
|
||||
{
|
||||
title: '用户',
|
||||
minWidth: 180,
|
||||
slots: { default: 'userId' },
|
||||
field: 'userId',
|
||||
formatter: ({ cellValue }) => {
|
||||
if (cellValue === 0) {
|
||||
return '系统';
|
||||
}
|
||||
return userList.find((user) => user.id === cellValue)?.nickname || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'roleName',
|
||||
@@ -103,15 +122,21 @@ export function useGridFormSchemaMessage(): VbenFormSchema[] {
|
||||
fieldName: 'conversationId',
|
||||
label: '对话编号',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入对话编号',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'userId',
|
||||
label: '用户编号',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
api: getSimpleUserList,
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择用户编号',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -144,7 +169,9 @@ export function useGridColumnsMessage(): VxeTableGridOptions['columns'] {
|
||||
{
|
||||
title: '用户',
|
||||
minWidth: 180,
|
||||
slots: { default: 'userId' },
|
||||
field: 'userId',
|
||||
formatter: ({ cellValue }) =>
|
||||
userList.find((user) => user.id === cellValue)?.nickname || '-',
|
||||
},
|
||||
{
|
||||
field: 'roleName',
|
||||
|
||||
@@ -3,10 +3,10 @@ import { ref } from 'vue';
|
||||
|
||||
import { DocAlert, Page } from '@vben/common-ui';
|
||||
|
||||
import { Card, Tabs } from 'ant-design-vue';
|
||||
import { Tabs } from 'ant-design-vue';
|
||||
|
||||
import ChatConversationList from './modules/ChatConversationList.vue';
|
||||
import ChatMessageList from './modules/ChatMessageList.vue';
|
||||
import ChatConversationList from './modules/conversation-list.vue';
|
||||
import ChatMessageList from './modules/message-list.vue';
|
||||
|
||||
const activeTabName = ref('conversation');
|
||||
</script>
|
||||
@@ -16,15 +16,14 @@ const activeTabName = ref('conversation');
|
||||
<template #doc>
|
||||
<DocAlert title="AI 对话聊天" url="https://doc.iocoder.cn/ai/chat/" />
|
||||
</template>
|
||||
<Card>
|
||||
<Tabs v-model:active-key="activeTabName">
|
||||
<Tabs.TabPane tab="对话列表" key="conversation">
|
||||
<ChatConversationList />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="消息列表" key="message">
|
||||
<ChatMessageList />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
|
||||
<Tabs v-model:active-key="activeTabName">
|
||||
<Tabs.TabPane tab="对话列表" key="conversation">
|
||||
<ChatConversationList />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="消息列表" key="message">
|
||||
<ChatMessageList />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { AiChatConversationApi } from '#/api/ai/chat/conversation';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
@@ -14,7 +11,6 @@ import {
|
||||
deleteChatConversationByAdmin,
|
||||
getChatConversationPage,
|
||||
} from '#/api/ai/chat/conversation';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import {
|
||||
@@ -22,23 +18,20 @@ import {
|
||||
useGridFormSchemaConversation,
|
||||
} from '../data';
|
||||
|
||||
const userList = ref<SystemUserApi.User[]>([]); // 用户列表
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
/** 删除对话 */
|
||||
async function handleDelete(row: AiChatConversationApi.ChatConversation) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await deleteChatConversationByAdmin(row.id as number);
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||
});
|
||||
await deleteChatConversationByAdmin(row.id!);
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
@@ -66,32 +59,19 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<AiChatConversationApi.ChatConversation>,
|
||||
separator: false,
|
||||
});
|
||||
onMounted(async () => {
|
||||
// 获得用户列表
|
||||
userList.value = await getSimpleUserList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<Grid table-title="对话列表">
|
||||
<template #toolbar-tools>
|
||||
<TableAction :actions="[]" />
|
||||
</template>
|
||||
<template #userId="{ row }">
|
||||
<span>
|
||||
{{ userList.find((item) => item.id === row.userId)?.nickname }}
|
||||
</span>
|
||||
<span v-if="row.userId === 0">系统</span>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
@@ -1,9 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { AiChatConversationApi } from '#/api/ai/chat/conversation';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
@@ -14,28 +11,24 @@ import {
|
||||
deleteChatMessageByAdmin,
|
||||
getChatMessagePage,
|
||||
} from '#/api/ai/chat/message';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useGridColumnsMessage, useGridFormSchemaMessage } from '../data';
|
||||
|
||||
const userList = ref<SystemUserApi.User[]>([]); // 用户列表
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
/** 删除消息 */
|
||||
async function handleDelete(row: AiChatConversationApi.ChatConversation) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await deleteChatMessageByAdmin(row.id as number);
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||
});
|
||||
await deleteChatMessageByAdmin(row.id!);
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
@@ -63,31 +56,19 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<AiChatConversationApi.ChatConversation>,
|
||||
separator: false,
|
||||
});
|
||||
onMounted(async () => {
|
||||
// 获得用户列表
|
||||
userList.value = await getSimpleUserList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<Grid table-title="消息列表">
|
||||
<template #toolbar-tools>
|
||||
<TableAction :actions="[]" />
|
||||
</template>
|
||||
<template #userId="{ row }">
|
||||
<span>{{
|
||||
userList.find((item) => item.id === row.userId)?.nickname
|
||||
}}</span>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
@@ -44,7 +44,7 @@ watch(
|
||||
|
||||
<template>
|
||||
<div class="mb-5 w-full overflow-hidden break-words">
|
||||
<div class="mt-2 text-gray-600">
|
||||
<div class="mt-2">
|
||||
<Image class="rounded-lg" :src="detail?.picUrl" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -52,7 +52,7 @@ watch(
|
||||
<!-- 时间 -->
|
||||
<div class="mb-5 w-full overflow-hidden break-words">
|
||||
<div class="text-lg font-bold">时间</div>
|
||||
<div class="mt-2 text-gray-600">
|
||||
<div class="mt-2">
|
||||
<div>
|
||||
提交时间:{{ formatDate(detail.createTime, 'yyyy-MM-dd HH:mm:ss') }}
|
||||
</div>
|
||||
@@ -65,7 +65,7 @@ watch(
|
||||
<!-- 模型 -->
|
||||
<div class="mb-5 w-full overflow-hidden break-words">
|
||||
<div class="text-lg font-bold">模型</div>
|
||||
<div class="mt-2 text-gray-600">
|
||||
<div class="mt-2">
|
||||
{{ detail.model }}({{ detail.height }}x{{ detail.width }})
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,7 +73,7 @@ watch(
|
||||
<!-- 提示词 -->
|
||||
<div class="mb-5 w-full overflow-hidden break-words">
|
||||
<div class="text-lg font-bold">提示词</div>
|
||||
<div class="mt-2 text-gray-600">
|
||||
<div class="mt-2">
|
||||
{{ detail.prompt }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -81,7 +81,7 @@ watch(
|
||||
<!-- 图片地址 -->
|
||||
<div class="mb-5 w-full overflow-hidden break-words">
|
||||
<div class="text-lg font-bold">图片地址</div>
|
||||
<div class="mt-2 text-gray-600">
|
||||
<div class="mt-2">
|
||||
{{ detail.picUrl }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,7 +95,7 @@ watch(
|
||||
class="mb-5 w-full overflow-hidden break-words"
|
||||
>
|
||||
<div class="text-lg font-bold">采样方法</div>
|
||||
<div class="mt-2 text-gray-600">
|
||||
<div class="mt-2">
|
||||
{{
|
||||
StableDiffusionSamplers.find(
|
||||
(item) => item.key === detail?.options?.sampler,
|
||||
@@ -112,7 +112,7 @@ watch(
|
||||
class="mb-5 w-full overflow-hidden break-words"
|
||||
>
|
||||
<div class="text-lg font-bold">CLIP</div>
|
||||
<div class="mt-2 text-gray-600">
|
||||
<div class="mt-2">
|
||||
{{
|
||||
StableDiffusionClipGuidancePresets.find(
|
||||
(item) => item.key === detail?.options?.clipGuidancePreset,
|
||||
@@ -129,7 +129,7 @@ watch(
|
||||
class="mb-5 w-full overflow-hidden break-words"
|
||||
>
|
||||
<div class="text-lg font-bold">风格</div>
|
||||
<div class="mt-2 text-gray-600">
|
||||
<div class="mt-2">
|
||||
{{
|
||||
StableDiffusionStylePresets.find(
|
||||
(item) => item.key === detail?.options?.stylePreset,
|
||||
@@ -146,7 +146,7 @@ watch(
|
||||
class="mb-5 w-full overflow-hidden break-words"
|
||||
>
|
||||
<div class="text-lg font-bold">迭代步数</div>
|
||||
<div class="mt-2 text-gray-600">{{ detail?.options?.steps }}</div>
|
||||
<div class="mt-2">{{ detail?.options?.steps }}</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -157,7 +157,7 @@ watch(
|
||||
class="mb-5 w-full overflow-hidden break-words"
|
||||
>
|
||||
<div class="text-lg font-bold">引导系数</div>
|
||||
<div class="mt-2 text-gray-600">{{ detail?.options?.scale }}</div>
|
||||
<div class="mt-2">{{ detail?.options?.scale }}</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -168,7 +168,7 @@ watch(
|
||||
class="mb-5 w-full overflow-hidden break-words"
|
||||
>
|
||||
<div class="text-lg font-bold">随机因子</div>
|
||||
<div class="mt-2 text-gray-600">{{ detail?.options?.seed }}</div>
|
||||
<div class="mt-2">{{ detail?.options?.seed }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Dall3 专属 -->
|
||||
@@ -177,7 +177,7 @@ watch(
|
||||
class="mb-5 w-full overflow-hidden break-words"
|
||||
>
|
||||
<div class="text-lg font-bold">风格选择</div>
|
||||
<div class="mt-2 text-gray-600">
|
||||
<div class="mt-2">
|
||||
{{
|
||||
Dall3StyleList.find((item) => item.key === detail?.options?.style)?.name
|
||||
}}
|
||||
@@ -192,7 +192,7 @@ watch(
|
||||
class="mb-5 w-full overflow-hidden break-words"
|
||||
>
|
||||
<div class="text-lg font-bold">模型版本</div>
|
||||
<div class="mt-2 text-gray-600">{{ detail?.options?.version }}</div>
|
||||
<div class="mt-2">{{ detail?.options?.version }}</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -203,7 +203,7 @@ watch(
|
||||
class="mb-5 w-full overflow-hidden break-words"
|
||||
>
|
||||
<div class="text-lg font-bold">参考图</div>
|
||||
<div class="mt-2 text-gray-600">
|
||||
<div class="mt-2">
|
||||
<Image :src="detail.options.referImageUrl" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,7 @@ import ImageDetail from './ImageDetail.vue';
|
||||
// 暴露组件方法
|
||||
|
||||
const emits = defineEmits(['onRegeneration']);
|
||||
const router = useRouter(); // 路由
|
||||
const router = useRouter();
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
title: '图片详情',
|
||||
footer: false,
|
||||
|
||||
@@ -47,15 +47,15 @@ const platformOptions = [
|
||||
const models = ref<AiModelModelApi.Model[]>([]); // 模型列表
|
||||
|
||||
/** 绘画 start */
|
||||
const handleDrawStart = async () => {};
|
||||
async function handleDrawStart() {}
|
||||
|
||||
/** 绘画 complete */
|
||||
const handleDrawComplete = async () => {
|
||||
async function handleDrawComplete() {
|
||||
await imageListRef.value.getImageList();
|
||||
};
|
||||
}
|
||||
|
||||
/** 重新生成:将画图详情填充到对应平台 */
|
||||
const handleRegeneration = async (image: AiImageApi.Image) => {
|
||||
async function handleRegeneration(image: AiImageApi.Image) {
|
||||
// 切换平台
|
||||
selectPlatform.value = image.platform;
|
||||
// 根据不同平台填充 image
|
||||
@@ -79,7 +79,7 @@ const handleRegeneration = async (image: AiImageApi.Image) => {
|
||||
// No default
|
||||
}
|
||||
// TODO @fan:貌似 other 重新设置不行?
|
||||
};
|
||||
}
|
||||
|
||||
/** 组件挂载的时候 */
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
@@ -7,6 +8,13 @@ import { getDictOptions } from '@vben/hooks';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
let userList: SystemUserApi.User[] = [];
|
||||
async function getUserData() {
|
||||
userList = await getSimpleUserList();
|
||||
}
|
||||
|
||||
getUserData();
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
@@ -15,9 +23,11 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '用户编号',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
api: getSimpleUserList,
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择用户编号',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -25,6 +35,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '平台',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
placeholder: '请选择平台',
|
||||
allowClear: true,
|
||||
options: getDictOptions(DICT_TYPE.AI_PLATFORM, 'string'),
|
||||
},
|
||||
@@ -34,6 +45,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '绘画状态',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
placeholder: '请选择绘画状态',
|
||||
allowClear: true,
|
||||
options: getDictOptions(DICT_TYPE.AI_IMAGE_STATUS, 'number'),
|
||||
},
|
||||
@@ -43,8 +55,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '是否发布',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
|
||||
placeholder: '请选择是否发布',
|
||||
allowClear: true,
|
||||
options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -60,7 +73,12 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
}
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
export function useGridColumns(
|
||||
onPublicStatusChange?: (
|
||||
newStatus: boolean,
|
||||
row: any,
|
||||
) => PromiseLike<boolean | undefined>,
|
||||
): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
@@ -69,15 +87,20 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
field: 'picUrl',
|
||||
title: '图片',
|
||||
minWidth: 110,
|
||||
fixed: 'left',
|
||||
slots: { default: 'picUrl' },
|
||||
cellRender: {
|
||||
name: 'CellImage',
|
||||
},
|
||||
},
|
||||
{
|
||||
minWidth: 180,
|
||||
field: 'userId',
|
||||
title: '用户',
|
||||
slots: { default: 'userId' },
|
||||
minWidth: 180,
|
||||
formatter: ({ cellValue }) =>
|
||||
userList.find((user) => user.id === cellValue)?.nickname || '-',
|
||||
},
|
||||
{
|
||||
field: 'platform',
|
||||
@@ -105,7 +128,16 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
{
|
||||
minWidth: 100,
|
||||
title: '是否发布',
|
||||
slots: { default: 'publicStatus' },
|
||||
field: 'publicStatus',
|
||||
align: 'center',
|
||||
cellRender: {
|
||||
attrs: { beforeChange: onPublicStatusChange },
|
||||
name: 'CellSwitch',
|
||||
props: {
|
||||
checkedValue: true,
|
||||
unCheckedValue: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'prompt',
|
||||
|
||||
@@ -1,29 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { AiImageApi } from '#/api/ai/image';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { confirm, DocAlert, Page } from '@vben/common-ui';
|
||||
import { AiImageStatusEnum } from '@vben/constants';
|
||||
|
||||
import { Image, message, Switch } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { deleteImage, getImagePage, updateImage } from '#/api/ai/image';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
const userList = ref<SystemUserApi.User[]>([]); // 用户列表
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
/** 删除图片 */
|
||||
async function handleDelete(row: AiImageApi.Image) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||
@@ -31,36 +25,45 @@ async function handleDelete(row: AiImageApi.Image) {
|
||||
});
|
||||
try {
|
||||
await deleteImage(row.id as number);
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||
});
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
/** 修改是否发布 */
|
||||
const handleUpdatePublicStatusChange = async (row: AiImageApi.Image) => {
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const text = row.publicStatus ? '公开' : '私有';
|
||||
await confirm(`确认要"${text}"该图片吗?`).then(async () => {
|
||||
await updateImage({
|
||||
id: row.id,
|
||||
publicStatus: row.publicStatus,
|
||||
async function handleUpdatePublicStatusChange(
|
||||
newStatus: boolean,
|
||||
row: AiImageApi.Image,
|
||||
): Promise<boolean | undefined> {
|
||||
const text = newStatus ? '公开' : '私有';
|
||||
return new Promise((resolve, reject) => {
|
||||
confirm({
|
||||
content: `确认要将该图片切换为【${text}】吗?`,
|
||||
})
|
||||
.then(async () => {
|
||||
// 更新图片状态
|
||||
await updateImage({
|
||||
id: row.id,
|
||||
publicStatus: newStatus,
|
||||
});
|
||||
// 提示并返回成功
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
resolve(true);
|
||||
})
|
||||
.catch(() => {
|
||||
reject(new Error('取消操作'));
|
||||
});
|
||||
handleRefresh();
|
||||
});
|
||||
} catch {
|
||||
row.publicStatus = !row.publicStatus;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(),
|
||||
columns: useGridColumns(handleUpdatePublicStatusChange),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
@@ -76,6 +79,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
@@ -83,10 +87,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
} as VxeTableGridOptions<AiImageApi.Image>,
|
||||
});
|
||||
onMounted(async () => {
|
||||
// 获得下拉数据
|
||||
userList.value = await getSimpleUserList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -95,27 +95,6 @@ onMounted(async () => {
|
||||
<DocAlert title="AI 绘图创作" url="https://doc.iocoder.cn/ai/image/" />
|
||||
</template>
|
||||
<Grid table-title="绘画管理列表">
|
||||
<template #toolbar-tools>
|
||||
<TableAction :actions="[]" />
|
||||
</template>
|
||||
<template #picUrl="{ row }">
|
||||
<Image :src="row.picUrl" class="h-20 w-20" />
|
||||
</template>
|
||||
<template #userId="{ row }">
|
||||
<span>
|
||||
{{
|
||||
userList.find((item: SystemUserApi.User) => item.id === row.userId)
|
||||
?.nickname
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
<template #publicStatus="{ row }">
|
||||
<Switch
|
||||
v-model:checked="row.publicStatus"
|
||||
@change="handleUpdatePublicStatusChange(row)"
|
||||
:disabled="row.status !== AiImageStatusEnum.SUCCESS"
|
||||
/>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
|
||||
@@ -33,10 +33,10 @@ async function getList() {
|
||||
}
|
||||
const debounceGetList = useDebounceFn(getList, 80);
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
function handleQuery() {
|
||||
queryParams.pageNo = 1;
|
||||
getList();
|
||||
};
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -20,8 +20,8 @@ import ProcessStep from './ProcessStep.vue';
|
||||
import SplitStep from './SplitStep.vue';
|
||||
import UploadStep from './UploadStep.vue';
|
||||
|
||||
const route = useRoute(); // 路由
|
||||
const router = useRouter(); // 路由
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// 组件引用
|
||||
const uploadDocumentRef = ref();
|
||||
|
||||
@@ -25,8 +25,8 @@ import { useGridColumns, useGridFormSchema } from './data';
|
||||
defineOptions({ name: 'AiKnowledgeDocument' });
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
const route = useRoute(); // 路由
|
||||
const router = useRouter(); // 路由
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
gridApi.query();
|
||||
@@ -65,16 +65,16 @@ async function handleDelete(row: AiKnowledgeDocumentApi.KnowledgeDocument) {
|
||||
}
|
||||
}
|
||||
/** 跳转到知识库分段页面 */
|
||||
const handleSegment = (id: number) => {
|
||||
function handleSegment(id: number) {
|
||||
router.push({
|
||||
name: 'AiKnowledgeSegment',
|
||||
query: { documentId: id },
|
||||
});
|
||||
};
|
||||
}
|
||||
/** 修改是否发布 */
|
||||
const handleStatusChange = async (
|
||||
async function handleStatusChange(
|
||||
row: AiKnowledgeDocumentApi.KnowledgeDocument,
|
||||
) => {
|
||||
) {
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const text = row.status ? '启用' : '禁用';
|
||||
@@ -91,7 +91,7 @@ const handleStatusChange = async (
|
||||
? CommonStatusEnum.DISABLE
|
||||
: CommonStatusEnum.ENABLE;
|
||||
}
|
||||
};
|
||||
}
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
|
||||
@@ -20,8 +20,8 @@ import { searchKnowledgeSegment } from '#/api/ai/knowledge/segment';
|
||||
/** 文档召回测试 */
|
||||
defineOptions({ name: 'KnowledgeDocumentRetrieval' });
|
||||
|
||||
const route = useRoute(); // 路由
|
||||
const router = useRouter(); // 路由
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const loading = ref(false); // 加载状态
|
||||
const segments = ref<any[]>([]); // 召回结果
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { AiKnowledgeKnowledgeApi } from '#/api/ai/knowledge/knowledge';
|
||||
import type { AiKnowledgeSegmentApi } from '#/api/ai/knowledge/segment';
|
||||
|
||||
import { onMounted } from 'vue';
|
||||
@@ -41,12 +40,12 @@ function handleCreate() {
|
||||
}
|
||||
|
||||
/** 编辑 */
|
||||
function handleEdit(row: AiKnowledgeKnowledgeApi.Knowledge) {
|
||||
function handleEdit(row: AiKnowledgeSegmentApi.KnowledgeSegment) {
|
||||
formModalApi.setData(row).open();
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
async function handleDelete(row: AiKnowledgeKnowledgeApi.Knowledge) {
|
||||
async function handleDelete(row: AiKnowledgeSegmentApi.KnowledgeSegment) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||
duration: 0,
|
||||
@@ -88,7 +87,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<AiKnowledgeKnowledgeApi.Knowledge>,
|
||||
} as VxeTableGridOptions<AiKnowledgeSegmentApi.KnowledgeSegment>,
|
||||
});
|
||||
|
||||
/** 修改是否发布 */
|
||||
|
||||
@@ -3,13 +3,15 @@ import type { AiMindmapApi } from '#/api/ai/mindmap';
|
||||
|
||||
import { nextTick, onMounted, ref } from 'vue';
|
||||
|
||||
import { alert, Page } from '@vben/common-ui';
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { MindMapContentExample } from '@vben/constants';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { generateMindMap } from '#/api/ai/mindmap';
|
||||
|
||||
import Left from './modules/Left.vue';
|
||||
import Right from './modules/Right.vue';
|
||||
import Left from './modules/left.vue';
|
||||
import Right from './modules/right.vue';
|
||||
|
||||
const ctrl = ref<AbortController>(); // 请求控制
|
||||
const isGenerating = ref(false); // 是否正在生成思维导图
|
||||
@@ -26,8 +28,9 @@ function directGenerate(existPrompt: string) {
|
||||
generatedContent.value = existPrompt;
|
||||
isEnd.value = true;
|
||||
}
|
||||
|
||||
/** 提交生成 */
|
||||
function submit(data: AiMindmapApi.AiMindMapGenerateReq) {
|
||||
function handleSubmit(data: AiMindmapApi.AiMindMapGenerateReqVO) {
|
||||
isGenerating.value = true;
|
||||
isStart.value = true;
|
||||
isEnd.value = false;
|
||||
@@ -38,8 +41,8 @@ function submit(data: AiMindmapApi.AiMindMapGenerateReq) {
|
||||
onMessage: async (res: any) => {
|
||||
const { code, data, msg } = JSON.parse(res.data);
|
||||
if (code !== 0) {
|
||||
alert(`生成思维导图异常! ${msg}`);
|
||||
stopStream();
|
||||
message.error(`生成思维导图异常! ${msg}`);
|
||||
handleStopStream();
|
||||
return;
|
||||
}
|
||||
generatedContent.value = generatedContent.value + data;
|
||||
@@ -49,19 +52,20 @@ function submit(data: AiMindmapApi.AiMindMapGenerateReq) {
|
||||
onClose() {
|
||||
isEnd.value = true;
|
||||
leftRef.value?.setGeneratedContent(generatedContent.value);
|
||||
stopStream();
|
||||
handleStopStream();
|
||||
},
|
||||
onError(err) {
|
||||
console.error('生成思维导图失败', err);
|
||||
stopStream();
|
||||
handleStopStream();
|
||||
// 需要抛出异常,禁止重试
|
||||
throw err;
|
||||
},
|
||||
ctrl: ctrl.value,
|
||||
});
|
||||
}
|
||||
|
||||
/** 停止 stream 生成 */
|
||||
function stopStream() {
|
||||
function handleStopStream() {
|
||||
isGenerating.value = false;
|
||||
isStart.value = false;
|
||||
ctrl.value?.abort();
|
||||
@@ -80,7 +84,7 @@ onMounted(() => {
|
||||
ref="leftRef"
|
||||
class="mr-4"
|
||||
:is-generating="isGenerating"
|
||||
@submit="submit"
|
||||
@submit="handleSubmit"
|
||||
@direct-generate="directGenerate"
|
||||
/>
|
||||
<Right
|
||||
|
||||
@@ -8,7 +8,9 @@ import { Button, Textarea } from 'ant-design-vue';
|
||||
defineProps<{
|
||||
isGenerating: boolean;
|
||||
}>();
|
||||
|
||||
const emits = defineEmits(['submit', 'directGenerate']);
|
||||
|
||||
const formData = reactive({
|
||||
prompt: '',
|
||||
});
|
||||
@@ -27,7 +29,7 @@ defineExpose({
|
||||
<h3 class="text-primary h-7 w-full text-center text-xl leading-7">
|
||||
思维导图创作中心
|
||||
</h3>
|
||||
<div class="flex-grow overflow-y-auto">
|
||||
<div class="mt-4 flex-grow overflow-y-auto">
|
||||
<div>
|
||||
<b>您的需求?</b>
|
||||
<Textarea
|
||||
@@ -18,6 +18,7 @@ const props = defineProps<{
|
||||
isGenerating: boolean; // 是否正在生成
|
||||
isStart: boolean; // 开始状态,开始时需要清除 html
|
||||
}>();
|
||||
|
||||
const md = MarkdownIt();
|
||||
const contentRef = ref<HTMLDivElement>(); // 右侧出来 header 以下的区域
|
||||
const mdContainerRef = ref<HTMLDivElement>(); // markdown 的容器,用来滚动到底下的
|
||||
@@ -30,12 +31,14 @@ let markMap: Markmap | null = null;
|
||||
const transformer = new Transformer();
|
||||
let resizeObserver: null | ResizeObserver = null;
|
||||
const initialized = false;
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
contentAreaHeight.value = contentRef.value?.clientHeight || 0;
|
||||
// 先更新高度,再更新思维导图
|
||||
if (contentAreaHeight.value && !initialized) {
|
||||
/** 初始化思维导图 */
|
||||
// 初始化思维导图
|
||||
try {
|
||||
if (!markMap) {
|
||||
markMap = Markmap.create(svgRef.value!);
|
||||
@@ -52,11 +55,15 @@ onMounted(() => {
|
||||
resizeObserver.observe(contentRef.value);
|
||||
}
|
||||
});
|
||||
|
||||
/** 卸载 */
|
||||
onBeforeUnmount(() => {
|
||||
if (resizeObserver && contentRef.value) {
|
||||
resizeObserver.unobserve(contentRef.value);
|
||||
}
|
||||
});
|
||||
|
||||
/** 监听 props 变化 */
|
||||
watch(props, ({ generatedContent, isGenerating, isEnd, isStart }) => {
|
||||
// 开始生成的时候清空一下 markdown 的内容
|
||||
if (isStart) {
|
||||
@@ -73,7 +80,7 @@ watch(props, ({ generatedContent, isGenerating, isEnd, isStart }) => {
|
||||
});
|
||||
|
||||
/** 更新思维导图的展示 */
|
||||
const update = () => {
|
||||
function update() {
|
||||
try {
|
||||
const { root } = transformer.transform(
|
||||
processContent(props.generatedContent),
|
||||
@@ -83,7 +90,8 @@ const update = () => {
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 处理内容 */
|
||||
function processContent(text: string) {
|
||||
const arr: string[] = [];
|
||||
@@ -98,6 +106,7 @@ function processContent(text: string) {
|
||||
}
|
||||
return arr.join('\n');
|
||||
}
|
||||
|
||||
/** 下载图片:download SVG to png file */
|
||||
function downloadImage() {
|
||||
const svgElement = mindMapRef.value;
|
||||
@@ -112,6 +121,7 @@ function downloadImage() {
|
||||
drawWithImageSize: false,
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
scrollBottom() {
|
||||
mdContainerRef.value?.scrollTo(0, mdContainerRef.value?.scrollHeight);
|
||||
@@ -135,7 +145,6 @@ defineExpose({
|
||||
</div>
|
||||
</template>
|
||||
<div ref="contentRef" class="hide-scroll-bar box-border h-full">
|
||||
<!--展示 markdown 的容器,最终生成的是 html 字符串,直接用 v-html 嵌入-->
|
||||
<div
|
||||
v-if="isGenerating"
|
||||
ref="mdContainerRef"
|
||||
@@ -146,7 +155,6 @@ defineExpose({
|
||||
v-html="html"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div ref="mindMapRef" class="wh-full">
|
||||
<svg
|
||||
ref="svgRef"
|
||||
@@ -1,9 +1,14 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 关联数据 */
|
||||
let userList: SystemUserApi.User[] = [];
|
||||
getSimpleUserList().then((data) => (userList = data));
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
@@ -12,15 +17,21 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '用户编号',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
api: getSimpleUserList,
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择用户',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'prompt',
|
||||
label: '提示词',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入提示词',
|
||||
clearable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'createTime',
|
||||
@@ -44,9 +55,11 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
minWidth: 180,
|
||||
field: 'userId',
|
||||
title: '用户',
|
||||
slots: { default: 'userId' },
|
||||
minWidth: 180,
|
||||
formatter: ({ cellValue }) =>
|
||||
userList.find((user) => user.id === cellValue)?.nickname || '-',
|
||||
},
|
||||
{
|
||||
field: 'prompt',
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { AiMindmapApi } from '#/api/ai/mindmap';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { nextTick, onMounted, ref } from 'vue';
|
||||
|
||||
import { DocAlert, Page, useVbenDrawer } from '@vben/common-ui';
|
||||
|
||||
@@ -11,26 +8,23 @@ import { message } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { deleteMindMap, getMindMapPage } from '#/api/ai/mindmap';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import Right from '../index/modules/Right.vue';
|
||||
import Right from '../index/modules/right.vue';
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
const userList = ref<SystemUserApi.User[]>([]); // 用户列表
|
||||
const previewVisible = ref(false); // drawer 的显示隐藏
|
||||
const previewContent = ref('');
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
header: false,
|
||||
footer: false,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
/** 删除思维导图记录 */
|
||||
async function handleDelete(row: AiMindmapApi.MindMap) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||
@@ -38,14 +32,18 @@ async function handleDelete(row: AiMindmapApi.MindMap) {
|
||||
});
|
||||
try {
|
||||
await deleteMindMap(row.id as number);
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||
});
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
/** 预览思维导图 */
|
||||
async function openPreview(row: AiMindmapApi.MindMap) {
|
||||
drawerApi.setData(row.generatedContent).open();
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
@@ -67,6 +65,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
@@ -74,17 +73,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
} as VxeTableGridOptions<AiMindmapApi.MindMap>,
|
||||
});
|
||||
async function openPreview(row: AiMindmapApi.MindMap) {
|
||||
previewVisible.value = false;
|
||||
drawerApi.open();
|
||||
await nextTick();
|
||||
previewVisible.value = true;
|
||||
previewContent.value = row.generatedContent;
|
||||
}
|
||||
onMounted(async () => {
|
||||
// 获得下拉数据
|
||||
userList.value = await getSimpleUserList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -92,27 +80,16 @@ onMounted(async () => {
|
||||
<template #doc>
|
||||
<DocAlert title="AI 思维导图" url="https://doc.iocoder.cn/ai/mindmap/" />
|
||||
</template>
|
||||
|
||||
<Drawer class="w-3/5">
|
||||
<Right
|
||||
v-if="previewVisible"
|
||||
:generated-content="previewContent"
|
||||
:generated-content="drawerApi.getData() as any"
|
||||
:is-end="true"
|
||||
:is-generating="false"
|
||||
:is-start="false"
|
||||
/>
|
||||
</Drawer>
|
||||
<Grid table-title="思维导图管理列表">
|
||||
<template #toolbar-tools>
|
||||
<TableAction :actions="[]" />
|
||||
</template>
|
||||
<template #userId="{ row }">
|
||||
<span>
|
||||
{{
|
||||
userList.find((item: SystemUserApi.User) => item.id === row.userId)
|
||||
?.nickname
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
@@ -121,6 +98,7 @@ onMounted(async () => {
|
||||
type: 'link',
|
||||
icon: ACTION_ICON.EDIT,
|
||||
auth: ['ai:api-key:update'],
|
||||
disabled: !row.generatedContent,
|
||||
onClick: openPreview.bind(null, row),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
@@ -25,24 +26,33 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
options: getDictOptions(DICT_TYPE.AI_PLATFORM, 'string'),
|
||||
allowClear: true,
|
||||
},
|
||||
rules: z.string().min(1, { message: '请输入平台' }),
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'name',
|
||||
label: '名称',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'apiKey',
|
||||
label: '密钥',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入密钥',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'url',
|
||||
label: '自定义 API URL',
|
||||
componentProps: {
|
||||
placeholder: '请输入自定义 API URL',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
@@ -65,6 +75,10 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
fieldName: 'name',
|
||||
label: '名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入名称',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'platform',
|
||||
@@ -72,6 +86,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请选择平台',
|
||||
options: getDictOptions(DICT_TYPE.AI_PLATFORM, 'string'),
|
||||
},
|
||||
},
|
||||
@@ -81,6 +96,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请选择状态',
|
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||
},
|
||||
},
|
||||
@@ -97,18 +113,22 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.AI_PLATFORM },
|
||||
},
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: '名称',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'apiKey',
|
||||
title: '密钥',
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
field: 'url',
|
||||
title: '自定义 API URL',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
@@ -117,6 +137,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.COMMON_STATUS },
|
||||
},
|
||||
minWidth: 80,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
||||
@@ -23,27 +23,25 @@ function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 创建 */
|
||||
/** 创建 API 密钥 */
|
||||
function handleCreate() {
|
||||
formModalApi.setData(null).open();
|
||||
}
|
||||
|
||||
/** 编辑 */
|
||||
/** 编辑 API 密钥 */
|
||||
function handleEdit(row: AiModelApiKeyApi.ApiKey) {
|
||||
formModalApi.setData(row).open();
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
/** 删除 API 密钥 */
|
||||
async function handleDelete(row: AiModelApiKeyApi.ApiKey) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await deleteApiKey(row.id as number);
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||
});
|
||||
await deleteApiKey(row.id!);
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
@@ -71,6 +69,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
|
||||
@@ -27,7 +27,7 @@ const [Form, formApi] = useVbenForm({
|
||||
class: 'w-full',
|
||||
},
|
||||
formItemClass: 'col-span-2',
|
||||
labelWidth: 100,
|
||||
labelWidth: 120,
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema: useFormSchema(),
|
||||
@@ -76,7 +76,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-2/5" :title="getTitle">
|
||||
<Modal :title="getTitle" class="w-2/5">
|
||||
<Form class="mx-4" />
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { z } from '#/adapter/form';
|
||||
import { getSimpleKnowledgeList } from '#/api/ai/knowledge/knowledge';
|
||||
import { getModelSimpleList } from '#/api/ai/model/model';
|
||||
import { getToolSimpleList } from '#/api/ai/model/tool';
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
@@ -32,6 +33,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
fieldName: 'name',
|
||||
label: '角色名称',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入角色名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'ImageUpload',
|
||||
@@ -62,6 +66,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
fieldName: 'category',
|
||||
label: '角色类别',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入角色类别',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['formType'],
|
||||
show: (values) => {
|
||||
@@ -93,7 +100,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
placeholder: '请选择引用知识库',
|
||||
api: () => getSimpleKnowledgeList(),
|
||||
api: getSimpleKnowledgeList,
|
||||
labelField: 'name',
|
||||
mode: 'multiple',
|
||||
valueField: 'id',
|
||||
@@ -106,13 +113,24 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
placeholder: '请选择引用工具',
|
||||
api: () => getToolSimpleList(),
|
||||
api: getToolSimpleList,
|
||||
mode: 'multiple',
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'mcpClientNames',
|
||||
label: '引用 MCP',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
placeholder: '请选择 MCP',
|
||||
options: getDictOptions(DICT_TYPE.AI_MCP_CLIENT_NAME, 'string'),
|
||||
mode: 'multiple',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'publicStatus',
|
||||
label: '是否公开',
|
||||
@@ -137,7 +155,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: '请输入角色排序',
|
||||
class: 'w-full',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['formType'],
|
||||
@@ -209,8 +226,15 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
},
|
||||
{
|
||||
title: '角色头像',
|
||||
slots: { default: 'avatar' },
|
||||
field: 'avatar',
|
||||
minWidth: 140,
|
||||
cellRender: {
|
||||
name: 'CellImage',
|
||||
props: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '角色类别',
|
||||
@@ -229,13 +253,33 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
},
|
||||
{
|
||||
title: '知识库',
|
||||
slots: { default: 'knowledgeIds' },
|
||||
field: 'knowledgeIds',
|
||||
minWidth: 100,
|
||||
formatter: ({ cellValue }) => {
|
||||
return !cellValue || cellValue.length === 0
|
||||
? '-'
|
||||
: `引用${cellValue.length}个`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '工具',
|
||||
slots: { default: 'toolIds' },
|
||||
field: 'toolIds',
|
||||
minWidth: 100,
|
||||
formatter: ({ cellValue }) => {
|
||||
return !cellValue || cellValue.length === 0
|
||||
? '-'
|
||||
: `引用${cellValue.length}个`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'MCP',
|
||||
field: 'mcpClientNames',
|
||||
minWidth: 100,
|
||||
formatter: ({ cellValue }) => {
|
||||
return !cellValue || cellValue.length === 0
|
||||
? '-'
|
||||
: `引用${cellValue.length}个`;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'publicStatus',
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { AiModelChatRoleApi } from '#/api/ai/model/chatRole';
|
||||
|
||||
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Image, message } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { deleteChatRole, getChatRolePage } from '#/api/ai/model/chatRole';
|
||||
@@ -23,27 +23,25 @@ function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 创建 */
|
||||
/** 创建聊天角色 */
|
||||
function handleCreate() {
|
||||
formModalApi.setData({ formType: 'create' }).open();
|
||||
}
|
||||
|
||||
/** 编辑 */
|
||||
/** 编辑聊天角色 */
|
||||
function handleEdit(row: AiModelChatRoleApi.ChatRole) {
|
||||
formModalApi.setData({ formType: 'update', ...row }).open();
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
/** 删除聊天角色 */
|
||||
async function handleDelete(row: AiModelChatRoleApi.ChatRole) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await deleteChatRole(row.id as number);
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||
});
|
||||
await deleteChatRole(row.id!);
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
@@ -71,6 +69,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
@@ -100,17 +99,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #knowledgeIds="{ row }">
|
||||
<span v-if="!row.knowledgeIds || row.knowledgeIds.length === 0">-</span>
|
||||
<span v-else>引用 {{ row.knowledgeIds.length }} 个</span>
|
||||
</template>
|
||||
<template #toolIds="{ row }">
|
||||
<span v-if="!row.toolIds || row.toolIds.length === 0">-</span>
|
||||
<span v-else>引用 {{ row.toolIds.length }} 个</span>
|
||||
</template>
|
||||
<template #avatar="{ row }">
|
||||
<Image :src="row.avatar" class="h-8 w-8" />
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
|
||||
@@ -82,7 +82,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-2/5" :title="getTitle">
|
||||
<Modal :title="getTitle" class="w-2/5">
|
||||
<Form class="mx-4" />
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { AiModelApiKeyApi } from '#/api/ai/model/apiKey';
|
||||
|
||||
import { AiModelTypeEnum, CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { getApiKeySimpleList } from '#/api/ai/model/apiKey';
|
||||
|
||||
/** 关联数据 */
|
||||
let apiKeyList: AiModelApiKeyApi.ApiKey[] = [];
|
||||
getApiKeySimpleList().then((data) => (apiKeyList = data));
|
||||
|
||||
/** 新增/修改的表单 */
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
@@ -26,7 +32,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
options: getDictOptions(DICT_TYPE.AI_PLATFORM, 'string'),
|
||||
allowClear: true,
|
||||
},
|
||||
rules: z.string().min(1, { message: '请输入平台' }),
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
fieldName: 'type',
|
||||
@@ -47,8 +53,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: 'API 秘钥',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
placeholder: '请选择API 秘钥',
|
||||
api: () => getApiKeySimpleList(),
|
||||
placeholder: '请选择 API 秘钥',
|
||||
api: getApiKeySimpleList,
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
allowClear: true,
|
||||
@@ -60,12 +66,18 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
fieldName: 'name',
|
||||
label: '模型名字',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入模型名字',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'model',
|
||||
label: '模型标识',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入模型标识',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'sort',
|
||||
@@ -73,7 +85,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: '请输入模型排序',
|
||||
class: 'w-full',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
@@ -94,7 +105,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: '请输入温度参数',
|
||||
class: 'w-full',
|
||||
min: 0,
|
||||
max: 2,
|
||||
},
|
||||
@@ -114,7 +124,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
min: 0,
|
||||
max: 8192,
|
||||
placeholder: '请输入回复数 Token 数',
|
||||
class: 'w-full',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['type'],
|
||||
@@ -132,7 +141,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
min: 0,
|
||||
max: 20,
|
||||
placeholder: '请输入上下文数量',
|
||||
class: 'w-full',
|
||||
},
|
||||
dependencies: {
|
||||
triggerFields: ['type'],
|
||||
@@ -152,16 +160,28 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
fieldName: 'name',
|
||||
label: '模型名字',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入模型名字',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'model',
|
||||
label: '模型标识',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入模型标识',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'platform',
|
||||
label: '模型平台',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入模型平台',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -199,7 +219,12 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
},
|
||||
{
|
||||
title: 'API 秘钥',
|
||||
slots: { default: 'keyId' },
|
||||
field: 'keyId',
|
||||
formatter: ({ cellValue }) => {
|
||||
return (
|
||||
apiKeyList.find((apiKey) => apiKey.id === cellValue)?.name || '-'
|
||||
);
|
||||
},
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
@@ -219,7 +244,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
{
|
||||
field: 'temperature',
|
||||
title: '温度参数',
|
||||
minWidth: 80,
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
title: '回复数 Token 数',
|
||||
@@ -229,7 +254,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
{
|
||||
title: '上下文数量',
|
||||
field: 'maxContexts',
|
||||
minWidth: 100,
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { AiModelApiKeyApi } from '#/api/ai/model/apiKey';
|
||||
import type { AiModelModelApi } from '#/api/ai/model/model';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getApiKeySimpleList } from '#/api/ai/model/apiKey';
|
||||
import { deleteModel, getModelPage } from '#/api/ai/model/model';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import Form from './modules/form.vue';
|
||||
|
||||
const apiKeyList = ref([] as AiModelApiKeyApi.ApiKey[]);
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
@@ -28,27 +23,25 @@ function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 创建 */
|
||||
/** 创建模型配置 */
|
||||
function handleCreate() {
|
||||
formModalApi.setData(null).open();
|
||||
}
|
||||
|
||||
/** 编辑 */
|
||||
/** 编辑模型配置 */
|
||||
function handleEdit(row: AiModelModelApi.Model) {
|
||||
formModalApi.setData(row).open();
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
/** 删除模型配置 */
|
||||
async function handleDelete(row: AiModelModelApi.Model) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await deleteModel(row.id as number);
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||
});
|
||||
await deleteModel(row.id!);
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
@@ -76,6 +69,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
@@ -83,10 +77,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
} as VxeTableGridOptions<AiModelModelApi.Model>,
|
||||
});
|
||||
onMounted(async () => {
|
||||
// 获得下拉数据
|
||||
apiKeyList.value = await getApiKeySimpleList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -109,15 +99,6 @@ onMounted(async () => {
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #keyId="{ row }">
|
||||
<span>
|
||||
{{
|
||||
apiKeyList.find(
|
||||
(item: AiModelApiKeyApi.ApiKey) => item.id === row.keyId,
|
||||
)?.name
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
|
||||
@@ -77,7 +77,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-2/5" :title="getTitle">
|
||||
<Modal :title="getTitle" class="w-2/5">
|
||||
<Form class="mx-4" />
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -22,6 +22,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
fieldName: 'name',
|
||||
label: '工具名称',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
placeholder: '请输入工具名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Textarea',
|
||||
@@ -52,6 +55,10 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
fieldName: 'name',
|
||||
label: '工具名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入工具名称',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
@@ -60,6 +67,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||
placeholder: '请选择状态',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -80,14 +88,17 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
{
|
||||
field: 'id',
|
||||
title: '工具编号',
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: '工具名称',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
title: '工具描述',
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
@@ -96,6 +107,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.COMMON_STATUS },
|
||||
},
|
||||
minWidth: 80,
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
|
||||
@@ -23,27 +23,25 @@ function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 创建 */
|
||||
/** 创建工具 */
|
||||
function handleCreate() {
|
||||
formModalApi.setData(null).open();
|
||||
}
|
||||
|
||||
/** 编辑 */
|
||||
/** 编辑工具 */
|
||||
function handleEdit(row: AiModelToolApi.Tool) {
|
||||
formModalApi.setData(row).open();
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
/** 删除工具 */
|
||||
async function handleDelete(row: AiModelToolApi.Tool) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await deleteTool(row.id as number);
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||
});
|
||||
await deleteTool(row.id!);
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
@@ -71,6 +69,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
|
||||
@@ -76,7 +76,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-2/5" :title="getTitle">
|
||||
<Modal :title="getTitle" class="w-2/5">
|
||||
<Form class="mx-4" />
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
@@ -7,6 +8,10 @@ import { getDictOptions } from '@vben/hooks';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 关联数据 */
|
||||
let userList: SystemUserApi.User[] = [];
|
||||
getSimpleUserList().then((data) => (userList = data));
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
@@ -15,21 +20,28 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '用户编号',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
api: getSimpleUserList,
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择用户编号',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'title',
|
||||
label: '音乐名称',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入音乐名称',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
label: '绘画状态',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
placeholder: '请选择绘画状态',
|
||||
allowClear: true,
|
||||
options: getDictOptions(DICT_TYPE.AI_MUSIC_STATUS, 'number'),
|
||||
},
|
||||
@@ -39,6 +51,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '生成模式',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
placeholder: '请选择生成模式',
|
||||
allowClear: true,
|
||||
options: getDictOptions(DICT_TYPE.AI_GENERATE_MODE, 'number'),
|
||||
},
|
||||
@@ -57,15 +70,21 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '是否发布',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
|
||||
placeholder: '请选择是否发布',
|
||||
allowClear: true,
|
||||
options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
export function useGridColumns(
|
||||
onPublicStatusChange?: (
|
||||
newStatus: boolean,
|
||||
row: any,
|
||||
) => PromiseLike<boolean | undefined>,
|
||||
): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
@@ -82,7 +101,10 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
{
|
||||
minWidth: 180,
|
||||
title: '用户',
|
||||
slots: { default: 'userId' },
|
||||
field: 'userId',
|
||||
formatter: ({ cellValue }) => {
|
||||
return userList.find((user) => user.id === cellValue)?.nickname || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
@@ -143,7 +165,16 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
{
|
||||
minWidth: 100,
|
||||
title: '是否发布',
|
||||
slots: { default: 'publicStatus' },
|
||||
field: 'publicStatus',
|
||||
align: 'center',
|
||||
cellRender: {
|
||||
attrs: { beforeChange: onPublicStatusChange },
|
||||
name: 'CellSwitch',
|
||||
props: {
|
||||
checkedValue: true,
|
||||
unCheckedValue: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'taskId',
|
||||
|
||||
@@ -1,29 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { AiMusicApi } from '#/api/ai/music';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { confirm, DocAlert, Page } from '@vben/common-ui';
|
||||
import { AiMusicStatusEnum } from '@vben/constants';
|
||||
|
||||
import { Button, message, Switch } from 'ant-design-vue';
|
||||
import { Button, message } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { deleteMusic, getMusicPage, updateMusic } from '#/api/ai/music';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
const userList = ref<SystemUserApi.User[]>([]); // 用户列表
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
/** 删除音乐记录 */
|
||||
async function handleDelete(row: AiMusicApi.Music) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||
@@ -31,36 +25,45 @@ async function handleDelete(row: AiMusicApi.Music) {
|
||||
});
|
||||
try {
|
||||
await deleteMusic(row.id as number);
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||
});
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
/** 修改是否发布 */
|
||||
const handleUpdatePublicStatusChange = async (row: AiMusicApi.Music) => {
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const text = row.publicStatus ? '公开' : '私有';
|
||||
await confirm(`确认要"${text}"该图片吗?`).then(async () => {
|
||||
await updateMusic({
|
||||
id: row.id,
|
||||
publicStatus: row.publicStatus,
|
||||
async function handleUpdatePublicStatusChange(
|
||||
newStatus: boolean,
|
||||
row: AiMusicApi.Music,
|
||||
): Promise<boolean | undefined> {
|
||||
const text = newStatus ? '公开' : '私有';
|
||||
return new Promise((resolve, reject) => {
|
||||
confirm({
|
||||
content: `确认要将该音乐切换为【${text}】吗?`,
|
||||
})
|
||||
.then(async () => {
|
||||
// 更新音乐状态
|
||||
await updateMusic({
|
||||
id: row.id,
|
||||
publicStatus: newStatus,
|
||||
});
|
||||
// 提示并返回成功
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
resolve(true);
|
||||
})
|
||||
.catch(() => {
|
||||
reject(new Error('取消操作'));
|
||||
});
|
||||
handleRefresh();
|
||||
});
|
||||
} catch {
|
||||
row.publicStatus = !row.publicStatus;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(),
|
||||
columns: useGridColumns(handleUpdatePublicStatusChange),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
@@ -76,6 +79,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
@@ -83,10 +87,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
} as VxeTableGridOptions<AiMusicApi.Music>,
|
||||
});
|
||||
onMounted(async () => {
|
||||
// 获得下拉数据
|
||||
userList.value = await getSimpleUserList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -99,11 +99,6 @@ onMounted(async () => {
|
||||
<TableAction :actions="[]" />
|
||||
</template>
|
||||
|
||||
<template #userId="{ row }">
|
||||
<span>
|
||||
{{ userList.find((item) => item.id === row.userId)?.nickname }}
|
||||
</span>
|
||||
</template>
|
||||
<template #content="{ row }">
|
||||
<Button
|
||||
type="link"
|
||||
@@ -133,13 +128,6 @@ onMounted(async () => {
|
||||
封面
|
||||
</Button>
|
||||
</template>
|
||||
<template #publicStatus="{ row }">
|
||||
<Switch
|
||||
v-model:checked="row.publicStatus"
|
||||
@change="handleUpdatePublicStatusChange(row)"
|
||||
:disabled="row.status !== AiMusicStatusEnum.SUCCESS"
|
||||
/>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
|
||||
@@ -118,7 +118,7 @@ async function handleSave() {
|
||||
|
||||
// 保存成功,提示并跳转到列表页
|
||||
message.success('保存成功');
|
||||
tabs.closeCurrentTab();
|
||||
await tabs.closeCurrentTab();
|
||||
await router.push({ name: 'AiWorkflow' });
|
||||
} catch (error: any) {
|
||||
console.error('保存失败:', error);
|
||||
@@ -152,8 +152,8 @@ async function handleDeploy() {
|
||||
// 发布
|
||||
await deployModel(formData.value.id);
|
||||
message.success('发布成功');
|
||||
// TODO 返回列表页
|
||||
await router.push({ name: '/ai/workflow' });
|
||||
// 返回列表页
|
||||
await router.push({ name: 'AiWorkflow' });
|
||||
} catch (error: any) {
|
||||
console.error('发布失败:', error);
|
||||
message.warning(error.message || '发布失败');
|
||||
@@ -190,8 +190,8 @@ function handleBack() {
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
workflowId.value = route.query.id as string;
|
||||
actionType.value = route.query.type as string;
|
||||
workflowId.value = route.params.id as string;
|
||||
actionType.value = route.params.type as string;
|
||||
await initData();
|
||||
});
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ defineExpose({ validate });
|
||||
</fieldset>
|
||||
|
||||
<fieldset
|
||||
class="m-0 mt-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-4"
|
||||
class="bg-card m-0 mt-10 rounded-lg border border-gray-200 px-3 py-4"
|
||||
>
|
||||
<legend class="ml-2 px-2.5 text-base font-semibold text-gray-600">
|
||||
<h3>运行结果</h3>
|
||||
|
||||
@@ -28,10 +28,7 @@ function handleCreate() {
|
||||
function handleEdit(row: any) {
|
||||
router.push({
|
||||
name: 'AiWorkflowCreate',
|
||||
query: {
|
||||
id: row.id,
|
||||
type: 'update',
|
||||
},
|
||||
params: { id: row.id, type: 'update' },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,28 +3,24 @@ import type { AiWriteApi } from '#/api/ai/write';
|
||||
|
||||
import { nextTick, ref } from 'vue';
|
||||
|
||||
import { alert, Page } from '@vben/common-ui';
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { WriteExample } from '@vben/constants';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { writeStream } from '#/api/ai/write';
|
||||
|
||||
import Left from './components/Left.vue';
|
||||
import Right from './components/Right.vue';
|
||||
import Left from './modules/left.vue';
|
||||
import Right from './modules/right.vue';
|
||||
|
||||
const writeResult = ref(''); // 写作结果
|
||||
const isWriting = ref(false); // 是否正在写作中
|
||||
const abortController = ref<AbortController>(); // // 写作进行中 abort 控制器(控制 stream 写作)
|
||||
|
||||
/** 停止 stream 生成 */
|
||||
function stopStream() {
|
||||
abortController.value?.abort();
|
||||
isWriting.value = false;
|
||||
}
|
||||
const rightRef = ref<InstanceType<typeof Right>>(); // 写作面板
|
||||
|
||||
/** 执行写作 */
|
||||
const rightRef = ref<InstanceType<typeof Right>>();
|
||||
|
||||
function submit(data: Partial<AiWriteApi.Write>) {
|
||||
/** 提交写作 */
|
||||
function handleSubmit(data: Partial<AiWriteApi.Write>) {
|
||||
abortController.value = new AbortController();
|
||||
writeResult.value = '';
|
||||
isWriting.value = true;
|
||||
@@ -33,8 +29,8 @@ function submit(data: Partial<AiWriteApi.Write>) {
|
||||
onMessage: async (res: any) => {
|
||||
const { code, data, msg } = JSON.parse(res.data);
|
||||
if (code !== 0) {
|
||||
alert(`写作异常! ${msg}`);
|
||||
stopStream();
|
||||
message.error(`写作异常! ${msg}`);
|
||||
handleStopStream();
|
||||
return;
|
||||
}
|
||||
writeResult.value = writeResult.value + data;
|
||||
@@ -43,23 +39,29 @@ function submit(data: Partial<AiWriteApi.Write>) {
|
||||
rightRef.value?.scrollToBottom();
|
||||
},
|
||||
ctrl: abortController.value,
|
||||
onClose: stopStream,
|
||||
onClose: handleStopStream,
|
||||
onError: (error: any) => {
|
||||
console.error('写作异常', error);
|
||||
stopStream();
|
||||
handleStopStream();
|
||||
// 需要抛出异常,禁止重试
|
||||
throw error;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 停止 stream 生成 */
|
||||
function handleStopStream() {
|
||||
abortController.value?.abort();
|
||||
isWriting.value = false;
|
||||
}
|
||||
|
||||
/** 点击示例触发 */
|
||||
function handleExampleClick(type: keyof typeof WriteExample) {
|
||||
writeResult.value = WriteExample[type].data;
|
||||
}
|
||||
|
||||
/** 点击重置的时候清空写作的结果*/
|
||||
function reset() {
|
||||
function handleReset() {
|
||||
writeResult.value = '';
|
||||
}
|
||||
</script>
|
||||
@@ -70,13 +72,13 @@ function reset() {
|
||||
<Left
|
||||
:is-writing="isWriting"
|
||||
class="mr-4 h-full rounded-lg"
|
||||
@submit="submit"
|
||||
@reset="reset"
|
||||
@submit="handleSubmit"
|
||||
@reset="handleReset"
|
||||
@example="handleExampleClick"
|
||||
/>
|
||||
<Right
|
||||
:is-writing="isWriting"
|
||||
@stop-stream="stopStream"
|
||||
@stop-stream="handleStopStream"
|
||||
ref="rightRef"
|
||||
class="flex-grow"
|
||||
v-model:content="writeResult"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
// TODO @gjd:应该是 modules 模块,然后小写
|
||||
import type { AiWriteApi } from '#/api/ai/write';
|
||||
|
||||
import { ref } from 'vue';
|
||||
@@ -11,7 +10,7 @@ import { IconifyIcon } from '@vben/icons';
|
||||
import { createReusableTemplate } from '@vueuse/core';
|
||||
import { Button, message, Textarea } from 'ant-design-vue';
|
||||
|
||||
import Tag from './Tag.vue';
|
||||
import Tag from './tag.vue';
|
||||
|
||||
type TabType = AiWriteApi.Write['type'];
|
||||
|
||||
@@ -34,6 +33,7 @@ function omit(obj: Record<string, any>, keysToOmit: string[]) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 点击示例的时候,将定义好的文章作为示例展示出来 */
|
||||
function example(type: 'reply' | 'write') {
|
||||
formData.value = {
|
||||
@@ -79,13 +79,11 @@ const initData: AiWriteApi.Write = {
|
||||
length: 1,
|
||||
format: 1,
|
||||
};
|
||||
|
||||
const formData = ref<AiWriteApi.Write>({ ...initData });
|
||||
const recordFormData = {} as Record<AiWriteTypeEnum, AiWriteApi.Write>; // 用来记录切换之前所填写的数据,切换的时候给赋值回来
|
||||
|
||||
/** 用来记录切换之前所填写的数据,切换的时候给赋值回来 */
|
||||
const recordFormData = {} as Record<AiWriteTypeEnum, AiWriteApi.Write>;
|
||||
/** 切换tab */
|
||||
function switchTab(value: TabType) {
|
||||
/** 切换 tab */
|
||||
function handleSwitchTab(value: TabType) {
|
||||
if (value !== selectedTab.value) {
|
||||
// 保存之前的久数据
|
||||
recordFormData[selectedTab.value] = formData.value;
|
||||
@@ -96,8 +94,11 @@ function switchTab(value: TabType) {
|
||||
}
|
||||
|
||||
/** 提交写作 */
|
||||
function submit() {
|
||||
if (selectedTab.value === 2 && !formData.value.originalContent) {
|
||||
function handleSubmit() {
|
||||
if (
|
||||
selectedTab.value === AiWriteTypeEnum.REPLY &&
|
||||
!formData.value.originalContent
|
||||
) {
|
||||
message.warning('请输入原文');
|
||||
return;
|
||||
}
|
||||
@@ -105,12 +106,13 @@ function submit() {
|
||||
message.warning(`请输入${selectedTab.value === 1 ? '写作' : '回复'}内容`);
|
||||
return;
|
||||
}
|
||||
|
||||
emit('submit', {
|
||||
/** 撰写的时候没有 originalContent 字段*/
|
||||
// 撰写的时候没有 originalContent 字段
|
||||
...(selectedTab.value === 1
|
||||
? omit(formData.value, ['originalContent'])
|
||||
: formData.value),
|
||||
/** 使用选中 tab 值覆盖当前的 type 类型 */
|
||||
// 使用选中 tab 值覆盖当前的 type 类型
|
||||
type: selectedTab.value,
|
||||
});
|
||||
}
|
||||
@@ -156,7 +158,7 @@ function submit() {
|
||||
v-for="tab in tabs"
|
||||
:key="tab.value"
|
||||
:active="tab.value === selectedTab"
|
||||
:item-click="() => switchTab(tab.value)"
|
||||
:item-click="() => handleSwitchTab(tab.value)"
|
||||
:text="tab.text"
|
||||
class="relative z-20"
|
||||
/>
|
||||
@@ -167,7 +169,7 @@ function submit() {
|
||||
class="bg-card box-border h-full w-96 flex-grow overflow-y-auto px-7 pb-2 lg:block"
|
||||
>
|
||||
<div>
|
||||
<template v-if="selectedTab === 1">
|
||||
<template v-if="selectedTab === AiWriteTypeEnum.WRITING">
|
||||
<ReuseLabel
|
||||
:hint-click="() => example('write')"
|
||||
hint="示例"
|
||||
@@ -181,7 +183,6 @@ function submit() {
|
||||
show-count
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<ReuseLabel
|
||||
:hint-click="() => example('reply')"
|
||||
@@ -195,7 +196,6 @@ function submit() {
|
||||
placeholder="请输入原文"
|
||||
show-count
|
||||
/>
|
||||
|
||||
<ReuseLabel label="回复内容" />
|
||||
<Textarea
|
||||
v-model:value="formData.prompt"
|
||||
@@ -231,7 +231,7 @@ function submit() {
|
||||
<Button :disabled="isWriting" class="mr-2" @click="reset">
|
||||
重置
|
||||
</Button>
|
||||
<Button type="primary" :loading="isWriting" @click="submit">
|
||||
<Button type="primary" :loading="isWriting" @click="handleSubmit">
|
||||
生成
|
||||
</Button>
|
||||
</div>
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
// TODO @gjd:应该是 modules 模块,然后小写
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
@@ -9,16 +8,15 @@ import { Button, Card, message, Textarea } from 'ant-design-vue';
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
// 生成的结果
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
}, // 生成的结果
|
||||
isWriting: {
|
||||
// 是否正在生成文章
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
}, // 是否正在生成文章
|
||||
});
|
||||
|
||||
const emits = defineEmits(['update:content', 'stopStream']);
|
||||
const { copied, copy } = useClipboard();
|
||||
|
||||
@@ -58,7 +56,6 @@ watch(copied, (val) => {
|
||||
<template #title>
|
||||
<h3 class="m-0 flex shrink-0 items-center justify-between px-7">
|
||||
<span>预览</span>
|
||||
<!-- 展示在右上角 -->
|
||||
<Button
|
||||
type="primary"
|
||||
v-show="showCopy"
|
||||
@@ -75,12 +72,11 @@ watch(copied, (val) => {
|
||||
class="hide-scroll-bar box-border h-full overflow-y-auto"
|
||||
>
|
||||
<div
|
||||
class="bg-card relative box-border min-h-full w-full flex-grow p-3 sm:p-7"
|
||||
class="bg-card relative box-border min-h-full w-full flex-grow p-2 sm:p-5"
|
||||
>
|
||||
<!-- 终止生成内容的按钮 -->
|
||||
<Button
|
||||
v-show="isWriting"
|
||||
class="absolute bottom-2 left-1/2 z-40 flex -translate-x-1/2 sm:bottom-5"
|
||||
class="absolute bottom-1 left-1/2 z-40 flex -translate-x-1/2 sm:bottom-2"
|
||||
@click="emits('stopStream')"
|
||||
size="small"
|
||||
>
|
||||
@@ -94,7 +90,7 @@ watch(copied, (val) => {
|
||||
<Textarea
|
||||
id="inputId"
|
||||
v-model:value="compContent"
|
||||
auto-size
|
||||
:auto-size="true"
|
||||
:bordered="false"
|
||||
placeholder="生成的内容……"
|
||||
/>
|
||||
@@ -130,7 +126,7 @@ watch(copied, (val) => {
|
||||
}
|
||||
}
|
||||
|
||||
// markmap的tool样式覆盖
|
||||
// markmap 的 tool 样式覆盖
|
||||
:deep(.markmap) {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
<!-- 标签选项 -->
|
||||
<script setup lang="ts">
|
||||
// TODO @gjd:应该是 modules 模块,然后小写
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
[k: string]: any;
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
@@ -7,6 +8,10 @@ import { getDictOptions } from '@vben/hooks';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { getRangePickerDefaultProps } from '#/utils';
|
||||
|
||||
/** 关联数据 */
|
||||
let userList: SystemUserApi.User[] = [];
|
||||
getSimpleUserList().then((data) => (userList = data));
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
@@ -15,9 +20,11 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '用户编号',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: () => getSimpleUserList(),
|
||||
api: getSimpleUserList,
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择用户',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -26,6 +33,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请选择写作类型',
|
||||
options: getDictOptions(DICT_TYPE.AI_WRITE_TYPE, 'number'),
|
||||
},
|
||||
},
|
||||
@@ -35,7 +43,8 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: getDictOptions(DICT_TYPE.AI_PLATFORM, 'number'),
|
||||
placeholder: '请选择平台',
|
||||
options: getDictOptions(DICT_TYPE.AI_PLATFORM, 'string'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -62,12 +71,15 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
{
|
||||
minWidth: 180,
|
||||
title: '用户',
|
||||
slots: { default: 'userId' },
|
||||
field: 'userId',
|
||||
formatter: ({ cellValue }) => {
|
||||
return userList.find((user) => user.id === cellValue)?.nickname || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
title: '写作类型',
|
||||
minWidth: 100,
|
||||
minWidth: 120,
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.AI_WRITE_TYPE },
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { AiWriteApi } from '#/api/ai/write';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { DocAlert, Page } from '@vben/common-ui';
|
||||
|
||||
@@ -11,33 +8,30 @@ import { message } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { deleteWrite, getWritePage } from '#/api/ai/write';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
const userList = ref<SystemUserApi.User[]>([]); // 用户列表
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
async function handleDelete(row: AiWriteApi.AiWritePageReq) {
|
||||
/** 删除写作记录 */
|
||||
async function handleDelete(row: AiWriteApi.Write) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await deleteWrite(row.id as number);
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||
});
|
||||
await deleteWrite(row.id!);
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
|
||||
handleRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
@@ -59,16 +53,13 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<AiWriteApi.AiWritePageReq>,
|
||||
});
|
||||
onMounted(async () => {
|
||||
// 获得下拉数据
|
||||
userList.value = await getSimpleUserList();
|
||||
} as VxeTableGridOptions<AiWriteApi.Write>,
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -78,14 +69,6 @@ onMounted(async () => {
|
||||
<DocAlert title="AI 写作助手" url="https://doc.iocoder.cn/ai/write/" />
|
||||
</template>
|
||||
<Grid table-title="写作管理列表">
|
||||
<template #toolbar-tools>
|
||||
<TableAction :actions="[]" />
|
||||
</template>
|
||||
<template #userId="{ row }">
|
||||
<span>{{
|
||||
userList.find((item) => item.id === row.userId)?.nickname
|
||||
}}</span>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
|
||||
@@ -62,12 +62,26 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
componentProps: {
|
||||
min: 0,
|
||||
placeholder: '请输入分类排序',
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 重命名的表单 */
|
||||
export function useRenameFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '分类名',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入分类名',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
@@ -143,6 +157,11 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
props: { type: DICT_TYPE.COMMON_STATUS },
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'sort',
|
||||
title: '分类排序',
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '创建时间',
|
||||
|
||||
@@ -36,16 +36,14 @@ function handleEdit(row: BpmCategoryApi.Category) {
|
||||
/** 删除流程分类 */
|
||||
async function handleDelete(row: BpmCategoryApi.Category) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.code]),
|
||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await deleteCategory(row.id as number);
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.deleteSuccess', [row.code]),
|
||||
});
|
||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||
handleRefresh();
|
||||
} catch {
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
@@ -71,6 +69,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isHover: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
|
||||
@@ -26,6 +26,13 @@ const getTitle = computed(() => {
|
||||
});
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
formItemClass: 'col-span-2',
|
||||
labelWidth: 80,
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema: useFormSchema(),
|
||||
showDefaultActions: false,
|
||||
@@ -73,7 +80,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="getTitle">
|
||||
<Modal :title="getTitle" class="w-1/3">
|
||||
<Form class="mx-4" />
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -11,50 +11,36 @@ import { useVbenForm } from '#/adapter/form';
|
||||
import { getCategory, updateCategory } from '#/api/bpm/category';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useRenameFormSchema } from '../data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formData = ref<BpmCategoryApi.Category>();
|
||||
|
||||
// 定义表单结构
|
||||
const formSchema = [
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '分类名',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入分类名',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
];
|
||||
|
||||
// 创建表单
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
formItemClass: 'col-span-2',
|
||||
labelWidth: 80,
|
||||
},
|
||||
layout: 'horizontal',
|
||||
schema: formSchema,
|
||||
schema: useRenameFormSchema(),
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
// 创建模态窗
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
// 保存按钮回调
|
||||
async onConfirm() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
|
||||
// 提交表单,只更新流程分类名
|
||||
const formValues = await formApi.getValues();
|
||||
// 提交表单
|
||||
const data = {
|
||||
id: formData.value?.id,
|
||||
name: formValues.name, // 只更新流程分类名
|
||||
code: formData.value?.code,
|
||||
status: formData.value?.status,
|
||||
description: formData.value?.description,
|
||||
sort: formData.value?.sort,
|
||||
...formData.value,
|
||||
...(await formApi.getValues()),
|
||||
} as BpmCategoryApi.Category;
|
||||
|
||||
try {
|
||||
await updateCategory(data);
|
||||
// 关闭并提示
|
||||
@@ -65,29 +51,21 @@ const [Modal, modalApi] = useVbenModal({
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
|
||||
// 打开/关闭弹窗回调
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
formData.value = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
const data = modalApi.getData<BpmCategoryApi.Category>();
|
||||
|
||||
if (!data || !data.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
modalApi.lock();
|
||||
try {
|
||||
// 获取流程分类数据
|
||||
formData.value = await getCategory(data.id);
|
||||
// 仅设置 name 字段
|
||||
await formApi.setValues({
|
||||
name: formData.value.name,
|
||||
});
|
||||
// 设置到 values
|
||||
await formApi.setValues(formData.value);
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
@@ -96,7 +74,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal title="重命名流程分类">
|
||||
<Modal title="重命名流程分类" class="w-1/3">
|
||||
<Form class="mx-4" />
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
// 导出 BPMN 流程设计器相关组件
|
||||
export * from './package';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user