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