Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 495 KiB |
@@ -50,6 +50,7 @@ export namespace CrmContractApi {
|
|||||||
creatorName: string;
|
creatorName: string;
|
||||||
updateTime?: Date;
|
updateTime?: Date;
|
||||||
products?: ContractProduct[];
|
products?: ContractProduct[];
|
||||||
|
contactName?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export namespace CrmCustomerApi {
|
|||||||
ownerUserId: number; // 负责人的用户编号
|
ownerUserId: number; // 负责人的用户编号
|
||||||
ownerUserName?: string; // 负责人的用户名称
|
ownerUserName?: string; // 负责人的用户名称
|
||||||
ownerUserDept?: string; // 负责人的部门名称
|
ownerUserDept?: string; // 负责人的部门名称
|
||||||
|
ownerUserDeptName?: string; // 负责人的部门名称
|
||||||
lockStatus?: boolean;
|
lockStatus?: boolean;
|
||||||
dealStatus?: boolean;
|
dealStatus?: boolean;
|
||||||
mobile: string; // 手机号
|
mobile: string; // 手机号
|
||||||
@@ -34,7 +35,9 @@ export namespace CrmCustomerApi {
|
|||||||
creatorName?: string; // 创建人名称
|
creatorName?: string; // 创建人名称
|
||||||
createTime: Date; // 创建时间
|
createTime: Date; // 创建时间
|
||||||
updateTime: Date; // 更新时间
|
updateTime: Date; // 更新时间
|
||||||
|
poolDay?: number; // 距离进入公海天数
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomerImport {
|
export interface CustomerImport {
|
||||||
ownerUserId: number;
|
ownerUserId: number;
|
||||||
file: File;
|
file: File;
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export function deleteReceivablePlan(id: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 导出回款计划 Excel */
|
/** 导出回款计划 Excel */
|
||||||
export function exportReceivablePlan(params: PageParam) {
|
export function exportReceivablePlan(params: any) {
|
||||||
return requestClient.download('/crm/receivable-plan/export-excel', {
|
return requestClient.download('/crm/receivable-plan/export-excel', {
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export namespace InfraCodegenApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 更新代码生成请求 */
|
/** 更新代码生成请求 */
|
||||||
export interface CodegenUpdateReq {
|
export interface CodegenUpdateReqVO {
|
||||||
table: any | CodegenTable;
|
table: any | CodegenTable;
|
||||||
columns: CodegenColumn[];
|
columns: CodegenColumn[];
|
||||||
}
|
}
|
||||||
@@ -106,25 +106,36 @@ export function getCodegenTable(tableId: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 修改代码生成表定义 */
|
/** 修改代码生成表定义 */
|
||||||
export function updateCodegenTable(data: InfraCodegenApi.CodegenUpdateReq) {
|
export function updateCodegenTable(data: InfraCodegenApi.CodegenUpdateReqVO) {
|
||||||
return requestClient.put('/infra/codegen/update', data);
|
return requestClient.put('/infra/codegen/update', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 基于数据库的表结构,同步数据库的表和字段定义 */
|
/** 基于数据库的表结构,同步数据库的表和字段定义 */
|
||||||
export function syncCodegenFromDB(tableId: number) {
|
export function syncCodegenFromDB(tableId: number) {
|
||||||
return requestClient.put(`/infra/codegen/sync-from-db?tableId=${tableId}`);
|
return requestClient.put(
|
||||||
|
'/infra/codegen/sync-from-db',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
params: { tableId },
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 预览生成代码 */
|
/** 预览生成代码 */
|
||||||
export function previewCodegen(tableId: number) {
|
export function previewCodegen(tableId: number) {
|
||||||
return requestClient.get<InfraCodegenApi.CodegenPreview[]>(
|
return requestClient.get<InfraCodegenApi.CodegenPreview[]>(
|
||||||
`/infra/codegen/preview?tableId=${tableId}`,
|
'/infra/codegen/preview',
|
||||||
|
{
|
||||||
|
params: { tableId },
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 下载生成代码 */
|
/** 下载生成代码 */
|
||||||
export function downloadCodegen(tableId: number) {
|
export function downloadCodegen(tableId: number) {
|
||||||
return requestClient.download(`/infra/codegen/download?tableId=${tableId}`);
|
return requestClient.download('/infra/codegen/download', {
|
||||||
|
params: { tableId },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获得表定义 */
|
/** 获得表定义 */
|
||||||
|
|||||||
@@ -44,3 +44,10 @@ export function updateDataSourceConfig(
|
|||||||
export function deleteDataSourceConfig(id: number) {
|
export function deleteDataSourceConfig(id: number) {
|
||||||
return requestClient.delete(`/infra/data-source-config/delete?id=${id}`);
|
return requestClient.delete(`/infra/data-source-config/delete?id=${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 批量删除数据源配置 */
|
||||||
|
export function deleteDataSourceConfigList(ids: number[]) {
|
||||||
|
return requestClient.delete(
|
||||||
|
`/infra/data-source-config/delete-list?ids=${ids.join(',')}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export namespace InfraFileApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 文件预签名地址 */
|
/** 文件预签名地址 */
|
||||||
export interface FilePresignedUrlResp {
|
export interface FilePresignedUrlRespVO {
|
||||||
configId: number; // 文件配置编号
|
configId: number; // 文件配置编号
|
||||||
uploadUrl: string; // 文件上传 URL
|
uploadUrl: string; // 文件上传 URL
|
||||||
url: string; // 文件 URL
|
url: string; // 文件 URL
|
||||||
@@ -27,7 +27,7 @@ export namespace InfraFileApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 上传文件 */
|
/** 上传文件 */
|
||||||
export interface FileUploadReq {
|
export interface FileUploadReqVO {
|
||||||
file: globalThis.File;
|
file: globalThis.File;
|
||||||
directory?: string;
|
directory?: string;
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ export function deleteFileList(ids: number[]) {
|
|||||||
|
|
||||||
/** 获取文件预签名地址 */
|
/** 获取文件预签名地址 */
|
||||||
export function getFilePresignedUrl(name: string, directory?: string) {
|
export function getFilePresignedUrl(name: string, directory?: string) {
|
||||||
return requestClient.get<InfraFileApi.FilePresignedUrlResp>(
|
return requestClient.get<InfraFileApi.FilePresignedUrlRespVO>(
|
||||||
'/infra/file/presigned-url',
|
'/infra/file/presigned-url',
|
||||||
{
|
{
|
||||||
params: { name, directory },
|
params: { name, directory },
|
||||||
@@ -67,7 +67,7 @@ export function createFile(data: InfraFileApi.File) {
|
|||||||
|
|
||||||
/** 上传文件 */
|
/** 上传文件 */
|
||||||
export function uploadFile(
|
export function uploadFile(
|
||||||
data: InfraFileApi.FileUploadReq,
|
data: InfraFileApi.FileUploadReqVO,
|
||||||
onUploadProgress?: AxiosProgressEvent,
|
onUploadProgress?: AxiosProgressEvent,
|
||||||
) {
|
) {
|
||||||
// 特殊:由于 upload 内部封装,即使 directory 为 undefined,也会传递给后端
|
// 特殊:由于 upload 内部封装,即使 directory 为 undefined,也会传递给后端
|
||||||
|
|||||||
@@ -58,11 +58,12 @@ export function exportJob(params: any) {
|
|||||||
|
|
||||||
/** 任务状态修改 */
|
/** 任务状态修改 */
|
||||||
export function updateJobStatus(id: number, status: number) {
|
export function updateJobStatus(id: number, status: number) {
|
||||||
const params = {
|
return requestClient.put('/infra/job/update-status', undefined, {
|
||||||
|
params: {
|
||||||
id,
|
id,
|
||||||
status,
|
status,
|
||||||
};
|
},
|
||||||
return requestClient.put('/infra/job/update-status', {}, { params });
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 定时任务立即执行一次 */
|
/** 定时任务立即执行一次 */
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export function deleteAccount(id: number) {
|
|||||||
|
|
||||||
/** 生成公众号账号二维码 */
|
/** 生成公众号账号二维码 */
|
||||||
export function generateAccountQrCode(id: number) {
|
export function generateAccountQrCode(id: number) {
|
||||||
return requestClient.post(`/mp/account/generate-qr-code?id=${id}`);
|
return requestClient.put(`/mp/account/generate-qr-code?id=${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 清空公众号账号 API 配额 */
|
/** 清空公众号账号 API 配额 */
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export function deleteMailTemplate(id: number) {
|
|||||||
return requestClient.delete(`/system/mail-template/delete?id=${id}`);
|
return requestClient.delete(`/system/mail-template/delete?id=${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 批量删除邮件模板 */
|
/** 批量删除邮件模版 */
|
||||||
export function deleteMailTemplateList(ids: number[]) {
|
export function deleteMailTemplateList(ids: number[]) {
|
||||||
return requestClient.delete(
|
return requestClient.delete(
|
||||||
`/system/mail-template/delete-list?ids=${ids.join(',')}`,
|
`/system/mail-template/delete-list?ids=${ids.join(',')}`,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { requestClient } from '#/api/request';
|
|||||||
/** OAuth2.0 授权信息响应 */
|
/** OAuth2.0 授权信息响应 */
|
||||||
export namespace SystemOAuth2ClientApi {
|
export namespace SystemOAuth2ClientApi {
|
||||||
/** 授权信息 */
|
/** 授权信息 */
|
||||||
export interface AuthorizeInfoResp {
|
export interface AuthorizeInfoRespVO {
|
||||||
client: {
|
client: {
|
||||||
logo: string;
|
logo: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -17,7 +17,7 @@ export namespace SystemOAuth2ClientApi {
|
|||||||
|
|
||||||
/** 获得授权信息 */
|
/** 获得授权信息 */
|
||||||
export function getAuthorize(clientId: string) {
|
export function getAuthorize(clientId: string) {
|
||||||
return requestClient.get<SystemOAuth2ClientApi.AuthorizeInfoResp>(
|
return requestClient.get<SystemOAuth2ClientApi.AuthorizeInfoRespVO>(
|
||||||
`/system/oauth2/authorize?clientId=${clientId}`,
|
`/system/oauth2/authorize?clientId=${clientId}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,3 @@ export function deleteOAuth2Token(accessToken: string) {
|
|||||||
`/system/oauth2-token/delete?accessToken=${accessToken}`,
|
`/system/oauth2-token/delete?accessToken=${accessToken}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 批量删除 OAuth2.0 令牌 */
|
|
||||||
export function deleteOAuth2TokenList(accessTokens: string[]) {
|
|
||||||
return requestClient.delete(
|
|
||||||
`/system/oauth2-token/delete-list?accessTokens=${accessTokens.join(',')}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,19 +2,19 @@ import { requestClient } from '#/api/request';
|
|||||||
|
|
||||||
export namespace SystemPermissionApi {
|
export namespace SystemPermissionApi {
|
||||||
/** 分配用户角色请求 */
|
/** 分配用户角色请求 */
|
||||||
export interface AssignUserRoleReq {
|
export interface AssignUserRoleReqVO {
|
||||||
userId: number;
|
userId: number;
|
||||||
roleIds: number[];
|
roleIds: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 分配角色菜单请求 */
|
/** 分配角色菜单请求 */
|
||||||
export interface AssignRoleMenuReq {
|
export interface AssignRoleMenuReqVO {
|
||||||
roleId: number;
|
roleId: number;
|
||||||
menuIds: number[];
|
menuIds: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 分配角色数据权限请求 */
|
/** 分配角色数据权限请求 */
|
||||||
export interface AssignRoleDataScopeReq {
|
export interface AssignRoleDataScopeReqVO {
|
||||||
roleId: number;
|
roleId: number;
|
||||||
dataScope: number;
|
dataScope: number;
|
||||||
dataScopeDeptIds: number[];
|
dataScopeDeptIds: number[];
|
||||||
@@ -30,14 +30,14 @@ export async function getRoleMenuList(roleId: number) {
|
|||||||
|
|
||||||
/** 赋予角色菜单权限 */
|
/** 赋予角色菜单权限 */
|
||||||
export async function assignRoleMenu(
|
export async function assignRoleMenu(
|
||||||
data: SystemPermissionApi.AssignRoleMenuReq,
|
data: SystemPermissionApi.AssignRoleMenuReqVO,
|
||||||
) {
|
) {
|
||||||
return requestClient.post('/system/permission/assign-role-menu', data);
|
return requestClient.post('/system/permission/assign-role-menu', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 赋予角色数据权限 */
|
/** 赋予角色数据权限 */
|
||||||
export async function assignRoleDataScope(
|
export async function assignRoleDataScope(
|
||||||
data: SystemPermissionApi.AssignRoleDataScopeReq,
|
data: SystemPermissionApi.AssignRoleDataScopeReqVO,
|
||||||
) {
|
) {
|
||||||
return requestClient.post('/system/permission/assign-role-data-scope', data);
|
return requestClient.post('/system/permission/assign-role-data-scope', data);
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ export async function getUserRoleList(userId: number) {
|
|||||||
|
|
||||||
/** 赋予用户角色 */
|
/** 赋予用户角色 */
|
||||||
export async function assignUserRole(
|
export async function assignUserRole(
|
||||||
data: SystemPermissionApi.AssignUserRoleReq,
|
data: SystemPermissionApi.AssignUserRoleReqVO,
|
||||||
) {
|
) {
|
||||||
return requestClient.post('/system/permission/assign-user-role', data);
|
return requestClient.post('/system/permission/assign-user-role', data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ export namespace SystemSocialUserApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 社交绑定请求 */
|
/** 社交绑定请求 */
|
||||||
export interface SocialUserBindReq {
|
export interface SocialUserBindReqVO {
|
||||||
type: number;
|
type: number;
|
||||||
code: string;
|
code: string;
|
||||||
state: string;
|
state: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 取消社交绑定请求 */
|
/** 取消社交绑定请求 */
|
||||||
export interface SocialUserUnbindReq {
|
export interface SocialUserUnbindReqVO {
|
||||||
type: number;
|
type: number;
|
||||||
openid: string;
|
openid: string;
|
||||||
}
|
}
|
||||||
@@ -49,12 +49,12 @@ export function getSocialUser(id: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 社交绑定,使用 code 授权码 */
|
/** 社交绑定,使用 code 授权码 */
|
||||||
export function socialBind(data: SystemSocialUserApi.SocialUserBindReq) {
|
export function socialBind(data: SystemSocialUserApi.SocialUserBindReqVO) {
|
||||||
return requestClient.post<boolean>('/system/social-user/bind', data);
|
return requestClient.post<boolean>('/system/social-user/bind', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 取消社交绑定 */
|
/** 取消社交绑定 */
|
||||||
export function socialUnbind(data: SystemSocialUserApi.SocialUserUnbindReq) {
|
export function socialUnbind(data: SystemSocialUserApi.SocialUserUnbindReqVO) {
|
||||||
return requestClient.delete<boolean>('/system/social-user/unbind', { data });
|
return requestClient.delete<boolean>('/system/social-user/unbind', { data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { requestClient } from '#/api/request';
|
|||||||
|
|
||||||
export namespace SystemUserProfileApi {
|
export namespace SystemUserProfileApi {
|
||||||
/** 用户个人中心信息 */
|
/** 用户个人中心信息 */
|
||||||
export interface UserProfileResp {
|
export interface UserProfileRespVO {
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
nickname: string;
|
nickname: string;
|
||||||
@@ -19,13 +19,13 @@ export namespace SystemUserProfileApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 更新密码请求 */
|
/** 更新密码请求 */
|
||||||
export interface UpdatePasswordReq {
|
export interface UpdatePasswordReqVO {
|
||||||
oldPassword: string;
|
oldPassword: string;
|
||||||
newPassword: string;
|
newPassword: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 更新个人信息请求 */
|
/** 更新个人信息请求 */
|
||||||
export interface UpdateProfileReq {
|
export interface UpdateProfileReqVO {
|
||||||
nickname?: string;
|
nickname?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
mobile?: string;
|
mobile?: string;
|
||||||
@@ -36,19 +36,21 @@ export namespace SystemUserProfileApi {
|
|||||||
|
|
||||||
/** 获取登录用户信息 */
|
/** 获取登录用户信息 */
|
||||||
export function getUserProfile() {
|
export function getUserProfile() {
|
||||||
return requestClient.get<SystemUserProfileApi.UserProfileResp>(
|
return requestClient.get<SystemUserProfileApi.UserProfileRespVO>(
|
||||||
'/system/user/profile/get',
|
'/system/user/profile/get',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改用户个人信息 */
|
/** 修改用户个人信息 */
|
||||||
export function updateUserProfile(data: SystemUserProfileApi.UpdateProfileReq) {
|
export function updateUserProfile(
|
||||||
|
data: SystemUserProfileApi.UpdateProfileReqVO,
|
||||||
|
) {
|
||||||
return requestClient.put('/system/user/profile/update', data);
|
return requestClient.put('/system/user/profile/update', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改用户个人密码 */
|
/** 修改用户个人密码 */
|
||||||
export function updateUserPassword(
|
export function updateUserPassword(
|
||||||
data: SystemUserProfileApi.UpdatePasswordReq,
|
data: SystemUserProfileApi.UpdatePasswordReqVO,
|
||||||
) {
|
) {
|
||||||
return requestClient.put('/system/user/profile/update-password', data);
|
return requestClient.put('/system/user/profile/update-password', data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,7 @@
|
|||||||
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
|
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
|
||||||
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
|
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
|
||||||
// import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右侧框样式
|
// import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右侧框样式
|
||||||
import {
|
import { computed, h, onBeforeUnmount, onMounted, provide, ref } from 'vue';
|
||||||
computed,
|
|
||||||
defineEmits,
|
|
||||||
defineOptions,
|
|
||||||
defineProps,
|
|
||||||
h,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onMounted,
|
|
||||||
provide,
|
|
||||||
ref,
|
|
||||||
} from 'vue';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AlignLeftOutlined,
|
AlignLeftOutlined,
|
||||||
@@ -655,7 +645,7 @@ onBeforeUnmount(() => {
|
|||||||
type="file"
|
type="file"
|
||||||
id="files"
|
id="files"
|
||||||
ref="refFile"
|
ref="refFile"
|
||||||
style="display: none"
|
class="hidden"
|
||||||
accept=".xml, .bpmn"
|
accept=".xml, .bpmn"
|
||||||
@change="importLocalFile"
|
@change="importLocalFile"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineProps, h, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
import { h, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { BpmProcessInstanceStatus, DICT_TYPE } from '@vben/constants';
|
import { BpmProcessInstanceStatus, DICT_TYPE } from '@vben/constants';
|
||||||
import { UndoOutlined, ZoomInOutlined, ZoomOutOutlined } from '@vben/icons';
|
import { UndoOutlined, ZoomInOutlined, ZoomOutOutlined } from '@vben/icons';
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ import 'bpmn-js/dist/assets/diagram-js.css';
|
|||||||
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
|
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
|
||||||
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
|
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
|
||||||
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
|
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
|
||||||
|
// TODO @puhui999:样式问题:设计器那,位置不太对;
|
||||||
|
|
||||||
export { default as MyProcessDesigner } from './designer';
|
export { default as MyProcessDesigner } from './designer';
|
||||||
|
// TODO @puhui999:流程发起时,预览相关的,需要使用;
|
||||||
export { default as MyProcessViewer } from './designer/index2';
|
export { default as MyProcessViewer } from './designer/index2';
|
||||||
export { default as MyProcessPenal } from './penal';
|
export { default as MyProcessPenal } from './penal';
|
||||||
|
|
||||||
|
// TODO @puhui999:【有个迁移的打印】【新增】流程打印,由 [@Lesan](https://gitee.com/LesanOuO) 贡献 [#816](https://gitee.com/yudaocode/yudao-ui-admin-vue3/pulls/816/)、[#1418](https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1418/)、[#817](https://gitee.com/yudaocode/yudao-ui-admin-vue3/pulls/817/)、[#1419](https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1419/)、[#1424](https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1424)、[#819](https://gitee.com/yudaocode/yudao-ui-admin-vue3/pulls/819)、[#821](https://gitee.com/yudaocode/yudao-ui-admin-vue3/pulls/821/)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Component } from 'vue';
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
import { defineOptions, defineProps, ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
import { CustomConfigMap } from './data';
|
import { CustomConfigMap } from './data';
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { inject, nextTick, ref, toRaw, watch } from 'vue';
|
||||||
defineOptions,
|
|
||||||
defineProps,
|
|
||||||
inject,
|
|
||||||
nextTick,
|
|
||||||
ref,
|
|
||||||
toRaw,
|
|
||||||
watch,
|
|
||||||
} from 'vue';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Divider,
|
Divider,
|
||||||
|
|||||||
@@ -153,11 +153,7 @@ watch(
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="panel-tab__content">
|
<div class="panel-tab__content">
|
||||||
<Form
|
<Form :model="flowConditionForm">
|
||||||
:model="flowConditionForm"
|
|
||||||
:label-col="{ span: 6 }"
|
|
||||||
:wrapper-col="{ span: 18 }"
|
|
||||||
>
|
|
||||||
<Form.Item label="流转类型">
|
<Form.Item label="流转类型">
|
||||||
<Select v-model:value="flowConditionForm.type" @change="updateFlowType">
|
<Select v-model:value="flowConditionForm.type" @change="updateFlowType">
|
||||||
<Select.Option value="normal">普通流转路径</Select.Option>
|
<Select.Option value="normal">普通流转路径</Select.Option>
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ watch(
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="panel-tab__content">
|
<div class="panel-tab__content">
|
||||||
<Form :label-col="{ style: { width: '80px' } }">
|
<Form>
|
||||||
<FormItem label="流程表单">
|
<FormItem label="流程表单">
|
||||||
<!-- <Input v-model:value="formKey" @change="updateElementFormKey" />-->
|
<!-- <Input v-model:value="formKey" @change="updateElementFormKey" />-->
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { inject, nextTick, ref, watch } from 'vue';
|
import { inject, nextTick, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { IconifyIcon, PlusOutlined } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
import { cloneDeep } from '@vben/utils';
|
import { cloneDeep } from '@vben/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -290,7 +290,7 @@ watch(
|
|||||||
<div class="element-drawer__button">
|
<div class="element-drawer__button">
|
||||||
<Button type="primary" size="small" @click="openListenerForm(null, -1)">
|
<Button type="primary" size="small" @click="openListenerForm(null, -1)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<PlusOutlined />
|
<IconifyIcon icon="ep:plus" />
|
||||||
</template>
|
</template>
|
||||||
添加监听器
|
添加监听器
|
||||||
</Button>
|
</Button>
|
||||||
@@ -309,12 +309,7 @@ watch(
|
|||||||
:width="width as any"
|
:width="width as any"
|
||||||
:destroy-on-close="true"
|
:destroy-on-close="true"
|
||||||
>
|
>
|
||||||
<Form
|
<Form :model="listenerForm" ref="listenerFormRef">
|
||||||
:model="listenerForm"
|
|
||||||
ref="listenerFormRef"
|
|
||||||
:label-col="{ span: 6 }"
|
|
||||||
:wrapper-col="{ span: 18 }"
|
|
||||||
>
|
|
||||||
<FormItem
|
<FormItem
|
||||||
label="事件类型"
|
label="事件类型"
|
||||||
name="event"
|
name="event"
|
||||||
@@ -462,20 +457,23 @@ watch(
|
|||||||
</template>
|
</template>
|
||||||
</Form>
|
</Form>
|
||||||
<Divider />
|
<Divider />
|
||||||
<p class="listener-filed__title">
|
<div class="mb-2 flex justify-between">
|
||||||
<span><IconifyIcon icon="ep:menu" />注入字段:</span>
|
<span class="flex items-center">
|
||||||
<Button type="primary" @click="openListenerFieldForm(null, -1)">
|
<IconifyIcon icon="ep:menu" class="mr-2 text-gray-600" />
|
||||||
|
注入字段
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
title="添加字段"
|
||||||
|
@click="openListenerFieldForm(null, -1)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<IconifyIcon icon="ep:plus" />
|
||||||
|
</template>
|
||||||
添加字段
|
添加字段
|
||||||
</Button>
|
</Button>
|
||||||
</p>
|
</div>
|
||||||
<Table
|
<Table :data-source="fieldsListOfListener" size="small" bordered>
|
||||||
:data-source="fieldsListOfListener"
|
|
||||||
size="small"
|
|
||||||
:scroll="{ y: 240 }"
|
|
||||||
:pagination="false"
|
|
||||||
bordered
|
|
||||||
style="flex: none"
|
|
||||||
>
|
|
||||||
<TableColumn title="序号" width="50px">
|
<TableColumn title="序号" width="50px">
|
||||||
<template #default="{ index }">
|
<template #default="{ index }">
|
||||||
{{ index + 1 }}
|
{{ index + 1 }}
|
||||||
@@ -492,12 +490,12 @@ watch(
|
|||||||
/>
|
/>
|
||||||
<TableColumn
|
<TableColumn
|
||||||
title="字段值/表达式"
|
title="字段值/表达式"
|
||||||
width="100px"
|
width="120px"
|
||||||
:custom-render="
|
:custom-render="
|
||||||
({ record }: any) => record.string || record.expression
|
({ record }: any) => record.string || record.expression
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<TableColumn title="操作" width="130px">
|
<TableColumn title="操作" width="80px" fixed="right">
|
||||||
<template #default="{ record, index }">
|
<template #default="{ record, index }">
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -532,13 +530,7 @@ watch(
|
|||||||
width="600px"
|
width="600px"
|
||||||
:destroy-on-close="true"
|
:destroy-on-close="true"
|
||||||
>
|
>
|
||||||
<Form
|
<Form :model="listenerFieldForm" ref="listenerFieldFormRef">
|
||||||
:model="listenerFieldForm"
|
|
||||||
ref="listenerFieldFormRef"
|
|
||||||
:label-col="{ span: 6 }"
|
|
||||||
:wrapper-col="{ span: 18 }"
|
|
||||||
style="height: 136px"
|
|
||||||
>
|
|
||||||
<FormItem
|
<FormItem
|
||||||
label="字段名称:"
|
label="字段名称:"
|
||||||
name="name"
|
name="name"
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import type { BpmProcessListenerApi } from '#/api/bpm/processListener';
|
|||||||
|
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
import { ContentWrap } from '@vben/common-ui';
|
||||||
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
|
||||||
|
|
||||||
import { Button, Modal, Pagination, Table } from 'ant-design-vue';
|
import { Button, Modal, Pagination, Table } from 'ant-design-vue';
|
||||||
|
|
||||||
import { getProcessListenerPage } from '#/api/bpm/processListener';
|
import { getProcessListenerPage } from '#/api/bpm/processListener';
|
||||||
import { ContentWrap } from '#/components/content-wrap';
|
|
||||||
import { DictTag } from '#/components/dict-tag';
|
import { DictTag } from '#/components/dict-tag';
|
||||||
|
|
||||||
/** BPM 流程 表单 */
|
/** BPM 流程 表单 */
|
||||||
@@ -89,7 +89,7 @@ const select = async (row: BpmProcessListenerApi.ProcessListener) => {
|
|||||||
</template>
|
</template>
|
||||||
</Table.Column>
|
</Table.Column>
|
||||||
<Table.Column title="值" align="center" data-index="value" />
|
<Table.Column title="值" align="center" data-index="value" />
|
||||||
<Table.Column title="操作" align="center">
|
<Table.Column title="操作" align="center" fixed="right">
|
||||||
<template #default="{ record }">
|
<template #default="{ record }">
|
||||||
<Button type="primary" @click="select(record)"> 选择 </Button>
|
<Button type="primary" @click="select(record)"> 选择 </Button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { inject, nextTick, ref, watch } from 'vue';
|
import { inject, nextTick, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { MenuOutlined, PlusOutlined, SelectOutlined } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
import { cloneDeep } from '@vben/utils';
|
import { cloneDeep } from '@vben/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -300,11 +300,11 @@ watch(
|
|||||||
</Table>
|
</Table>
|
||||||
<div class="element-drawer__button">
|
<div class="element-drawer__button">
|
||||||
<Button size="small" type="primary" @click="openListenerForm(null)">
|
<Button size="small" type="primary" @click="openListenerForm(null)">
|
||||||
<template #icon><PlusOutlined /></template>
|
<template #icon> <IconifyIcon icon="ep:plus" /></template>
|
||||||
添加监听器
|
添加监听器
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="small" @click="openProcessListenerDialog">
|
<Button size="small" @click="openProcessListenerDialog">
|
||||||
<template #icon><SelectOutlined /></template>
|
<template #icon> <IconifyIcon icon="ep:select" /></template>
|
||||||
选择监听器
|
选择监听器
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -316,12 +316,7 @@ watch(
|
|||||||
:width="width"
|
:width="width"
|
||||||
:destroy-on-close="true"
|
:destroy-on-close="true"
|
||||||
>
|
>
|
||||||
<Form
|
<Form :model="listenerForm" ref="listenerFormRef">
|
||||||
:model="listenerForm"
|
|
||||||
:label-col="{ span: 8 }"
|
|
||||||
:wrapper-col="{ span: 16 }"
|
|
||||||
ref="listenerFormRef"
|
|
||||||
>
|
|
||||||
<FormItem
|
<FormItem
|
||||||
label="事件类型"
|
label="事件类型"
|
||||||
name="event"
|
name="event"
|
||||||
@@ -458,16 +453,22 @@ watch(
|
|||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<p class="listener-filed__title">
|
<div class="mb-2 flex justify-between">
|
||||||
<span><MenuOutlined />注入字段:</span>
|
<span class="flex items-center">
|
||||||
|
<IconifyIcon icon="ep:menu" class="mr-2 text-gray-600" />
|
||||||
|
注入字段
|
||||||
|
</span>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
|
||||||
type="primary"
|
type="primary"
|
||||||
|
title="添加字段"
|
||||||
@click="openListenerFieldForm(null)"
|
@click="openListenerFieldForm(null)"
|
||||||
>
|
>
|
||||||
|
<template #icon>
|
||||||
|
<IconifyIcon icon="ep:plus" />
|
||||||
|
</template>
|
||||||
添加字段
|
添加字段
|
||||||
</Button>
|
</Button>
|
||||||
</p>
|
</div>
|
||||||
<Table
|
<Table
|
||||||
:data="fieldsListOfListener"
|
:data="fieldsListOfListener"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -533,13 +534,7 @@ watch(
|
|||||||
:width="600"
|
:width="600"
|
||||||
:destroy-on-close="true"
|
:destroy-on-close="true"
|
||||||
>
|
>
|
||||||
<Form
|
<Form :model="listenerFieldForm" ref="listenerFieldFormRef">
|
||||||
:model="listenerFieldForm"
|
|
||||||
:label-col="{ span: 8 }"
|
|
||||||
:wrapper-col="{ span: 16 }"
|
|
||||||
ref="listenerFieldFormRef"
|
|
||||||
style="height: 136px"
|
|
||||||
>
|
|
||||||
<FormItem
|
<FormItem
|
||||||
label="字段名称:"
|
label="字段名称:"
|
||||||
name="name"
|
name="name"
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ watch(
|
|||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
<div v-else>除了UserTask以外节点的多实例待实现</div>
|
<div v-else>除了UserTask以外节点的多实例待实现</div>
|
||||||
<!-- 与Simple设计器配置合并,保留以前的代码 -->
|
<!-- 与Simple设计器配置合并,保留以前的代码 -->
|
||||||
<Form :label-col="{ span: 6 }" style="display: none">
|
<Form class="hidden">
|
||||||
<FormItem label="快捷配置">
|
<FormItem label="快捷配置">
|
||||||
<Button size="small" @click="() => changeConfig('依次审批')">
|
<Button size="small" @click="() => changeConfig('依次审批')">
|
||||||
依次审批
|
依次审批
|
||||||
@@ -467,7 +467,7 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<!-- add by 芋艿:由于「元素变量」暂时用不到,所以这里 display 为 none -->
|
<!-- add by 芋艿:由于「元素变量」暂时用不到,所以这里 display 为 none -->
|
||||||
<FormItem label="元素变量" key="elementVariable" style="display: none">
|
<FormItem label="元素变量" key="elementVariable" class="hidden">
|
||||||
<Input
|
<Input
|
||||||
v-model:value="loopInstanceForm.elementVariable"
|
v-model:value="loopInstanceForm.elementVariable"
|
||||||
allow-clear
|
allow-clear
|
||||||
@@ -485,7 +485,7 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<!-- add by 芋艿:由于「异步状态」暂时用不到,所以这里 display 为 none -->
|
<!-- add by 芋艿:由于「异步状态」暂时用不到,所以这里 display 为 none -->
|
||||||
<FormItem label="异步状态" key="async" style="display: none">
|
<FormItem label="异步状态" key="async" class="hidden">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-model:checked="loopInstanceForm.asyncBefore"
|
v-model:checked="loopInstanceForm.asyncBefore"
|
||||||
@change="() => updateLoopAsync('asyncBefore')"
|
@change="() => updateLoopAsync('asyncBefore')"
|
||||||
|
|||||||
@@ -161,25 +161,15 @@ watch(
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="panel-tab__content">
|
<div class="panel-tab__content">
|
||||||
<Table :data="elementPropertyList" :scroll="{ y: 240 }" bordered>
|
<Table :data="elementPropertyList" size="small" bordered>
|
||||||
<TableColumn title="序号" width="50">
|
<TableColumn title="序号" width="50">
|
||||||
<template #default="{ index }">
|
<template #default="{ index }">
|
||||||
{{ index + 1 }}
|
{{ index + 1 }}
|
||||||
</template>
|
</template>
|
||||||
</TableColumn>
|
</TableColumn>
|
||||||
<TableColumn
|
<TableColumn title="属性名" data-index="name" />
|
||||||
title="属性名"
|
<TableColumn title="属性值" data-index="value" />
|
||||||
data-index="name"
|
<TableColumn title="操作">
|
||||||
:min-width="100"
|
|
||||||
:ellipsis="{ showTitle: true }"
|
|
||||||
/>
|
|
||||||
<TableColumn
|
|
||||||
title="属性值"
|
|
||||||
data-index="value"
|
|
||||||
:min-width="100"
|
|
||||||
:ellipsis="{ showTitle: true }"
|
|
||||||
/>
|
|
||||||
<TableColumn title="操作" width="110">
|
|
||||||
<template #default="{ record, index }">
|
<template #default="{ record, index }">
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
@@ -215,11 +205,7 @@ watch(
|
|||||||
:width="600"
|
:width="600"
|
||||||
:destroy-on-close="true"
|
:destroy-on-close="true"
|
||||||
>
|
>
|
||||||
<Form
|
<Form :model="propertyForm" ref="attributeFormRef">
|
||||||
:model="propertyForm"
|
|
||||||
ref="attributeFormRef"
|
|
||||||
:label-col="{ span: 6 }"
|
|
||||||
>
|
|
||||||
<FormItem label="属性名:" name="name">
|
<FormItem label="属性名:" name="name">
|
||||||
<Input v-model:value="propertyForm.name" allow-clear />
|
<Input v-model:value="propertyForm.name" allow-clear />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="panel-tab__content">
|
<div class="panel-tab__content">
|
||||||
<div class="panel-tab__content--title">
|
<div class="panel-tab__content--title">
|
||||||
<span>
|
<span class="flex items-center">
|
||||||
<IconifyIcon icon="ep:menu" style="margin-right: 8px; color: #555" />
|
<IconifyIcon icon="ep:menu" class="mr-2 text-gray-600" />
|
||||||
消息列表
|
消息列表
|
||||||
</span>
|
</span>
|
||||||
<Button type="primary" title="创建新消息" @click="openModel('message')">
|
<Button type="primary" title="创建新消息" @click="openModel('message')">
|
||||||
@@ -95,33 +95,19 @@ onMounted(() => {
|
|||||||
创建新消息
|
创建新消息
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Table :data-source="messageList" :bordered="true" :pagination="false">
|
<Table :data-source="messageList" size="small" bordered>
|
||||||
<TableColumn title="序号" width="60px">
|
<TableColumn title="序号" width="60px">
|
||||||
<template #default="{ index }">
|
<template #default="{ index }">
|
||||||
{{ index + 1 }}
|
{{ index + 1 }}
|
||||||
</template>
|
</template>
|
||||||
</TableColumn>
|
</TableColumn>
|
||||||
<TableColumn
|
<TableColumn title="消息ID" data-index="id" />
|
||||||
title="消息ID"
|
<TableColumn title="消息名称" data-index="name" />
|
||||||
data-index="id"
|
|
||||||
:width="300"
|
|
||||||
:ellipsis="{ showTitle: true }"
|
|
||||||
/>
|
|
||||||
<TableColumn
|
|
||||||
title="消息名称"
|
|
||||||
data-index="name"
|
|
||||||
:width="300"
|
|
||||||
:ellipsis="{ showTitle: true }"
|
|
||||||
/>
|
|
||||||
</Table>
|
</Table>
|
||||||
<div
|
<div class="panel-tab__content--title mt-2 border-t border-gray-200 pt-2">
|
||||||
class="panel-tab__content--title"
|
<span class="flex items-center">
|
||||||
style="padding-top: 8px; margin-top: 8px; border-top: 1px solid #eee"
|
<IconifyIcon icon="ep:menu" class="mr-2 text-gray-600" />
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<IconifyIcon icon="ep:menu" style="margin-right: 8px; color: #555">
|
|
||||||
信号列表
|
信号列表
|
||||||
</IconifyIcon>
|
|
||||||
</span>
|
</span>
|
||||||
<Button type="primary" title="创建新信号" @click="openModel('signal')">
|
<Button type="primary" title="创建新信号" @click="openModel('signal')">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
@@ -130,24 +116,14 @@ onMounted(() => {
|
|||||||
创建新信号
|
创建新信号
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Table :data-source="signalList" :bordered="true" :pagination="false">
|
<Table :data-source="signalList" size="small" bordered>
|
||||||
<TableColumn title="序号" width="60px">
|
<TableColumn title="序号" width="60px">
|
||||||
<template #default="{ index }">
|
<template #default="{ index }">
|
||||||
{{ index + 1 }}
|
{{ index + 1 }}
|
||||||
</template>
|
</template>
|
||||||
</TableColumn>
|
</TableColumn>
|
||||||
<TableColumn
|
<TableColumn title="信号ID" data-index="id" />
|
||||||
title="信号ID"
|
<TableColumn title="信号名称" data-index="name" />
|
||||||
data-index="id"
|
|
||||||
:width="300"
|
|
||||||
:ellipsis="{ showTitle: true }"
|
|
||||||
/>
|
|
||||||
<TableColumn
|
|
||||||
title="信号名称"
|
|
||||||
data-index="name"
|
|
||||||
:width="300"
|
|
||||||
:ellipsis="{ showTitle: true }"
|
|
||||||
/>
|
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
@@ -157,11 +133,7 @@ onMounted(() => {
|
|||||||
width="400px"
|
width="400px"
|
||||||
:destroy-on-close="true"
|
:destroy-on-close="true"
|
||||||
>
|
>
|
||||||
<Form
|
<Form :model="modelObjectForm">
|
||||||
:model="modelObjectForm"
|
|
||||||
:label-col="{ span: 9 }"
|
|
||||||
:wrapper-col="{ span: 15 }"
|
|
||||||
>
|
|
||||||
<FormItem :label="modelConfig.idLabel">
|
<FormItem :label="modelConfig.idLabel">
|
||||||
<Input v-model:value="modelObjectForm.id" allow-clear />
|
<Input v-model:value="modelObjectForm.id" allow-clear />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ watch(
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="panel-tab__content">
|
<div class="panel-tab__content">
|
||||||
<Form :label-col="{ span: 9 }" :wrapper-col="{ span: 15 }">
|
<Form>
|
||||||
<!-- add by 芋艿:由于「异步延续」暂时用不到,所以这里 display 为 none -->
|
<!-- add by 芋艿:由于「异步延续」暂时用不到,所以这里 display 为 none -->
|
||||||
<FormItem label="异步延续" style="display: none">
|
<FormItem label="异步延续" class="hidden">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-model:checked="taskConfigForm.asyncBefore"
|
v-model:checked="taskConfigForm.asyncBefore"
|
||||||
@change="changeTaskAsync"
|
@change="changeTaskAsync"
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ watch(
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<Form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
<Form>
|
||||||
<FormItem label="实例名称">
|
<FormItem label="实例名称">
|
||||||
<Input
|
<Input
|
||||||
v-model:value="formData.processInstanceName"
|
v-model:value="formData.processInstanceName"
|
||||||
@@ -341,12 +341,7 @@ watch(
|
|||||||
@ok="saveVariable"
|
@ok="saveVariable"
|
||||||
@cancel="variableDialogVisible = false"
|
@cancel="variableDialogVisible = false"
|
||||||
>
|
>
|
||||||
<Form
|
<Form :model="varialbeFormData" ref="varialbeFormRef">
|
||||||
:model="varialbeFormData"
|
|
||||||
:label-col="{ span: 6 }"
|
|
||||||
:wrapper-col="{ span: 18 }"
|
|
||||||
ref="varialbeFormRef"
|
|
||||||
>
|
|
||||||
<FormItem label="源:" name="source">
|
<FormItem label="源:" name="source">
|
||||||
<Input v-model:value="varialbeFormData.source" allow-clear />
|
<Input v-model:value="varialbeFormData.source" allow-clear />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression';
|
|||||||
|
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
import { ContentWrap } from '@vben/common-ui';
|
||||||
import { CommonStatusEnum } from '@vben/constants';
|
import { CommonStatusEnum } from '@vben/constants';
|
||||||
|
|
||||||
import { Button, Modal, Pagination, Table, TableColumn } from 'ant-design-vue';
|
import { Button, Modal, Pagination, Table, TableColumn } from 'ant-design-vue';
|
||||||
|
|
||||||
import { getProcessExpressionPage } from '#/api/bpm/processExpression';
|
import { getProcessExpressionPage } from '#/api/bpm/processExpression';
|
||||||
import { ContentWrap } from '#/components/content-wrap';
|
|
||||||
|
|
||||||
/** BPM 流程 表单 */
|
/** BPM 流程 表单 */
|
||||||
defineOptions({ name: 'ProcessExpressionDialog' });
|
defineOptions({ name: 'ProcessExpressionDialog' });
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ watch(
|
|||||||
width="400px"
|
width="400px"
|
||||||
:destroy-on-close="true"
|
:destroy-on-close="true"
|
||||||
>
|
>
|
||||||
<Form :model="newMessageForm" size="small" :label-col="{ span: 6 }">
|
<Form :model="newMessageForm" size="small">
|
||||||
<Form.Item label="消息ID">
|
<Form.Item label="消息ID">
|
||||||
<Input v-model:value="newMessageForm.id" allow-clear />
|
<Input v-model:value="newMessageForm.id" allow-clear />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
|
||||||
defineOptions,
|
|
||||||
defineProps,
|
|
||||||
nextTick,
|
|
||||||
onBeforeUnmount,
|
|
||||||
ref,
|
|
||||||
toRaw,
|
|
||||||
watch,
|
|
||||||
} from 'vue';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FormItem,
|
FormItem,
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
<Form>
|
||||||
<FormItem label="规则类型" name="candidateStrategy">
|
<FormItem label="规则类型" name="candidateStrategy">
|
||||||
<Select
|
<Select
|
||||||
v-model:value="userTaskForm.candidateStrategy"
|
v-model:value="userTaskForm.candidateStrategy"
|
||||||
|
|||||||
@@ -3,12 +3,7 @@ import type { Ref } from 'vue';
|
|||||||
|
|
||||||
import { computed, nextTick, onMounted, ref, toRaw, watch } from 'vue';
|
import { computed, nextTick, onMounted, ref, toRaw, watch } from 'vue';
|
||||||
|
|
||||||
import {
|
import { IconifyIcon } from '@vben/icons';
|
||||||
CheckCircleFilled,
|
|
||||||
ExclamationCircleFilled,
|
|
||||||
IconifyIcon,
|
|
||||||
QuestionCircleFilled,
|
|
||||||
} from '@vben/icons';
|
|
||||||
|
|
||||||
import { Button, DatePicker, Input, Modal, Tooltip } from 'ant-design-vue';
|
import { Button, DatePicker, Input, Modal, Tooltip } from 'ant-design-vue';
|
||||||
|
|
||||||
@@ -240,7 +235,11 @@ watch(
|
|||||||
循环
|
循环
|
||||||
</Button>
|
</Button>
|
||||||
</Button.Group>
|
</Button.Group>
|
||||||
<CheckCircleFilled v-if="valid" style="color: green; margin-left: 8px" />
|
<IconifyIcon
|
||||||
|
icon="ant-design:check-circle-filled"
|
||||||
|
v-if="valid"
|
||||||
|
style="color: green; margin-left: 8px"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; align-items: center; margin-top: 10px">
|
<div style="display: flex; align-items: center; margin-top: 10px">
|
||||||
<span>条件:</span>
|
<span>条件:</span>
|
||||||
@@ -254,11 +253,15 @@ watch(
|
|||||||
>
|
>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<Tooltip v-if="!valid" title="格式错误" placement="top">
|
<Tooltip v-if="!valid" title="格式错误" placement="top">
|
||||||
<ExclamationCircleFilled style="color: orange" />
|
<IconifyIcon
|
||||||
|
icon="ant-design:exclamation-circle-filled"
|
||||||
|
class="text-orange-400"
|
||||||
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip :title="helpText" placement="top">
|
<Tooltip :title="helpText" placement="top">
|
||||||
<QuestionCircleFilled
|
<IconifyIcon
|
||||||
style="color: #409eff; cursor: pointer"
|
icon="ant-design:question-circle-filled"
|
||||||
|
class="cursor-pointer text-[#409eff]"
|
||||||
@click="showHelp = true"
|
@click="showHelp = true"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -351,7 +354,3 @@ watch(
|
|||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* 相关样式 */
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
<!--
|
|
||||||
参考自 https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/components/ContentWrap/src/ContentWrap.vue
|
|
||||||
保证和 yudao-ui-admin-vue3 功能的一致性
|
|
||||||
-->
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { CSSProperties } from 'vue';
|
|
||||||
|
|
||||||
import { ShieldQuestion } from '@vben/icons';
|
|
||||||
|
|
||||||
import { Card, Tooltip } from 'ant-design-vue';
|
|
||||||
|
|
||||||
defineOptions({ name: 'ContentWrap' });
|
|
||||||
|
|
||||||
withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
bodyStyle?: CSSProperties;
|
|
||||||
message?: string;
|
|
||||||
title?: string;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
bodyStyle: () => ({ padding: '10px' }),
|
|
||||||
title: '',
|
|
||||||
message: '',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Card :body-style="bodyStyle" :title="title" class="mb-4">
|
|
||||||
<template v-if="title" #title>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="text-base font-bold">{{ title }}</span>
|
|
||||||
<Tooltip placement="right">
|
|
||||||
<template #title>
|
|
||||||
<div class="max-w-[200px]">{{ message }}</div>
|
|
||||||
</template>
|
|
||||||
<ShieldQuestion :size="14" class="ml-1" />
|
|
||||||
</Tooltip>
|
|
||||||
<div class="flex flex-grow pl-5">
|
|
||||||
<slot name="header"></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #extra>
|
|
||||||
<slot name="extra"></slot>
|
|
||||||
</template>
|
|
||||||
<slot></slot>
|
|
||||||
</Card>
|
|
||||||
</template>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { default as ContentWrap } from './content-wrap.vue';
|
|
||||||
@@ -1,9 +1,3 @@
|
|||||||
import { defineAsyncComponent } from 'vue';
|
|
||||||
|
|
||||||
export const AsyncOperateLog = defineAsyncComponent(
|
|
||||||
() => import('./operate-log.vue'),
|
|
||||||
);
|
|
||||||
|
|
||||||
export { default as OperateLog } from './operate-log.vue';
|
export { default as OperateLog } from './operate-log.vue';
|
||||||
|
|
||||||
export type { OperateLogProps } from './typing';
|
export type { OperateLogProps } from './typing';
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ function getUserTypeColor(userType: number) {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<!-- TODO @xingyu:有没可能美化下? -->
|
||||||
<Timeline>
|
<Timeline>
|
||||||
<Timeline.Item
|
<Timeline.Item
|
||||||
v-for="log in logList"
|
v-for="log in logList"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { FileUploadProps } from './typing';
|
|||||||
|
|
||||||
import type { AxiosProgressEvent } from '#/api/infra/file';
|
import type { AxiosProgressEvent } from '#/api/infra/file';
|
||||||
|
|
||||||
import { ref, toRefs, watch } from 'vue';
|
import { computed, ref, toRefs, watch } from 'vue';
|
||||||
|
|
||||||
import { CloudUpload } from '@vben/icons';
|
import { CloudUpload } from '@vben/icons';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
@@ -22,8 +22,10 @@ defineOptions({ name: 'FileUpload', inheritAttrs: false });
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<FileUploadProps>(), {
|
const props = withDefaults(defineProps<FileUploadProps>(), {
|
||||||
value: () => [],
|
value: () => [],
|
||||||
|
modelValue: undefined,
|
||||||
directory: undefined,
|
directory: undefined,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
drag: false,
|
||||||
helpText: '',
|
helpText: '',
|
||||||
maxSize: 2,
|
maxSize: 2,
|
||||||
maxNumber: 1,
|
maxNumber: 1,
|
||||||
@@ -33,7 +35,14 @@ const props = withDefaults(defineProps<FileUploadProps>(), {
|
|||||||
resultField: '',
|
resultField: '',
|
||||||
showDescription: false,
|
showDescription: false,
|
||||||
});
|
});
|
||||||
const emit = defineEmits(['change', 'update:value', 'delete', 'returnText']);
|
const emit = defineEmits([
|
||||||
|
'change',
|
||||||
|
'update:value',
|
||||||
|
'update:modelValue',
|
||||||
|
'delete',
|
||||||
|
'returnText',
|
||||||
|
'preview',
|
||||||
|
]);
|
||||||
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
|
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
|
||||||
const isInnerOperate = ref<boolean>(false);
|
const isInnerOperate = ref<boolean>(false);
|
||||||
const { getStringAccept } = useUploadType({
|
const { getStringAccept } = useUploadType({
|
||||||
@@ -43,13 +52,25 @@ const { getStringAccept } = useUploadType({
|
|||||||
maxSizeRef: maxSize,
|
maxSizeRef: maxSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 计算当前绑定的值,优先使用 modelValue
|
||||||
|
const currentValue = computed(() => {
|
||||||
|
return props.modelValue === undefined ? props.value : props.modelValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 判断是否使用 modelValue
|
||||||
|
const isUsingModelValue = computed(() => {
|
||||||
|
return props.modelValue !== undefined;
|
||||||
|
});
|
||||||
|
|
||||||
const fileList = ref<UploadProps['fileList']>([]);
|
const fileList = ref<UploadProps['fileList']>([]);
|
||||||
const isLtMsg = ref<boolean>(true); // 文件大小错误提示
|
const isLtMsg = ref<boolean>(true); // 文件大小错误提示
|
||||||
const isActMsg = ref<boolean>(true); // 文件类型错误提示
|
const isActMsg = ref<boolean>(true); // 文件类型错误提示
|
||||||
const isFirstRender = ref<boolean>(true); // 是否第一次渲染
|
const isFirstRender = ref<boolean>(true); // 是否第一次渲染
|
||||||
|
const uploadNumber = ref<number>(0); // 上传文件计数器
|
||||||
|
const uploadList = ref<any[]>([]); // 临时上传列表
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.value,
|
currentValue,
|
||||||
(v) => {
|
(v) => {
|
||||||
if (isInnerOperate.value) {
|
if (isInnerOperate.value) {
|
||||||
isInnerOperate.value = false;
|
isInnerOperate.value = false;
|
||||||
@@ -94,15 +115,40 @@ async function handleRemove(file: UploadFile) {
|
|||||||
const value = getValue();
|
const value = getValue();
|
||||||
isInnerOperate.value = true;
|
isInnerOperate.value = true;
|
||||||
emit('update:value', value);
|
emit('update:value', value);
|
||||||
|
emit('update:modelValue', value);
|
||||||
emit('change', value);
|
emit('change', value);
|
||||||
emit('delete', file);
|
emit('delete', file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理文件预览
|
||||||
|
function handlePreview(file: UploadFile) {
|
||||||
|
emit('preview', file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理文件数量超限
|
||||||
|
function handleExceed() {
|
||||||
|
message.error($t('ui.upload.maxNumber', [maxNumber.value]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理上传错误
|
||||||
|
function handleUploadError(error: any) {
|
||||||
|
console.error('上传错误:', error);
|
||||||
|
message.error($t('ui.upload.uploadError'));
|
||||||
|
// 上传失败时减少计数器
|
||||||
|
uploadNumber.value = Math.max(0, uploadNumber.value - 1);
|
||||||
|
}
|
||||||
|
|
||||||
async function beforeUpload(file: File) {
|
async function beforeUpload(file: File) {
|
||||||
const fileContent = await file.text();
|
const fileContent = await file.text();
|
||||||
emit('returnText', fileContent);
|
emit('returnText', fileContent);
|
||||||
|
|
||||||
|
// 检查文件数量限制
|
||||||
|
if (fileList.value!.length >= props.maxNumber) {
|
||||||
|
message.error($t('ui.upload.maxNumber', [props.maxNumber]));
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
|
}
|
||||||
|
|
||||||
const { maxSize, accept } = props;
|
const { maxSize, accept } = props;
|
||||||
const isAct = checkFileType(file, accept);
|
const isAct = checkFileType(file, accept);
|
||||||
if (!isAct) {
|
if (!isAct) {
|
||||||
@@ -110,6 +156,7 @@ async function beforeUpload(file: File) {
|
|||||||
isActMsg.value = false;
|
isActMsg.value = false;
|
||||||
// 防止弹出多个错误提示
|
// 防止弹出多个错误提示
|
||||||
setTimeout(() => (isActMsg.value = true), 1000);
|
setTimeout(() => (isActMsg.value = true), 1000);
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
}
|
}
|
||||||
const isLt = file.size / 1024 / 1024 > maxSize;
|
const isLt = file.size / 1024 / 1024 > maxSize;
|
||||||
if (isLt) {
|
if (isLt) {
|
||||||
@@ -117,8 +164,12 @@ async function beforeUpload(file: File) {
|
|||||||
isLtMsg.value = false;
|
isLtMsg.value = false;
|
||||||
// 防止弹出多个错误提示
|
// 防止弹出多个错误提示
|
||||||
setTimeout(() => (isLtMsg.value = true), 1000);
|
setTimeout(() => (isLtMsg.value = true), 1000);
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
}
|
}
|
||||||
return (isAct && !isLt) || Upload.LIST_IGNORE;
|
|
||||||
|
// 只有在验证通过后才增加计数器
|
||||||
|
uploadNumber.value++;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function customRequest(info: UploadRequestOption<any>) {
|
async function customRequest(info: UploadRequestOption<any>) {
|
||||||
@@ -133,17 +184,48 @@ async function customRequest(info: UploadRequestOption<any>) {
|
|||||||
info.onProgress!({ percent });
|
info.onProgress!({ percent });
|
||||||
};
|
};
|
||||||
const res = await api?.(info.file as File, progressEvent);
|
const res = await api?.(info.file as File, progressEvent);
|
||||||
|
|
||||||
|
// 处理上传成功后的逻辑
|
||||||
|
handleUploadSuccess(res, info.file as File);
|
||||||
|
|
||||||
info.onSuccess!(res);
|
info.onSuccess!(res);
|
||||||
message.success($t('ui.upload.uploadSuccess'));
|
message.success($t('ui.upload.uploadSuccess'));
|
||||||
|
|
||||||
// 更新文件
|
|
||||||
const value = getValue();
|
|
||||||
isInnerOperate.value = true;
|
|
||||||
emit('update:value', value);
|
|
||||||
emit('change', value);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
info.onError!(error);
|
info.onError!(error);
|
||||||
|
handleUploadError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理上传成功
|
||||||
|
function handleUploadSuccess(res: any, file: File) {
|
||||||
|
// 删除临时文件
|
||||||
|
const index = fileList.value?.findIndex((item) => item.name === file.name);
|
||||||
|
if (index !== -1) {
|
||||||
|
fileList.value?.splice(index!, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到临时上传列表
|
||||||
|
const fileUrl = res?.url || res?.data || res;
|
||||||
|
uploadList.value.push({
|
||||||
|
name: file.name,
|
||||||
|
url: fileUrl,
|
||||||
|
status: UploadResultStatus.DONE,
|
||||||
|
uid: file.name + Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查是否所有文件都上传完成
|
||||||
|
if (uploadList.value.length >= uploadNumber.value) {
|
||||||
|
fileList.value?.push(...uploadList.value);
|
||||||
|
uploadList.value = [];
|
||||||
|
uploadNumber.value = 0;
|
||||||
|
|
||||||
|
// 更新值
|
||||||
|
const value = getValue();
|
||||||
|
isInnerOperate.value = true;
|
||||||
|
emit('update:value', value);
|
||||||
|
emit('update:modelValue', value);
|
||||||
|
emit('change', value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,11 +238,26 @@ function getValue() {
|
|||||||
}
|
}
|
||||||
return item?.url || item?.response?.url || item?.response;
|
return item?.url || item?.response?.url || item?.response;
|
||||||
});
|
});
|
||||||
// add by 芋艿:【特殊】单个文件的情况,获取首个元素,保证返回的是 String 类型
|
|
||||||
|
// 单个文件的情况,根据输入参数类型决定返回格式
|
||||||
if (props.maxNumber === 1) {
|
if (props.maxNumber === 1) {
|
||||||
return list.length > 0 ? list[0] : '';
|
const singleValue = list.length > 0 ? list[0] : '';
|
||||||
|
// 如果原始值是字符串或 modelValue 是字符串,返回字符串
|
||||||
|
if (
|
||||||
|
isString(props.value) ||
|
||||||
|
(isUsingModelValue.value && isString(props.modelValue))
|
||||||
|
) {
|
||||||
|
return singleValue;
|
||||||
}
|
}
|
||||||
return list;
|
return singleValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多文件情况,根据输入参数类型决定返回格式
|
||||||
|
if (isUsingModelValue.value) {
|
||||||
|
return Array.isArray(props.modelValue) ? list : list.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.isArray(props.value) ? list : list.join(',');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -177,15 +274,34 @@ function getValue() {
|
|||||||
:multiple="multiple"
|
:multiple="multiple"
|
||||||
list-type="text"
|
list-type="text"
|
||||||
:progress="{ showInfo: true }"
|
:progress="{ showInfo: true }"
|
||||||
|
:show-upload-list="{
|
||||||
|
showPreviewIcon: true,
|
||||||
|
showRemoveIcon: true,
|
||||||
|
showDownloadIcon: true,
|
||||||
|
}"
|
||||||
@remove="handleRemove"
|
@remove="handleRemove"
|
||||||
|
@preview="handlePreview"
|
||||||
|
@reject="handleExceed"
|
||||||
>
|
>
|
||||||
<div v-if="fileList && fileList.length < maxNumber">
|
<div v-if="drag" class="upload-drag-area">
|
||||||
|
<p class="ant-upload-drag-icon">
|
||||||
|
<CloudUpload />
|
||||||
|
</p>
|
||||||
|
<p class="ant-upload-text">点击或拖拽文件到此区域上传</p>
|
||||||
|
<p class="ant-upload-hint">
|
||||||
|
支持{{ accept.join('/') }}格式文件,不超过{{ maxSize }}MB
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="fileList && fileList.length < maxNumber">
|
||||||
<Button>
|
<Button>
|
||||||
<CloudUpload />
|
<CloudUpload />
|
||||||
{{ $t('ui.upload.upload') }}
|
{{ $t('ui.upload.upload') }}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showDescription" class="mt-2 flex flex-wrap items-center">
|
<div
|
||||||
|
v-if="showDescription && !drag"
|
||||||
|
class="mt-2 flex flex-wrap items-center"
|
||||||
|
>
|
||||||
请上传不超过
|
请上传不超过
|
||||||
<div class="text-primary mx-1 font-bold">{{ maxSize }}MB</div>
|
<div class="text-primary mx-1 font-bold">{{ maxSize }}MB</div>
|
||||||
的
|
的
|
||||||
@@ -195,3 +311,35 @@ function getValue() {
|
|||||||
</Upload>
|
</Upload>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.upload-drag-area {
|
||||||
|
border: 2px dashed #d9d9d9;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #fafafa;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-drag-area:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-upload-drag-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #d9d9d9;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-upload-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-upload-hint {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { FileUploadProps } from './typing';
|
|||||||
|
|
||||||
import type { AxiosProgressEvent } from '#/api/infra/file';
|
import type { AxiosProgressEvent } from '#/api/infra/file';
|
||||||
|
|
||||||
import { ref, toRefs, watch } from 'vue';
|
import { computed, ref, toRefs, watch } from 'vue';
|
||||||
|
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
@@ -22,6 +22,7 @@ defineOptions({ name: 'ImageUpload', inheritAttrs: false });
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<FileUploadProps>(), {
|
const props = withDefaults(defineProps<FileUploadProps>(), {
|
||||||
value: () => [],
|
value: () => [],
|
||||||
|
modelValue: undefined,
|
||||||
directory: undefined,
|
directory: undefined,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
listType: 'picture-card',
|
listType: 'picture-card',
|
||||||
@@ -34,7 +35,12 @@ const props = withDefaults(defineProps<FileUploadProps>(), {
|
|||||||
resultField: '',
|
resultField: '',
|
||||||
showDescription: true,
|
showDescription: true,
|
||||||
});
|
});
|
||||||
const emit = defineEmits(['change', 'update:value', 'delete']);
|
const emit = defineEmits([
|
||||||
|
'change',
|
||||||
|
'update:value',
|
||||||
|
'update:modelValue',
|
||||||
|
'delete',
|
||||||
|
]);
|
||||||
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
|
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
|
||||||
const isInnerOperate = ref<boolean>(false);
|
const isInnerOperate = ref<boolean>(false);
|
||||||
const { getStringAccept } = useUploadType({
|
const { getStringAccept } = useUploadType({
|
||||||
@@ -43,6 +49,16 @@ const { getStringAccept } = useUploadType({
|
|||||||
maxNumberRef: maxNumber,
|
maxNumberRef: maxNumber,
|
||||||
maxSizeRef: maxSize,
|
maxSizeRef: maxSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 计算当前绑定的值,优先使用 modelValue
|
||||||
|
const currentValue = computed(() => {
|
||||||
|
return props.modelValue === undefined ? props.value : props.modelValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 判断是否使用 modelValue
|
||||||
|
const isUsingModelValue = computed(() => {
|
||||||
|
return props.modelValue !== undefined;
|
||||||
|
});
|
||||||
const previewOpen = ref<boolean>(false); // 是否展示预览
|
const previewOpen = ref<boolean>(false); // 是否展示预览
|
||||||
const previewImage = ref<string>(''); // 预览图片
|
const previewImage = ref<string>(''); // 预览图片
|
||||||
const previewTitle = ref<string>(''); // 预览标题
|
const previewTitle = ref<string>(''); // 预览标题
|
||||||
@@ -51,9 +67,11 @@ const fileList = ref<UploadProps['fileList']>([]);
|
|||||||
const isLtMsg = ref<boolean>(true); // 文件大小错误提示
|
const isLtMsg = ref<boolean>(true); // 文件大小错误提示
|
||||||
const isActMsg = ref<boolean>(true); // 文件类型错误提示
|
const isActMsg = ref<boolean>(true); // 文件类型错误提示
|
||||||
const isFirstRender = ref<boolean>(true); // 是否第一次渲染
|
const isFirstRender = ref<boolean>(true); // 是否第一次渲染
|
||||||
|
const uploadNumber = ref<number>(0); // 上传文件计数器
|
||||||
|
const uploadList = ref<any[]>([]); // 临时上传列表
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.value,
|
currentValue,
|
||||||
async (v) => {
|
async (v) => {
|
||||||
if (isInnerOperate.value) {
|
if (isInnerOperate.value) {
|
||||||
isInnerOperate.value = false;
|
isInnerOperate.value = false;
|
||||||
@@ -122,6 +140,7 @@ async function handleRemove(file: UploadFile) {
|
|||||||
const value = getValue();
|
const value = getValue();
|
||||||
isInnerOperate.value = true;
|
isInnerOperate.value = true;
|
||||||
emit('update:value', value);
|
emit('update:value', value);
|
||||||
|
emit('update:modelValue', value);
|
||||||
emit('change', value);
|
emit('change', value);
|
||||||
emit('delete', file);
|
emit('delete', file);
|
||||||
}
|
}
|
||||||
@@ -133,6 +152,12 @@ function handleCancel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function beforeUpload(file: File) {
|
async function beforeUpload(file: File) {
|
||||||
|
// 检查文件数量限制
|
||||||
|
if (fileList.value!.length >= props.maxNumber) {
|
||||||
|
message.error($t('ui.upload.maxNumber', [props.maxNumber]));
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
|
}
|
||||||
|
|
||||||
const { maxSize, accept } = props;
|
const { maxSize, accept } = props;
|
||||||
const isAct = checkImgType(file, accept);
|
const isAct = checkImgType(file, accept);
|
||||||
if (!isAct) {
|
if (!isAct) {
|
||||||
@@ -140,6 +165,7 @@ async function beforeUpload(file: File) {
|
|||||||
isActMsg.value = false;
|
isActMsg.value = false;
|
||||||
// 防止弹出多个错误提示
|
// 防止弹出多个错误提示
|
||||||
setTimeout(() => (isActMsg.value = true), 1000);
|
setTimeout(() => (isActMsg.value = true), 1000);
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
}
|
}
|
||||||
const isLt = file.size / 1024 / 1024 > maxSize;
|
const isLt = file.size / 1024 / 1024 > maxSize;
|
||||||
if (isLt) {
|
if (isLt) {
|
||||||
@@ -147,8 +173,12 @@ async function beforeUpload(file: File) {
|
|||||||
isLtMsg.value = false;
|
isLtMsg.value = false;
|
||||||
// 防止弹出多个错误提示
|
// 防止弹出多个错误提示
|
||||||
setTimeout(() => (isLtMsg.value = true), 1000);
|
setTimeout(() => (isLtMsg.value = true), 1000);
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
}
|
}
|
||||||
return (isAct && !isLt) || Upload.LIST_IGNORE;
|
|
||||||
|
// 只有在验证通过后才增加计数器
|
||||||
|
uploadNumber.value++;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function customRequest(info: UploadRequestOption<any>) {
|
async function customRequest(info: UploadRequestOption<any>) {
|
||||||
@@ -163,20 +193,59 @@ async function customRequest(info: UploadRequestOption<any>) {
|
|||||||
info.onProgress!({ percent });
|
info.onProgress!({ percent });
|
||||||
};
|
};
|
||||||
const res = await api?.(info.file as File, progressEvent);
|
const res = await api?.(info.file as File, progressEvent);
|
||||||
|
|
||||||
|
// 处理上传成功后的逻辑
|
||||||
|
handleUploadSuccess(res, info.file as File);
|
||||||
|
|
||||||
info.onSuccess!(res);
|
info.onSuccess!(res);
|
||||||
message.success($t('ui.upload.uploadSuccess'));
|
message.success($t('ui.upload.uploadSuccess'));
|
||||||
|
|
||||||
// 更新文件
|
|
||||||
const value = getValue();
|
|
||||||
isInnerOperate.value = true;
|
|
||||||
emit('update:value', value);
|
|
||||||
emit('change', value);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
info.onError!(error);
|
info.onError!(error);
|
||||||
|
handleUploadError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理上传成功
|
||||||
|
function handleUploadSuccess(res: any, file: File) {
|
||||||
|
// 删除临时文件
|
||||||
|
const index = fileList.value?.findIndex((item) => item.name === file.name);
|
||||||
|
if (index !== -1) {
|
||||||
|
fileList.value?.splice(index!, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到临时上传列表
|
||||||
|
const fileUrl = res?.url || res?.data || res;
|
||||||
|
uploadList.value.push({
|
||||||
|
name: file.name,
|
||||||
|
url: fileUrl,
|
||||||
|
status: UploadResultStatus.DONE,
|
||||||
|
uid: file.name + Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查是否所有文件都上传完成
|
||||||
|
if (uploadList.value.length >= uploadNumber.value) {
|
||||||
|
fileList.value?.push(...uploadList.value);
|
||||||
|
uploadList.value = [];
|
||||||
|
uploadNumber.value = 0;
|
||||||
|
|
||||||
|
// 更新值
|
||||||
|
const value = getValue();
|
||||||
|
isInnerOperate.value = true;
|
||||||
|
emit('update:value', value);
|
||||||
|
emit('update:modelValue', value);
|
||||||
|
emit('change', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理上传错误
|
||||||
|
function handleUploadError(error: any) {
|
||||||
|
console.error('上传错误:', error);
|
||||||
|
message.error($t('ui.upload.uploadError'));
|
||||||
|
// 上传失败时减少计数器
|
||||||
|
uploadNumber.value = Math.max(0, uploadNumber.value - 1);
|
||||||
|
}
|
||||||
|
|
||||||
function getValue() {
|
function getValue() {
|
||||||
const list = (fileList.value || [])
|
const list = (fileList.value || [])
|
||||||
.filter((item) => item?.status === UploadResultStatus.DONE)
|
.filter((item) => item?.status === UploadResultStatus.DONE)
|
||||||
@@ -186,11 +255,26 @@ function getValue() {
|
|||||||
}
|
}
|
||||||
return item?.url || item?.response?.url || item?.response;
|
return item?.url || item?.response?.url || item?.response;
|
||||||
});
|
});
|
||||||
// add by 芋艿:【特殊】单个文件的情况,获取首个元素,保证返回的是 String 类型
|
|
||||||
|
// 单个文件的情况,根据输入参数类型决定返回格式
|
||||||
if (props.maxNumber === 1) {
|
if (props.maxNumber === 1) {
|
||||||
return list.length > 0 ? list[0] : '';
|
const singleValue = list.length > 0 ? list[0] : '';
|
||||||
|
// 如果原始值是字符串或 modelValue 是字符串,返回字符串
|
||||||
|
if (
|
||||||
|
isString(props.value) ||
|
||||||
|
(isUsingModelValue.value && isString(props.modelValue))
|
||||||
|
) {
|
||||||
|
return singleValue;
|
||||||
}
|
}
|
||||||
return list;
|
return singleValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多文件情况,根据输入参数类型决定返回格式
|
||||||
|
if (isUsingModelValue.value) {
|
||||||
|
return Array.isArray(props.modelValue) ? list : list.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.isArray(props.value) ? list : list.join(',');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ export interface FileUploadProps {
|
|||||||
// 上传的目录
|
// 上传的目录
|
||||||
directory?: string;
|
directory?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
drag?: boolean; // 是否支持拖拽上传
|
||||||
helpText?: string;
|
helpText?: string;
|
||||||
listType?: UploadListType;
|
listType?: UploadListType;
|
||||||
// 最大数量的文件,Infinity不限制
|
// 最大数量的文件,Infinity不限制
|
||||||
maxNumber?: number;
|
maxNumber?: number;
|
||||||
|
modelValue?: string | string[]; // v-model 支持
|
||||||
// 文件最大多少MB
|
// 文件最大多少MB
|
||||||
maxSize?: number;
|
maxSize?: number;
|
||||||
// 是否支持多选
|
// 是否支持多选
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export function getUploadUrl(): string {
|
|||||||
* @param file 文件
|
* @param file 文件
|
||||||
*/
|
*/
|
||||||
function createFile0(
|
function createFile0(
|
||||||
vo: InfraFileApi.FilePresignedUrlResp,
|
vo: InfraFileApi.FilePresignedUrlRespVO,
|
||||||
file: File,
|
file: File,
|
||||||
): InfraFileApi.File {
|
): InfraFileApi.File {
|
||||||
const fileVO = {
|
const fileVO = {
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: 'BpmProcessInstanceReport',
|
name: 'BpmProcessInstanceReport',
|
||||||
meta: {
|
meta: {
|
||||||
title: '数据报表',
|
title: '数据报表',
|
||||||
activeMenu: '/bpm/manager/model',
|
activePath: '/bpm/manager/model',
|
||||||
icon: 'carbon:data-2',
|
icon: 'carbon:data-2',
|
||||||
hideInMenu: true,
|
hideInMenu: true,
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
|
|||||||
@@ -16,73 +16,72 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: 'CrmClueDetail',
|
name: 'CrmClueDetail',
|
||||||
meta: {
|
meta: {
|
||||||
title: '线索详情',
|
title: '线索详情',
|
||||||
activeMenu: '/crm/clue',
|
activePath: '/crm/clue',
|
||||||
},
|
},
|
||||||
component: () => import('#/views/crm/clue/modules/detail.vue'),
|
component: () => import('#/views/crm/clue/detail/index.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'customer/detail/:id',
|
path: 'customer/detail/:id',
|
||||||
name: 'CrmCustomerDetail',
|
name: 'CrmCustomerDetail',
|
||||||
meta: {
|
meta: {
|
||||||
title: '客户详情',
|
title: '客户详情',
|
||||||
activeMenu: '/crm/customer',
|
activePath: '/crm/customer',
|
||||||
},
|
},
|
||||||
component: () => import('#/views/crm/customer/modules/detail.vue'),
|
component: () => import('#/views/crm/customer/detail/index.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'business/detail/:id',
|
path: 'business/detail/:id',
|
||||||
name: 'CrmBusinessDetail',
|
name: 'CrmBusinessDetail',
|
||||||
meta: {
|
meta: {
|
||||||
title: '商机详情',
|
title: '商机详情',
|
||||||
activeMenu: '/crm/business',
|
activePath: '/crm/business',
|
||||||
},
|
},
|
||||||
component: () => import('#/views/crm/business/modules/detail.vue'),
|
component: () => import('#/views/crm/business/detail/index.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'contract/detail/:id',
|
path: 'contract/detail/:id',
|
||||||
name: 'CrmContractDetail',
|
name: 'CrmContractDetail',
|
||||||
meta: {
|
meta: {
|
||||||
title: '合同详情',
|
title: '合同详情',
|
||||||
activeMenu: '/crm/contract',
|
activePath: '/crm/contract',
|
||||||
},
|
},
|
||||||
component: () => import('#/views/crm/contract/modules/detail.vue'),
|
component: () => import('#/views/crm/contract/detail/index.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'receivable-plan/detail/:id',
|
path: 'receivable-plan/detail/:id',
|
||||||
name: 'CrmReceivablePlanDetail',
|
name: 'CrmReceivablePlanDetail',
|
||||||
meta: {
|
meta: {
|
||||||
title: '回款计划详情',
|
title: '回款计划详情',
|
||||||
activeMenu: '/crm/receivable-plan',
|
activePath: '/crm/receivable-plan',
|
||||||
},
|
},
|
||||||
component: () =>
|
component: () => import('#/views/crm/receivable/plan/detail/index.vue'),
|
||||||
import('#/views/crm/receivable/plan/modules/detail.vue'),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'receivable/detail/:id',
|
path: 'receivable/detail/:id',
|
||||||
name: 'CrmReceivableDetail',
|
name: 'CrmReceivableDetail',
|
||||||
meta: {
|
meta: {
|
||||||
title: '回款详情',
|
title: '回款详情',
|
||||||
activeMenu: '/crm/receivable',
|
activePath: '/crm/receivable',
|
||||||
},
|
},
|
||||||
component: () => import('#/views/crm/receivable/modules/detail.vue'),
|
component: () => import('#/views/crm/receivable/detail/index.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'contact/detail/:id',
|
path: 'contact/detail/:id',
|
||||||
name: 'CrmContactDetail',
|
name: 'CrmContactDetail',
|
||||||
meta: {
|
meta: {
|
||||||
title: '联系人详情',
|
title: '联系人详情',
|
||||||
activeMenu: '/crm/contact',
|
activePath: '/crm/contact',
|
||||||
},
|
},
|
||||||
component: () => import('#/views/crm/contact/modules/detail.vue'),
|
component: () => import('#/views/crm/contact/detail/index.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'product/detail/:id',
|
path: 'product/detail/:id',
|
||||||
name: 'CrmProductDetail',
|
name: 'CrmProductDetail',
|
||||||
meta: {
|
meta: {
|
||||||
title: '产品详情',
|
title: '产品详情',
|
||||||
activeMenu: '/crm/product',
|
activePath: '/crm/product',
|
||||||
},
|
},
|
||||||
component: () => import('#/views/crm/product/modules/detail.vue'),
|
component: () => import('#/views/crm/product/detail/index.vue'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router';
|
|||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: '/infra/job/job-log',
|
path: '/infra/job/log',
|
||||||
component: () => import('#/views/infra/job/logger/index.vue'),
|
component: () => import('#/views/infra/job/logger/index.vue'),
|
||||||
name: 'InfraJobLog',
|
name: 'InfraJobLog',
|
||||||
meta: {
|
meta: {
|
||||||
@@ -14,25 +14,16 @@ const routes: RouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/codegen',
|
path: '/infra/codegen/edit',
|
||||||
name: 'CodegenEdit',
|
component: () => import('#/views/infra/codegen/edit/index.vue'),
|
||||||
|
name: 'InfraCodegenEdit',
|
||||||
meta: {
|
meta: {
|
||||||
title: '代码生成',
|
title: '生成配置修改',
|
||||||
icon: 'ic:baseline-view-in-ar',
|
icon: 'ic:baseline-view-in-ar',
|
||||||
|
activePath: '/infra/codegen',
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
hideInMenu: true,
|
hideInMenu: true,
|
||||||
},
|
},
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '/codegen/edit',
|
|
||||||
name: 'InfraCodegenEdit',
|
|
||||||
component: () => import('#/views/infra/codegen/edit/index.vue'),
|
|
||||||
meta: {
|
|
||||||
title: '修改生成配置',
|
|
||||||
activeMenu: '/infra/codegen',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: 'ProductSpuAdd',
|
name: 'ProductSpuAdd',
|
||||||
meta: {
|
meta: {
|
||||||
title: '商品添加',
|
title: '商品添加',
|
||||||
activeMenu: '/mall/product/spu',
|
activePath: '/mall/product/spu',
|
||||||
},
|
},
|
||||||
component: () => import('#/views/mall/product/spu/modules/form.vue'),
|
component: () => import('#/views/mall/product/spu/modules/form.vue'),
|
||||||
},
|
},
|
||||||
@@ -25,7 +25,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: 'ProductSpuEdit',
|
name: 'ProductSpuEdit',
|
||||||
meta: {
|
meta: {
|
||||||
title: '商品编辑',
|
title: '商品编辑',
|
||||||
activeMenu: '/mall/product/spu',
|
activePath: '/mall/product/spu',
|
||||||
},
|
},
|
||||||
component: () => import('#/views/mall/product/spu/modules/form.vue'),
|
component: () => import('#/views/mall/product/spu/modules/form.vue'),
|
||||||
},
|
},
|
||||||
@@ -34,7 +34,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: 'ProductSpuDetail',
|
name: 'ProductSpuDetail',
|
||||||
meta: {
|
meta: {
|
||||||
title: '商品详情',
|
title: '商品详情',
|
||||||
activeMenu: '/crm/business',
|
activePath: '/crm/business',
|
||||||
},
|
},
|
||||||
component: () => import('#/views/mall/product/spu/modules/detail.vue'),
|
component: () => import('#/views/mall/product/spu/modules/detail.vue'),
|
||||||
},
|
},
|
||||||
@@ -55,7 +55,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: 'TradeOrderDetail',
|
name: 'TradeOrderDetail',
|
||||||
meta: {
|
meta: {
|
||||||
title: '订单详情',
|
title: '订单详情',
|
||||||
activeMenu: '/mall/trade/order',
|
activePath: '/mall/trade/order',
|
||||||
},
|
},
|
||||||
component: () => import('#/views/mall/trade/order/modules/detail.vue'),
|
component: () => import('#/views/mall/trade/order/modules/detail.vue'),
|
||||||
},
|
},
|
||||||
@@ -64,7 +64,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: 'TradeAfterSaleDetail',
|
name: 'TradeAfterSaleDetail',
|
||||||
meta: {
|
meta: {
|
||||||
title: '退款详情',
|
title: '退款详情',
|
||||||
activeMenu: '/mall/trade/after-sale',
|
activePath: '/mall/trade/after-sale',
|
||||||
},
|
},
|
||||||
component: () =>
|
component: () =>
|
||||||
import('#/views/mall/trade/afterSale/modules/detail.vue'),
|
import('#/views/mall/trade/afterSale/modules/detail.vue'),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const authStore = useAuthStore();
|
|||||||
const activeName = ref('basicInfo');
|
const activeName = ref('basicInfo');
|
||||||
|
|
||||||
/** 加载个人信息 */
|
/** 加载个人信息 */
|
||||||
const profile = ref<SystemUserProfileApi.UserProfileResp>();
|
const profile = ref<SystemUserProfileApi.UserProfileRespVO>();
|
||||||
async function loadProfile() {
|
async function loadProfile() {
|
||||||
profile.value = await getUserProfile();
|
profile.value = await getUserProfile();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { useVbenForm, z } from '#/adapter/form';
|
|||||||
import { updateUserProfile } from '#/api/system/user/profile';
|
import { updateUserProfile } from '#/api/system/user/profile';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
profile?: SystemUserProfileApi.UserProfileResp;
|
profile?: SystemUserProfileApi.UserProfileRespVO;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'success'): void;
|
(e: 'success'): void;
|
||||||
@@ -78,7 +78,7 @@ async function handleSubmit(values: Recordable<any>) {
|
|||||||
try {
|
try {
|
||||||
formApi.setLoading(true);
|
formApi.setLoading(true);
|
||||||
// 提交表单
|
// 提交表单
|
||||||
await updateUserProfile(values as SystemUserProfileApi.UpdateProfileReq);
|
await updateUserProfile(values as SystemUserProfileApi.UpdateProfileReqVO);
|
||||||
// 关闭并提示
|
// 关闭并提示
|
||||||
emit('success');
|
emit('success');
|
||||||
message.success($t('ui.actionMessage.operationSuccess'));
|
message.success($t('ui.actionMessage.operationSuccess'));
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { CropperAvatar } from '#/components/cropper';
|
|||||||
import { useUpload } from '#/components/upload/use-upload';
|
import { useUpload } from '#/components/upload/use-upload';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
profile?: SystemUserProfileApi.UserProfileResp;
|
profile?: SystemUserProfileApi.UserProfileRespVO;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -32,13 +32,12 @@ function onRefresh() {
|
|||||||
async function handleDelete(row: AiChatConversationApi.ChatConversation) {
|
async function handleDelete(row: AiChatConversationApi.ChatConversation) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteChatConversationByAdmin(row.id as number);
|
await deleteChatConversationByAdmin(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -29,13 +29,12 @@ function onRefresh() {
|
|||||||
async function handleDelete(row: AiChatConversationApi.ChatConversation) {
|
async function handleDelete(row: AiChatConversationApi.ChatConversation) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteChatMessageByAdmin(row.id as number);
|
await deleteChatMessageByAdmin(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -27,13 +27,12 @@ function onRefresh() {
|
|||||||
async function handleDelete(row: AiImageApi.Image) {
|
async function handleDelete(row: AiImageApi.Image) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteImage(row.id as number);
|
await deleteImage(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -52,13 +52,12 @@ function handleEdit(id: number) {
|
|||||||
async function handleDelete(row: AiKnowledgeDocumentApi.KnowledgeDocument) {
|
async function handleDelete(row: AiKnowledgeDocumentApi.KnowledgeDocument) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteKnowledgeDocument(row.id as number);
|
await deleteKnowledgeDocument(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -42,13 +42,12 @@ function handleEdit(row: AiKnowledgeKnowledgeApi.Knowledge) {
|
|||||||
async function handleDelete(row: AiKnowledgeKnowledgeApi.Knowledge) {
|
async function handleDelete(row: AiKnowledgeKnowledgeApi.Knowledge) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteKnowledge(row.id as number);
|
await deleteKnowledge(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -49,13 +49,12 @@ function handleEdit(row: AiKnowledgeKnowledgeApi.Knowledge) {
|
|||||||
async function handleDelete(row: AiKnowledgeKnowledgeApi.Knowledge) {
|
async function handleDelete(row: AiKnowledgeKnowledgeApi.Knowledge) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteKnowledgeSegment(row.id as number);
|
await deleteKnowledgeSegment(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -34,13 +34,12 @@ function onRefresh() {
|
|||||||
async function handleDelete(row: AiMindmapApi.MindMap) {
|
async function handleDelete(row: AiMindmapApi.MindMap) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteMindMap(row.id as number);
|
await deleteMindMap(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -37,13 +37,12 @@ function handleEdit(row: AiModelApiKeyApi.ApiKey) {
|
|||||||
async function handleDelete(row: AiModelApiKeyApi.ApiKey) {
|
async function handleDelete(row: AiModelApiKeyApi.ApiKey) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteApiKey(row.id as number);
|
await deleteApiKey(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -37,13 +37,12 @@ function handleEdit(row: AiModelChatRoleApi.ChatRole) {
|
|||||||
async function handleDelete(row: AiModelChatRoleApi.ChatRole) {
|
async function handleDelete(row: AiModelChatRoleApi.ChatRole) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteChatRole(row.id as number);
|
await deleteChatRole(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -42,13 +42,12 @@ function handleEdit(row: AiModelModelApi.Model) {
|
|||||||
async function handleDelete(row: AiModelModelApi.Model) {
|
async function handleDelete(row: AiModelModelApi.Model) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteModel(row.id as number);
|
await deleteModel(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -37,13 +37,12 @@ function handleEdit(row: AiModelToolApi.Tool) {
|
|||||||
async function handleDelete(row: AiModelToolApi.Tool) {
|
async function handleDelete(row: AiModelToolApi.Tool) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteTool(row.id as number);
|
await deleteTool(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -27,13 +27,12 @@ function onRefresh() {
|
|||||||
async function handleDelete(row: AiMusicApi.Music) {
|
async function handleDelete(row: AiMusicApi.Music) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteMusic(row.id as number);
|
await deleteMusic(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -39,13 +39,12 @@ function handleEdit(row: any) {
|
|||||||
async function handleDelete(row: any) {
|
async function handleDelete(row: any) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteWorkflow(row.id as number);
|
await deleteWorkflow(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -26,13 +26,12 @@ function onRefresh() {
|
|||||||
async function handleDelete(row: AiWriteApi.AiWritePageReq) {
|
async function handleDelete(row: AiWriteApi.AiWritePageReq) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.id]),
|
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteWrite(row.id as number);
|
await deleteWrite(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -37,13 +37,12 @@ function handleEdit(row: BpmCategoryApi.Category) {
|
|||||||
async function handleDelete(row: BpmCategoryApi.Category) {
|
async function handleDelete(row: BpmCategoryApi.Category) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.code]),
|
content: $t('ui.actionMessage.deleting', [row.code]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteCategory(row.id as number);
|
await deleteCategory(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.code]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.code]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -60,13 +60,12 @@ function handleCopy(row: BpmFormApi.Form) {
|
|||||||
async function handleDelete(row: BpmFormApi.Form) {
|
async function handleDelete(row: BpmFormApi.Form) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteForm(row.id as number);
|
await deleteForm(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -41,13 +41,12 @@ function handleEdit(row: BpmUserGroupApi.UserGroup) {
|
|||||||
async function handleDelete(row: BpmUserGroupApi.UserGroup) {
|
async function handleDelete(row: BpmUserGroupApi.UserGroup) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteUserGroup(row.id as number);
|
await deleteUserGroup(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -314,12 +314,10 @@ defineExpose({ validate });
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="流程类型" name="type" class="mb-5">
|
<Form.Item label="流程类型" name="type" class="mb-5">
|
||||||
<Radio.Group v-model:value="modelData.type">
|
<Radio.Group v-model:value="modelData.type">
|
||||||
<!-- TODO BPMN 流程类型需要整合,暂时禁用 -->
|
|
||||||
<Radio
|
<Radio
|
||||||
v-for="dict in getDictOptions(DICT_TYPE.BPM_MODEL_TYPE, 'number')"
|
v-for="dict in getDictOptions(DICT_TYPE.BPM_MODEL_TYPE, 'number')"
|
||||||
:key="dict.value"
|
:key="dict.value as number"
|
||||||
:value="dict.value"
|
:value="dict.value"
|
||||||
:disabled="dict.value === 10"
|
|
||||||
>
|
>
|
||||||
{{ dict.label }}
|
{{ dict.label }}
|
||||||
</Radio>
|
</Radio>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { BpmModelApi } from '#/api/bpm/model';
|
|||||||
|
|
||||||
import { inject, onBeforeUnmount, provide, ref, shallowRef, watch } from 'vue';
|
import { inject, onBeforeUnmount, provide, ref, shallowRef, watch } from 'vue';
|
||||||
|
|
||||||
|
import { ContentWrap } from '@vben/common-ui';
|
||||||
import { BpmModelFormType } from '@vben/constants';
|
import { BpmModelFormType } from '@vben/constants';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
@@ -18,7 +19,6 @@ import {
|
|||||||
import CustomContentPadProvider from '#/components/bpmn-process-designer/package/designer/plugins/content-pad';
|
import CustomContentPadProvider from '#/components/bpmn-process-designer/package/designer/plugins/content-pad';
|
||||||
// 自定义左侧菜单(修改 默认任务 为 用户任务)
|
// 自定义左侧菜单(修改 默认任务 为 用户任务)
|
||||||
import CustomPaletteProvider from '#/components/bpmn-process-designer/package/designer/plugins/palette';
|
import CustomPaletteProvider from '#/components/bpmn-process-designer/package/designer/plugins/palette';
|
||||||
import { ContentWrap } from '#/components/content-wrap';
|
|
||||||
|
|
||||||
defineOptions({ name: 'BpmModelEditor' });
|
defineOptions({ name: 'BpmModelEditor' });
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import ContentWrap from '#/components/content-wrap/content-wrap.vue';
|
import { ContentWrap } from '@vben/common-ui';
|
||||||
|
|
||||||
import { SimpleProcessDesigner } from '#/components/simple-process-design';
|
import { SimpleProcessDesigner } from '#/components/simple-process-design';
|
||||||
|
|
||||||
defineOptions({ name: 'SimpleModelDesign' });
|
defineOptions({ name: 'SimpleModelDesign' });
|
||||||
@@ -30,7 +31,7 @@ async function validateConfig() {
|
|||||||
defineExpose({ validateConfig });
|
defineExpose({ validateConfig });
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<ContentWrap :body-style="{ padding: '20px 16px' }">
|
<ContentWrap class="px-4 py-5">
|
||||||
<SimpleProcessDesigner
|
<SimpleProcessDesigner
|
||||||
:model-form-id="modelFormId"
|
:model-form-id="modelFormId"
|
||||||
:model-name="modelName"
|
:model-name="modelName"
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import type { BpmOALeaveApi } from '#/api/bpm/oa/leave';
|
|||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
import { ContentWrap } from '@vben/common-ui';
|
||||||
|
|
||||||
import { getLeave } from '#/api/bpm/oa/leave';
|
import { getLeave } from '#/api/bpm/oa/leave';
|
||||||
import { ContentWrap } from '#/components/content-wrap';
|
|
||||||
import { Description } from '#/components/description';
|
import { Description } from '#/components/description';
|
||||||
|
|
||||||
import { useDetailFormSchema } from './data';
|
import { useDetailFormSchema } from './data';
|
||||||
|
|||||||
@@ -40,13 +40,12 @@ function handleEdit(row: BpmProcessExpressionApi.ProcessExpression) {
|
|||||||
async function handleDelete(row: BpmProcessExpressionApi.ProcessExpression) {
|
async function handleDelete(row: BpmProcessExpressionApi.ProcessExpression) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteProcessExpression(row.id as number);
|
await deleteProcessExpression(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
|
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
|
||||||
import type { SystemUserApi } from '#/api/system/user';
|
import type { SystemUserApi } from '#/api/system/user';
|
||||||
|
|
||||||
// TODO @jason:业务表单审批时,读取不到界面,参见 https://t.zsxq.com/eif2e
|
|
||||||
import { nextTick, onMounted, ref, shallowRef, watch } from 'vue';
|
import { nextTick, onMounted, ref, shallowRef, watch } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
@@ -156,7 +155,6 @@ async function getApprovalDetail() {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
|
// 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
|
||||||
|
|
||||||
BusinessFormComponent.value = registerComponent(
|
BusinessFormComponent.value = registerComponent(
|
||||||
data?.processDefinition?.formCustomViewPath || '',
|
data?.processDefinition?.formCustomViewPath || '',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -40,13 +40,12 @@ function handleEdit(row: BpmProcessListenerApi.ProcessListener) {
|
|||||||
async function handleDelete(row: BpmProcessListenerApi.ProcessListener) {
|
async function handleDelete(row: BpmProcessListenerApi.ProcessListener) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
key: 'action_key_msg',
|
duration: 0,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteProcessListener(row.id as number);
|
await deleteProcessListener(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
key: 'action_key_msg',
|
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -42,14 +42,11 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'status',
|
fieldName: 'status',
|
||||||
label: '流程状态',
|
label: '审批状态',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
options: getDictOptions(
|
options: getDictOptions(DICT_TYPE.BPM_TASK_STATUS, 'number'),
|
||||||
DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
|
placeholder: '请选择审批状态',
|
||||||
'number',
|
|
||||||
),
|
|
||||||
placeholder: '请选择流程状态',
|
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export const CONTRACT_EXPIRY_TYPE = [
|
|||||||
{ label: '已过期', value: 2 },
|
{ label: '已过期', value: 2 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** 左侧菜单 */
|
||||||
export const useLeftSides = (
|
export const useLeftSides = (
|
||||||
customerTodayContactCount: Ref<number>,
|
customerTodayContactCount: Ref<number>,
|
||||||
clueFollowCount: Ref<number>,
|
clueFollowCount: Ref<number>,
|
||||||
|
|||||||
@@ -78,12 +78,12 @@ async function getCount() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 激活时 */
|
/** 激活时 */
|
||||||
onActivated(async () => {
|
onActivated(() => {
|
||||||
getCount();
|
getCount();
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(async () => {
|
onMounted(() => {
|
||||||
getCount();
|
getCount();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -104,9 +104,9 @@ onMounted(async () => {
|
|||||||
</List.Item.Meta>
|
</List.Item.Meta>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<Badge
|
<Badge
|
||||||
|
v-if="item.count.value > 0"
|
||||||
:color="item.menu === leftMenu ? 'blue' : 'red'"
|
:color="item.menu === leftMenu ? 'blue' : 'red'"
|
||||||
:count="item.count.value"
|
:count="item.count.value"
|
||||||
:show-zero="true"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
},
|
},
|
||||||
toolbarConfig: {
|
toolbarConfig: {
|
||||||
refresh: true,
|
refresh: true,
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
allowClear: true,
|
allowClear: true,
|
||||||
options: AUDIT_STATUS,
|
options: AUDIT_STATUS,
|
||||||
},
|
},
|
||||||
defaultValue: 10,
|
defaultValue: AUDIT_STATUS[0]!.value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -75,6 +75,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
},
|
},
|
||||||
toolbarConfig: {
|
toolbarConfig: {
|
||||||
refresh: true,
|
refresh: true,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ function handleProcessDetail(row: CrmContractApi.Contract) {
|
|||||||
function handleContractDetail(row: CrmContractApi.Contract) {
|
function handleContractDetail(row: CrmContractApi.Contract) {
|
||||||
push({ name: 'CrmContractDetail', params: { id: row.id } });
|
push({ name: 'CrmContractDetail', params: { id: row.id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 打开客户详情 */
|
/** 打开客户详情 */
|
||||||
function handleCustomerDetail(row: CrmContractApi.Contract) {
|
function handleCustomerDetail(row: CrmContractApi.Contract) {
|
||||||
push({ name: 'CrmCustomerDetail', params: { id: row.id } });
|
push({ name: 'CrmCustomerDetail', params: { id: row.id } });
|
||||||
@@ -53,7 +54,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
allowClear: true,
|
allowClear: true,
|
||||||
options: CONTRACT_EXPIRY_TYPE,
|
options: CONTRACT_EXPIRY_TYPE,
|
||||||
},
|
},
|
||||||
defaultValue: 1,
|
defaultValue: CONTRACT_EXPIRY_TYPE[0]!.value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -75,6 +76,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
},
|
},
|
||||||
toolbarConfig: {
|
toolbarConfig: {
|
||||||
refresh: true,
|
refresh: true,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
},
|
},
|
||||||
toolbarConfig: {
|
toolbarConfig: {
|
||||||
refresh: true,
|
refresh: true,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
allowClear: true,
|
allowClear: true,
|
||||||
options: SCENE_TYPES,
|
options: SCENE_TYPES,
|
||||||
},
|
},
|
||||||
defaultValue: 1,
|
defaultValue: SCENE_TYPES[0]!.value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -53,6 +53,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
},
|
},
|
||||||
toolbarConfig: {
|
toolbarConfig: {
|
||||||
refresh: true,
|
refresh: true,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
allowClear: true,
|
allowClear: true,
|
||||||
options: CONTACT_STATUS,
|
options: CONTACT_STATUS,
|
||||||
},
|
},
|
||||||
defaultValue: 1,
|
defaultValue: CONTACT_STATUS[0]!.value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'sceneType',
|
fieldName: 'sceneType',
|
||||||
@@ -41,7 +41,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
allowClear: true,
|
allowClear: true,
|
||||||
options: SCENE_TYPES,
|
options: SCENE_TYPES,
|
||||||
},
|
},
|
||||||
defaultValue: 1,
|
defaultValue: SCENE_TYPES[0]!.value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -63,6 +63,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
},
|
},
|
||||||
toolbarConfig: {
|
toolbarConfig: {
|
||||||
refresh: true,
|
refresh: true,
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
allowClear: true,
|
allowClear: true,
|
||||||
options: AUDIT_STATUS,
|
options: AUDIT_STATUS,
|
||||||
},
|
},
|
||||||
defaultValue: 10,
|
defaultValue: AUDIT_STATUS[0]!.value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -70,6 +70,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
},
|
},
|
||||||
toolbarConfig: {
|
toolbarConfig: {
|
||||||
refresh: true,
|
refresh: true,
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
allowClear: true,
|
allowClear: true,
|
||||||
options: RECEIVABLE_REMIND_TYPE,
|
options: RECEIVABLE_REMIND_TYPE,
|
||||||
},
|
},
|
||||||
defaultValue: 1,
|
defaultValue: RECEIVABLE_REMIND_TYPE[0]!.value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -70,6 +70,7 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
},
|
},
|
||||||
toolbarConfig: {
|
toolbarConfig: {
|
||||||
refresh: true,
|
refresh: true,
|
||||||
|
|||||||
52
apps/web-antd/src/views/crm/business/components/data.ts
Normal file
52
apps/web-antd/src/views/crm/business/components/data.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
/** 商机关联列表列定义 */
|
||||||
|
export function useBusinessDetailListColumns(): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
width: 50,
|
||||||
|
fixed: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: '商机名称',
|
||||||
|
fixed: 'left',
|
||||||
|
slots: { default: 'name' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'customerName',
|
||||||
|
title: '客户名称',
|
||||||
|
fixed: 'left',
|
||||||
|
slots: { default: 'customerName' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'totalPrice',
|
||||||
|
title: '商机金额(元)',
|
||||||
|
formatter: 'formatAmount2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'dealTime',
|
||||||
|
title: '预计成交日期',
|
||||||
|
formatter: 'formatDate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'ownerUserName',
|
||||||
|
title: '负责人',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'ownerUserDeptName',
|
||||||
|
title: '所属部门',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'statusTypeName',
|
||||||
|
title: '商机状态组',
|
||||||
|
fixed: 'right',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'statusName',
|
||||||
|
title: '商机阶段',
|
||||||
|
fixed: 'right',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- 商机选择对话框:用于联系人详情中关联已有商机 -->
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { CrmBusinessApi } from '#/api/crm/business';
|
import type { CrmBusinessApi } from '#/api/crm/business';
|
||||||
@@ -13,8 +14,8 @@ import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
|||||||
import { getBusinessPageByCustomer } from '#/api/crm/business';
|
import { getBusinessPageByCustomer } from '#/api/crm/business';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useDetailListColumns } from './detail-data';
|
import Form from '../modules/form.vue';
|
||||||
import Form from './form.vue';
|
import { useBusinessDetailListColumns } from './data';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
customerId?: number; // 关联联系人与商机时,需要传入 customerId 进行筛选
|
customerId?: number; // 关联联系人与商机时,需要传入 customerId 进行筛选
|
||||||
@@ -35,7 +36,7 @@ function setCheckedRows({ records }: { records: CrmBusinessApi.Business[] }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 刷新表格 */
|
/** 刷新表格 */
|
||||||
function onRefresh() {
|
function handleRefresh() {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ function handleCustomerDetail(row: CrmBusinessApi.Business) {
|
|||||||
push({ name: 'CrmCustomerDetail', params: { id: row.customerId } });
|
push({ name: 'CrmCustomerDetail', params: { id: row.customerId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 商机关联弹窗 */
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
async onConfirm() {
|
async onConfirm() {
|
||||||
if (checkedRows.value.length === 0) {
|
if (checkedRows.value.length === 0) {
|
||||||
@@ -71,25 +73,9 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
modalApi.unlock();
|
modalApi.unlock();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onOpenChange(isOpen: boolean) {
|
|
||||||
if (!isOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 加载数据
|
|
||||||
const data = modalApi.getData<any>();
|
|
||||||
if (!data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
modalApi.lock();
|
|
||||||
try {
|
|
||||||
// 设置到 values
|
|
||||||
// await formApi.setValues(formData.value);
|
|
||||||
} finally {
|
|
||||||
modalApi.unlock();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 商机选择表格 */
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: [
|
schema: [
|
||||||
@@ -101,7 +87,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useDetailListColumns(),
|
columns: useBusinessDetailListColumns(),
|
||||||
height: 600,
|
height: 600,
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -133,7 +119,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal title="关联商机" class="w-2/5">
|
<Modal title="关联商机" class="w-2/5">
|
||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="handleRefresh" />
|
||||||
<Grid>
|
<Grid>
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<TableAction
|
<TableAction
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- 商机列表:用于【客户】【联系人】详情中,展示其关联的商机列表 -->
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { CrmBusinessApi } from '#/api/crm/business';
|
import type { CrmBusinessApi } from '#/api/crm/business';
|
||||||
@@ -22,9 +23,9 @@ import {
|
|||||||
import { BizTypeEnum } from '#/api/crm/permission';
|
import { BizTypeEnum } from '#/api/crm/permission';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useDetailListColumns } from './detail-data';
|
import Form from '../modules/form.vue';
|
||||||
|
import { useBusinessDetailListColumns } from './data';
|
||||||
import ListModal from './detail-list-modal.vue';
|
import ListModal from './detail-list-modal.vue';
|
||||||
import Form from './form.vue';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
bizId: number; // 业务编号
|
bizId: number; // 业务编号
|
||||||
@@ -51,7 +52,7 @@ function setCheckedRows({ records }: { records: CrmBusinessApi.Business[] }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 刷新表格 */
|
/** 刷新表格 */
|
||||||
function onRefresh() {
|
function handleRefresh() {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,10 +63,12 @@ function handleCreate() {
|
|||||||
.open();
|
.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 关联商机 */
|
||||||
function handleCreateBusiness() {
|
function handleCreateBusiness() {
|
||||||
detailListModalApi.setData({ customerId: props.customerId }).open();
|
detailListModalApi.setData({ customerId: props.customerId }).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 解除商机关联 */
|
||||||
async function handleDeleteContactBusinessList() {
|
async function handleDeleteContactBusinessList() {
|
||||||
if (checkedRows.value.length === 0) {
|
if (checkedRows.value.length === 0) {
|
||||||
message.error('请先选择商机后操作!');
|
message.error('请先选择商机后操作!');
|
||||||
@@ -83,7 +86,7 @@ async function handleDeleteContactBusinessList() {
|
|||||||
if (res) {
|
if (res) {
|
||||||
// 提示并返回成功
|
// 提示并返回成功
|
||||||
message.success($t('ui.actionMessage.operationSuccess'));
|
message.success($t('ui.actionMessage.operationSuccess'));
|
||||||
onRefresh();
|
handleRefresh();
|
||||||
resolve(true);
|
resolve(true);
|
||||||
} else {
|
} else {
|
||||||
reject(new Error($t('ui.actionMessage.operationFailed')));
|
reject(new Error($t('ui.actionMessage.operationFailed')));
|
||||||
@@ -105,18 +108,20 @@ function handleCustomerDetail(row: CrmBusinessApi.Business) {
|
|||||||
push({ name: 'CrmCustomerDetail', params: { id: row.customerId } });
|
push({ name: 'CrmCustomerDetail', params: { id: row.customerId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 创建联系人关联的商机 */
|
||||||
async function handleCreateContactBusinessList(businessIds: number[]) {
|
async function handleCreateContactBusinessList(businessIds: number[]) {
|
||||||
const data = {
|
const data = {
|
||||||
contactId: props.bizId,
|
contactId: props.bizId,
|
||||||
businessIds,
|
businessIds,
|
||||||
} as CrmContactApi.ContactBusinessReq;
|
} as CrmContactApi.ContactBusinessReq;
|
||||||
await createContactBusinessList(data);
|
await createContactBusinessList(data);
|
||||||
onRefresh();
|
handleRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 商机关联表格 */
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useDetailListColumns(),
|
columns: useBusinessDetailListColumns(),
|
||||||
height: 600,
|
height: 600,
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -144,6 +149,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
},
|
},
|
||||||
toolbarConfig: {
|
toolbarConfig: {
|
||||||
refresh: true,
|
refresh: true,
|
||||||
@@ -159,7 +165,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="handleRefresh" />
|
||||||
<DetailListModal
|
<DetailListModal
|
||||||
:customer-id="customerId"
|
:customer-id="customerId"
|
||||||
@success="handleCreateContactBusinessList"
|
@success="handleCreateContactBusinessList"
|
||||||
1
apps/web-antd/src/views/crm/business/components/index.ts
Normal file
1
apps/web-antd/src/views/crm/business/components/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default as BusinessDetailsList } from './detail-list.vue';
|
||||||
@@ -26,17 +26,27 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
label: '商机名称',
|
label: '商机名称',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入商机名称',
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'ownerUserId',
|
fieldName: 'ownerUserId',
|
||||||
label: '负责人',
|
label: '负责人',
|
||||||
component: 'ApiSelect',
|
component: 'ApiSelect',
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['id'],
|
||||||
|
disabled: (values) => values.id,
|
||||||
|
},
|
||||||
componentProps: {
|
componentProps: {
|
||||||
api: () => getSimpleUserList(),
|
api: () => getSimpleUserList(),
|
||||||
fieldNames: {
|
fieldNames: {
|
||||||
label: 'nickname',
|
label: 'nickname',
|
||||||
value: 'id',
|
value: 'id',
|
||||||
},
|
},
|
||||||
|
placeholder: '请选择负责人',
|
||||||
|
allowClear: true,
|
||||||
},
|
},
|
||||||
defaultValue: userStore.userInfo?.id,
|
defaultValue: userStore.userInfo?.id,
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
@@ -51,6 +61,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
label: 'name',
|
label: 'name',
|
||||||
value: 'id',
|
value: 'id',
|
||||||
},
|
},
|
||||||
|
placeholder: '请选择客户',
|
||||||
|
allowClear: true,
|
||||||
},
|
},
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
@@ -77,6 +89,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
label: 'name',
|
label: 'name',
|
||||||
value: 'id',
|
value: 'id',
|
||||||
},
|
},
|
||||||
|
placeholder: '请选择商机状态组',
|
||||||
|
allowClear: true,
|
||||||
},
|
},
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
@@ -88,11 +102,11 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
fieldName: 'dealTime',
|
fieldName: 'dealTime',
|
||||||
label: '预计成交日期',
|
label: '预计成交日期',
|
||||||
component: 'DatePicker',
|
component: 'DatePicker',
|
||||||
rules: 'required',
|
|
||||||
componentProps: {
|
componentProps: {
|
||||||
showTime: false,
|
showTime: false,
|
||||||
format: 'YYYY-MM-DD HH:mm:ss',
|
format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
valueFormat: 'x',
|
valueFormat: 'x',
|
||||||
|
placeholder: '请选择预计成交日期',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -100,6 +114,10 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
label: '产品清单',
|
label: '产品清单',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
formItemClass: 'col-span-3',
|
formItemClass: 'col-span-3',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入产品清单',
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'totalProductPrice',
|
fieldName: 'totalProductPrice',
|
||||||
@@ -108,6 +126,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
componentProps: {
|
componentProps: {
|
||||||
min: 0,
|
min: 0,
|
||||||
precision: 2,
|
precision: 2,
|
||||||
|
disabled: true,
|
||||||
|
placeholder: '请输入产品总金额',
|
||||||
},
|
},
|
||||||
rules: z.number().min(0).optional().default(0),
|
rules: z.number().min(0).optional().default(0),
|
||||||
},
|
},
|
||||||
@@ -118,6 +138,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
componentProps: {
|
componentProps: {
|
||||||
min: 0,
|
min: 0,
|
||||||
precision: 2,
|
precision: 2,
|
||||||
|
placeholder: '请输入整单折扣',
|
||||||
},
|
},
|
||||||
rules: z.number().min(0).max(100).optional().default(0),
|
rules: z.number().min(0).max(100).optional().default(0),
|
||||||
},
|
},
|
||||||
@@ -129,6 +150,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
min: 0,
|
min: 0,
|
||||||
precision: 2,
|
precision: 2,
|
||||||
disabled: true,
|
disabled: true,
|
||||||
|
placeholder: '请输入折扣后金额',
|
||||||
},
|
},
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['totalProductPrice', 'discountPercent'],
|
triggerFields: ['totalProductPrice', 'discountPercent'],
|
||||||
@@ -170,83 +192,83 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
|||||||
field: 'name',
|
field: 'name',
|
||||||
title: '商机名称',
|
title: '商机名称',
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
minWidth: 240,
|
width: 160,
|
||||||
slots: { default: 'name' },
|
slots: { default: 'name' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'customerName',
|
field: 'customerName',
|
||||||
title: '客户名称',
|
title: '客户名称',
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
minWidth: 240,
|
width: 120,
|
||||||
slots: { default: 'customerName' },
|
slots: { default: 'customerName' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'totalPrice',
|
field: 'totalPrice',
|
||||||
title: '商机金额(元)',
|
title: '商机金额(元)',
|
||||||
minWidth: 140,
|
width: 140,
|
||||||
formatter: 'formatAmount2',
|
formatter: 'formatAmount2',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'dealTime',
|
field: 'dealTime',
|
||||||
title: '预计成交日期',
|
title: '预计成交日期',
|
||||||
formatter: 'formatDate',
|
formatter: 'formatDate',
|
||||||
minWidth: 180,
|
width: 180,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'remark',
|
field: 'remark',
|
||||||
title: '备注',
|
title: '备注',
|
||||||
minWidth: 200,
|
width: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'contactNextTime',
|
field: 'contactNextTime',
|
||||||
title: '下次联系时间',
|
title: '下次联系时间',
|
||||||
formatter: 'formatDate',
|
formatter: 'formatDateTime',
|
||||||
minWidth: 180,
|
width: 180,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'ownerUserName',
|
field: 'ownerUserName',
|
||||||
title: '负责人',
|
title: '负责人',
|
||||||
minWidth: 120,
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'ownerUserDeptName',
|
field: 'ownerUserDeptName',
|
||||||
title: '所属部门',
|
title: '所属部门',
|
||||||
minWidth: 120,
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'contactLastTime',
|
field: 'contactLastTime',
|
||||||
title: '最后跟进时间',
|
title: '最后跟进时间',
|
||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
minWidth: 180,
|
width: 180,
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'createTime',
|
|
||||||
title: '创建时间',
|
|
||||||
formatter: 'formatDateTime',
|
|
||||||
minWidth: 180,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'creatorName',
|
|
||||||
title: '创建人',
|
|
||||||
minWidth: 120,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'updateTime',
|
field: 'updateTime',
|
||||||
title: '更新时间',
|
title: '更新时间',
|
||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
minWidth: 180,
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'createTime',
|
||||||
|
title: '创建时间',
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'creatorName',
|
||||||
|
title: '创建人',
|
||||||
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'statusTypeName',
|
field: 'statusTypeName',
|
||||||
title: '商机状态组',
|
title: '商机状态组',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
minWidth: 120,
|
width: 140,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'statusName',
|
field: 'statusName',
|
||||||
title: '商机阶段',
|
title: '商机阶段',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
minWidth: 120,
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { Ref } from 'vue';
|
||||||
|
import type { CrmBusinessApi } from '#/api/crm/business';
|
||||||
import type { DescriptionItemSchema } from '#/components/description';
|
import type { DescriptionItemSchema } from '#/components/description';
|
||||||
|
|
||||||
import { erpPriceInputFormatter, formatDateTime } from '@vben/utils';
|
import { erpPriceInputFormatter, formatDateTime } from '@vben/utils';
|
||||||
|
|
||||||
|
import { DEFAULT_STATUSES, getBusinessStatusSimpleList } from '#/api/crm/business/status';
|
||||||
|
|
||||||
/** 详情页的字段 */
|
/** 详情页的字段 */
|
||||||
export function useDetailSchema(): DescriptionItemSchema[] {
|
export function useDetailSchema(): DescriptionItemSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -72,53 +76,62 @@ export function useDetailBaseSchema(): DescriptionItemSchema[] {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 详情列表的字段 */
|
/** 商机状态更新表单 */
|
||||||
export function useDetailListColumns(): VxeTableGridOptions['columns'] {
|
export function useStatusFormSchema(
|
||||||
|
formData: Ref<CrmBusinessApi.Business | undefined>,
|
||||||
|
): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'checkbox',
|
fieldName: 'id',
|
||||||
width: 50,
|
component: 'Input',
|
||||||
fixed: 'left',
|
dependencies: {
|
||||||
|
triggerFields: [''],
|
||||||
|
show: () => false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'name',
|
fieldName: 'statusId',
|
||||||
title: '商机名称',
|
label: '商机状态',
|
||||||
fixed: 'left',
|
component: 'Input',
|
||||||
slots: { default: 'name' },
|
dependencies: {
|
||||||
|
triggerFields: [''],
|
||||||
|
show: () => false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'customerName',
|
fieldName: 'endStatus',
|
||||||
title: '客户名称',
|
label: '商机状态',
|
||||||
fixed: 'left',
|
component: 'Input',
|
||||||
slots: { default: 'customerName' },
|
dependencies: {
|
||||||
|
triggerFields: [''],
|
||||||
|
show: () => false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'totalPrice',
|
fieldName: 'status',
|
||||||
title: '商机金额(元)',
|
label: '商机阶段',
|
||||||
formatter: 'formatAmount2',
|
component: 'Select',
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: [''],
|
||||||
|
async componentProps() {
|
||||||
|
const statusList = await getBusinessStatusSimpleList(
|
||||||
|
formData.value?.statusTypeId ?? 0,
|
||||||
|
);
|
||||||
|
const statusOptions = statusList.map((item) => ({
|
||||||
|
label: `${item.name}(赢单率:${item.percent}%)`,
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
const options = DEFAULT_STATUSES.map((item) => ({
|
||||||
|
label: `${item.name}(赢单率:${item.percent}%)`,
|
||||||
|
value: item.endStatus,
|
||||||
|
}));
|
||||||
|
statusOptions.push(...options);
|
||||||
|
return {
|
||||||
|
options: statusOptions,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
{
|
|
||||||
field: 'dealTime',
|
|
||||||
title: '预计成交日期',
|
|
||||||
formatter: 'formatDate',
|
|
||||||
},
|
},
|
||||||
{
|
rules: 'required',
|
||||||
field: 'ownerUserName',
|
|
||||||
title: '负责人',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'ownerUserDeptName',
|
|
||||||
title: '所属部门',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'statusTypeName',
|
|
||||||
title: '商机状态组',
|
|
||||||
fixed: 'right',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'statusName',
|
|
||||||
title: '商机阶段',
|
|
||||||
fixed: 'right',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -14,30 +14,27 @@ import { getBusiness } from '#/api/crm/business';
|
|||||||
import { getOperateLogPage } from '#/api/crm/operateLog';
|
import { getOperateLogPage } from '#/api/crm/operateLog';
|
||||||
import { BizTypeEnum } from '#/api/crm/permission';
|
import { BizTypeEnum } from '#/api/crm/permission';
|
||||||
import { useDescription } from '#/components/description';
|
import { useDescription } from '#/components/description';
|
||||||
import { AsyncOperateLog } from '#/components/operate-log';
|
import { OperateLog } from '#/components/operate-log';
|
||||||
import {
|
import { $t } from '#/locales';
|
||||||
BusinessDetailsInfo,
|
import { ContactDetailsList } from '#/views/crm/contact/components';
|
||||||
BusinessForm,
|
import { ContractDetailsList } from '#/views/crm/contract/components';
|
||||||
UpStatusForm,
|
|
||||||
} from '#/views/crm/business';
|
|
||||||
import { ContactDetailsList } from '#/views/crm/contact';
|
|
||||||
import { ContractDetailsList } from '#/views/crm/contract';
|
|
||||||
import { FollowUp } from '#/views/crm/followup';
|
import { FollowUp } from '#/views/crm/followup';
|
||||||
import { PermissionList, TransferForm } from '#/views/crm/permission';
|
import { PermissionList, TransferForm } from '#/views/crm/permission';
|
||||||
import { ProductDetailsList } from '#/views/crm/product';
|
import { ProductDetailsList } from '#/views/crm/product/components';
|
||||||
|
|
||||||
import { useDetailSchema } from './detail-data';
|
import Form from '../modules/form.vue';
|
||||||
|
import UpStatusForm from './modules/status-form.vue';
|
||||||
const loading = ref(false);
|
import { useDetailSchema } from './data';
|
||||||
|
import BusinessDetailsInfo from './modules/info.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const tabs = useTabs();
|
const tabs = useTabs();
|
||||||
|
|
||||||
const businessId = ref(0);
|
const loading = ref(false); // 加载中
|
||||||
|
const businessId = ref(0); // 商机编号
|
||||||
const business = ref<CrmBusinessApi.Business>({} as CrmBusinessApi.Business);
|
const business = ref<CrmBusinessApi.Business>({} as CrmBusinessApi.Business); // 商机详情
|
||||||
const businessLogList = ref<SystemOperateLogApi.OperateLog[]>([]);
|
const logList = ref<SystemOperateLogApi.OperateLog[]>([]);
|
||||||
const permissionListRef = ref<InstanceType<typeof PermissionList>>(); // 团队成员列表 Ref
|
const permissionListRef = ref<InstanceType<typeof PermissionList>>(); // 团队成员列表 Ref
|
||||||
|
|
||||||
const [Descriptions] = useDescription({
|
const [Descriptions] = useDescription({
|
||||||
@@ -50,7 +47,7 @@ const [Descriptions] = useDescription({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
connectedComponent: BusinessForm,
|
connectedComponent: Form,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -65,17 +62,20 @@ const [UpStatusModal, upStatusModalApi] = useVbenModal({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** 加载详情 */
|
/** 加载详情 */
|
||||||
async function loadBusinessDetail() {
|
async function getBusinessDetail() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const data = await getBusiness(businessId.value);
|
try {
|
||||||
const logList = await getOperateLogPage({
|
business.value = await getBusiness(businessId.value);
|
||||||
|
// 操作日志
|
||||||
|
const res = await getOperateLogPage({
|
||||||
bizType: BizTypeEnum.CRM_BUSINESS,
|
bizType: BizTypeEnum.CRM_BUSINESS,
|
||||||
bizId: businessId.value,
|
bizId: businessId.value,
|
||||||
});
|
});
|
||||||
businessLogList.value = logList.list;
|
logList.value = res.list;
|
||||||
business.value = data;
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 返回列表页 */
|
/** 返回列表页 */
|
||||||
function handleBack() {
|
function handleBack() {
|
||||||
@@ -83,12 +83,12 @@ function handleBack() {
|
|||||||
router.push('/crm/business');
|
router.push('/crm/business');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑 */
|
/** 编辑商机 */
|
||||||
function handleEdit() {
|
function handleEdit() {
|
||||||
formModalApi.setData({ id: businessId.value }).open();
|
formModalApi.setData({ id: businessId.value }).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 转移线索 */
|
/** 转移商机 */
|
||||||
function handleTransfer() {
|
function handleTransfer() {
|
||||||
transferModalApi.setData({ bizType: BizTypeEnum.CRM_BUSINESS }).open();
|
transferModalApi.setData({ bizType: BizTypeEnum.CRM_BUSINESS }).open();
|
||||||
}
|
}
|
||||||
@@ -98,18 +98,18 @@ async function handleUpdateStatus() {
|
|||||||
upStatusModalApi.setData(business.value).open();
|
upStatusModalApi.setData(business.value).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载数据
|
/** 加载数据 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
businessId.value = Number(route.params.id);
|
businessId.value = Number(route.params.id);
|
||||||
loadBusinessDetail();
|
getBusinessDetail();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height :title="business?.name" :loading="loading">
|
<Page auto-content-height :title="business?.name" :loading="loading">
|
||||||
<FormModal @success="loadBusinessDetail" />
|
<FormModal @success="getBusinessDetail" />
|
||||||
<TransferModal @success="loadBusinessDetail" />
|
<TransferModal @success="getBusinessDetail" />
|
||||||
<UpStatusModal @success="loadBusinessDetail" />
|
<UpStatusModal @success="getBusinessDetail" />
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
@@ -138,12 +138,12 @@ onMounted(() => {
|
|||||||
</Card>
|
</Card>
|
||||||
<Card class="mt-4 min-h-[60%]">
|
<Card class="mt-4 min-h-[60%]">
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tabs.TabPane tab="详细资料" key="1" :force-render="true">
|
<Tabs.TabPane tab="跟进记录" key="1" :force-render="true">
|
||||||
<BusinessDetailsInfo :business="business" />
|
|
||||||
</Tabs.TabPane>
|
|
||||||
<Tabs.TabPane tab="跟进记录" key="2" :force-render="true">
|
|
||||||
<FollowUp :biz-id="businessId" :biz-type="BizTypeEnum.CRM_BUSINESS" />
|
<FollowUp :biz-id="businessId" :biz-type="BizTypeEnum.CRM_BUSINESS" />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="详细资料" key="2" :force-render="true">
|
||||||
|
<BusinessDetailsInfo :business="business" />
|
||||||
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="联系人" key="3" :force-render="true">
|
<Tabs.TabPane tab="联系人" key="3" :force-render="true">
|
||||||
<ContactDetailsList
|
<ContactDetailsList
|
||||||
:biz-id="businessId"
|
:biz-id="businessId"
|
||||||
@@ -165,7 +165,10 @@ onMounted(() => {
|
|||||||
:biz-type="BizTypeEnum.CRM_BUSINESS"
|
:biz-type="BizTypeEnum.CRM_BUSINESS"
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="团队成员" key="6" :force-render="true">
|
<Tabs.TabPane tab="操作日志" key="6" :force-render="true">
|
||||||
|
<OperateLog :log-list="logList" />
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="团队成员" key="7" :force-render="true">
|
||||||
<PermissionList
|
<PermissionList
|
||||||
ref="permissionListRef"
|
ref="permissionListRef"
|
||||||
:biz-id="businessId"
|
:biz-id="businessId"
|
||||||
@@ -174,9 +177,6 @@ onMounted(() => {
|
|||||||
@quit-team="handleBack"
|
@quit-team="handleBack"
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="操作日志" key="7" :force-render="true">
|
|
||||||
<AsyncOperateLog :log-list="businessLogList" />
|
|
||||||
</Tabs.TabPane>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Card>
|
</Card>
|
||||||
</Page>
|
</Page>
|
||||||
@@ -6,7 +6,7 @@ import { Divider } from 'ant-design-vue';
|
|||||||
import { useDescription } from '#/components/description';
|
import { useDescription } from '#/components/description';
|
||||||
import { useFollowUpDetailSchema } from '#/views/crm/followup/data';
|
import { useFollowUpDetailSchema } from '#/views/crm/followup/data';
|
||||||
|
|
||||||
import { useDetailBaseSchema } from './detail-data';
|
import { useDetailBaseSchema } from '../data';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
business: CrmBusinessApi.Business; // 商机信息
|
business: CrmBusinessApi.Business; // 商机信息
|
||||||
@@ -9,12 +9,10 @@ import { message } from 'ant-design-vue';
|
|||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import { updateBusinessStatus } from '#/api/crm/business';
|
import { updateBusinessStatus } from '#/api/crm/business';
|
||||||
import {
|
|
||||||
DEFAULT_STATUSES,
|
|
||||||
getBusinessStatusSimpleList,
|
|
||||||
} from '#/api/crm/business/status';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
import { useStatusFormSchema } from '../data';
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
|
|
||||||
const formData = ref<CrmBusinessApi.Business>();
|
const formData = ref<CrmBusinessApi.Business>();
|
||||||
@@ -28,60 +26,7 @@ const [Form, formApi] = useVbenForm({
|
|||||||
labelWidth: 120,
|
labelWidth: 120,
|
||||||
},
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: [
|
schema: useStatusFormSchema(formData),
|
||||||
{
|
|
||||||
fieldName: 'id',
|
|
||||||
component: 'Input',
|
|
||||||
dependencies: {
|
|
||||||
triggerFields: [''],
|
|
||||||
show: () => false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldName: 'statusId',
|
|
||||||
label: '商机状态',
|
|
||||||
component: 'Input',
|
|
||||||
dependencies: {
|
|
||||||
triggerFields: [''],
|
|
||||||
show: () => false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldName: 'endStatus',
|
|
||||||
label: '商机状态',
|
|
||||||
component: 'Input',
|
|
||||||
dependencies: {
|
|
||||||
triggerFields: [''],
|
|
||||||
show: () => false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldName: 'status',
|
|
||||||
label: '商机阶段',
|
|
||||||
component: 'Select',
|
|
||||||
dependencies: {
|
|
||||||
triggerFields: [''],
|
|
||||||
async componentProps() {
|
|
||||||
const statusList = await getBusinessStatusSimpleList(
|
|
||||||
formData.value?.statusTypeId ?? 0,
|
|
||||||
);
|
|
||||||
const statusOptions = statusList.map((item) => ({
|
|
||||||
label: `${item.name}(赢单率:${item.percent}%)`,
|
|
||||||
value: item.id,
|
|
||||||
}));
|
|
||||||
const options = DEFAULT_STATUSES.map((item) => ({
|
|
||||||
label: `${`${item.name}(赢单率:${item.percent}`}%)`,
|
|
||||||
value: item.endStatus,
|
|
||||||
}));
|
|
||||||
statusOptions.push(...options);
|
|
||||||
return {
|
|
||||||
options: statusOptions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: 'required',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { defineAsyncComponent } from 'vue';
|
|
||||||
|
|
||||||
export const BusinessForm = defineAsyncComponent(
|
|
||||||
() => import('./modules/form.vue'),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const BusinessDetailsInfo = defineAsyncComponent(
|
|
||||||
() => import('./modules/detail-info.vue'),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const BusinessDetailsList = defineAsyncComponent(
|
|
||||||
() => import('./modules/detail-list.vue'),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const BusinessDetails = defineAsyncComponent(
|
|
||||||
() => import('./modules/detail.vue'),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const BusinessDetailsListModal = defineAsyncComponent(
|
|
||||||
() => import('./modules/detail-list-modal.vue'),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const UpStatusForm = defineAsyncComponent(
|
|
||||||
() => import('./modules/up-status-form.vue'),
|
|
||||||
);
|
|
||||||
@@ -2,12 +2,13 @@
|
|||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { CrmBusinessApi } from '#/api/crm/business';
|
import type { CrmBusinessApi } from '#/api/crm/business';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { Button, message, Tabs } from 'ant-design-vue';
|
||||||
|
|
||||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
@@ -21,6 +22,7 @@ import { useGridColumns, useGridFormSchema } from './data';
|
|||||||
import Form from './modules/form.vue';
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
|
const sceneType = ref('1');
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
connectedComponent: Form,
|
connectedComponent: Form,
|
||||||
@@ -28,13 +30,23 @@ const [FormModal, formModalApi] = useVbenModal({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** 刷新表格 */
|
/** 刷新表格 */
|
||||||
function onRefresh() {
|
function handleRefresh() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理场景类型的切换 */
|
||||||
|
function handleChangeSceneType(key: number | string) {
|
||||||
|
sceneType.value = key.toString();
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function handleExport() {
|
async function handleExport() {
|
||||||
const data = await exportBusiness(await gridApi.formApi.getValues());
|
const formValues = await gridApi.formApi.getValues();
|
||||||
|
const data = await exportBusiness({
|
||||||
|
sceneType: sceneType.value,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
downloadFileFromBlobPart({ fileName: '商机.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '商机.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,13 +65,12 @@ async function handleDelete(row: CrmBusinessApi.Business) {
|
|||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
duration: 0,
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteBusiness(row.id as number);
|
await deleteBusiness(row.id!);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||||
onRefresh();
|
handleRefresh();
|
||||||
} catch {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,6 +99,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
return await getBusinessPage({
|
return await getBusinessPage({
|
||||||
pageNo: page.currentPage,
|
pageNo: page.currentPage,
|
||||||
pageSize: page.pageSize,
|
pageSize: page.pageSize,
|
||||||
|
sceneType: sceneType.value,
|
||||||
...formValues,
|
...formValues,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -95,6 +107,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
},
|
},
|
||||||
toolbarConfig: {
|
toolbarConfig: {
|
||||||
refresh: true,
|
refresh: true,
|
||||||
@@ -117,8 +130,15 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="handleRefresh" />
|
||||||
<Grid table-title="商机列表">
|
<Grid>
|
||||||
|
<template #top>
|
||||||
|
<Tabs class="-mt-11" @change="handleChangeSceneType">
|
||||||
|
<Tabs.TabPane tab="我负责的" key="1" />
|
||||||
|
<Tabs.TabPane tab="我参与的" key="2" />
|
||||||
|
<Tabs.TabPane tab="下属负责的" key="3" />
|
||||||
|
</Tabs>
|
||||||
|
</template>
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<TableAction
|
<TableAction
|
||||||
:actions="[
|
:actions="[
|
||||||
@@ -176,3 +196,8 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
:deep(.vxe-toolbar div) {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from '#/api/crm/business';
|
} from '#/api/crm/business';
|
||||||
import { BizTypeEnum } from '#/api/crm/permission';
|
import { BizTypeEnum } from '#/api/crm/permission';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import { ProductEditTable } from '#/views/crm/product';
|
import { ProductEditTable } from '#/views/crm/product/components';
|
||||||
|
|
||||||
import { useFormSchema } from '../data';
|
import { useFormSchema } from '../data';
|
||||||
|
|
||||||
@@ -56,7 +56,6 @@ const [Form, formApi] = useVbenForm({
|
|||||||
},
|
},
|
||||||
labelWidth: 120,
|
labelWidth: 120,
|
||||||
},
|
},
|
||||||
// 一共3列
|
|
||||||
wrapperClass: 'grid-cols-3',
|
wrapperClass: 'grid-cols-3',
|
||||||
layout: 'vertical',
|
layout: 'vertical',
|
||||||
schema: useFormSchema(),
|
schema: useFormSchema(),
|
||||||
@@ -90,7 +89,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
}
|
}
|
||||||
// 加载数据
|
// 加载数据
|
||||||
const data = modalApi.getData<CrmBusinessApi.Business>();
|
const data = modalApi.getData<CrmBusinessApi.Business>();
|
||||||
if (!data) {
|
if (!data || !data.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
modalApi.lock();
|
modalApi.lock();
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
label: '状态组名',
|
label: '状态组名',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入状态组名',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'deptIds',
|
fieldName: 'deptIds',
|
||||||
@@ -77,3 +80,33 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 商机状态阶段列表列配置 */
|
||||||
|
export function useFormColumns(): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
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' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const [FormModal, formModalApi] = useVbenModal({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** 刷新表格 */
|
/** 刷新表格 */
|
||||||
function onRefresh() {
|
function handleRefresh() {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,27 +31,26 @@ function handleCreate() {
|
|||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 编辑商机状态 */
|
||||||
|
function handleEdit(row: CrmBusinessStatusApi.BusinessStatus) {
|
||||||
|
formModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
/** 删除商机状态 */
|
/** 删除商机状态 */
|
||||||
async function handleDelete(row: CrmBusinessStatusApi.BusinessStatus) {
|
async function handleDelete(row: CrmBusinessStatusApi.BusinessStatus) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
duration: 0,
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteBusinessStatus(row.id as number);
|
await deleteBusinessStatus(row.id!);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
||||||
onRefresh();
|
handleRefresh();
|
||||||
} catch {
|
} catch {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑商机状态 */
|
|
||||||
function handleEdit(row: CrmBusinessStatusApi.BusinessStatus) {
|
|
||||||
formModalApi.setData(row).open();
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(),
|
columns: useGridColumns(),
|
||||||
@@ -70,6 +69,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'id',
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
},
|
},
|
||||||
toolbarConfig: {
|
toolbarConfig: {
|
||||||
refresh: true,
|
refresh: true,
|
||||||
@@ -92,7 +92,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="handleRefresh" />
|
||||||
<Grid table-title="商机状态列表">
|
<Grid table-title="商机状态列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<TableAction
|
<TableAction
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
} from '#/api/crm/business/status';
|
} from '#/api/crm/business/status';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useFormSchema } from '../data';
|
import { useFormColumns, useFormSchema } from '../data';
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
const formData = ref<CrmBusinessStatusApi.BusinessStatus>();
|
const formData = ref<CrmBusinessStatusApi.BusinessStatus>();
|
||||||
@@ -72,7 +72,6 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
}
|
}
|
||||||
// 加载数据
|
// 加载数据
|
||||||
const data = modalApi.getData<CrmBusinessStatusApi.BusinessStatus>();
|
const data = modalApi.getData<CrmBusinessStatusApi.BusinessStatus>();
|
||||||
|
|
||||||
modalApi.lock();
|
modalApi.lock();
|
||||||
try {
|
try {
|
||||||
if (!data || !data.id) {
|
if (!data || !data.id) {
|
||||||
@@ -82,20 +81,19 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
deptIds: [],
|
deptIds: [],
|
||||||
statuses: [],
|
statuses: [],
|
||||||
};
|
};
|
||||||
addStatus();
|
await handleAddStatus();
|
||||||
} else {
|
} else {
|
||||||
formData.value = await getBusinessStatus(data.id);
|
formData.value = await getBusinessStatus(data.id);
|
||||||
if (
|
if (
|
||||||
!formData.value?.statuses?.length ||
|
!formData.value?.statuses?.length ||
|
||||||
formData.value?.statuses?.length === 0
|
formData.value?.statuses?.length === 0
|
||||||
) {
|
) {
|
||||||
addStatus();
|
await handleAddStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 设置到 values
|
// 设置到 values
|
||||||
|
|
||||||
await formApi.setValues(formData.value as any);
|
await formApi.setValues(formData.value as any);
|
||||||
gridApi.grid.reloadData(
|
await gridApi.grid.reloadData(
|
||||||
(formData.value!.statuses =
|
(formData.value!.statuses =
|
||||||
formData.value?.statuses?.concat(DEFAULT_STATUSES)) as any,
|
formData.value?.statuses?.concat(DEFAULT_STATUSES)) as any,
|
||||||
);
|
);
|
||||||
@@ -106,20 +104,20 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** 添加状态 */
|
/** 添加状态 */
|
||||||
async function addStatus() {
|
async function handleAddStatus() {
|
||||||
formData.value!.statuses!.unshift({
|
formData.value!.statuses!.unshift({
|
||||||
name: '',
|
name: '',
|
||||||
percent: undefined,
|
percent: undefined,
|
||||||
} as any);
|
} as any);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
gridApi.grid.reloadData(formData.value!.statuses as any);
|
await gridApi.grid.reloadData(formData.value!.statuses as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除状态 */
|
/** 删除状态 */
|
||||||
async function deleteStatusArea(row: any, rowIndex: number) {
|
async function deleteStatusArea(row: any, rowIndex: number) {
|
||||||
gridApi.grid.remove(row);
|
await gridApi.grid.remove(row);
|
||||||
formData.value!.statuses!.splice(rowIndex, 1);
|
formData.value!.statuses!.splice(rowIndex, 1);
|
||||||
gridApi.grid.reloadData(formData.value!.statuses as any);
|
await gridApi.grid.reloadData(formData.value!.statuses as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格配置 */
|
/** 表格配置 */
|
||||||
@@ -129,32 +127,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
trigger: 'click',
|
trigger: 'click',
|
||||||
mode: 'cell',
|
mode: 'cell',
|
||||||
},
|
},
|
||||||
columns: [
|
columns: useFormColumns(),
|
||||||
{
|
|
||||||
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),
|
data: formData.value?.statuses?.concat(DEFAULT_STATUSES),
|
||||||
border: true,
|
border: true,
|
||||||
showOverflow: true,
|
showOverflow: true,
|
||||||
@@ -162,6 +135,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
keepSource: true,
|
keepSource: true,
|
||||||
rowConfig: {
|
rowConfig: {
|
||||||
keyField: 'row_id',
|
keyField: 'row_id',
|
||||||
|
isHover: true,
|
||||||
},
|
},
|
||||||
pagerConfig: {
|
pagerConfig: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -184,7 +158,11 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template #name="{ row }">
|
<template #name="{ row }">
|
||||||
<Input v-if="!row.endStatus" v-model:value="row.name" />
|
<Input
|
||||||
|
v-if="!row.endStatus"
|
||||||
|
v-model:value="row.name"
|
||||||
|
placeholder="请输入状态名"
|
||||||
|
/>
|
||||||
<span v-else>{{ row.name }}</span>
|
<span v-else>{{ row.name }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template #percent="{ row }">
|
<template #percent="{ row }">
|
||||||
@@ -194,6 +172,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
:min="0"
|
:min="0"
|
||||||
:max="100"
|
:max="100"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
|
placeholder="请输入赢单率"
|
||||||
/>
|
/>
|
||||||
<span v-else>{{ row.percent }}</span>
|
<span v-else>{{ row.percent }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -204,7 +183,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
label: $t('ui.actionTitle.create'),
|
label: $t('ui.actionTitle.create'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
ifShow: () => !row.endStatus,
|
ifShow: () => !row.endStatus,
|
||||||
onClick: addStatus,
|
onClick: handleAddStatus,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: $t('common.delete'),
|
label: $t('common.delete'),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user