Merge remote-tracking branch 'yudao/dev' into dev

pull/160/head
jason 2025-06-27 23:20:20 +08:00
commit 249b43ab09
185 changed files with 6101 additions and 1366 deletions

View File

@ -24,6 +24,7 @@ import {
ImagePreviewGroup,
Popconfirm,
Switch,
Tag,
} from 'ant-design-vue';
import { DictTag } from '#/components/dict-tag';
@ -113,6 +114,35 @@ setupVbenVxeTable({
},
});
// 表格配置项可以用 cellRender: { name: 'CellTag' },
vxeUI.renderer.add('CellTag', {
renderTableDefault(renderOpts, params) {
const { props } = renderOpts;
const { column, row } = params;
return h(Tag, { color: props?.color }, () => row[column.field]);
},
});
vxeUI.renderer.add('CellTags', {
renderTableDefault(renderOpts, params) {
const { props } = renderOpts;
const { column, row } = params;
if (!row[column.field] || row[column.field].length === 0) {
return '';
}
return h(
'div',
{ class: 'flex items-center justify-center' },
{
default: () =>
row[column.field].map((item: any) =>
h(Tag, { color: props?.color }, { default: () => item }),
),
},
);
},
});
// 表格配置项可以用 cellRender: { name: 'CellDict', props:{dictType: ''} },
vxeUI.renderer.add('CellDict', {
renderTableDefault(renderOpts, params) {

View File

@ -3,7 +3,7 @@ import type { PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace AiChatConversationApi {
export interface ChatConversationVO {
export interface ChatConversation {
id: number; // ID 编号
userId: number; // 用户编号
title: string; // 对话标题
@ -26,21 +26,21 @@ export namespace AiChatConversationApi {
// 获得【我的】聊天对话
export function getChatConversationMy(id: number) {
return requestClient.get<AiChatConversationApi.ChatConversationVO>(
return requestClient.get<AiChatConversationApi.ChatConversation>(
`/ai/chat/conversation/get-my?id=${id}`,
);
}
// 新增【我的】聊天对话
export function createChatConversationMy(
data: AiChatConversationApi.ChatConversationVO,
data: AiChatConversationApi.ChatConversation,
) {
return requestClient.post('/ai/chat/conversation/create-my', data);
}
// 更新【我的】聊天对话
export function updateChatConversationMy(
data: AiChatConversationApi.ChatConversationVO,
data: AiChatConversationApi.ChatConversation,
) {
return requestClient.put(`/ai/chat/conversation/update-my`, data);
}
@ -57,7 +57,7 @@ export function deleteChatConversationMyByUnpinned() {
// 获得【我的】聊天对话列表
export function getChatConversationMyList() {
return requestClient.get<AiChatConversationApi.ChatConversationVO[]>(
return requestClient.get<AiChatConversationApi.ChatConversation[]>(
`/ai/chat/conversation/my-list`,
);
}
@ -65,7 +65,7 @@ export function getChatConversationMyList() {
// 获得【我的】聊天对话列表
export function getChatConversationPage(params: any) {
return requestClient.get<
PageResult<AiChatConversationApi.ChatConversationVO[]>
PageResult<AiChatConversationApi.ChatConversation[]>
>(`/ai/chat/conversation/page`, { params });
}

View File

@ -9,7 +9,7 @@ import { requestClient } from '#/api/request';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
const accessStore = useAccessStore();
export namespace AiChatMessageApi {
export interface ChatMessageVO {
export interface ChatMessage {
id: number; // 编号
conversationId: number; // 对话编号
type: string; // 消息类型
@ -36,7 +36,7 @@ export namespace AiChatMessageApi {
export function getChatMessageListByConversationId(
conversationId: null | number,
) {
return requestClient.get<AiChatMessageApi.ChatMessageVO[]>(
return requestClient.get<AiChatMessageApi.ChatMessage[]>(
`/ai/chat/message/list-by-conversation-id?conversationId=${conversationId}`,
);
}
@ -84,7 +84,7 @@ export function deleteByConversationId(conversationId: number) {
}
// 获得消息分页
export function getChatMessagePage(params: any) {
return requestClient.get<PageResult<AiChatMessageApi.ChatMessageVO>>(
return requestClient.get<PageResult<AiChatMessageApi.ChatMessage>>(
'/ai/chat/message/page',
{ params },
);

View File

@ -3,14 +3,14 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace AiImageApi {
export interface ImageMidjourneyButtonsVO {
export interface ImageMidjourneyButtons {
customId: string; // MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识
emoji: string; // 图标 emoji
label: string; // Make Variations 文本
style: number; // 样式: 2Primary、3Green
}
// AI 绘图 VO
export interface ImageVO {
// AI 绘图
export interface Image {
id: number; // 编号
platform: string; // 平台
model: string; // 模型
@ -23,12 +23,12 @@ export namespace AiImageApi {
errorMessage: string; // 错误信息
options: any; // 配置 Map<string, string>
taskId: number; // 任务编号
buttons: ImageMidjourneyButtonsVO[]; // mj 操作按钮
buttons: ImageMidjourneyButtons[]; // mj 操作按钮
createTime: Date; // 创建时间
finishTime: Date; // 完成时间
}
export interface ImageDrawReqVO {
export interface ImageDrawReq {
prompt: string; // 提示词
modelId: number; // 模型
style: string; // 图像生成的风格
@ -37,7 +37,7 @@ export namespace AiImageApi {
options: object; // 绘制参数Map<String, String>
}
export interface ImageMidjourneyImagineReqVO {
export interface ImageMidjourneyImagineReq {
prompt: string; // 提示词
modelId: number; // 模型
base64Array?: string[]; // size不能为空
@ -46,7 +46,7 @@ export namespace AiImageApi {
version: string; // 版本
}
export interface ImageMidjourneyActionVO {
export interface ImageMidjourneyAction {
id: number; // 图片编号
customId: string; // MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识
}
@ -54,26 +54,25 @@ export namespace AiImageApi {
// 获取【我的】绘图分页
export function getImagePageMy(params: PageParam) {
return requestClient.get<PageResult<AiImageApi.ImageVO>>(
'/ai/image/my-page',
{ params },
);
return requestClient.get<PageResult<AiImageApi.Image>>('/ai/image/my-page', {
params,
});
}
// 获取【我的】绘图记录
export function getImageMy(id: number) {
return requestClient.get<AiImageApi.ImageVO>(`/ai/image/get-my?id=${id}`);
return requestClient.get<AiImageApi.Image>(`/ai/image/get-my?id=${id}`);
}
// 获取【我的】绘图记录列表
export function getImageListMyByIds(ids: number[]) {
return requestClient.get<AiImageApi.ImageVO[]>(`/ai/image/my-list-by-ids`, {
return requestClient.get<AiImageApi.Image[]>(`/ai/image/my-list-by-ids`, {
params: { ids: ids.join(',') },
});
}
// 生成图片
export function drawImage(data: AiImageApi.ImageDrawReqVO) {
export function drawImage(data: AiImageApi.ImageDrawReq) {
return requestClient.post(`/ai/image/draw`, data);
}
@ -84,21 +83,19 @@ export function deleteImageMy(id: number) {
// ================ midjourney 专属 ================
// 【Midjourney】生成图片
export function midjourneyImagine(
data: AiImageApi.ImageMidjourneyImagineReqVO,
) {
export function midjourneyImagine(data: AiImageApi.ImageMidjourneyImagineReq) {
return requestClient.post(`/ai/image/midjourney/imagine`, data);
}
// 【Midjourney】Action 操作(二次生成图片)
export function midjourneyAction(data: AiImageApi.ImageMidjourneyActionVO) {
export function midjourneyAction(data: AiImageApi.ImageMidjourneyAction) {
return requestClient.post(`/ai/image/midjourney/action`, data);
}
// ================ 绘图管理 ================
// 查询绘画分页
export function getImagePage(params: any) {
return requestClient.get<AiImageApi.ImageVO[]>(`/ai/image/page`, { params });
return requestClient.get<AiImageApi.Image[]>(`/ai/image/page`, { params });
}
// 更新绘画发布状态

View File

@ -3,7 +3,7 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace AiKnowledgeDocumentApi {
export interface KnowledgeDocumentVO {
export interface KnowledgeDocument {
id: number; // 编号
knowledgeId: number; // 知识库编号
name: string; // 文档名称
@ -18,7 +18,7 @@ export namespace AiKnowledgeDocumentApi {
// 查询知识库文档分页
export function getKnowledgeDocumentPage(params: PageParam) {
return requestClient.get<
PageResult<AiKnowledgeDocumentApi.KnowledgeDocumentVO>
PageResult<AiKnowledgeDocumentApi.KnowledgeDocument>
>('/ai/knowledge/document/page', { params });
}

View File

@ -3,7 +3,7 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace AiKnowledgeKnowledgeApi {
export interface KnowledgeVO {
export interface Knowledge {
id: number; // 编号
name: string; // 知识库名称
description: string; // 知识库描述
@ -15,7 +15,7 @@ export namespace AiKnowledgeKnowledgeApi {
// 查询知识库分页
export function getKnowledgePage(params: PageParam) {
return requestClient.get<PageResult<AiKnowledgeKnowledgeApi.KnowledgeVO>>(
return requestClient.get<PageResult<AiKnowledgeKnowledgeApi.Knowledge>>(
'/ai/knowledge/page',
{ params },
);
@ -23,17 +23,17 @@ export function getKnowledgePage(params: PageParam) {
// 查询知识库详情
export function getKnowledge(id: number) {
return requestClient.get<AiKnowledgeKnowledgeApi.KnowledgeVO>(
return requestClient.get<AiKnowledgeKnowledgeApi.Knowledge>(
`/ai/knowledge/get?id=${id}`,
);
}
// 新增知识库
export function createKnowledge(data: AiKnowledgeKnowledgeApi.KnowledgeVO) {
export function createKnowledge(data: AiKnowledgeKnowledgeApi.Knowledge) {
return requestClient.post('/ai/knowledge/create', data);
}
// 修改知识库
export function updateKnowledge(data: AiKnowledgeKnowledgeApi.KnowledgeVO) {
export function updateKnowledge(data: AiKnowledgeKnowledgeApi.Knowledge) {
return requestClient.put('/ai/knowledge/update', data);
}
@ -44,7 +44,7 @@ export function deleteKnowledge(id: number) {
// 获取知识库简单列表
export function getSimpleKnowledgeList() {
return requestClient.get<AiKnowledgeKnowledgeApi.KnowledgeVO[]>(
return requestClient.get<AiKnowledgeKnowledgeApi.Knowledge[]>(
'/ai/knowledge/simple-list',
);
}

View File

@ -3,8 +3,8 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace AiKnowledgeSegmentApi {
// AI 知识库分段 VO
export interface KnowledgeSegmentVO {
// AI 知识库分段
export interface KnowledgeSegment {
id: number; // 编号
documentId: number; // 文档编号
knowledgeId: number; // 知识库编号
@ -20,27 +20,28 @@ export namespace AiKnowledgeSegmentApi {
// 查询知识库分段分页
export function getKnowledgeSegmentPage(params: PageParam) {
return requestClient.get<
PageResult<AiKnowledgeSegmentApi.KnowledgeSegmentVO>
>('/ai/knowledge/segment/page', { params });
return requestClient.get<PageResult<AiKnowledgeSegmentApi.KnowledgeSegment>>(
'/ai/knowledge/segment/page',
{ params },
);
}
// 查询知识库分段详情
export function getKnowledgeSegment(id: number) {
return requestClient.get<AiKnowledgeSegmentApi.KnowledgeSegmentVO>(
return requestClient.get<AiKnowledgeSegmentApi.KnowledgeSegment>(
`/ai/knowledge/segment/get?id=${id}`,
);
}
// 新增知识库分段
export function createKnowledgeSegment(
data: AiKnowledgeSegmentApi.KnowledgeSegmentVO,
data: AiKnowledgeSegmentApi.KnowledgeSegment,
) {
return requestClient.post('/ai/knowledge/segment/create', data);
}
// 修改知识库分段
export function updateKnowledgeSegment(
data: AiKnowledgeSegmentApi.KnowledgeSegmentVO,
data: AiKnowledgeSegmentApi.KnowledgeSegment,
) {
return requestClient.put('/ai/knowledge/segment/update', data);
}

View File

@ -7,8 +7,8 @@ import { requestClient } from '#/api/request';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
const accessStore = useAccessStore();
export namespace AiMindmapApi {
// AI 思维导图 VO
export interface MindMapVO {
// AI 思维导图
export interface MindMap {
id: number; // 编号
userId: number; // 用户编号
prompt: string; // 生成内容提示
@ -18,8 +18,8 @@ export namespace AiMindmapApi {
errorMessage: string; // 错误信息
}
// AI 思维导图生成 VO
export interface AiMindMapGenerateReqVO {
// AI 思维导图生成
export interface AiMindMapGenerateReq {
prompt: string;
}
}
@ -32,7 +32,7 @@ export function generateMindMap({
ctrl,
}: {
ctrl: AbortController;
data: AiMindmapApi.AiMindMapGenerateReqVO;
data: AiMindmapApi.AiMindMapGenerateReq;
onClose?: (...args: any[]) => void;
onError?: (...args: any[]) => void;
onMessage?: (res: any) => void;

View File

@ -3,7 +3,7 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace AiModelApiKeyApi {
export interface ApiKeyVO {
export interface ApiKey {
id: number; // 编号
name: string; // 名称
apiKey: string; // 密钥
@ -15,7 +15,7 @@ export namespace AiModelApiKeyApi {
// 查询 API 密钥分页
export function getApiKeyPage(params: PageParam) {
return requestClient.get<PageResult<AiModelApiKeyApi.ApiKeyVO>>(
return requestClient.get<PageResult<AiModelApiKeyApi.ApiKey>>(
'/ai/api-key/page',
{ params },
);
@ -23,24 +23,22 @@ export function getApiKeyPage(params: PageParam) {
// 获得 API 密钥列表
export function getApiKeySimpleList() {
return requestClient.get<AiModelApiKeyApi.ApiKeyVO[]>(
return requestClient.get<AiModelApiKeyApi.ApiKey[]>(
'/ai/api-key/simple-list',
);
}
// 查询 API 密钥详情
export function getApiKey(id: number) {
return requestClient.get<AiModelApiKeyApi.ApiKeyVO>(
`/ai/api-key/get?id=${id}`,
);
return requestClient.get<AiModelApiKeyApi.ApiKey>(`/ai/api-key/get?id=${id}`);
}
// 新增 API 密钥
export function createApiKey(data: AiModelApiKeyApi.ApiKeyVO) {
export function createApiKey(data: AiModelApiKeyApi.ApiKey) {
return requestClient.post('/ai/api-key/create', data);
}
// 修改 API 密钥
export function updateApiKey(data: AiModelApiKeyApi.ApiKeyVO) {
export function updateApiKey(data: AiModelApiKeyApi.ApiKey) {
return requestClient.put('/ai/api-key/update', data);
}

View File

@ -3,7 +3,7 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace AiModelChatRoleApi {
export interface ChatRoleVO {
export interface ChatRole {
id: number; // 角色编号
modelId: number; // 模型编号
name: string; // 角色名称
@ -19,8 +19,8 @@ export namespace AiModelChatRoleApi {
toolIds?: number[]; // 引用的工具 ID 列表
}
// AI 聊天角色 分页请求 vo
export interface ChatRolePageReqVO {
// AI 聊天角色 分页请求
export interface ChatRolePageReq {
name?: string; // 角色名称
category?: string; // 角色类别
publicStatus: boolean; // 是否公开
@ -31,7 +31,7 @@ export namespace AiModelChatRoleApi {
// 查询聊天角色分页
export function getChatRolePage(params: PageParam) {
return requestClient.get<PageResult<AiModelChatRoleApi.ChatRoleVO>>(
return requestClient.get<PageResult<AiModelChatRoleApi.ChatRole>>(
'/ai/chat-role/page',
{ params },
);
@ -39,17 +39,17 @@ export function getChatRolePage(params: PageParam) {
// 查询聊天角色详情
export function getChatRole(id: number) {
return requestClient.get<AiModelChatRoleApi.ChatRoleVO>(
return requestClient.get<AiModelChatRoleApi.ChatRole>(
`/ai/chat-role/get?id=${id}`,
);
}
// 新增聊天角色
export function createChatRole(data: AiModelChatRoleApi.ChatRoleVO) {
export function createChatRole(data: AiModelChatRoleApi.ChatRole) {
return requestClient.post('/ai/chat-role/create', data);
}
// 修改聊天角色
export function updateChatRole(data: AiModelChatRoleApi.ChatRoleVO) {
export function updateChatRole(data: AiModelChatRoleApi.ChatRole) {
return requestClient.put('/ai/chat-role/update', data);
}
@ -60,7 +60,7 @@ export function deleteChatRole(id: number) {
// ======= chat 聊天
// 获取 my role
export function getMyPage(params: AiModelChatRoleApi.ChatRolePageReqVO) {
export function getMyPage(params: AiModelChatRoleApi.ChatRolePageReq) {
return requestClient.get('/ai/chat-role/my-page', { params });
}
@ -70,12 +70,12 @@ export function getCategoryList() {
}
// 创建角色
export function createMy(data: AiModelChatRoleApi.ChatRoleVO) {
export function createMy(data: AiModelChatRoleApi.ChatRole) {
return requestClient.post('/ai/chat-role/create-my', data);
}
// 更新角色
export function updateMy(data: AiModelChatRoleApi.ChatRoleVO) {
export function updateMy(data: AiModelChatRoleApi.ChatRole) {
return requestClient.put('/ai/chat-role/update', data);
}

View File

@ -3,7 +3,7 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace AiModelModelApi {
export interface ModelVO {
export interface Model {
id: number; // 编号
keyId: number; // API 秘钥编号
name: string; // 模型名字
@ -20,7 +20,7 @@ export namespace AiModelModelApi {
// 查询模型分页
export function getModelPage(params: PageParam) {
return requestClient.get<PageResult<AiModelModelApi.ModelVO>>(
return requestClient.get<PageResult<AiModelModelApi.Model>>(
'/ai/model/page',
{ params },
);
@ -28,7 +28,7 @@ export function getModelPage(params: PageParam) {
// 获得模型列表
export function getModelSimpleList(type?: number) {
return requestClient.get<AiModelModelApi.ModelVO[]>('/ai/model/simple-list', {
return requestClient.get<AiModelModelApi.Model[]>('/ai/model/simple-list', {
params: {
type,
},
@ -37,15 +37,15 @@ export function getModelSimpleList(type?: number) {
// 查询模型详情
export function getModel(id: number) {
return requestClient.get<AiModelModelApi.ModelVO>(`/ai/model/get?id=${id}`);
return requestClient.get<AiModelModelApi.Model>(`/ai/model/get?id=${id}`);
}
// 新增模型
export function createModel(data: AiModelModelApi.ModelVO) {
export function createModel(data: AiModelModelApi.Model) {
return requestClient.post('/ai/model/create', data);
}
// 修改模型
export function updateModel(data: AiModelModelApi.ModelVO) {
export function updateModel(data: AiModelModelApi.Model) {
return requestClient.put('/ai/model/update', data);
}

View File

@ -3,7 +3,7 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace AiModelToolApi {
export interface ToolVO {
export interface Tool {
id: number; // 工具编号
name: string; // 工具名称
description: string; // 工具描述
@ -13,22 +13,22 @@ export namespace AiModelToolApi {
// 查询工具分页
export function getToolPage(params: PageParam) {
return requestClient.get<PageResult<AiModelToolApi.ToolVO>>('/ai/tool/page', {
return requestClient.get<PageResult<AiModelToolApi.Tool>>('/ai/tool/page', {
params,
});
}
// 查询工具详情
export function getTool(id: number) {
return requestClient.get<AiModelToolApi.ToolVO>(`/ai/tool/get?id=${id}`);
return requestClient.get<AiModelToolApi.Tool>(`/ai/tool/get?id=${id}`);
}
// 新增工具
export function createTool(data: AiModelToolApi.ToolVO) {
export function createTool(data: AiModelToolApi.Tool) {
return requestClient.post('/ai/tool/create', data);
}
// 修改工具
export function updateTool(data: AiModelToolApi.ToolVO) {
export function updateTool(data: AiModelToolApi.Tool) {
return requestClient.put('/ai/tool/update', data);
}
@ -39,5 +39,5 @@ export function deleteTool(id: number) {
// 获取工具简单列表
export function getToolSimpleList() {
return requestClient.get<AiModelToolApi.ToolVO[]>('/ai/tool/simple-list');
return requestClient.get<AiModelToolApi.Tool[]>('/ai/tool/simple-list');
}

View File

@ -3,8 +3,8 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace AiMusicApi {
// AI 音乐 VO
export interface MusicVO {
// AI 音乐
export interface Music {
id: number; // 编号
userId: number; // 用户编号
title: string; // 音乐名称
@ -28,7 +28,7 @@ export namespace AiMusicApi {
// 查询音乐分页
export function getMusicPage(params: PageParam) {
return requestClient.get<PageResult<AiMusicApi.MusicVO>>(`/ai/music/page`, {
return requestClient.get<PageResult<AiMusicApi.Music>>(`/ai/music/page`, {
params,
});
}

View File

@ -11,7 +11,7 @@ import { requestClient } from '#/api/request';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
const accessStore = useAccessStore();
export namespace AiWriteApi {
export interface WriteVO {
export interface Write {
type: AiWriteTypeEnum.REPLY | AiWriteTypeEnum.WRITING; // 1:撰写 2:回复
prompt: string; // 写作内容提示 1。撰写 2回复
originalContent: string; // 原文
@ -27,14 +27,14 @@ export namespace AiWriteApi {
createTime?: Date; // 创建时间
}
export interface AiWritePageReqVO extends PageParam {
export interface AiWritePageReq extends PageParam {
userId?: number; // 用户编号
type?: AiWriteTypeEnum; // 写作类型
platform?: string; // 平台
createTime?: [string, string]; // 创建时间
}
export interface AiWriteRespVo {
export interface AiWriteResp {
id: number;
userId: number;
type: number;
@ -60,7 +60,7 @@ export function writeStream({
ctrl,
}: {
ctrl: AbortController;
data: Partial<AiWriteApi.WriteVO>;
data: Partial<AiWriteApi.Write>;
onClose?: (...args: any[]) => void;
onError?: (...args: any[]) => void;
onMessage?: (res: any) => void;
@ -83,7 +83,7 @@ export function writeStream({
// 获取写作列表
export function getWritePage(params: any) {
return requestClient.get<PageResult<AiWriteApi.AiWritePageReqVO>>(
return requestClient.get<PageResult<AiWriteApi.AiWritePageReq>>(
`/ai/write/page`,
{ params },
);

View File

@ -5,20 +5,20 @@ import { requestClient } from '#/api/request';
export namespace CrmBusinessStatusApi {
/** 商机状态信息 */
export interface BusinessStatusType {
id: number;
[x: string]: any;
id?: number;
name: string;
percent: number;
sort: number;
}
/** 商机状态组信息 */
export interface BusinessStatus {
id: number;
id?: number;
name: string;
deptIds: number[];
deptNames: string[];
creator: string;
createTime: Date;
deptIds?: number[];
deptNames?: string[];
creator?: string;
createTime?: Date;
statuses?: BusinessStatusType[];
}
}

View File

@ -121,10 +121,9 @@ export function putCustomerPool(id: number) {
/** 更新客户的成交状态 */
export function updateCustomerDealStatus(id: number, dealStatus: boolean) {
return requestClient.put('/crm/customer/update-deal-status', {
id,
dealStatus,
});
return requestClient.put(
`/crm/customer/update-deal-status?id=${id}&dealStatus=${dealStatus}`,
);
}
/** 进入公海客户提醒的客户列表 */

View File

@ -1,5 +1,3 @@
import type { PageParam } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace CrmStatisticsCustomerApi {
@ -93,10 +91,84 @@ export namespace CrmStatisticsCustomerApi {
customerDealCycle: number;
customerDealCount: number;
}
export interface CustomerSummaryParams {
times: string[];
interval: number;
deptId: number;
userId: number;
userIds: number[];
}
}
export function getDatas(activeTabName: any, params: any) {
switch (activeTabName) {
case 'conversionStat': {
return getContractSummary(params);
}
case 'customerSummary': {
return getCustomerSummaryByUser(params);
}
case 'dealCycleByArea': {
return getCustomerDealCycleByArea(params);
}
case 'dealCycleByProduct': {
return getCustomerDealCycleByProduct(params);
}
case 'dealCycleByUser': {
return getCustomerDealCycleByUser(params);
}
case 'followUpSummary': {
return getFollowUpSummaryByUser(params);
}
case 'followUpType': {
return getFollowUpSummaryByType(params);
}
case 'poolSummary': {
return getPoolSummaryByUser(params);
}
default: {
return [];
}
}
}
export function getChartDatas(activeTabName: any, params: any) {
switch (activeTabName) {
case 'conversionStat': {
return getCustomerSummaryByDate(params);
}
case 'customerSummary': {
return getCustomerSummaryByDate(params);
}
case 'dealCycleByArea': {
return getCustomerDealCycleByArea(params);
}
case 'dealCycleByProduct': {
return getCustomerDealCycleByProduct(params);
}
case 'dealCycleByUser': {
return getCustomerDealCycleByUser(params);
}
case 'followUpSummary': {
return getFollowUpSummaryByDate(params);
}
case 'followUpType': {
return getFollowUpSummaryByType(params);
}
case 'poolSummary': {
return getPoolSummaryByDate(params);
}
default: {
return [];
}
}
}
/** 客户总量分析(按日期) */
export function getCustomerSummaryByDate(params: PageParam) {
export function getCustomerSummaryByDate(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.CustomerSummaryByDate[]>(
'/crm/statistics-customer/get-customer-summary-by-date',
{ params },
@ -104,7 +176,9 @@ export function getCustomerSummaryByDate(params: PageParam) {
}
/** 客户总量分析(按用户) */
export function getCustomerSummaryByUser(params: PageParam) {
export function getCustomerSummaryByUser(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.CustomerSummaryByUser[]>(
'/crm/statistics-customer/get-customer-summary-by-user',
{ params },
@ -112,7 +186,9 @@ export function getCustomerSummaryByUser(params: PageParam) {
}
/** 客户跟进次数分析(按日期) */
export function getFollowUpSummaryByDate(params: PageParam) {
export function getFollowUpSummaryByDate(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.FollowUpSummaryByDate[]>(
'/crm/statistics-customer/get-follow-up-summary-by-date',
{ params },
@ -120,7 +196,9 @@ export function getFollowUpSummaryByDate(params: PageParam) {
}
/** 客户跟进次数分析(按用户) */
export function getFollowUpSummaryByUser(params: PageParam) {
export function getFollowUpSummaryByUser(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.FollowUpSummaryByUser[]>(
'/crm/statistics-customer/get-follow-up-summary-by-user',
{ params },
@ -128,7 +206,9 @@ export function getFollowUpSummaryByUser(params: PageParam) {
}
/** 获取客户跟进方式统计数 */
export function getFollowUpSummaryByType(params: PageParam) {
export function getFollowUpSummaryByType(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.FollowUpSummaryByType[]>(
'/crm/statistics-customer/get-follow-up-summary-by-type',
{ params },
@ -136,7 +216,9 @@ export function getFollowUpSummaryByType(params: PageParam) {
}
/** 合同摘要信息(客户转化率页面) */
export function getContractSummary(params: PageParam) {
export function getContractSummary(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.CustomerContractSummary[]>(
'/crm/statistics-customer/get-contract-summary',
{ params },
@ -144,7 +226,9 @@ export function getContractSummary(params: PageParam) {
}
/** 获取客户公海分析(按日期) */
export function getPoolSummaryByDate(params: PageParam) {
export function getPoolSummaryByDate(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.PoolSummaryByDate[]>(
'/crm/statistics-customer/get-pool-summary-by-date',
{ params },
@ -152,7 +236,9 @@ export function getPoolSummaryByDate(params: PageParam) {
}
/** 获取客户公海分析(按用户) */
export function getPoolSummaryByUser(params: PageParam) {
export function getPoolSummaryByUser(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.PoolSummaryByUser[]>(
'/crm/statistics-customer/get-pool-summary-by-user',
{ params },
@ -160,7 +246,9 @@ export function getPoolSummaryByUser(params: PageParam) {
}
/** 获取客户成交周期(按日期) */
export function getCustomerDealCycleByDate(params: PageParam) {
export function getCustomerDealCycleByDate(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.CustomerDealCycleByDate[]>(
'/crm/statistics-customer/get-customer-deal-cycle-by-date',
{ params },
@ -168,7 +256,9 @@ export function getCustomerDealCycleByDate(params: PageParam) {
}
/** 获取客户成交周期(按用户) */
export function getCustomerDealCycleByUser(params: PageParam) {
export function getCustomerDealCycleByUser(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.CustomerDealCycleByUser[]>(
'/crm/statistics-customer/get-customer-deal-cycle-by-user',
{ params },
@ -176,7 +266,9 @@ export function getCustomerDealCycleByUser(params: PageParam) {
}
/** 获取客户成交周期(按地区) */
export function getCustomerDealCycleByArea(params: PageParam) {
export function getCustomerDealCycleByArea(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<CrmStatisticsCustomerApi.CustomerDealCycleByArea[]>(
'/crm/statistics-customer/get-customer-deal-cycle-by-area',
{ params },
@ -184,7 +276,9 @@ export function getCustomerDealCycleByArea(params: PageParam) {
}
/** 获取客户成交周期(按产品) */
export function getCustomerDealCycleByProduct(params: PageParam) {
export function getCustomerDealCycleByProduct(
params: CrmStatisticsCustomerApi.CustomerSummaryParams,
) {
return requestClient.get<
CrmStatisticsCustomerApi.CustomerDealCycleByProduct[]
>('/crm/statistics-customer/get-customer-deal-cycle-by-product', { params });

View File

@ -1,4 +1,4 @@
import type { PageParam, PageResult } from '@vben/request';
import type { PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
@ -25,8 +25,42 @@ export namespace CrmStatisticsFunnelApi {
}
}
export function getDatas(activeTabName: any, params: any) {
switch (activeTabName) {
case 'businessInversionRateSummary': {
return getBusinessPageByDate(params);
}
case 'businessSummary': {
return getBusinessPageByDate(params);
}
case 'funnel': {
return getBusinessSummaryByEndStatus(params);
}
default: {
return [];
}
}
}
export function getChartDatas(activeTabName: any, params: any) {
switch (activeTabName) {
case 'businessInversionRateSummary': {
return getBusinessInversionRateSummaryByDate(params);
}
case 'businessSummary': {
return getBusinessSummaryByDate(params);
}
case 'funnel': {
return getFunnelSummary(params);
}
default: {
return [];
}
}
}
/** 获取销售漏斗统计数据 */
export function getFunnelSummary(params: PageParam) {
export function getFunnelSummary(params: any) {
return requestClient.get<CrmStatisticsFunnelApi.FunnelSummary>(
'/crm/statistics-funnel/get-funnel-summary',
{ params },
@ -34,7 +68,7 @@ export function getFunnelSummary(params: PageParam) {
}
/** 获取商机结束状态统计 */
export function getBusinessSummaryByEndStatus(params: PageParam) {
export function getBusinessSummaryByEndStatus(params: any) {
return requestClient.get<Record<string, number>>(
'/crm/statistics-funnel/get-business-summary-by-end-status',
{ params },
@ -42,7 +76,7 @@ export function getBusinessSummaryByEndStatus(params: PageParam) {
}
/** 获取新增商机分析(按日期) */
export function getBusinessSummaryByDate(params: PageParam) {
export function getBusinessSummaryByDate(params: any) {
return requestClient.get<CrmStatisticsFunnelApi.BusinessSummaryByDate[]>(
'/crm/statistics-funnel/get-business-summary-by-date',
{ params },
@ -50,7 +84,7 @@ export function getBusinessSummaryByDate(params: PageParam) {
}
/** 获取商机转化率分析(按日期) */
export function getBusinessInversionRateSummaryByDate(params: PageParam) {
export function getBusinessInversionRateSummaryByDate(params: any) {
return requestClient.get<
CrmStatisticsFunnelApi.BusinessInversionRateSummaryByDate[]
>('/crm/statistics-funnel/get-business-inversion-rate-summary-by-date', {
@ -59,7 +93,7 @@ export function getBusinessInversionRateSummaryByDate(params: PageParam) {
}
/** 获取商机列表(按日期) */
export function getBusinessPageByDate(params: PageParam) {
export function getBusinessPageByDate(params: any) {
return requestClient.get<PageResult<any>>(
'/crm/statistics-funnel/get-business-page-by-date',
{ params },

View File

@ -1,5 +1,3 @@
import type { PageParam } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace CrmStatisticsPerformanceApi {
@ -10,10 +8,17 @@ export namespace CrmStatisticsPerformanceApi {
lastMonthCount: number;
lastYearCount: number;
}
export interface PerformanceParams {
times: string[];
deptId: number;
userId: number;
}
}
/** 员工获得合同金额统计 */
export function getContractPricePerformance(params: PageParam) {
export function getContractPricePerformance(
params: CrmStatisticsPerformanceApi.PerformanceParams,
) {
return requestClient.get<CrmStatisticsPerformanceApi.Performance[]>(
'/crm/statistics-performance/get-contract-price-performance',
{ params },
@ -21,7 +26,9 @@ export function getContractPricePerformance(params: PageParam) {
}
/** 员工获得回款统计 */
export function getReceivablePricePerformance(params: PageParam) {
export function getReceivablePricePerformance(
params: CrmStatisticsPerformanceApi.PerformanceParams,
) {
return requestClient.get<CrmStatisticsPerformanceApi.Performance[]>(
'/crm/statistics-performance/get-receivable-price-performance',
{ params },
@ -29,7 +36,9 @@ export function getReceivablePricePerformance(params: PageParam) {
}
/** 员工获得签约合同数量统计 */
export function getContractCountPerformance(params: PageParam) {
export function getContractCountPerformance(
params: CrmStatisticsPerformanceApi.PerformanceParams,
) {
return requestClient.get<CrmStatisticsPerformanceApi.Performance[]>(
'/crm/statistics-performance/get-contract-count-performance',
{ params },

View File

@ -36,6 +36,26 @@ export namespace CrmStatisticsPortraitApi {
}
}
export function getDatas(activeTabName: any, params: any) {
switch (activeTabName) {
case 'area': {
return getCustomerArea(params);
}
case 'industry': {
return getCustomerIndustry(params);
}
case 'level': {
return getCustomerLevel(params);
}
case 'source': {
return getCustomerSource(params);
}
default: {
return [];
}
}
}
/** 获取客户行业统计数据 */
export function getCustomerIndustry(params: PageParam) {
return requestClient.get<CrmStatisticsPortraitApi.CustomerIndustry[]>(

View File

@ -11,6 +11,38 @@ export namespace CrmStatisticsRankApi {
}
}
export function getDatas(activeTabName: any, params: any) {
switch (activeTabName) {
case 'contactCountRank': {
return getContactsCountRank(params);
}
case 'contractCountRank': {
return getContractCountRank(params);
}
case 'contractPriceRank': {
return getContractPriceRank(params);
}
case 'customerCountRank': {
return getCustomerCountRank(params);
}
case 'followCountRank': {
return getFollowCountRank(params);
}
case 'followCustomerCountRank': {
return getFollowCustomerCountRank(params);
}
case 'productSalesRank': {
return getProductSalesRank(params);
}
case 'receivablePriceRank': {
return getReceivablePriceRank(params);
}
default: {
return [];
}
}
}
/** 获得合同排行榜 */
export function getContractPriceRank(params: PageParam) {
return requestClient.get<CrmStatisticsRankApi.Rank[]>(

View File

@ -121,7 +121,7 @@ const apiSelectRule = [
field: 'data',
title: '请求参数 JSON 格式',
props: {
autosize: true,
autoSize: true,
type: 'textarea',
placeholder: '{"type": 1}',
},
@ -155,7 +155,7 @@ const apiSelectRule = [
info: `data 为接口返回值,需要写一个匿名函数解析返回值为选择器 options 列表
(data: any)=>{ label: string; value: any }[]`,
props: {
autosize: true,
autoSize: true,
rows: { minRows: 2, maxRows: 6 },
type: 'textarea',
placeholder: `

View File

@ -63,6 +63,7 @@ const [Modal, modalApi] = useVbenModal({
});
// TODO xingyu modalApi ? trigger-node-config.vue conditionDialog
// useVbenModal
defineExpose({ modalApi });
</script>
<template>

View File

@ -41,13 +41,13 @@ const props = defineProps({
const { hasAccessByCodes } = useAccess();
/** 缓存处理后的actions */
/** 缓存处理后的 actions */
const processedActions = ref<any[]>([]);
const processedDropdownActions = ref<any[]>([]);
/** 用于比较的字符串化版本 */
const actionsStringified = ref('');
const dropdownActionsStringified = ref('');
const actionsStringField = ref('');
const dropdownActionsStringField = ref('');
function isIfShow(action: ActionItem): boolean {
const ifShow = action.ifShow;
@ -65,7 +65,7 @@ function isIfShow(action: ActionItem): boolean {
return isIfShow;
}
/** 处理actions的纯函数 */
/** 处理 actions 的纯函数 */
function processActions(actions: ActionItem[]): any[] {
return actions
.filter((action: ActionItem) => {
@ -84,7 +84,7 @@ function processActions(actions: ActionItem[]): any[] {
});
}
/** 处理下拉菜单actions的纯函数 */
/** 处理下拉菜单 actions 的纯函数 */
function processDropdownActions(
dropDownActions: ActionItem[],
divider: boolean,
@ -108,10 +108,10 @@ function processDropdownActions(
});
}
/** 监听actions变化并更新缓存 */
/** 监听 actions 变化并更新缓存 */
watchEffect(() => {
const rawActions = toRaw(props.actions) || [];
const currentStringified = JSON.stringify(
const currentStringField = JSON.stringify(
rawActions.map((a) => ({
...a,
onClick: undefined, // 便
@ -121,16 +121,16 @@ watchEffect(() => {
})),
);
if (currentStringified !== actionsStringified.value) {
actionsStringified.value = currentStringified;
if (currentStringField !== actionsStringField.value) {
actionsStringField.value = currentStringField;
processedActions.value = processActions(rawActions);
}
});
/** 监听dropDownActions变化并更新缓存 */
/** 监听 dropDownActions 变化并更新缓存 */
watchEffect(() => {
const rawDropDownActions = toRaw(props.dropDownActions) || [];
const currentStringified = JSON.stringify({
const currentStringField = JSON.stringify({
actions: rawDropDownActions.map((a) => ({
...a,
onClick: undefined, // 便
@ -141,8 +141,8 @@ watchEffect(() => {
divider: props.divider,
});
if (currentStringified !== dropdownActionsStringified.value) {
dropdownActionsStringified.value = currentStringified;
if (currentStringField !== dropdownActionsStringField.value) {
dropdownActionsStringField.value = currentStringField;
processedDropdownActions.value = processDropdownActions(
rawDropDownActions,
props.divider,
@ -154,14 +154,14 @@ const getActions = computed(() => processedActions.value);
const getDropdownList = computed(() => processedDropdownActions.value);
/** 缓存Space组件的size计算结果 */
/** 缓存 Space 组件的 size 计算结果 */
const spaceSize = computed(() => {
return unref(getActions)?.some((item: ActionItem) => item.type === 'link')
? 0
: 8;
});
/** 缓存PopConfirm属性 */
/** 缓存 PopConfirm 属性 */
const popConfirmPropsMap = new Map<string, any>();
function getPopConfirmProps(attrs: PopConfirm) {
@ -191,12 +191,13 @@ function getPopConfirmProps(attrs: PopConfirm) {
return originAttrs;
}
/** 缓存Button属性 */
/** 缓存 Button 属性 */
const buttonPropsMap = new Map<string, any>();
function getButtonProps(action: ActionItem) {
const key = JSON.stringify({
type: action.type,
danger: action.danger || false,
disabled: action.disabled,
loading: action.loading,
size: action.size,
@ -207,7 +208,8 @@ function getButtonProps(action: ActionItem) {
}
const res = {
type: action.type || 'primary',
type: action.type || 'link',
danger: action.danger || false,
disabled: action.disabled,
loading: action.loading,
size: action.size,
@ -217,7 +219,7 @@ function getButtonProps(action: ActionItem) {
return res;
}
/** 缓存Tooltip属性 */
/** 缓存 Tooltip 属性 */
const tooltipPropsMap = new Map<string, any>();
function getTooltipProps(tooltip: any | string) {
@ -243,7 +245,7 @@ function handleMenuClick(e: any) {
}
}
/** 生成稳定的key */
/** 生成稳定的 key */
function getActionKey(action: ActionItem, index: number) {
return `${action.label || ''}-${action.type || ''}-${index}`;
}

View File

@ -30,12 +30,12 @@ export const AiModelTypeEnum = {
EMBEDDING: 5, // 向量
RERANK: 6, // 重排
};
export interface ImageModelVO {
export interface ImageModel {
key: string;
name: string;
image?: string;
}
export const OtherPlatformEnum: ImageModelVO[] = [
export const OtherPlatformEnum: ImageModel[] = [
{
key: AiPlatformEnum.TONG_YI,
name: '通义万相',
@ -98,7 +98,7 @@ export const ImageHotEnglishWords = [
'The Great Wall of China',
]; // 图片热词(英文)
export const StableDiffusionSamplers: ImageModelVO[] = [
export const StableDiffusionSamplers: ImageModel[] = [
{
key: 'DDIM',
name: 'DDIM',
@ -141,7 +141,7 @@ export const StableDiffusionSamplers: ImageModelVO[] = [
},
];
export const StableDiffusionStylePresets: ImageModelVO[] = [
export const StableDiffusionStylePresets: ImageModel[] = [
{
key: '3d-model',
name: '3d-model',
@ -213,7 +213,7 @@ export const StableDiffusionStylePresets: ImageModelVO[] = [
},
];
export const StableDiffusionClipGuidancePresets: ImageModelVO[] = [
export const StableDiffusionClipGuidancePresets: ImageModel[] = [
{
key: 'NONE',
name: 'NONE',
@ -330,14 +330,14 @@ export const InfraApiErrorLogProcessStatusEnum = {
DONE: 1, // 已处理
IGNORE: 2, // 已忽略
};
export interface ImageSizeVO {
export interface ImageSize {
key: string;
name?: string;
style: string;
width: string;
height: string;
}
export const Dall3SizeList: ImageSizeVO[] = [
export const Dall3SizeList: ImageSize[] = [
{
key: '1024x1024',
name: '1:1',
@ -361,7 +361,7 @@ export const Dall3SizeList: ImageSizeVO[] = [
},
];
export const Dall3Models: ImageModelVO[] = [
export const Dall3Models: ImageModel[] = [
{
key: 'dall-e-3',
name: 'DALL·E 3',
@ -374,7 +374,7 @@ export const Dall3Models: ImageModelVO[] = [
},
];
export const Dall3StyleList: ImageModelVO[] = [
export const Dall3StyleList: ImageModel[] = [
{
key: 'vivid',
name: '清晰',
@ -386,7 +386,7 @@ export const Dall3StyleList: ImageModelVO[] = [
image: `/static/imgs/ai/ziran.jpg`,
},
];
export const MidjourneyModels: ImageModelVO[] = [
export const MidjourneyModels: ImageModel[] = [
{
key: 'midjourney',
name: 'MJ',
@ -428,7 +428,7 @@ export const NijiVersionList = [
},
];
export const MidjourneySizeList: ImageSizeVO[] = [
export const MidjourneySizeList: ImageSize[] = [
{
key: '1:1',
width: '1',

View File

@ -44,7 +44,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
const searchName = ref<string>(''); //
const activeConversationId = ref<null | number>(null); // null
const hoverConversationId = ref<null | number>(null); //
const conversationList = ref([] as AiChatConversationApi.ChatConversationVO[]); //
const conversationList = ref([] as AiChatConversationApi.ChatConversation[]); //
const conversationMap = ref<any>({}); // ()
const loading = ref<boolean>(false); //
const loadingTime = ref<any>();
@ -118,7 +118,7 @@ async function getChatConversationList() {
/** 按照 creteTime 创建时间,进行分组 */
async function getConversationGroupByCreateTime(
list: AiChatConversationApi.ChatConversationVO[],
list: AiChatConversationApi.ChatConversation[],
) {
// (30)
// noinspection NonAsciiCharacters
@ -164,7 +164,7 @@ async function getConversationGroupByCreateTime(
async function createConversation() {
// 1.
const conversationId = await createChatConversationMy(
{} as unknown as AiChatConversationApi.ChatConversationVO,
{} as unknown as AiChatConversationApi.ChatConversation,
);
// 2.
await getChatConversationList();
@ -176,7 +176,7 @@ async function createConversation() {
/** 修改对话的标题 */
async function updateConversationTitle(
conversation: AiChatConversationApi.ChatConversationVO,
conversation: AiChatConversationApi.ChatConversation,
) {
// 1.
prompt({
@ -188,7 +188,7 @@ async function updateConversationTitle(
await updateChatConversationMy({
id: conversation.id,
title: scope.value,
} as AiChatConversationApi.ChatConversationVO);
} as AiChatConversationApi.ChatConversation);
message.success('重命名成功');
// 3.
await getChatConversationList();
@ -230,7 +230,7 @@ async function updateConversationTitle(
/** 删除聊天对话 */
async function deleteChatConversation(
conversation: AiChatConversationApi.ChatConversationVO,
conversation: AiChatConversationApi.ChatConversation,
) {
try {
//
@ -260,9 +260,7 @@ async function handleClearConversation() {
}
/** 对话置顶 */
async function handleTop(
conversation: AiChatConversationApi.ChatConversationVO,
) {
async function handleTop(conversation: AiChatConversationApi.ChatConversation) {
//
conversation.pinned = !conversation.pinned;
await updateChatConversationMy(conversation);
@ -307,7 +305,7 @@ onMounted(async () => {
<template>
<Layout.Sider
width="280px"
class="!bg-primary-foreground conversation-container relative flex h-full flex-col justify-between overflow-hidden p-4"
class="conversation-container relative flex h-full flex-col justify-between overflow-hidden p-4"
>
<Drawer />
<!-- 左顶部对话 -->
@ -360,7 +358,9 @@ onMounted(async () => {
<div
class="conversation flex cursor-pointer flex-row items-center justify-between rounded-lg px-2 leading-10"
:class="[
conversation.id === activeConversationId ? 'bg-gray-100' : '',
conversation.id === activeConversationId
? 'bg-primary-200'
: '',
]"
>
<div class="title-wrapper flex items-center">
@ -420,7 +420,7 @@ onMounted(async () => {
<!-- 左底部工具栏 -->
<div
class="absolute bottom-1 left-0 right-0 mb-4 flex items-center justify-between bg-gray-50 px-5 leading-9 text-gray-400 shadow-sm"
class="bg-card absolute bottom-1 left-0 right-0 mb-4 flex items-center justify-between px-5 leading-9 text-gray-400 shadow-sm"
>
<div
class="flex cursor-pointer items-center text-gray-400"

View File

@ -17,7 +17,7 @@ import { $t } from '#/locales';
import { useFormSchema } from '../../data';
const emit = defineEmits(['success']);
const formData = ref<AiChatConversationApi.ChatConversationVO>();
const formData = ref<AiChatConversationApi.ChatConversation>();
const [Form, formApi] = useVbenForm({
commonConfig: {
@ -41,7 +41,7 @@ const [Modal, modalApi] = useVbenModal({
modalApi.lock();
//
const data =
(await formApi.getValues()) as AiChatConversationApi.ChatConversationVO;
(await formApi.getValues()) as AiChatConversationApi.ChatConversation;
try {
await updateChatConversationMy(data);
@ -59,7 +59,7 @@ const [Modal, modalApi] = useVbenModal({
return;
}
//
const data = modalApi.getData<AiChatConversationApi.ChatConversationVO>();
const data = modalApi.getData<AiChatConversationApi.ChatConversation>();
if (!data || !data.id) {
return;
}

View File

@ -21,11 +21,11 @@ import MessageKnowledge from './MessageKnowledge.vue';
// props
const props = defineProps({
conversation: {
type: Object as PropType<AiChatConversationApi.ChatConversationVO>,
type: Object as PropType<AiChatConversationApi.ChatConversation>,
required: true,
},
list: {
type: Array as PropType<AiChatMessageApi.ChatMessageVO[]>,
type: Array as PropType<AiChatMessageApi.ChatMessage[]>,
required: true,
},
});
@ -95,12 +95,12 @@ async function onDelete(id: number) {
}
/** 刷新 */
async function onRefresh(message: AiChatMessageApi.ChatMessageVO) {
async function onRefresh(message: AiChatMessageApi.ChatMessage) {
emits('onRefresh', message);
}
/** 编辑 */
async function onEdit(message: AiChatMessageApi.ChatMessageVO) {
async function onEdit(message: AiChatMessageApi.ChatMessage) {
emits('onEdit', message);
}

View File

@ -18,7 +18,7 @@ const props = defineProps({
required: true,
},
roleList: {
type: Array as PropType<AiModelChatRoleApi.ChatRoleVO[]>,
type: Array as PropType<AiModelChatRoleApi.ChatRole[]>,
required: true,
},
showMore: {
@ -79,7 +79,7 @@ async function handleTabsScroll() {
}"
>
<!-- 更多操作 -->
<div v-if="showMore" class="absolute right-3 top-0">
<div v-if="showMore" class="absolute right-2 top-0">
<Dropdown>
<Button type="link">
<IconifyIcon icon="lucide:ellipsis-vertical" />
@ -89,7 +89,7 @@ async function handleTabsScroll() {
<Menu.Item @click="handleMoreClick(['edit', role])">
<div class="flex items-center">
<IconifyIcon icon="lucide:edit" color="#787878" />
<span>编辑</span>
<span class="text-primary">编辑</span>
</div>
</Menu.Item>
<Menu.Item @click="handleMoreClick(['delete', role])">
@ -108,12 +108,12 @@ async function handleTabsScroll() {
<Avatar :src="role.avatar" class="h-10 w-10 overflow-hidden" />
</div>
<div class="ml-2 w-full">
<div class="ml-2 w-4/5">
<div class="h-20">
<div class="max-w-36 text-lg font-bold text-gray-600">
<div class="max-w-32 text-lg font-bold">
{{ role.name }}
</div>
<div class="mt-2 text-sm text-gray-400">
<div class="mt-2 text-sm">
{{ role.description }}
</div>
</div>

View File

@ -36,12 +36,12 @@ const myRoleParams = reactive({
pageNo: 1,
pageSize: 50,
});
const myRoleList = ref<AiModelChatRoleApi.ChatRoleVO[]>([]); // my
const myRoleList = ref<AiModelChatRoleApi.ChatRole[]>([]); // my
const publicRoleParams = reactive({
pageNo: 1,
pageSize: 50,
});
const publicRoleList = ref<AiModelChatRoleApi.ChatRoleVO[]>([]); // public
const publicRoleList = ref<AiModelChatRoleApi.ChatRole[]>([]); // public
const activeCategory = ref<string>('全部'); //
const categoryList = ref<string[]>([]); //
@ -55,7 +55,7 @@ async function handleTabsClick(tab: any) {
/** 获取 my role 我的角色 */
async function getMyRole(append?: boolean) {
const params: AiModelChatRoleApi.ChatRolePageReqVO = {
const params: AiModelChatRoleApi.ChatRolePageReq = {
...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.ChatRolePageReqVO = {
const params: AiModelChatRoleApi.ChatRolePageReq = {
...publicRoleParams,
category: activeCategory.value === '全部' ? '' : activeCategory.value,
name: search.value,
@ -148,9 +148,9 @@ async function handlerCardPage(type: string) {
/** 选择 card 角色:新建聊天对话 */
async function handlerCardUse(role: any) {
// 1.
const data: AiChatConversationApi.ChatConversationVO = {
const data: AiChatConversationApi.ChatConversation = {
roleId: role.id,
} as unknown as AiChatConversationApi.ChatConversationVO;
} as unknown as AiChatConversationApi.ChatConversation;
const conversationId = await createChatConversationMy(data);
// 2.
@ -174,7 +174,7 @@ onMounted(async () => {
<template>
<Drawer>
<Layout
class="absolute inset-0 flex h-full w-full flex-col overflow-hidden bg-white"
class="bg-card absolute inset-0 flex h-full w-full flex-col overflow-hidden"
>
<FormModal @success="handlerAddRoleSuccess" />

View File

@ -34,14 +34,14 @@ const [FormModal, formModalApi] = useVbenModal({
//
const conversationListRef = ref();
const activeConversationId = ref<null | number>(null); //
const activeConversation = ref<AiChatConversationApi.ChatConversationVO | null>(
const activeConversation = ref<AiChatConversationApi.ChatConversation | null>(
null,
); // Conversation
const conversationInProgress = ref(false); // true
//
const messageRef = ref();
const activeMessageList = ref<AiChatMessageApi.ChatMessageVO[]>([]); //
const activeMessageList = ref<AiChatMessageApi.ChatMessage[]>([]); //
const activeMessageListLoading = ref<boolean>(false); // activeMessageList
const activeMessageListLoadingTimer = ref<any>(); // activeMessageListLoading Timer
//
@ -65,7 +65,7 @@ async function getConversation(id: null | number) {
if (!id) {
return;
}
const conversation: AiChatConversationApi.ChatConversationVO =
const conversation: AiChatConversationApi.ChatConversation =
await getChatConversationMy(id);
if (!conversation) {
return;
@ -81,7 +81,7 @@ async function getConversation(id: null | number) {
* @return 是否切换成功
*/
async function handleConversationClick(
conversation: AiChatConversationApi.ChatConversationVO,
conversation: AiChatConversationApi.ChatConversation,
) {
//
if (conversationInProgress.value) {
@ -103,7 +103,7 @@ async function handleConversationClick(
/** 删除某个对话*/
async function handlerConversationDelete(
delConversation: AiChatConversationApi.ChatConversationVO,
delConversation: AiChatConversationApi.ChatConversation,
) {
//
if (activeConversationId.value === delConversation.id) {
@ -303,13 +303,11 @@ async function doSendMessage(content: string) {
await doSendMessageStream({
conversationId: activeConversationId.value,
content,
} as AiChatMessageApi.ChatMessageVO);
} as AiChatMessageApi.ChatMessage);
}
/** 真正执行【发送】消息操作 */
async function doSendMessageStream(
userMessage: AiChatMessageApi.ChatMessageVO,
) {
async function doSendMessageStream(userMessage: AiChatMessageApi.ChatMessage) {
// AbortController 便
conversationInAbortController.value = new AbortController();
//
@ -326,14 +324,14 @@ async function doSendMessageStream(
type: 'user',
content: userMessage.content,
createTime: new Date(),
} as AiChatMessageApi.ChatMessageVO,
} as AiChatMessageApi.ChatMessage,
{
id: -2,
conversationId: activeConversationId.value,
type: 'assistant',
content: '思考中...',
createTime: new Date(),
} as AiChatMessageApi.ChatMessageVO,
} as AiChatMessageApi.ChatMessage,
);
// 1.2
await nextTick();
@ -398,12 +396,12 @@ async function stopStream() {
}
/** 编辑 message设置为 prompt可以再次编辑 */
function handleMessageEdit(message: AiChatMessageApi.ChatMessageVO) {
function handleMessageEdit(message: AiChatMessageApi.ChatMessage) {
prompt.value = message.content;
}
/** 刷新 message基于指定消息再次发起对话 */
function handleMessageRefresh(message: AiChatMessageApi.ChatMessageVO) {
function handleMessageRefresh(message: AiChatMessageApi.ChatMessage) {
doSendMessage(message.content);
}
@ -497,6 +495,7 @@ onMounted(async () => {
<Layout class="absolute left-0 top-0 m-4 h-full w-full flex-1">
<!-- 左侧对话列表 -->
<ConversationList
class="!bg-card"
:active-id="activeConversationId as any"
ref="conversationListRef"
@on-conversation-create="handleConversationCreateSuccess"
@ -506,9 +505,9 @@ onMounted(async () => {
/>
<!-- 右侧详情部分 -->
<Layout class="mx-4 bg-white">
<Layout class="bg-card mx-4">
<Layout.Header
class="flex items-center justify-between !bg-gray-50 shadow-none"
class="!bg-card border-border flex items-center justify-between border-b"
>
<div class="text-lg font-bold">
{{ activeConversation?.title ? activeConversation?.title : '对话' }}
@ -567,9 +566,9 @@ onMounted(async () => {
</div>
</Layout.Content>
<Layout.Footer class="m-0 flex flex-col !bg-white p-0">
<Layout.Footer class="!bg-card m-0 flex flex-col p-0">
<form
class="my-5 mb-5 mt-2 flex flex-col rounded-xl border border-gray-200 px-2 py-2.5"
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"

View File

@ -46,7 +46,7 @@ export function useGridColumnsConversation(): VxeTableGridOptions['columns'] {
},
{
title: '用户',
width: 180,
minWidth: 180,
slots: { default: 'userId' },
},
{
@ -141,7 +141,7 @@ export function useGridColumnsMessage(): VxeTableGridOptions['columns'] {
},
{
title: '用户',
width: 180,
minWidth: 180,
slots: { default: 'userId' },
},
{

View File

@ -29,7 +29,7 @@ function onRefresh() {
}
/** 删除 */
async function handleDelete(row: AiChatConversationApi.ChatConversationVO) {
async function handleDelete(row: AiChatConversationApi.ChatConversation) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
key: 'action_key_msg',
@ -72,7 +72,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<AiChatConversationApi.ChatConversationVO>,
} as VxeTableGridOptions<AiChatConversationApi.ChatConversation>,
separator: false,
});
onMounted(async () => {

View File

@ -26,7 +26,7 @@ function onRefresh() {
}
/** 删除 */
async function handleDelete(row: AiChatConversationApi.ChatConversationVO) {
async function handleDelete(row: AiChatConversationApi.ChatConversation) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
key: 'action_key_msg',
@ -69,7 +69,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<AiChatConversationApi.ChatConversationVO>,
} as VxeTableGridOptions<AiChatConversationApi.ChatConversation>,
separator: false,
});
onMounted(async () => {

View File

@ -16,7 +16,7 @@ import { AiImageStatusEnum } from '#/utils';
const props = defineProps({
detail: {
type: Object as PropType<AiImageApi.ImageVO>,
type: Object as PropType<AiImageApi.Image>,
default: () => ({}),
},
});
@ -25,13 +25,13 @@ const emits = defineEmits(['onBtnClick', 'onMjBtnClick']);
const cardImageRef = ref<any>(); // image ref
/** 处理点击事件 */
async function handleButtonClick(type: string, detail: AiImageApi.ImageVO) {
async function handleButtonClick(type: string, detail: AiImageApi.Image) {
emits('onBtnClick', type, detail);
}
/** 处理 Midjourney 按钮点击事件 */
async function handleMidjourneyBtnClick(
button: AiImageApi.ImageMidjourneyButtonsVO,
button: AiImageApi.ImageMidjourneyButtons,
) {
//
await confirm(`确认操作 "${button.label} ${button.emoji}" ?`);

View File

@ -23,7 +23,7 @@ const props = defineProps({
required: true,
},
});
const detail = ref<AiImageApi.ImageVO>({} as AiImageApi.ImageVO);
const detail = ref<AiImageApi.Image>({} as AiImageApi.Image);
/** 获取图片详情 */
async function getImageDetail(id: number) {

View File

@ -35,7 +35,7 @@ const queryParams = reactive({
pageSize: 10,
});
const pageTotal = ref<number>(0); // page size
const imageList = ref<AiImageApi.ImageVO[]>([]); // image
const imageList = ref<AiImageApi.Image[]>([]); // image
const imageListRef = ref<any>(); // ref
//
const inProgressImageMap = ref<{}>({}); // image key image value image
@ -85,7 +85,7 @@ async function refreshWatchImages() {
if (imageIds.length === 0) {
return;
}
const list = (await getImageListMyByIds(imageIds)) as AiImageApi.ImageVO[];
const list = (await getImageListMyByIds(imageIds)) as AiImageApi.Image[];
const newWatchImages: any = {};
list.forEach((image) => {
if (image.status === AiImageStatusEnum.IN_PROGRESS) {
@ -106,7 +106,7 @@ async function refreshWatchImages() {
/** 图片的点击事件 */
async function handleImageButtonClick(
type: string,
imageDetail: AiImageApi.ImageVO,
imageDetail: AiImageApi.Image,
) {
//
if (type === 'more') {
@ -138,14 +138,14 @@ async function handleImageButtonClick(
/** 处理 Midjourney 按钮点击事件 */
async function handleImageMidjourneyButtonClick(
button: AiImageApi.ImageMidjourneyButtonsVO,
imageDetail: AiImageApi.ImageVO,
button: AiImageApi.ImageMidjourneyButtons,
imageDetail: AiImageApi.Image,
) {
// 1. params
const data = {
id: imageDetail.id,
customId: button.customId,
} as AiImageApi.ImageMidjourneyActionVO;
} as AiImageApi.ImageMidjourneyAction;
// 2. action
await midjourneyAction(data);
// 3.

View File

@ -17,8 +17,8 @@ import { AiPlatformEnum, ImageHotWords, OtherPlatformEnum } from '#/utils';
//
const props = defineProps({
models: {
type: Array<AiModelModelApi.ModelVO>,
default: () => [] as AiModelModelApi.ModelVO[],
type: Array<AiModelModelApi.Model>,
default: () => [] as AiModelModelApi.Model[],
},
});
const emits = defineEmits(['onDrawStart', 'onDrawComplete']);
@ -31,7 +31,7 @@ const prompt = ref<string>(''); // 提示词
const width = ref<number>(512); //
const height = ref<number>(512); //
const otherPlatform = ref<string>(AiPlatformEnum.TONG_YI); //
const platformModels = ref<AiModelModelApi.ModelVO[]>([]); //
const platformModels = ref<AiModelModelApi.Model[]>([]); //
const modelId = ref<number>(); //
/** 选择热词 */
@ -64,7 +64,7 @@ async function handleGenerateImage() {
width: width.value, //
height: height.value, //
options: {},
} as unknown as AiImageApi.ImageDrawReqVO;
} as unknown as AiImageApi.ImageDrawReq;
await drawImage(form);
} finally {
//
@ -75,7 +75,7 @@ async function handleGenerateImage() {
}
/** 填充值 */
async function settingValues(detail: AiImageApi.ImageVO) {
async function settingValues(detail: AiImageApi.Image) {
prompt.value = detail.prompt;
width.value = detail.width;
height.value = detail.height;
@ -85,7 +85,7 @@ async function settingValues(detail: AiImageApi.ImageVO) {
async function handlerPlatformChange(platform: any) {
//
platformModels.value = props.models.filter(
(item: AiModelModelApi.ModelVO) => item.platform === platform,
(item: AiModelModelApi.Model) => item.platform === platform,
);
modelId.value =
platformModels.value.length > 0 && platformModels.value[0]

View File

@ -2,7 +2,7 @@
<script setup lang="ts">
import type { AiImageApi } from '#/api/ai/image';
import type { AiModelModelApi } from '#/api/ai/model/model';
import type { ImageModelVO, ImageSizeVO } from '#/utils';
import type { ImageModel, ImageSize } from '#/utils';
import { ref } from 'vue';
@ -22,8 +22,8 @@ import {
//
const props = defineProps({
models: {
type: Array<AiModelModelApi.ModelVO>,
default: () => [] as AiModelModelApi.ModelVO[],
type: Array<AiModelModelApi.Model>,
default: () => [] as AiModelModelApi.Model[],
},
});
const emits = defineEmits(['onDrawStart', 'onDrawComplete']);
@ -50,7 +50,7 @@ async function handleHotWordClick(hotWord: string) {
}
/** 选择 model 模型 */
async function handleModelClick(model: ImageModelVO) {
async function handleModelClick(model: ImageModel) {
selectModel.value = model.key;
//
//
@ -76,12 +76,12 @@ async function handleModelClick(model: ImageModelVO) {
}
/** 选择 style 样式 */
async function handleStyleClick(imageStyle: ImageModelVO) {
async function handleStyleClick(imageStyle: ImageModel) {
style.value = imageStyle.key;
}
/** 选择 size 大小 */
async function handleSizeClick(imageSize: ImageSizeVO) {
async function handleSizeClick(imageSize: ImageSize) {
selectSize.value = imageSize.key;
}
@ -107,7 +107,7 @@ async function handleGenerateImage() {
emits('onDrawStart', AiPlatformEnum.OPENAI);
const imageSize = Dall3SizeList.find(
(item) => item.key === selectSize.value,
) as ImageSizeVO;
) as ImageSize;
const form = {
platform: AiPlatformEnum.OPENAI,
prompt: prompt.value, //
@ -118,7 +118,7 @@ async function handleGenerateImage() {
options: {
style: style.value, //
},
} as AiImageApi.ImageDrawReqVO;
} as AiImageApi.ImageDrawReq;
//
await drawImage(form);
} finally {
@ -130,13 +130,13 @@ async function handleGenerateImage() {
}
/** 填充值 */
async function settingValues(detail: AiImageApi.ImageVO) {
async function settingValues(detail: AiImageApi.Image) {
prompt.value = detail.prompt;
selectModel.value = detail.model;
style.value = detail.options?.style;
const imageSize = Dall3SizeList.find(
(item) => item.key === `${detail.width}x${detail.height}`,
) as ImageSizeVO;
) as ImageSize;
await handleSizeClick(imageSize);
}

View File

@ -2,7 +2,7 @@
<script setup lang="ts">
import type { AiImageApi } from '#/api/ai/image';
import type { AiModelModelApi } from '#/api/ai/model/model';
import type { ImageModelVO, ImageSizeVO } from '#/utils';
import type { ImageModel, ImageSize } from '#/utils';
import { ref } from 'vue';
@ -33,8 +33,8 @@ import {
//
const props = defineProps({
models: {
type: Array<AiModelModelApi.ModelVO>,
default: () => [] as AiModelModelApi.ModelVO[],
type: Array<AiModelModelApi.Model>,
default: () => [] as AiModelModelApi.Model[],
},
});
const emits = defineEmits(['onDrawStart', 'onDrawComplete']);
@ -64,12 +64,12 @@ async function handleHotWordClick(hotWord: string) {
}
/** 点击 size 尺寸 */
async function handleSizeClick(imageSize: ImageSizeVO) {
async function handleSizeClick(imageSize: ImageSize) {
selectSize.value = imageSize.key;
}
/** 点击 model 模型 */
async function handleModelClick(model: ImageModelVO) {
async function handleModelClick(model: ImageModel) {
selectModel.value = model.key;
versionList.value =
model.key === 'niji' ? NijiVersionList : MidjourneyVersions;
@ -99,7 +99,7 @@ async function handleGenerateImage() {
//
const imageSize = MidjourneySizeList.find(
(item) => selectSize.value === item.key,
) as ImageSizeVO;
) as ImageSize;
const req = {
prompt: prompt.value,
modelId: matchedModel.id,
@ -107,7 +107,7 @@ async function handleGenerateImage() {
height: imageSize.height,
version: selectVersion.value,
referImageUrl: referImageUrl.value,
} as AiImageApi.ImageMidjourneyImagineReqVO;
} as AiImageApi.ImageMidjourneyImagineReq;
await midjourneyImagine(req);
} finally {
//
@ -118,18 +118,18 @@ async function handleGenerateImage() {
}
/** 填充值 */
async function settingValues(detail: AiImageApi.ImageVO) {
async function settingValues(detail: AiImageApi.Image) {
//
prompt.value = detail.prompt;
// image size
const imageSize = MidjourneySizeList.find(
(item) => item.key === `${detail.width}:${detail.height}`,
) as ImageSizeVO;
) as ImageSize;
selectSize.value = imageSize.key;
//
const model = MidjourneyModels.find(
(item) => item.key === detail.options?.model,
) as ImageModelVO;
) as ImageModel;
await handleModelClick(model);
//
selectVersion.value = versionList.value.find(

View File

@ -28,8 +28,8 @@ import {
//
const props = defineProps({
models: {
type: Array<AiModelModelApi.ModelVO>,
default: () => [] as AiModelModelApi.ModelVO[],
type: Array<AiModelModelApi.Model>,
default: () => [] as AiModelModelApi.Model[],
},
});
@ -106,7 +106,7 @@ async function handleGenerateImage() {
clipGuidancePreset: clipGuidancePreset.value, // CLIP
stylePreset: stylePreset.value, //
},
} as unknown as AiImageApi.ImageDrawReqVO;
} as unknown as AiImageApi.ImageDrawReq;
await drawImage(form);
} finally {
//
@ -117,7 +117,7 @@ async function handleGenerateImage() {
}
/** 填充值 */
async function settingValues(detail: AiImageApi.ImageVO) {
async function settingValues(detail: AiImageApi.Image) {
prompt.value = detail.prompt;
width.value = detail.width;
height.value = detail.height;

View File

@ -44,7 +44,7 @@ const platformOptions = [
},
];
const models = ref<AiModelModelApi.ModelVO[]>([]); //
const models = ref<AiModelModelApi.Model[]>([]); //
/** 绘画 start */
const handleDrawStart = async () => {};
@ -55,7 +55,7 @@ const handleDrawComplete = async () => {
};
/** 重新生成:将画图详情填充到对应平台 */
const handleRegeneration = async (image: AiImageApi.ImageVO) => {
const handleRegeneration = async (image: AiImageApi.Image) => {
//
selectPlatform.value = image.platform;
// image
@ -90,9 +90,9 @@ onMounted(async () => {
<template>
<Page auto-content-height>
<div class="bg-card absolute inset-0 flex h-full w-full flex-row">
<div class="left-0 flex w-96 flex-col p-4">
<div class="segmented flex justify-center">
<div class="absolute inset-0 m-4 flex h-full w-full flex-row">
<div class="bg-card left-0 mr-4 flex w-96 flex-col rounded-lg p-4">
<div class="flex justify-center">
<Segmented
v-model:value="selectPlatform"
:options="platformOptions"
@ -125,7 +125,7 @@ onMounted(async () => {
/>
</div>
</div>
<div class="bg-card ml-4 flex-1">
<div class="bg-card flex-1">
<ImageList ref="imageListRef" @on-regeneration="handleRegeneration" />
</div>
</div>

View File

@ -24,7 +24,7 @@ function onRefresh() {
}
/** 删除 */
async function handleDelete(row: AiImageApi.ImageVO) {
async function handleDelete(row: AiImageApi.Image) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
key: 'action_key_msg',
@ -41,7 +41,7 @@ async function handleDelete(row: AiImageApi.ImageVO) {
}
}
/** 修改是否发布 */
const handleUpdatePublicStatusChange = async (row: AiImageApi.ImageVO) => {
const handleUpdatePublicStatusChange = async (row: AiImageApi.Image) => {
try {
//
const text = row.publicStatus ? '公开' : '私有';
@ -82,7 +82,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<AiImageApi.ImageVO>,
} as VxeTableGridOptions<AiImageApi.Image>,
});
onMounted(async () => {
//

View File

@ -11,7 +11,7 @@ import { Image, Input, Pagination } from 'ant-design-vue';
import { getImagePageMy } from '#/api/ai/image';
const loading = ref(true); //
const list = ref<AiImageApi.ImageVO[]>([]); //
const list = ref<AiImageApi.Image[]>([]); //
const total = ref(0); //
const queryParams = reactive({
pageNo: 1,

View File

@ -49,7 +49,7 @@ function handleEdit(id: number) {
}
/** 删除 */
async function handleDelete(row: AiKnowledgeDocumentApi.KnowledgeDocumentVO) {
async function handleDelete(row: AiKnowledgeDocumentApi.KnowledgeDocument) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: 'action_key_msg',
@ -74,7 +74,7 @@ const handleSegment = (id: number) => {
};
/** 修改是否发布 */
const handleStatusChange = async (
row: AiKnowledgeDocumentApi.KnowledgeDocumentVO,
row: AiKnowledgeDocumentApi.KnowledgeDocument,
) => {
try {
//
@ -120,7 +120,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<AiKnowledgeDocumentApi.KnowledgeDocumentVO>,
} as VxeTableGridOptions<AiKnowledgeDocumentApi.KnowledgeDocument>,
});
/** 初始化 */
onMounted(() => {

View File

@ -34,12 +34,12 @@ function handleCreate() {
}
/** 编辑 */
function handleEdit(row: AiKnowledgeKnowledgeApi.KnowledgeVO) {
function handleEdit(row: AiKnowledgeKnowledgeApi.Knowledge) {
formModalApi.setData(row).open();
}
/** 删除 */
async function handleDelete(row: AiKnowledgeKnowledgeApi.KnowledgeVO) {
async function handleDelete(row: AiKnowledgeKnowledgeApi.Knowledge) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: 'action_key_msg',
@ -98,7 +98,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<AiKnowledgeKnowledgeApi.KnowledgeVO>,
} as VxeTableGridOptions<AiKnowledgeKnowledgeApi.Knowledge>,
});
</script>

View File

@ -18,7 +18,7 @@ import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<AiKnowledgeKnowledgeApi.KnowledgeVO>();
const formData = ref<AiKnowledgeKnowledgeApi.Knowledge>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['AI 知识库'])
@ -47,7 +47,7 @@ const [Modal, modalApi] = useVbenModal({
modalApi.lock();
//
const data =
(await formApi.getValues()) as AiKnowledgeKnowledgeApi.KnowledgeVO;
(await formApi.getValues()) as AiKnowledgeKnowledgeApi.Knowledge;
try {
await (formData.value?.id
? updateKnowledge(data)
@ -66,7 +66,7 @@ const [Modal, modalApi] = useVbenModal({
return;
}
//
const data = modalApi.getData<AiKnowledgeKnowledgeApi.KnowledgeVO>();
const data = modalApi.getData<AiKnowledgeKnowledgeApi.Knowledge>();
if (!data || !data.id) {
return;
}

View File

@ -41,12 +41,12 @@ function handleCreate() {
}
/** 编辑 */
function handleEdit(row: AiKnowledgeKnowledgeApi.KnowledgeVO) {
function handleEdit(row: AiKnowledgeKnowledgeApi.Knowledge) {
formModalApi.setData(row).open();
}
/** 删除 */
async function handleDelete(row: AiKnowledgeKnowledgeApi.KnowledgeVO) {
async function handleDelete(row: AiKnowledgeKnowledgeApi.Knowledge) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
key: 'action_key_msg',
@ -89,13 +89,11 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<AiKnowledgeKnowledgeApi.KnowledgeVO>,
} as VxeTableGridOptions<AiKnowledgeKnowledgeApi.Knowledge>,
});
/** 修改是否发布 */
async function handleStatusChange(
row: AiKnowledgeSegmentApi.KnowledgeSegmentVO,
) {
async function handleStatusChange(row: AiKnowledgeSegmentApi.KnowledgeSegment) {
try {
//
const text = row.status ? '启用' : '禁用';

View File

@ -18,7 +18,7 @@ import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<AiKnowledgeSegmentApi.KnowledgeSegmentVO>();
const formData = ref<AiKnowledgeSegmentApi.KnowledgeSegment>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['分段'])
@ -47,7 +47,7 @@ const [Modal, modalApi] = useVbenModal({
modalApi.lock();
//
const data =
(await formApi.getValues()) as AiKnowledgeSegmentApi.KnowledgeSegmentVO;
(await formApi.getValues()) as AiKnowledgeSegmentApi.KnowledgeSegment;
try {
await (formData.value?.id
? updateKnowledgeSegment(data)
@ -66,7 +66,7 @@ const [Modal, modalApi] = useVbenModal({
return;
}
//
const data = modalApi.getData<AiKnowledgeSegmentApi.KnowledgeSegmentVO>();
const data = modalApi.getData<AiKnowledgeSegmentApi.KnowledgeSegment>();
if (!data || !data.id) {
await formApi.setValues(data);
return;

View File

@ -27,7 +27,7 @@ function directGenerate(existPrompt: string) {
isEnd.value = true;
}
/** 提交生成 */
function submit(data: AiMindmapApi.AiMindMapGenerateReqVO) {
function submit(data: AiMindmapApi.AiMindMapGenerateReq) {
isGenerating.value = true;
isStart.value = true;
isEnd.value = false;

View File

@ -31,7 +31,7 @@ function onRefresh() {
}
/** 删除 */
async function handleDelete(row: AiMindmapApi.MindMapVO) {
async function handleDelete(row: AiMindmapApi.MindMap) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
key: 'action_key_msg',
@ -73,9 +73,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<AiMindmapApi.MindMapVO>,
} as VxeTableGridOptions<AiMindmapApi.MindMap>,
});
async function openPreview(row: AiMindmapApi.MindMapVO) {
async function openPreview(row: AiMindmapApi.MindMap) {
previewVisible.value = false;
drawerApi.open();
await nextTick();

View File

@ -29,12 +29,12 @@ function handleCreate() {
}
/** 编辑 */
function handleEdit(row: AiModelApiKeyApi.ApiKeyVO) {
function handleEdit(row: AiModelApiKeyApi.ApiKey) {
formModalApi.setData(row).open();
}
/** 删除 */
async function handleDelete(row: AiModelApiKeyApi.ApiKeyVO) {
async function handleDelete(row: AiModelApiKeyApi.ApiKey) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: 'action_key_msg',
@ -77,7 +77,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<AiModelApiKeyApi.ApiKeyVO>,
} as VxeTableGridOptions<AiModelApiKeyApi.ApiKey>,
});
</script>

View File

@ -14,7 +14,7 @@ import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<AiModelApiKeyApi.ApiKeyVO>();
const formData = ref<AiModelApiKeyApi.ApiKey>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['API 密钥'])
@ -42,7 +42,7 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
//
const data = (await formApi.getValues()) as AiModelApiKeyApi.ApiKeyVO;
const data = (await formApi.getValues()) as AiModelApiKeyApi.ApiKey;
try {
await (formData.value?.id ? updateApiKey(data) : createApiKey(data));
//
@ -59,7 +59,7 @@ const [Modal, modalApi] = useVbenModal({
return;
}
//
const data = modalApi.getData<AiModelApiKeyApi.ApiKeyVO>();
const data = modalApi.getData<AiModelApiKeyApi.ApiKey>();
if (!data || !data.id) {
return;
}

View File

@ -29,12 +29,12 @@ function handleCreate() {
}
/** 编辑 */
function handleEdit(row: AiModelChatRoleApi.ChatRoleVO) {
function handleEdit(row: AiModelChatRoleApi.ChatRole) {
formModalApi.setData({ formType: 'update', ...row }).open();
}
/** 删除 */
async function handleDelete(row: AiModelChatRoleApi.ChatRoleVO) {
async function handleDelete(row: AiModelChatRoleApi.ChatRole) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: 'action_key_msg',
@ -77,7 +77,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<AiModelChatRoleApi.ChatRoleVO>,
} as VxeTableGridOptions<AiModelChatRoleApi.ChatRole>,
});
</script>

View File

@ -19,7 +19,7 @@ import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<AiModelChatRoleApi.ChatRoleVO>();
const formData = ref<AiModelChatRoleApi.ChatRole>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['聊天角色'])
@ -47,7 +47,7 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
//
const data = (await formApi.getValues()) as AiModelChatRoleApi.ChatRoleVO;
const data = (await formApi.getValues()) as AiModelChatRoleApi.ChatRole;
try {
await (formData.value?.id ? updateChatRole(data) : createChatRole(data));
//
@ -64,7 +64,7 @@ const [Modal, modalApi] = useVbenModal({
return;
}
//
const data = modalApi.getData<AiModelChatRoleApi.ChatRoleVO>();
const data = modalApi.getData<AiModelChatRoleApi.ChatRole>();
if (!data || !data.id) {
await formApi.setValues(data);
return;

View File

@ -17,7 +17,7 @@ import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const apiKeyList = ref([] as AiModelApiKeyApi.ApiKeyVO[]);
const apiKeyList = ref([] as AiModelApiKeyApi.ApiKey[]);
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
@ -34,12 +34,12 @@ function handleCreate() {
}
/** 编辑 */
function handleEdit(row: AiModelModelApi.ModelVO) {
function handleEdit(row: AiModelModelApi.Model) {
formModalApi.setData(row).open();
}
/** 删除 */
async function handleDelete(row: AiModelModelApi.ModelVO) {
async function handleDelete(row: AiModelModelApi.Model) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: 'action_key_msg',
@ -82,7 +82,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<AiModelModelApi.ModelVO>,
} as VxeTableGridOptions<AiModelModelApi.Model>,
});
onMounted(async () => {
//

View File

@ -15,7 +15,7 @@ import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<AiModelModelApi.ModelVO>();
const formData = ref<AiModelModelApi.Model>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['模型配置'])
@ -43,7 +43,7 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
//
const data = (await formApi.getValues()) as AiModelModelApi.ModelVO;
const data = (await formApi.getValues()) as AiModelModelApi.Model;
try {
await (formData.value?.id ? updateModel(data) : createModel(data));
//
@ -60,7 +60,7 @@ const [Modal, modalApi] = useVbenModal({
return;
}
//
const data = modalApi.getData<AiModelModelApi.ModelVO>();
const data = modalApi.getData<AiModelModelApi.Model>();
if (!data || !data.id) {
return;
}

View File

@ -29,12 +29,12 @@ function handleCreate() {
}
/** 编辑 */
function handleEdit(row: AiModelToolApi.ToolVO) {
function handleEdit(row: AiModelToolApi.Tool) {
formModalApi.setData(row).open();
}
/** 删除 */
async function handleDelete(row: AiModelToolApi.ToolVO) {
async function handleDelete(row: AiModelToolApi.Tool) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: 'action_key_msg',
@ -77,7 +77,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<AiModelToolApi.ToolVO>,
} as VxeTableGridOptions<AiModelToolApi.Tool>,
});
</script>

View File

@ -14,7 +14,7 @@ import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<AiModelToolApi.ToolVO>();
const formData = ref<AiModelToolApi.Tool>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['工具'])
@ -42,7 +42,7 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
//
const data = (await formApi.getValues()) as AiModelToolApi.ToolVO;
const data = (await formApi.getValues()) as AiModelToolApi.Tool;
try {
await (formData.value?.id ? updateTool(data) : createTool(data));
//
@ -59,7 +59,7 @@ const [Modal, modalApi] = useVbenModal({
return;
}
//
const data = modalApi.getData<AiModelToolApi.ToolVO>();
const data = modalApi.getData<AiModelToolApi.Tool>();
if (!data || !data.id) {
return;
}

View File

@ -26,7 +26,7 @@ defineExpose({
>
<Textarea
v-model:value="formData.desc"
:autosize="{ minRows: 6, maxRows: 6 }"
:auto-size="{ minRows: 6, maxRows: 6 }"
:maxlength="1200"
:show-count="true"
placeholder="一首关于糟糕分手的欢快歌曲"

View File

@ -28,7 +28,7 @@ defineExpose({
<Title title="歌词" desc="自己编写歌词或使用Ai生成歌词两节/8行效果最佳">
<Textarea
v-model:value="formData.lyric"
:autosize="{ minRows: 6, maxRows: 6 }"
:auto-size="{ minRows: 6, maxRows: 6 }"
:maxlength="1200"
:show-count="true"
placeholder="请输入您自己的歌词"
@ -60,7 +60,7 @@ defineExpose({
>
<Textarea
v-model="formData.style"
:autosize="{ minRows: 4, maxRows: 4 }"
:auto-size="{ minRows: 4, maxRows: 4 }"
:maxlength="256"
show-count
placeholder="输入音乐风格(英文)"

View File

@ -100,15 +100,6 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
minWidth: 180,
slots: { default: 'content' },
},
{
field: 'status',
title: '绘画状态',
minWidth: 100,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.AI_IMAGE_STATUS },
},
},
{
field: 'duration',
title: '时长(秒)',
@ -139,9 +130,12 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
},
},
{
field: 'tags',
title: '风格标签',
minWidth: 180,
slots: { default: 'tags' },
cellRender: {
name: 'CellTags',
},
},
{
minWidth: 100,

View File

@ -7,7 +7,7 @@ import { onMounted, ref } from 'vue';
import { confirm, DocAlert, Page } from '@vben/common-ui';
import { Button, message, Switch, Tag } from 'ant-design-vue';
import { Button, message, Switch } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteMusic, getMusicPage, updateMusic } from '#/api/ai/music';
@ -24,7 +24,7 @@ function onRefresh() {
}
/** 删除 */
async function handleDelete(row: AiMusicApi.MusicVO) {
async function handleDelete(row: AiMusicApi.Music) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
key: 'action_key_msg',
@ -41,7 +41,7 @@ async function handleDelete(row: AiMusicApi.MusicVO) {
}
}
/** 修改是否发布 */
const handleUpdatePublicStatusChange = async (row: AiMusicApi.MusicVO) => {
const handleUpdatePublicStatusChange = async (row: AiMusicApi.Music) => {
try {
//
const text = row.publicStatus ? '公开' : '私有';
@ -82,7 +82,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<AiMusicApi.MusicVO>,
} as VxeTableGridOptions<AiMusicApi.Music>,
});
onMounted(async () => {
//
@ -101,9 +101,9 @@ onMounted(async () => {
</template>
<template #userId="{ row }">
<span>{{
userList.find((item) => item.id === row.userId)?.nickname
}}</span>
<span>
{{ userList.find((item) => item.id === row.userId)?.nickname }}
</span>
</template>
<template #content="{ row }">
<Button
@ -141,11 +141,6 @@ onMounted(async () => {
:disabled="row.status !== AiMusicStatusEnum.SUCCESS"
/>
</template>
<template #tags="{ row }">
<Tag v-for="tag in row.tags" :key="tag" class="ml-1">
{{ tag }}
</Tag>
</template>
<template #actions="{ row }">
<TableAction
:actions="[

View File

@ -17,7 +17,7 @@ import {
import Tag from './Tag.vue';
type TabType = AiWriteApi.WriteVO['type'];
type TabType = AiWriteApi.Write['type'];
defineProps<{
isWriting: boolean;
@ -26,7 +26,7 @@ defineProps<{
const emit = defineEmits<{
(e: 'example', param: 'reply' | 'write'): void;
(e: 'reset'): void;
(e: 'submit', params: Partial<AiWriteApi.WriteVO>): void;
(e: 'submit', params: Partial<AiWriteApi.Write>): void;
}>();
function omit(obj: Record<string, any>, keysToOmit: string[]) {
@ -74,7 +74,7 @@ const [DefineLabel, ReuseLabel] = createReusableTemplate<{
label: string;
}>();
const initData: AiWriteApi.WriteVO = {
const initData: AiWriteApi.Write = {
type: 1,
prompt: '',
originalContent: '',
@ -84,10 +84,10 @@ const initData: AiWriteApi.WriteVO = {
format: 1,
};
const formData = ref<AiWriteApi.WriteVO>({ ...initData });
const formData = ref<AiWriteApi.Write>({ ...initData });
/** 用来记录切换之前所填写的数据,切换的时候给赋值回来 */
const recordFormData = {} as Record<AiWriteTypeEnum, AiWriteApi.WriteVO>;
const recordFormData = {} as Record<AiWriteTypeEnum, AiWriteApi.Write>;
/** 切换tab */
function switchTab(value: TabType) {
if (value !== selectedTab.value) {
@ -123,8 +123,10 @@ function submit() {
<template>
<DefineTab v-slot="{ active, text, itemClick }">
<span
:class="active ? 'text-black shadow-md' : 'hover:bg-gray-200'"
class="relative z-10 inline-block w-1/2 cursor-pointer rounded-full text-center leading-7 text-gray-400 hover:text-black"
:class="
active ? 'bg-primary-600 text-white shadow-md' : 'hover:bg-primary-200'
"
class="relative z-10 inline-block w-1/2 cursor-pointer rounded-full text-center leading-7 hover:text-black"
@click="itemClick"
>
{{ text }}
@ -136,7 +138,7 @@ function submit() {
<span>{{ label }}</span>
<span
v-if="hint"
class="flex cursor-pointer select-none items-center text-xs text-purple-500"
class="text-primary-500 flex cursor-pointer select-none items-center text-xs"
@click="hintClick"
>
<IconifyIcon icon="lucide:circle-help" />
@ -145,14 +147,14 @@ function submit() {
</h3>
</DefineLabel>
<div class="flex flex-col" v-bind="$attrs">
<div class="flex w-full justify-center bg-gray-50 pt-2">
<div class="z-10 w-72 rounded-full bg-gray-200 p-1">
<div class="bg-card flex w-full justify-center pt-2">
<div class="bg-card z-10 w-72 rounded-full p-1">
<div
:class="
selectedTab === AiWriteTypeEnum.REPLY &&
'after:translate-x-[100%] after:transform'
"
class="relative flex items-center after:absolute after:left-0 after:top-0 after:block after:h-7 after:w-1/2 after:rounded-full after:bg-white after:transition-transform after:content-['']"
class="after:bg-card relative flex items-center after:absolute after:left-0 after:top-0 after:block after:h-7 after:w-1/2 after:rounded-full after:transition-transform after:content-['']"
>
<ReuseTab
v-for="tab in tabs"
@ -166,7 +168,7 @@ function submit() {
</div>
</div>
<div
class="box-border h-full w-96 flex-grow overflow-y-auto bg-gray-50 px-7 pb-2 lg:block"
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">
@ -233,11 +235,7 @@ function submit() {
<Button :disabled="isWriting" class="mr-2" @click="reset">
重置
</Button>
<Button
:loading="isWriting"
class="bg-purple-500 text-white"
@click="submit"
>
<Button type="primary" :loading="isWriting" @click="submit">
生成
</Button>
</div>

View File

@ -54,22 +54,18 @@ watch(copied, (val) => {
});
</script>
<template>
<Card class="my-card flex h-full flex-col">
<Card class="flex h-full flex-col">
<template #title>
<h3 class="m-0 flex shrink-0 items-center justify-between px-7">
<span>预览</span>
<!-- 展示在右上角 -->
<Button
class="flex bg-purple-500 text-white"
type="primary"
v-show="showCopy"
@click="copyContent"
size="small"
>
<template #icon>
<div class="flex items-center justify-center">
<IconifyIcon icon="lucide:copy" />
</div>
</template>
<IconifyIcon icon="lucide:copy" />
复制
</Button>
</h3>
@ -79,7 +75,7 @@ watch(copied, (val) => {
class="hide-scroll-bar box-border h-full overflow-y-auto"
>
<div
class="relative box-border min-h-full w-full flex-grow bg-white p-3 sm:p-7"
class="bg-card relative box-border min-h-full w-full flex-grow p-3 sm:p-7"
>
<!-- 终止生成内容的按钮 -->
<Button
@ -98,7 +94,7 @@ watch(copied, (val) => {
<Textarea
id="inputId"
v-model:value="compContent"
autosize
auto-size
:bordered="false"
placeholder="生成的内容……"
/>

View File

@ -21,8 +21,10 @@ const emits = defineEmits<{
<span
v-for="tag in props.tags"
:key="tag.value"
class="mb-2 cursor-pointer rounded border-2 border-solid border-gray-200 bg-gray-200 px-1 text-xs leading-6"
:class="modelValue === tag.value && '!border-purple-500 !text-purple-500'"
class="bg-card border-card-100 mb-2 cursor-pointer rounded border-2 border-solid px-1 text-xs leading-6"
:class="
modelValue === tag.value && '!border-primary-500 !text-primary-500'
"
@click="emits('update:modelValue', tag.value)"
>
{{ tag.label }}

View File

@ -24,7 +24,7 @@ function stopStream() {
/** 执行写作 */
const rightRef = ref<InstanceType<typeof Right>>();
function submit(data: Partial<AiWriteApi.WriteVO>) {
function submit(data: Partial<AiWriteApi.Write>) {
abortController.value = new AbortController();
writeResult.value = '';
isWriting.value = true;
@ -66,10 +66,10 @@ function reset() {
<template>
<Page auto-content-height>
<div class="absolute bottom-0 left-0 right-0 top-0 flex">
<div class="absolute bottom-0 left-0 right-0 top-0 m-4 flex">
<Left
:is-writing="isWriting"
class="h-full"
class="mr-4 h-full rounded-lg"
@submit="submit"
@reset="reset"
@example="handleExampleClick"

View File

@ -23,7 +23,7 @@ function onRefresh() {
}
/** 删除 */
async function handleDelete(row: AiWriteApi.AiWritePageReqVO) {
async function handleDelete(row: AiWriteApi.AiWritePageReq) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
key: 'action_key_msg',
@ -65,7 +65,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<AiWriteApi.AiWritePageReqVO>,
} as VxeTableGridOptions<AiWriteApi.AiWritePageReq>,
});
onMounted(async () => {
//

View File

@ -21,7 +21,7 @@ const props = defineProps<{
type: 'copy' | 'create' | 'edit';
}>();
//
/** 流程表单详情 */
const flowFormConfig = ref();
const [FormModal, formModalApi] = useVbenModal({
@ -31,7 +31,7 @@ const [FormModal, formModalApi] = useVbenModal({
const designerRef = ref<InstanceType<typeof FcDesigner>>();
//
/** 表单设计器配置 */
const designerConfig = ref({
switchType: [], // ,
autoActive: true, //
@ -80,7 +80,7 @@ const currentFormId = computed(() => {
});
//
async function loadFormConfig(id: number | string) {
async function loadFormConfig(id: number) {
try {
const formDetail = await getFormDetail(id);
flowFormConfig.value = formDetail;
@ -106,8 +106,7 @@ async function initializeDesigner() {
}
}
// TODO @ziye使 /** */
//
/** 保存表单 */
function handleSave() {
formModalApi
.setData({
@ -118,7 +117,7 @@ function handleSave() {
.open();
}
//
/** 返回列表页 */
function onBack() {
router.push({
path: '/bpm/manager/form',
@ -137,7 +136,11 @@ onMounted(() => {
<Page auto-content-height>
<FormModal @success="onBack" />
<FcDesigner class="my-designer" ref="designerRef" :config="designerConfig">
<FcDesigner
class="h-full min-h-[500px]"
ref="designerRef"
:config="designerConfig"
>
<template #handle>
<Button size="small" type="primary" @click="handleSave">
<IconifyIcon icon="mdi:content-save" />
@ -147,10 +150,3 @@ onMounted(() => {
</FcDesigner>
</Page>
</template>
<style scoped>
.my-designer {
height: 100%;
min-height: 500px;
}
</style>

View File

@ -20,7 +20,13 @@ export function useGridColumns(): VxeTableGridOptions<BpmProcessDefinitionApi.Pr
field: 'icon',
title: '流程图标',
minWidth: 100,
slots: { default: 'icon' },
cellRender: {
name: 'CellImage',
props: {
width: 24,
height: 24,
},
},
},
{
field: 'startUsers',
@ -47,7 +53,9 @@ export function useGridColumns(): VxeTableGridOptions<BpmProcessDefinitionApi.Pr
field: 'version',
title: '流程版本',
minWidth: 80,
slots: { default: 'version' },
cellRender: {
name: 'CellTag',
},
},
{
field: 'deploymentTime',

View File

@ -6,7 +6,7 @@ import { useRoute, useRouter } from 'vue-router';
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, Tag, Tooltip } from 'ant-design-vue';
import { Button, Tooltip } from 'ant-design-vue';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getProcessDefinitionPage } from '#/api/bpm/definition';
@ -93,16 +93,6 @@ onMounted(() => {
<DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
</template>
<Grid table-title="">
<template #icon="{ row }">
<Image
v-if="row.icon"
:src="row.icon"
:width="24"
:height="24"
class="rounded"
/>
<span v-else> </span>
</template>
<template #startUsers="{ row }">
<template v-if="!row.startUsers?.length"></template>
<template v-else-if="row.startUsers.length === 1">
@ -135,9 +125,6 @@ onMounted(() => {
</Button>
<span v-else></span>
</template>
<template #version="{ row }">
<Tag>v{{ row.version }}</Tag>
</template>
<template #actions="{ row }">
<TableAction
:actions="[

View File

@ -102,7 +102,7 @@ defineExpose({ validate });
DICT_TYPE.BPM_MODEL_FORM_TYPE,
'number',
)"
:key="dict.value as string"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}

View File

@ -4,7 +4,7 @@ import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue';
import dayjs from 'dayjs';
import { formatDateTime } from '@vben/utils';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
@ -186,12 +186,12 @@ export function useDetailFormSchema(): DescriptionItemSchema[] {
{
label: '开始时间',
field: 'startTime',
content: (data) => dayjs(data?.startTime).format('YYYY-MM-DD HH:mm:ss'),
content: (data) => formatDateTime(data?.startTime) as string,
},
{
label: '结束时间',
field: 'endTime',
content: (data) => dayjs(data?.endTime).format('YYYY-MM-DD HH:mm:ss'),
content: (data) => formatDateTime(data?.endTime) as string,
},
{
label: '原因',

View File

@ -88,6 +88,12 @@ export function useGridColumns(
onTaskClick: (task: BpmProcessInstanceApi.Task) => void,
): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
title: '流程编号',
minWidth: 320,
fixed: 'left',
},
{
field: 'name',
title: '流程名称',
@ -167,12 +173,6 @@ export function useGridColumns(
},
},
},
{
field: 'id',
title: '流程编号',
minWidth: 320,
},
{
title: '操作',
width: 180,

View File

@ -41,12 +41,6 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
title: '发起人',
minWidth: 120,
},
{
field: 'createTime',
title: '发起时间',
minWidth: 180,
formatter: 'formatDateTime',
},
{
field: 'name',
title: '当前任务',

View File

@ -93,12 +93,13 @@ onMounted(async () => {
<Card class="w-1/5">
<List item-layout="horizontal" :data-source="leftSides">
<template #renderItem="{ item }">
<List.Item>
<List.Item
@click="sideClick(item)"
class="cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"
>
<List.Item.Meta>
<template #title>
<a @click="sideClick(item)">
{{ item.name }}
</a>
{{ item.name }}
</template>
</List.Item.Meta>
<template #extra>

View File

@ -1,14 +1,17 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { useUserStore } from '@vben/stores';
import { erpPriceMultiply } from '@vben/utils';
import { z } from '#/adapter/form';
import { getBusinessStatusTypeSimpleList } from '#/api/crm/business/status';
import { getCustomerSimpleList } from '#/api/crm/customer';
import { getSimpleUserList } from '#/api/system/user';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
const userStore = useUserStore();
return [
{
fieldName: 'id',
@ -35,6 +38,7 @@ export function useFormSchema(): VbenFormSchema[] {
value: 'id',
},
},
defaultValue: userStore.userInfo?.id,
rules: 'required',
},
{
@ -50,7 +54,7 @@ export function useFormSchema(): VbenFormSchema[] {
},
dependencies: {
triggerFields: ['id'],
disabled: (values) => !values.customerId,
disabled: (values) => values.customerDefault,
},
rules: 'required',
},
@ -103,8 +107,9 @@ export function useFormSchema(): VbenFormSchema[] {
component: 'InputNumber',
componentProps: {
min: 0,
precision: 2,
},
rules: 'required',
rules: z.number().min(0).optional().default(0),
},
{
fieldName: 'discountPercent',
@ -114,15 +119,19 @@ export function useFormSchema(): VbenFormSchema[] {
min: 0,
precision: 2,
},
rules: 'required',
rules: z.number().min(0).max(100).optional().default(0),
},
{
fieldName: 'totalPrice',
label: '折扣后金额',
component: 'InputNumber',
componentProps: {
min: 0,
precision: 2,
disabled: true,
},
dependencies: {
triggerFields: ['totalProductPrice', 'discountPercent'],
disabled: () => true,
trigger(values, form) {
const discountPrice =
erpPriceMultiply(
@ -157,69 +166,83 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
field: 'name',
title: '商机名称',
fixed: 'left',
minWidth: 240,
slots: { default: 'name' },
},
{
field: 'customerName',
title: '客户名称',
fixed: 'left',
minWidth: 240,
slots: { default: 'customerName' },
},
{
field: 'totalPrice',
title: '商机金额(元)',
minWidth: 140,
formatter: 'formatAmount2',
},
{
field: 'dealTime',
title: '预计成交日期',
formatter: 'formatDate',
minWidth: 180,
},
{
field: 'remark',
title: '备注',
minWidth: 200,
},
{
field: 'contactNextTime',
title: '下次联系时间',
formatter: 'formatDate',
minWidth: 180,
},
{
field: 'ownerUserName',
title: '负责人',
minWidth: 120,
},
{
field: 'ownerUserDeptName',
title: '所属部门',
minWidth: 120,
},
{
field: 'contactLastTime',
title: '最后跟进时间',
formatter: 'formatDateTime',
},
{
field: 'updateTime',
title: '更新时间',
formatter: 'formatDateTime',
minWidth: 180,
},
{
field: 'createTime',
title: '创建时间',
formatter: 'formatDateTime',
minWidth: 180,
},
{
field: 'creatorName',
title: '创建人',
minWidth: 120,
},
{
field: 'updateTime',
title: '更新时间',
formatter: 'formatDateTime',
minWidth: 180,
},
{
field: 'statusTypeName',
title: '商机状态组',
fixed: 'right',
minWidth: 120,
},
{
field: 'statusName',
title: '商机阶段',
fixed: 'right',
minWidth: 120,
},
{
title: '操作',

View File

@ -86,7 +86,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
ajax: {
query: async ({ page }, formValues) => {
return await getBusinessPage({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});

View File

@ -108,7 +108,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
ajax: {
query: async ({ page }, formValues) => {
return await getBusinessPageByCustomer({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
customerId: props.customerId,
...formValues,

View File

@ -15,7 +15,10 @@ import {
getBusinessPageByContact,
getBusinessPageByCustomer,
} from '#/api/crm/business';
import { createContactBusinessList } from '#/api/crm/contact';
import {
createContactBusinessList,
deleteContactBusinessList,
} from '#/api/crm/contact';
import { BizTypeEnum } from '#/api/crm/permission';
import { $t } from '#/locales';
@ -73,7 +76,7 @@ async function handleDeleteContactBusinessList() {
content: `确定要将${checkedRows.value.map((item) => item.name).join(',')}解除关联吗?`,
})
.then(async () => {
const res = await createContactBusinessList({
const res = await deleteContactBusinessList({
contactId: props.bizId,
businessIds: checkedRows.value.map((item) => item.id),
});
@ -121,14 +124,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
query: async ({ page }, formValues) => {
if (props.bizType === BizTypeEnum.CRM_CUSTOMER) {
return await getBusinessPageByCustomer({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
customerId: props.customerId,
...formValues,
});
} else if (props.bizType === BizTypeEnum.CRM_CONTACT) {
return await getBusinessPageByContact({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
contactId: props.contactId,
...formValues,

View File

@ -3,9 +3,7 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { handleTree } from '@vben/utils';
import { z } from '#/adapter/form';
import { getDeptList } from '#/api/system/dept';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
@ -38,17 +36,13 @@ export function useFormSchema(): VbenFormSchema[] {
placeholder: '请选择应用部门',
treeDefaultExpandAll: true,
},
help: '不选择部门时,默认全公司生效',
},
{
fieldName: 'status',
label: '状态',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
buttonStyle: 'solid',
optionType: 'button',
},
rules: z.number().default(CommonStatusEnum.ENABLE),
fieldName: 'statuses',
label: '阶段设置',
component: 'Input',
rules: 'required',
},
];
}

View File

@ -61,7 +61,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
ajax: {
query: async ({ page }, formValues) => {
return await getBusinessStatusPage({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});

View File

@ -1,15 +1,17 @@
<script lang="ts" setup>
import type { CrmBusinessStatusApi } from '#/api/crm/business/status';
import { computed, ref } from 'vue';
import { computed, nextTick, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { Input, InputNumber, message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
createBusinessStatus,
DEFAULT_STATUSES,
getBusinessStatus,
updateBusinessStatus,
} from '#/api/crm/business/status';
@ -49,6 +51,10 @@ const [Modal, modalApi] = useVbenModal({
const data =
(await formApi.getValues()) as CrmBusinessStatusApi.BusinessStatus;
try {
if (formData.value?.statuses && formData.value.statuses.length > 0) {
data.statuses = formData.value.statuses;
data.statuses.splice(-3, 3);
}
await (formData.value?.id
? updateBusinessStatus(data)
: createBusinessStatus(data));
@ -62,30 +68,159 @@ const [Modal, modalApi] = useVbenModal({
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
//
const data = modalApi.getData<CrmBusinessStatusApi.BusinessStatus>();
if (!data || !data.id) {
return;
}
modalApi.lock();
try {
formData.value = await getBusinessStatus(data.id as number);
// values
if (formData.value) {
await formApi.setValues(formData.value);
if (!data || !data.id) {
formData.value = {
id: undefined,
name: '',
deptIds: [],
statuses: [],
};
addStatus();
} else {
formData.value = await getBusinessStatus(data.id as number);
if (
!formData.value?.statuses?.length ||
formData.value?.statuses?.length === 0
) {
addStatus();
}
}
// values
await formApi.setValues(formData.value as any);
gridApi.grid.reloadData(
(formData.value!.statuses =
formData.value?.statuses?.concat(DEFAULT_STATUSES)) as any,
);
} finally {
modalApi.unlock();
}
},
});
/** 添加状态 */
async function addStatus() {
formData.value!.statuses!.unshift({
name: '',
percent: undefined,
} as any);
await nextTick();
gridApi.grid.reloadData(formData.value!.statuses as any);
}
/** 删除状态 */
async function deleteStatusArea(row: any, rowIndex: number) {
gridApi.grid.remove(row);
formData.value!.statuses!.splice(rowIndex, 1);
gridApi.grid.reloadData(formData.value!.statuses as any);
}
/** 表格配置 */
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
editConfig: {
trigger: 'click',
mode: 'cell',
},
columns: [
{
field: 'defaultStatus',
title: '阶段',
minWidth: 100,
slots: { default: 'defaultStatus' },
},
{
field: 'name',
title: '阶段名称',
minWidth: 100,
slots: { default: 'name' },
},
{
field: 'percent',
title: '赢单率(%',
minWidth: 100,
slots: { default: 'percent' },
},
{
title: '操作',
width: 130,
fixed: 'right',
slots: { default: 'actions' },
},
],
data: formData.value?.statuses?.concat(DEFAULT_STATUSES),
border: true,
showOverflow: true,
autoResize: true,
keepSource: true,
rowConfig: {
keyField: 'row_id',
},
pagerConfig: {
enabled: false,
},
toolbarConfig: {
enabled: false,
},
},
});
</script>
<template>
<Modal :title="getTitle" class="w-1/2">
<Form class="mx-4" />
<Form class="mx-4">
<template #statuses>
<Grid class="w-full">
<template #defaultStatus="{ row, rowIndex }">
<span>
{{ row.defaultStatus ? '结束' : `阶段${rowIndex + 1}` }}
</span>
</template>
<template #name="{ row }">
<Input v-if="!row.endStatus" v-model:value="row.name" />
<span v-else>{{ row.name }}</span>
</template>
<template #percent="{ row }">
<InputNumber
v-if="!row.endStatus"
v-model:value="row.percent"
:min="0"
:max="100"
:precision="2"
/>
<span v-else>{{ row.percent }}</span>
</template>
<template #actions="{ row, rowIndex }">
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create'),
type: 'link',
ifShow: () => !row.endStatus,
onClick: addStatus,
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
ifShow: () => !row.endStatus,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: deleteStatusArea.bind(null, row, rowIndex),
},
},
]"
/>
</template>
</Grid>
</template>
</Form>
</Modal>
</template>

View File

@ -1,12 +1,15 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { useUserStore } from '@vben/stores';
import { getAreaTree } from '#/api/system/area';
import { getSimpleUserList } from '#/api/system/user';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
const userStore = useUserStore();
return [
{
fieldName: 'id',
@ -46,6 +49,7 @@ export function useFormSchema(): VbenFormSchema[] {
valueField: 'id',
allowClear: true,
},
defaultValue: userStore.userInfo?.id,
rules: 'required',
},
{
@ -164,6 +168,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
field: 'name',
title: '线索名称',
fixed: 'left',
minWidth: 240,
slots: {
default: 'name',
},
@ -171,6 +176,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'source',
title: '线索来源',
minWidth: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.CRM_CUSTOMER_SOURCE },
@ -179,22 +185,27 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'mobile',
title: '手机',
minWidth: 120,
},
{
field: 'telephone',
title: '电话',
minWidth: 120,
},
{
field: 'email',
title: '邮箱',
minWidth: 120,
},
{
field: 'detailAddress',
title: '地址',
minWidth: 120,
},
{
field: 'industryId',
title: '客户行业',
minWidth: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY },
@ -203,6 +214,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'level',
title: '客户级别',
minWidth: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.CRM_CUSTOMER_LEVEL },
@ -211,34 +223,41 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'ownerUserName',
title: '负责人',
minWidth: 120,
},
{
field: 'ownerUserDeptName',
title: '所属部门',
minWidth: 120,
},
{
field: 'contactNextTime',
title: '下次联系时间',
formatter: 'formatDateTime',
minWidth: 180,
},
{
field: 'contactLastTime',
title: '最后跟进时间',
formatter: 'formatDateTime',
minWidth: 180,
},
{
field: 'updateTime',
title: '更新时间',
formatter: 'formatDateTime',
minWidth: 180,
},
{
field: 'createTime',
title: '创建时间',
formatter: 'formatDateTime',
minWidth: 120,
},
{
field: 'creatorName',
title: '创建人',
minWidth: 120,
},
{
title: '操作',

View File

@ -79,7 +79,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
ajax: {
query: async ({ page }, formValues) => {
return await getCluePage({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});

View File

@ -26,9 +26,10 @@ const [Form, formApi] = useVbenForm({
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 120,
labelWidth: 100,
},
// 3
wrapperClass: 'grid-cols-2',
layout: 'horizontal',
schema: useFormSchema(),
showDefaultActions: false,

View File

@ -1,6 +1,8 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { useUserStore } from '@vben/stores';
import { getSimpleContactList } from '#/api/crm/contact';
import { getCustomerSimpleList } from '#/api/crm/customer';
import { getAreaTree } from '#/api/system/area';
@ -9,6 +11,7 @@ import { DICT_TYPE, getDictOptions } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
const userStore = useUserStore();
return [
{
fieldName: 'id',
@ -35,6 +38,7 @@ export function useFormSchema(): VbenFormSchema[] {
value: 'id',
},
},
defaultValue: userStore.userInfo?.id,
},
{
fieldName: 'customerId',
@ -43,7 +47,7 @@ export function useFormSchema(): VbenFormSchema[] {
componentProps: {
api: () => getCustomerSimpleList(),
fieldNames: {
label: 'nickname',
label: 'name',
value: 'id',
},
},
@ -188,17 +192,20 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
field: 'name',
title: '联系人姓名',
fixed: 'left',
minWidth: 240,
slots: { default: 'name' },
},
{
field: 'customerName',
title: '客户名称',
fixed: 'left',
minWidth: 240,
slots: { default: 'customerName' },
},
{
field: 'sex',
title: '性别',
minWidth: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.SYSTEM_USER_SEX },
@ -207,26 +214,32 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'mobile',
title: '手机',
minWidth: 120,
},
{
field: 'telephone',
title: '电话',
minWidth: 120,
},
{
field: 'email',
title: '邮箱',
minWidth: 120,
},
{
field: 'post',
title: '职位',
minWidth: 120,
},
{
field: 'detailAddress',
title: '地址',
minWidth: 120,
},
{
field: 'master',
title: '关键决策人',
minWidth: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
@ -235,34 +248,41 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'parentId',
title: '直属上级',
minWidth: 120,
slots: { default: 'parentId' },
},
{
field: 'ownerUserName',
title: '负责人',
minWidth: 120,
},
{
field: 'ownerUserDeptName',
title: '所属部门',
minWidth: 120,
},
{
field: 'contactNextTime',
title: '下次联系时间',
formatter: 'formatDateTime',
minWidth: 180,
},
{
field: 'remark',
title: '备注',
minWidth: 200,
},
{
field: 'createTime',
title: '创建时间',
formatter: 'formatDateTime',
minWidth: 180,
},
{
field: 'updateTime',
title: '更新时间',
formatter: 'formatDateTime',
minWidth: 180,
},
{
title: '操作',

View File

@ -90,7 +90,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
ajax: {
query: async ({ page }, formValues) => {
return await getContactPage({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
sceneType: sceneType.value,
...formValues,

View File

@ -108,7 +108,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
ajax: {
query: async ({ page }, formValues) => {
return await getContactPageByCustomer({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
customerId: props.customerId,
...formValues,

View File

@ -121,14 +121,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
query: async ({ page }, formValues) => {
if (props.bizType === BizTypeEnum.CRM_CUSTOMER) {
return await getContactPageByCustomer({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
customerId: props.bizId,
...formValues,
});
} else if (props.bizType === BizTypeEnum.CRM_BUSINESS) {
return await getContactPageByBusiness({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
businessId: props.bizId,
...formValues,

View File

@ -60,6 +60,8 @@ const [Modal, modalApi] = useVbenModal({
//
const data = modalApi.getData<CrmContactApi.Contact>();
if (!data || !data.id) {
// values
await formApi.setValues(data);
return;
}
modalApi.lock();

View File

@ -1,6 +1,7 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { useUserStore } from '@vben/stores';
import { erpPriceMultiply, floatToFixed2 } from '@vben/utils';
import { z } from '#/adapter/form';
@ -12,6 +13,7 @@ import { DICT_TYPE } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
const userStore = useUserStore();
return [
{
fieldName: 'id',
@ -27,7 +29,7 @@ export function useFormSchema(): VbenFormSchema[] {
component: 'Input',
componentProps: {
placeholder: '保存时自动生成',
disabled: () => true,
disabled: true,
},
},
{
@ -50,6 +52,7 @@ export function useFormSchema(): VbenFormSchema[] {
value: 'id',
},
},
defaultValue: userStore.userInfo?.id,
rules: 'required',
},
{
@ -58,22 +61,45 @@ export function useFormSchema(): VbenFormSchema[] {
component: 'ApiSelect',
rules: 'required',
componentProps: {
api: getCustomerSimpleList,
labelField: 'name',
valueField: 'id',
api: () => getCustomerSimpleList(),
fieldNames: {
label: 'name',
value: 'id',
},
placeholder: '请选择客户',
},
},
{
fieldName: 'businessId',
label: '商机名称',
component: 'ApiSelect',
component: 'Select',
componentProps: {
api: getSimpleBusinessList,
labelField: 'name',
valueField: 'id',
options: [],
placeholder: '请选择商机',
},
dependencies: {
triggerFields: ['customerId'],
disabled: (values) => !values.customerId,
async componentProps(values) {
if (!values.customerId) {
return {
options: [],
placeholder: '请选择客户',
};
}
const res = await getSimpleBusinessList();
const list = res.filter(
(item) => item.customerId === values.customerId,
);
return {
options: list.map((item) => ({
label: item.name,
value: item.id,
})),
placeholder: '请选择商机',
};
},
},
},
{
fieldName: 'orderDate',
@ -117,17 +143,39 @@ export function useFormSchema(): VbenFormSchema[] {
value: 'id',
},
},
defaultValue: userStore.userInfo?.id,
},
{
fieldName: 'signContactId',
label: '客户签约人',
component: 'ApiSelect',
component: 'Select',
componentProps: {
api: getSimpleContactList,
labelField: 'name',
valueField: 'id',
options: [],
placeholder: '请选择客户签约人',
},
dependencies: {
triggerFields: ['customerId'],
disabled: (values) => !values.customerId,
async componentProps(values) {
if (!values.customerId) {
return {
options: [],
placeholder: '请选择客户',
};
}
const res = await getSimpleContactList();
const list = res.filter(
(item) => item.customerId === values.customerId,
);
return {
options: list.map((item) => ({
label: item.name,
value: item.id,
})),
placeholder: '请选择客户签约人',
};
},
},
},
{
fieldName: 'remark',
@ -150,25 +198,31 @@ export function useFormSchema(): VbenFormSchema[] {
component: 'InputNumber',
componentProps: {
min: 0,
precision: 2,
},
rules: z.number().min(0).optional().default(0),
},
{
fieldName: 'discountPercent',
label: '整单折扣(%',
component: 'InputNumber',
rules: z.number().min(0).max(100).default(0),
componentProps: {
min: 0,
precision: 2,
},
rules: z.number().min(0).max(100).optional().default(0),
},
{
fieldName: 'totalPrice',
label: '折扣后金额',
component: 'InputNumber',
componentProps: {
min: 0,
precision: 2,
disabled: true,
},
dependencies: {
triggerFields: ['totalProductPrice', 'discountPercent'],
disabled: () => true,
trigger(values, form) {
const discountPrice =
erpPriceMultiply(
@ -203,9 +257,11 @@ export function useGridFormSchema(): VbenFormSchema[] {
label: '客户',
component: 'ApiSelect',
componentProps: {
api: getCustomerSimpleList,
labelField: 'name',
valueField: 'id',
api: () => getCustomerSimpleList(),
fieldNames: {
label: 'name',
value: 'id',
},
placeholder: '请选择客户',
},
},
@ -223,20 +279,20 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
title: '合同名称',
field: 'name',
minWidth: 150,
minWidth: 220,
fixed: 'left',
slots: { default: 'name' },
},
{
title: '客户名称',
field: 'customerName',
minWidth: 150,
minWidth: 240,
slots: { default: 'customerName' },
},
{
title: '商机名称',
field: 'businessName',
minWidth: 150,
minWidth: 220,
slots: { default: 'businessName' },
},
{

View File

@ -72,13 +72,13 @@ async function handleDelete(row: CrmContractApi.Contract) {
/** 提交审核 */
async function handleSubmit(row: CrmContractApi.Contract) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.submitting', [row.name]),
content: '提交审核中...',
key: 'action_key_msg',
});
try {
await submitContract(row.id as number);
message.success({
content: $t('ui.actionMessage.submitSuccess', [row.name]),
content: '提交审核成功',
key: 'action_key_msg',
});
onRefresh();
@ -127,7 +127,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
ajax: {
query: async ({ page }, formValues) => {
return await getContractPage({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
sceneType: sceneType.value,
...formValues,

View File

@ -45,7 +45,15 @@ function onRefresh() {
/** 创建合同 */
function handleCreate() {
formModalApi.setData(null).open();
formModalApi
.setData(
props.bizType === BizTypeEnum.CRM_CUSTOMER
? {
customerId: props.bizId,
}
: { businessId: props.bizId },
)
.open();
}
/** 查看合同详情 */
@ -63,14 +71,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
query: async ({ page }, formValues) => {
if (props.bizType === BizTypeEnum.CRM_CUSTOMER) {
return await getContractPageByCustomer({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
customerId: props.bizId,
...formValues,
});
} else if (props.bizType === BizTypeEnum.CRM_CONTACT) {
return await getContractPageByBusiness({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
businessId: props.bizId,
...formValues,

View File

@ -90,6 +90,8 @@ const [Modal, modalApi] = useVbenModal({
//
const data = modalApi.getData<CrmContractApi.Contract>();
if (!data || !data.id) {
// values
await formApi.setValues(data);
return;
}
modalApi.lock();

View File

@ -1,12 +1,15 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { useUserStore } from '@vben/stores';
import { getAreaTree } from '#/api/system/area';
import { getSimpleUserList } from '#/api/system/user';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
const userStore = useUserStore();
return [
{
fieldName: 'id',
@ -27,7 +30,7 @@ export function useFormSchema(): VbenFormSchema[] {
label: '客户来源',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE),
options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE, 'number'),
},
rules: 'required',
},
@ -47,6 +50,7 @@ export function useFormSchema(): VbenFormSchema[] {
value: 'id',
},
},
defaultValue: userStore.userInfo?.id,
rules: 'required',
},
{
@ -74,7 +78,7 @@ export function useFormSchema(): VbenFormSchema[] {
label: '客户行业',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY),
options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY, 'number'),
},
},
{
@ -82,7 +86,7 @@ export function useFormSchema(): VbenFormSchema[] {
label: '客户级别',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL),
options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL, 'number'),
},
},
{
@ -154,6 +158,8 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
field: 'name',
title: '客户名称',
fixed: 'left',
align: 'left',
minWidth: 280,
slots: {
default: 'name',
},
@ -161,6 +167,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'source',
title: '客户来源',
minWidth: 100,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.CRM_CUSTOMER_SOURCE },
@ -169,22 +176,32 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'mobile',
title: '手机',
minWidth: 100,
},
{
field: 'telephone',
title: '电话',
minWidth: 100,
},
{
field: 'email',
title: '邮箱',
minWidth: 100,
},
{
field: 'areaName',
title: '地址',
minWidth: 140,
},
{
field: 'detailAddress',
title: '地址',
minWidth: 140,
},
{
field: 'industryId',
title: '客户行业',
minWidth: 80,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY },
@ -193,6 +210,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'level',
title: '客户级别',
minWidth: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.CRM_CUSTOMER_LEVEL },
@ -201,30 +219,36 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'ownerUserName',
title: '负责人',
minWidth: 80,
},
{
field: 'ownerUserDeptName',
title: '所属部门',
minWidth: 100,
},
{
field: 'contactNextTime',
title: '下次联系时间',
formatter: 'formatDateTime',
minWidth: 160,
},
{
field: 'contactLastTime',
title: '最后跟进时间',
formatter: 'formatDateTime',
minWidth: 160,
},
{
field: 'updateTime',
title: '更新时间',
formatter: 'formatDateTime',
minWidth: 160,
},
{
field: 'createTime',
title: '创建时间',
formatter: 'formatDateTime',
minWidth: 160,
},
{
title: '操作',

View File

@ -96,7 +96,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
ajax: {
query: async ({ page }, formValues) => {
return await getCustomerPage({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
sceneType: sceneType.value,
...formValues,

View File

@ -73,7 +73,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
ajax: {
query: async ({ page }, formValues) => {
return await getCustomerLimitConfigPage({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
type: configType.value,
...formValues,

View File

@ -8,13 +8,20 @@ import { useRoute, useRouter } from 'vue-router';
import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { useTabs } from '@vben/hooks';
import { Button, Card, message, Tabs } from 'ant-design-vue';
import { Card, message, Tabs } from 'ant-design-vue';
import { getCustomer, updateCustomerDealStatus } from '#/api/crm/customer';
import {
getCustomer,
lockCustomer,
putCustomerPool,
receiveCustomer,
updateCustomerDealStatus,
} from '#/api/crm/customer';
import { getOperateLogPage } from '#/api/crm/operateLog';
import { BizTypeEnum } from '#/api/crm/permission';
import { useDescription } from '#/components/description';
import { AsyncOperateLog } from '#/components/operate-log';
import { ACTION_ICON, TableAction } from '#/components/table-action';
import { BusinessDetailsList } from '#/views/crm/business';
import { ContactDetailsList } from '#/views/crm/contact';
import { ContractDetailsList } from '#/views/crm/contract';
@ -99,18 +106,45 @@ function handleTransfer() {
}
/** 锁定客户 */
function handleLock() {
transferModalApi.setData({ id: customerId.value }).open();
}
/** 解锁客户 */
function handleUnlock() {
transferModalApi.setData({ id: customerId.value }).open();
function handleLock(lockStatus: boolean): Promise<boolean | undefined> {
return new Promise((resolve, reject) => {
confirm({
content: `确定锁定客户【${customer.value.name}】吗?`,
})
.then(async () => {
const res = await lockCustomer(customerId.value, lockStatus);
if (res) {
message.success(lockStatus ? '锁定客户成功' : '解锁客户成功');
resolve(true);
} else {
reject(new Error(lockStatus ? '锁定客户失败' : '解锁客户失败'));
}
})
.catch(() => {
reject(new Error('取消操作'));
});
});
}
/** 领取客户 */
function handleReceive() {
transferModalApi.setData({ id: customerId.value }).open();
function handleReceive(): Promise<boolean | undefined> {
return new Promise((resolve, reject) => {
confirm({
content: `确定领取客户【${customer.value.name}】吗?`,
})
.then(async () => {
const res = await receiveCustomer([customerId.value]);
if (res) {
message.success('领取客户成功');
resolve(true);
} else {
reject(new Error('领取客户失败'));
}
})
.catch(() => {
reject(new Error('取消操作'));
});
});
}
/** 分配客户 */
@ -119,8 +153,24 @@ function handleDistributeForm() {
}
/** 客户放入公海 */
function handlePutPool() {
transferModalApi.setData({ id: customerId.value }).open();
function handlePutPool(): Promise<boolean | undefined> {
return new Promise((resolve, reject) => {
confirm({
content: `确定将客户【${customer.value.name}】放入公海吗?`,
})
.then(async () => {
const res = await putCustomerPool(customerId.value);
if (res) {
message.success('放入公海成功');
resolve(true);
} else {
reject(new Error('放入公海失败'));
}
})
.catch(() => {
reject(new Error('取消操作'));
});
});
}
/** 更新成交状态操作 */
@ -161,61 +211,62 @@ onMounted(() => {
<TransferModal @success="loadCustomerDetail" />
<DistributeModal @success="loadCustomerDetail" />
<template #extra>
<div class="flex items-center gap-2">
<Button
v-if="permissionListRef?.validateWrite"
type="primary"
@click="handleEdit"
v-access:code="['crm:customer:update']"
>
{{ $t('ui.actionTitle.edit') }}
</Button>
<Button
v-if="permissionListRef?.validateOwnerUser"
type="primary"
@click="handleTransfer"
>
转移
</Button>
<Button
v-if="permissionListRef?.validateWrite"
@click="handleUpdateDealStatus"
>
更改成交状态
</Button>
<Button
v-if="customer.lockStatus && permissionListRef?.validateOwnerUser"
@click="handleUnlock"
>
解锁
</Button>
<Button
v-if="!customer.lockStatus && permissionListRef?.validateOwnerUser"
@click="handleLock"
>
锁定
</Button>
<Button
v-if="!customer.ownerUserId"
type="primary"
@click="handleReceive"
>
领取
</Button>
<Button
v-if="!customer.ownerUserId"
type="primary"
@click="handleDistributeForm"
>
分配
</Button>
<Button
v-if="customer.ownerUserId && permissionListRef?.validateOwnerUser"
@click="handlePutPool"
>
放入公海
</Button>
</div>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.edit'),
type: 'primary',
icon: ACTION_ICON.EDIT,
auth: ['crm:customer:update'],
ifShow: permissionListRef?.validateWrite,
onClick: handleEdit,
},
{
label: '转移',
type: 'primary',
ifShow: permissionListRef?.validateOwnerUser,
onClick: handleTransfer,
},
{
label: '更改成交状态',
type: 'default',
ifShow: permissionListRef?.validateWrite,
onClick: handleUpdateDealStatus,
},
{
label: '锁定',
type: 'default',
ifShow:
!customer.lockStatus && permissionListRef?.validateOwnerUser,
onClick: handleLock.bind(null, true),
},
{
label: '解锁',
type: 'default',
ifShow: customer.lockStatus && permissionListRef?.validateOwnerUser,
onClick: handleLock.bind(null, false),
},
{
label: '领取',
type: 'primary',
ifShow: !customer.ownerUserId,
onClick: handleReceive,
},
{
label: '分配',
type: 'default',
ifShow: !customer.ownerUserId,
onClick: handleDistributeForm,
},
{
label: '放入公海',
type: 'default',
ifShow:
!!customer.ownerUserId && permissionListRef?.validateOwnerUser,
onClick: handlePutPool,
},
]"
/>
</template>
<Card class="min-h-[10%]">
<Description :data="customer" />

View File

@ -40,7 +40,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
ajax: {
query: async ({ page }, formValues) => {
return await getCustomerPage({
page: page.currentPage,
pageNo: page.currentPage,
pageSize: page.pageSize,
pool: true,
...formValues,

Some files were not shown because too many files have changed in this diff Show More