From afbc2e800ce82b741e3bf760378eb4bd5f56f770 Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Wed, 7 May 2025 10:27:29 +0800 Subject: [PATCH 1/4] feat: crm business --- apps/web-antd/src/views/crm/business/data.ts | 211 ++++++++++++++++++ .../web-antd/src/views/crm/business/index.vue | 195 +++++++++++++--- .../src/views/crm/business/modules/form.vue | 86 +++++++ 3 files changed, 463 insertions(+), 29 deletions(-) create mode 100644 apps/web-antd/src/views/crm/business/data.ts create mode 100644 apps/web-antd/src/views/crm/business/modules/form.vue diff --git a/apps/web-antd/src/views/crm/business/data.ts b/apps/web-antd/src/views/crm/business/data.ts new file mode 100644 index 00000000..a010beda --- /dev/null +++ b/apps/web-antd/src/views/crm/business/data.ts @@ -0,0 +1,211 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { CrmBusinessApi } from '#/api/crm/business'; + +import { useAccess } from '@vben/access'; + +import { getRangePickerDefaultProps } from '#/utils'; + +const { hasAccessByCodes } = useAccess(); + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '商机名称', + component: 'Input', + rules: 'required', + }, + { + fieldName: 'customerId', + label: '客户', + component: 'Input', + rules: 'required', + }, + { + fieldName: 'totalPrice', + label: '商机金额', + component: 'InputNumber', + componentProps: { + min: 0, + controlsPosition: 'right', + placeholder: '请输入商机金额', + }, + rules: 'required', + }, + { + fieldName: 'dealTime', + label: '预计成交日期', + component: 'DatePicker', + rules: 'required', + componentProps: { + showTime: false, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + }, + { + fieldName: 'contactNextTime', + label: '下次联系时间', + component: 'DatePicker', + componentProps: { + showTime: false, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '商机名称', + component: 'Input', + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onActionClick: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '商机名称', + minWidth: 160, + fixed: 'left', + slots: { + default: 'name', + }, + }, + { + field: 'customerName', + title: '客户名称', + minWidth: 120, + fixed: 'left', + slots: { + default: 'customerName', + }, + }, + { + field: 'totalPrice', + title: '商机金额(元)', + minWidth: 140, + formatter: 'formatAmount', + }, + { + field: 'dealTime', + title: '预计成交日期', + minWidth: 180, + formatter: 'formatDate', + }, + { + field: 'remark', + title: '备注', + minWidth: 200, + }, + { + field: 'contactNextTime', + title: '下次联系时间', + minWidth: 180, + formatter: 'formatDate', + }, + { + field: 'ownerUserName', + title: '负责人', + minWidth: 100, + }, + { + field: 'ownerUserDeptName', + title: '所属部门', + minWidth: 100, + }, + { + field: 'contactLastTime', + title: '最后跟进时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'updateTime', + title: '更新时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 100, + }, + { + field: 'statusTypeName', + title: '商机状态组', + minWidth: 140, + fixed: 'right', + }, + { + field: 'statusName', + title: '商机阶段', + minWidth: 120, + fixed: 'right', + }, + { + field: 'operation', + title: '操作', + width: 130, + fixed: 'right', + align: 'center', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: '商机', + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + { + code: 'edit', + show: hasAccessByCodes(['crm:business:update']), + }, + { + code: 'delete', + show: hasAccessByCodes(['crm:business:delete']), + }, + ], + }, + }, + ]; +} diff --git a/apps/web-antd/src/views/crm/business/index.vue b/apps/web-antd/src/views/crm/business/index.vue index 7ac461c3..14d2965c 100644 --- a/apps/web-antd/src/views/crm/business/index.vue +++ b/apps/web-antd/src/views/crm/business/index.vue @@ -1,38 +1,175 @@ diff --git a/apps/web-antd/src/views/crm/business/modules/form.vue b/apps/web-antd/src/views/crm/business/modules/form.vue new file mode 100644 index 00000000..dd8dbd61 --- /dev/null +++ b/apps/web-antd/src/views/crm/business/modules/form.vue @@ -0,0 +1,86 @@ + + + From abe1c32b3e76864dd2f50d24cf36b2f19dfbaebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=90=E5=A4=9C?= <278898052@qq.com> Date: Wed, 7 May 2025 10:54:56 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=A1=A8=E8=BE=BE=E5=BC=8F=E7=AE=A1=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/api/bpm/processExpression/index.ts | 53 ++++++ .../src/views/bpm/processExpression/data.ts | 151 ++++++++++++++++++ .../src/views/bpm/processExpression/index.vue | 136 +++++++++++++--- .../bpm/processExpression/modules/form.vue | 83 ++++++++++ 4 files changed, 402 insertions(+), 21 deletions(-) create mode 100644 apps/web-antd/src/api/bpm/processExpression/index.ts create mode 100644 apps/web-antd/src/views/bpm/processExpression/data.ts create mode 100644 apps/web-antd/src/views/bpm/processExpression/modules/form.vue diff --git a/apps/web-antd/src/api/bpm/processExpression/index.ts b/apps/web-antd/src/api/bpm/processExpression/index.ts new file mode 100644 index 00000000..9444dd26 --- /dev/null +++ b/apps/web-antd/src/api/bpm/processExpression/index.ts @@ -0,0 +1,53 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace BpmProcessExpressionApi { + /** BPM 流程表达式 VO */ + export interface ProcessExpressionVO { + id: number; // 编号 + name: string; // 表达式名字 + status: number; // 表达式状态 + expression: string; // 表达式 + } +} + +/** 查询流程表达式分页 */ +export async function getProcessExpressionPage(params: PageParam) { + return requestClient.get< + PageResult + >('/bpm/process-expression/page', { params }); +} + +/** 查询流程表达式详情 */ +export async function getProcessExpression(id: number) { + return requestClient.get( + `/bpm/process-expression/get?id=${id}`, + ); +} + +/** 新增流程表达式 */ +export async function createProcessExpression( + data: BpmProcessExpressionApi.ProcessExpressionVO, +) { + return requestClient.post('/bpm/process-expression/create', data); +} + +/** 修改流程表达式 */ +export async function updateProcessExpression( + data: BpmProcessExpressionApi.ProcessExpressionVO, +) { + return requestClient.put('/bpm/process-expression/update', data); +} + +/** 删除流程表达式 */ +export async function deleteProcessExpression(id: number) { + return requestClient.delete( + `/bpm/process-expression/delete?id=${id}`, + ); +} + +/** 导出流程表达式 */ +export async function exportProcessExpression(params: any) { + return requestClient.download('/bpm/process-expression/export-excel', params); +} diff --git a/apps/web-antd/src/views/bpm/processExpression/data.ts b/apps/web-antd/src/views/bpm/processExpression/data.ts new file mode 100644 index 00000000..fd148b86 --- /dev/null +++ b/apps/web-antd/src/views/bpm/processExpression/data.ts @@ -0,0 +1,151 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression'; + +import { useAccess } from '@vben/access'; + +import { z } from '#/adapter/form'; +import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; + +const { hasAccessByCodes } = useAccess(); +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '名字', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'expression', + label: '表达式', + component: 'Textarea', + componentProps: { + placeholder: '请输入表达式', + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '名字', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + placeholder: ['开始时间', '结束时间'], + valueFormat: 'YYYY-MM-DD HH:mm:ss', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onActionClick: OnActionClickFn, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'name', + title: '名字', + minWidth: 200, + }, + + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'expression', + title: '表达式', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'operation', + title: '操作', + minWidth: 180, + align: 'center', + fixed: 'right', + cellRender: { + attrs: { + nameField: 'name', + nameTitle: '流程表达式', + onClick: onActionClick, + }, + name: 'CellOperation', + options: [ + { + code: 'edit', + show: hasAccessByCodes(['bpm:process-expression:update']), + }, + { + code: 'delete', + show: hasAccessByCodes(['bpm:process-expression:delete']), + }, + ], + }, + }, + ]; +} diff --git a/apps/web-antd/src/views/bpm/processExpression/index.vue b/apps/web-antd/src/views/bpm/processExpression/index.vue index 80bf15bf..0e32548c 100644 --- a/apps/web-antd/src/views/bpm/processExpression/index.vue +++ b/apps/web-antd/src/views/bpm/processExpression/index.vue @@ -1,31 +1,125 @@ diff --git a/apps/web-antd/src/views/bpm/processExpression/modules/form.vue b/apps/web-antd/src/views/bpm/processExpression/modules/form.vue new file mode 100644 index 00000000..9a9a526a --- /dev/null +++ b/apps/web-antd/src/views/bpm/processExpression/modules/form.vue @@ -0,0 +1,83 @@ + + + From e68fff58e8d7aedc32585fc6e42d386e0ee2bdc5 Mon Sep 17 00:00:00 2001 From: zyf0624 <0624afe1@gmail.com> Date: Wed, 7 May 2025 12:04:15 +0800 Subject: [PATCH 3/4] fix: tsconfig moduleResolution (#6122) Co-authored-by: pzzyf <2279948211@qq.com> --- internal/tailwind-config/tsconfig.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/tailwind-config/tsconfig.json b/internal/tailwind-config/tsconfig.json index b2ec3b61..dbd3bcc8 100644 --- a/internal/tailwind-config/tsconfig.json +++ b/internal/tailwind-config/tsconfig.json @@ -1,6 +1,9 @@ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/node.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, "include": ["src"], "exclude": ["node_modules"] } From 84ef207d9c4c35f25e084a05f85de8aceb8eb86b Mon Sep 17 00:00:00 2001 From: wyc001122 <498040880@qq.com> Date: Wed, 7 May 2025 12:04:48 +0800 Subject: [PATCH 4/4] docs(@vben/docs): update settings doc (#6128) Co-authored-by: wyc001122 --- docs/src/en/guide/essentials/settings.md | 62 +++++++++++++++++++++--- docs/src/guide/essentials/settings.md | 35 ++++++++++--- 2 files changed, 84 insertions(+), 13 deletions(-) diff --git a/docs/src/en/guide/essentials/settings.md b/docs/src/en/guide/essentials/settings.md index b0b7bc83..856d2d8c 100644 --- a/docs/src/en/guide/essentials/settings.md +++ b/docs/src/en/guide/essentials/settings.md @@ -60,6 +60,29 @@ VITE_INJECT_APP_LOADING=true VITE_ARCHIVER=true ``` +```bash [.env.production] +# Public Path for Resources, must start and end with / +VITE_BASE=/ + +# API URL +VITE_GLOB_API_URL=https://mock-napi.vben.pro/api + +# Whether to enable compression, can be set to none, brotli, gzip +VITE_COMPRESS=gzip + +# Whether to enable PWA +VITE_PWA=false + +# vue-router mode +VITE_ROUTER_HISTORY=hash + +# Whether to inject global loading +VITE_INJECT_APP_LOADING=true + +# Whether to generate dist.zip after packaging +VITE_ARCHIVER=true +``` + ::: ## Dynamic Configuration in Production Environment @@ -142,6 +165,7 @@ import { defineOverridesPreferences } from '@vben/preferences'; /** * @description Project configuration file * Only a part of the configuration in the project needs to be covered, and unnecessary configurations do not need to be covered. The default configuration will be automatically used + * !!! Please clear the cache after changing the configuration, otherwise it may not take effect */ export const overridesPreferences = defineOverridesPreferences({ // overrides @@ -172,7 +196,7 @@ const defaultPreferences: Preferences = { isMobile: false, layout: 'sidebar-nav', locale: 'zh-CN', - loginExpiredMode: 'modal', + loginExpiredMode: 'page', name: 'Vben Admin', preferencesButtonPosition: 'auto', watermark: false, @@ -191,14 +215,16 @@ const defaultPreferences: Preferences = { enable: true, icp: '', icpLink: '', + settingShow: true, }, footer: { - enable: true, + enable: false, fixed: false, }, header: { enable: true, hidden: false, + menuAlign: 'start', mode: 'fixed', }, logo: { @@ -220,23 +246,28 @@ const defaultPreferences: Preferences = { sidebar: { autoActivateChild: false, collapsed: false, + collapsedButton: true, collapsedShowTitle: false, enable: true, expandOnHover: true, - extraCollapse: true, + extraCollapse: false, + fixedButton: true, hidden: false, - width: 230, + width: 224, }, tabbar: { draggable: true, enable: true, - height: 36, + height: 38, keepAlive: true, + maxCount: 0, + middleClickToClose: false, persist: true, showIcon: true, showMaximize: true, showMore: true, styleType: 'chrome', + wheelable: true, }, theme: { builtinType: 'default', @@ -247,7 +278,7 @@ const defaultPreferences: Preferences = { mode: 'dark', radius: '0.5', semiDarkHeader: false, - semiDarkSidebar: true, + semiDarkSidebar: false, }, transition: { enable: true, @@ -261,9 +292,9 @@ const defaultPreferences: Preferences = { languageToggle: true, lockScreen: true, notification: true, + refresh: true, sidebarToggle: true, themeToggle: true, - refresh: true, }, }; ``` @@ -345,6 +376,8 @@ interface CopyrightPreferences { icp: string; /** Link to the ICP */ icpLink: string; + /** Whether to show in settings panel */ + settingShow?: boolean; } interface FooterPreferences { @@ -359,6 +392,8 @@ interface HeaderPreferences { enable: boolean; /** Whether the header is hidden, css-hidden */ hidden: boolean; + /** Header menu alignment */ + menuAlign: LayoutHeaderMenuAlignType; /** Header display mode */ mode: LayoutHeaderModeType; } @@ -379,8 +414,12 @@ interface NavigationPreferences { styleType: NavigationStyleType; } interface SidebarPreferences { + /** Automatically activate child menu when clicking on directory */ + autoActivateChild: boolean; /** Whether the sidebar is collapsed */ collapsed: boolean; + /** Whether the sidebar collapse button is visible */ + collapsedButton: boolean; /** Whether to show title when sidebar is collapsed */ collapsedShowTitle: boolean; /** Whether the sidebar is visible */ @@ -389,6 +428,8 @@ interface SidebarPreferences { expandOnHover: boolean; /** Whether the sidebar extension area is collapsed */ extraCollapse: boolean; + /** Whether the sidebar fixed button is visible */ + fixedButton: boolean; /** Whether the sidebar is hidden - css */ hidden: boolean; /** Sidebar width */ @@ -417,6 +458,10 @@ interface TabbarPreferences { height: number; /** Whether tab caching is enabled */ keepAlive: boolean; + /** Maximum number of tabs */ + maxCount: number; + /** Whether to close tab when middle-clicked */ + middleClickToClose: boolean; /** Whether tabs are persistent */ persist: boolean; /** Whether icons in multiple tabs are enabled */ @@ -427,6 +472,8 @@ interface TabbarPreferences { showMore: boolean; /** Tab style */ styleType: TabsStyleType; + /** Whether mouse wheel response is enabled */ + wheelable: boolean; } interface ThemePreferences { /** Built-in theme name */ @@ -514,5 +561,6 @@ interface Preferences { - The `overridesPreferences` method only needs to override a part of the configurations in the project. There's no need to override configurations that are not needed; they will automatically use the default settings. - Any configuration item can be overridden. You just need to override it within the `overridesPreferences` method. Do not modify the default configuration file. +- Please clear the cache after changing the configuration, otherwise it may not take effect. ::: diff --git a/docs/src/guide/essentials/settings.md b/docs/src/guide/essentials/settings.md index 58c36dfd..e25bad70 100644 --- a/docs/src/guide/essentials/settings.md +++ b/docs/src/guide/essentials/settings.md @@ -195,7 +195,7 @@ const defaultPreferences: Preferences = { isMobile: false, layout: 'sidebar-nav', locale: 'zh-CN', - loginExpiredMode: 'modal', + loginExpiredMode: 'page', name: 'Vben Admin', preferencesButtonPosition: 'auto', watermark: false, @@ -214,14 +214,16 @@ const defaultPreferences: Preferences = { enable: true, icp: '', icpLink: '', + settingShow: true, }, footer: { - enable: true, + enable: false, fixed: false, }, header: { enable: true, hidden: false, + menuAlign: 'start', mode: 'fixed', }, logo: { @@ -243,23 +245,28 @@ const defaultPreferences: Preferences = { sidebar: { autoActivateChild: false, collapsed: false, + collapsedButton: true, collapsedShowTitle: false, enable: true, expandOnHover: true, - extraCollapse: true, + extraCollapse: false, + fixedButton: true, hidden: false, - width: 230, + width: 224, }, tabbar: { draggable: true, enable: true, - height: 36, + height: 38, keepAlive: true, + maxCount: 0, + middleClickToClose: false, persist: true, showIcon: true, showMaximize: true, showMore: true, styleType: 'chrome', + wheelable: true, }, theme: { builtinType: 'default', @@ -270,7 +277,7 @@ const defaultPreferences: Preferences = { mode: 'dark', radius: '0.5', semiDarkHeader: false, - semiDarkSidebar: true, + semiDarkSidebar: false, }, transition: { enable: true, @@ -369,6 +376,8 @@ interface CopyrightPreferences { icp: string; /** 备案号链接 */ icpLink: string; + /** 设置面板是否显示*/ + settingShow?: boolean; } interface FooterPreferences { @@ -383,6 +392,8 @@ interface HeaderPreferences { enable: boolean; /** 顶栏是否隐藏,css-隐藏 */ hidden: boolean; + /** 顶栏菜单位置 */ + menuAlign: LayoutHeaderMenuAlignType; /** header显示模式 */ mode: LayoutHeaderModeType; } @@ -404,8 +415,12 @@ interface NavigationPreferences { } interface SidebarPreferences { + /** 点击目录时自动激活子菜单 */ + autoActivateChild: boolean; /** 侧边栏是否折叠 */ collapsed: boolean; + /** 侧边栏折叠按钮是否可见 */ + collapsedButton: boolean; /** 侧边栏折叠时,是否显示title */ collapsedShowTitle: boolean; /** 侧边栏是否可见 */ @@ -414,6 +429,8 @@ interface SidebarPreferences { expandOnHover: boolean; /** 侧边栏扩展区域是否折叠 */ extraCollapse: boolean; + /** 侧边栏固定按钮是否可见 */ + fixedButton: boolean; /** 侧边栏是否隐藏 - css */ hidden: boolean; /** 侧边栏宽度 */ @@ -442,6 +459,10 @@ interface TabbarPreferences { height: number; /** 开启标签页缓存功能 */ keepAlive: boolean; + /** 限制最大数量 */ + maxCount: number; + /** 是否点击中键时关闭标签 */ + middleClickToClose: boolean; /** 是否持久化标签 */ persist: boolean; /** 是否开启多标签页图标 */ @@ -452,6 +473,8 @@ interface TabbarPreferences { showMore: boolean; /** 标签页风格 */ styleType: TabsStyleType; + /** 是否开启鼠标滚轮响应 */ + wheelable: boolean; } interface ThemePreferences {