From 0597aa602a854f6d1a3c81b6615cd59751907388 Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Fri, 6 Jun 2025 11:27:12 +0800 Subject: [PATCH 01/59] fix: crm detail --- .../src/views/crm/business/modules/detail.vue | 1 + .../src/views/crm/contract/modules/detail.vue | 2 +- .../views/crm/product/modules/detail-list.vue | 55 ++++++++++++------- .../web-antd/src/views/crm/receivable/data.ts | 8 +-- .../crm/receivable/modules/detail-list.vue | 22 +------- .../src/views/crm/receivable/plan/data.ts | 53 +++++++----------- .../receivable/plan/modules/detail-list.vue | 14 +---- 7 files changed, 61 insertions(+), 94 deletions(-) diff --git a/apps/web-antd/src/views/crm/business/modules/detail.vue b/apps/web-antd/src/views/crm/business/modules/detail.vue index aa91e7c76..aecb77e03 100644 --- a/apps/web-antd/src/views/crm/business/modules/detail.vue +++ b/apps/web-antd/src/views/crm/business/modules/detail.vue @@ -183,6 +183,7 @@ onMounted(() => { diff --git a/apps/web-antd/src/views/crm/contract/modules/detail.vue b/apps/web-antd/src/views/crm/contract/modules/detail.vue index bf49f9a0c..384400703 100644 --- a/apps/web-antd/src/views/crm/contract/modules/detail.vue +++ b/apps/web-antd/src/views/crm/contract/modules/detail.vue @@ -161,8 +161,8 @@ onMounted(() => { diff --git a/apps/web-antd/src/views/crm/product/modules/detail-list.vue b/apps/web-antd/src/views/crm/product/modules/detail-list.vue index 96e3d09f6..a2fac8d21 100644 --- a/apps/web-antd/src/views/crm/product/modules/detail-list.vue +++ b/apps/web-antd/src/views/crm/product/modules/detail-list.vue @@ -1,32 +1,49 @@ diff --git a/apps/web-antd/src/views/crm/receivable/plan/data.ts b/apps/web-antd/src/views/crm/receivable/plan/data.ts index 6d57a4e7a..26453cb39 100644 --- a/apps/web-antd/src/views/crm/receivable/plan/data.ts +++ b/apps/web-antd/src/views/crm/receivable/plan/data.ts @@ -310,12 +310,6 @@ export function useDetailBaseSchema(): DescriptionItemSchema[] { /** 详情列表的字段 */ export function useDetailListColumns(): VxeTableGridOptions['columns'] { return [ - { - title: '回款编号', - field: 'no', - minWidth: 150, - fixed: 'left', - }, { title: '客户名称', field: 'customerName', @@ -323,29 +317,36 @@ export function useDetailListColumns(): VxeTableGridOptions['columns'] { }, { title: '合同编号', - field: 'contract', + field: 'contractNo', minWidth: 150, }, { - title: '回款日期', - field: 'returnTime', + title: '期数', + field: 'period', minWidth: 150, - formatter: 'formatDateTime', }, { - title: '回款金额(元)', + title: '计划回款(元)', field: 'price', minWidth: 150, formatter: 'formatNumber', }, { - title: '回款方式', - field: 'returnType', + title: '计划回款日期', + field: 'returnTime', minWidth: 150, - cellRender: { - name: 'CellDict', - props: { type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE }, - }, + formatter: 'formatDateTime', + }, + { + title: '提前几天提醒', + field: 'remindDays', + minWidth: 150, + }, + { + title: '提醒日期', + field: 'remindTime', + minWidth: 150, + formatter: 'formatDateTime', }, { title: '负责人', @@ -357,26 +358,10 @@ export function useDetailListColumns(): VxeTableGridOptions['columns'] { field: 'remark', minWidth: 150, }, - { - title: '合同金额(元)', - field: 'contract.totalPrice', - minWidth: 150, - formatter: 'formatNumber', - }, - { - title: '回款状态', - field: 'auditStatus', - minWidth: 100, - fixed: 'right', - cellRender: { - name: 'CellDict', - props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, - }, - }, { title: '操作', field: 'actions', - width: 130, + width: 240, fixed: 'right', slots: { default: 'actions' }, }, diff --git a/apps/web-antd/src/views/crm/receivable/plan/modules/detail-list.vue b/apps/web-antd/src/views/crm/receivable/plan/modules/detail-list.vue index dde0f0a98..06cae9bd2 100644 --- a/apps/web-antd/src/views/crm/receivable/plan/modules/detail-list.vue +++ b/apps/web-antd/src/views/crm/receivable/plan/modules/detail-list.vue @@ -3,8 +3,6 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { CrmReceivableApi } from '#/api/crm/receivable'; import type { CrmReceivablePlanApi } from '#/api/crm/receivable/plan'; -import { ref } from 'vue'; - import { useVbenModal } from '@vben/common-ui'; import { message } from 'ant-design-vue'; @@ -35,11 +33,6 @@ const [ReceivableFormModal, receivableFormModalApi] = useVbenModal({ destroyOnClose: true, }); -const checkedRows = ref([]); -function setCheckedRows({ records }: { records: CrmReceivablePlanApi.Plan[] }) { - checkedRows.value = records; -} - /** 刷新表格 */ function onRefresh() { gridApi.query(); @@ -81,7 +74,7 @@ async function handleDelete(row: CrmReceivablePlanApi.Plan) { const [Grid, gridApi] = useVbenVxeGrid({ gridOptions: { columns: useDetailListColumns(), - height: 600, + height: 400, keepSource: true, proxyConfig: { ajax: { @@ -110,10 +103,6 @@ const [Grid, gridApi] = useVbenVxeGrid({ search: true, }, } as VxeTableGridOptions, - gridEvents: { - checkboxAll: setCheckedRows, - checkboxChange: setCheckedRows, - }, }); @@ -143,6 +132,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ type: 'link', icon: ACTION_ICON.ADD, auth: ['crm:receivable-plan:create'], + disabled: !!row.receivableId, onClick: handleCreateReceivable.bind(null, row), }, { From 21729309237df5fb66d006b83f916e33d791d53f Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Fri, 6 Jun 2025 11:38:17 +0800 Subject: [PATCH 02/59] =?UTF-8?q?perf:=20detail=20=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E4=B8=8E=20list=20form=E5=88=86=E5=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/views/crm/business/data.ts | 125 +----------- .../views/crm/business/modules/detail-data.ts | 126 ++++++++++++ .../crm/business/modules/detail-info.vue | 2 +- .../business/modules/detail-list-modal.vue | 2 +- .../crm/business/modules/detail-list.vue | 2 +- .../src/views/crm/business/modules/detail.vue | 2 +- apps/web-antd/src/views/crm/clue/data.ts | 105 ---------- .../src/views/crm/clue/modules/detail-data.ts | 107 ++++++++++ .../views/crm/clue/modules/detail-info.vue | 2 +- .../src/views/crm/clue/modules/detail.vue | 2 +- apps/web-antd/src/views/crm/contact/data.ts | 162 --------------- .../views/crm/contact/modules/detail-data.ts | 165 +++++++++++++++ .../views/crm/contact/modules/detail-info.vue | 2 +- .../crm/contact/modules/detail-list-modal.vue | 2 +- .../views/crm/contact/modules/detail-list.vue | 2 +- .../src/views/crm/contact/modules/detail.vue | 2 +- apps/web-antd/src/views/crm/contract/data.ts | 187 +---------------- .../views/crm/contract/modules/detail-data.ts | 188 ++++++++++++++++++ .../crm/contract/modules/detail-info.vue | 2 +- .../crm/contract/modules/detail-list.vue | 2 +- .../src/views/crm/contract/modules/detail.vue | 2 +- apps/web-antd/src/views/crm/customer/data.ts | 99 --------- .../views/crm/customer/modules/detail-data.ts | 101 ++++++++++ apps/web-antd/src/views/crm/product/data.ts | 120 +---------- .../views/crm/product/modules/detail-data.ts | 116 +++++++++++ .../views/crm/product/modules/detail-info.vue | 2 +- .../views/crm/product/modules/detail-list.vue | 2 +- .../src/views/crm/product/modules/detail.vue | 2 +- .../web-antd/src/views/crm/receivable/data.ts | 148 -------------- .../crm/receivable/modules/detail-data.ts | 150 ++++++++++++++ .../crm/receivable/modules/detail-info.vue | 2 +- .../crm/receivable/modules/detail-list.vue | 2 +- .../views/crm/receivable/modules/detail.vue | 2 +- .../src/views/crm/receivable/plan/data.ts | 139 ------------- .../receivable/plan/modules/detail-data.ts | 141 +++++++++++++ .../receivable/plan/modules/detail-info.vue | 2 +- .../receivable/plan/modules/detail-list.vue | 2 +- .../crm/receivable/plan/modules/detail.vue | 2 +- 38 files changed, 1119 insertions(+), 1104 deletions(-) create mode 100644 apps/web-antd/src/views/crm/business/modules/detail-data.ts create mode 100644 apps/web-antd/src/views/crm/clue/modules/detail-data.ts create mode 100644 apps/web-antd/src/views/crm/contact/modules/detail-data.ts create mode 100644 apps/web-antd/src/views/crm/contract/modules/detail-data.ts create mode 100644 apps/web-antd/src/views/crm/customer/modules/detail-data.ts create mode 100644 apps/web-antd/src/views/crm/product/modules/detail-data.ts create mode 100644 apps/web-antd/src/views/crm/receivable/modules/detail-data.ts create mode 100644 apps/web-antd/src/views/crm/receivable/plan/modules/detail-data.ts diff --git a/apps/web-antd/src/views/crm/business/data.ts b/apps/web-antd/src/views/crm/business/data.ts index 9505414f8..f2e5fa0e5 100644 --- a/apps/web-antd/src/views/crm/business/data.ts +++ b/apps/web-antd/src/views/crm/business/data.ts @@ -1,13 +1,10 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; -import type { DescriptionItemSchema } from '#/components/description'; - -import { formatDateTime } from '@vben/utils'; import { getBusinessStatusTypeSimpleList } from '#/api/crm/business/status'; import { getCustomerSimpleList } from '#/api/crm/customer'; import { getSimpleUserList } from '#/api/system/user'; -import { erpPriceInputFormatter, erpPriceMultiply } from '#/utils'; +import { erpPriceMultiply } from '#/utils'; /** 新增/修改的表单 */ export function useFormSchema(): VbenFormSchema[] { @@ -225,123 +222,3 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { }, ]; } - -/** 详情页的字段 */ -export function useDetailSchema(): DescriptionItemSchema[] { - return [ - { - field: 'customerName', - label: '客户名称', - }, - { - field: 'totalPrice', - label: '商机金额(元)', - content: (data) => erpPriceInputFormatter(data.totalPrice), - }, - { - field: 'statusTypeName', - label: '商机组', - }, - { - field: 'ownerUserName', - label: '负责人', - }, - { - field: 'createTime', - label: '创建时间', - content: (data) => formatDateTime(data?.createTime) as string, - }, - ]; -} - -/** 详情页的基础字段 */ -export function useDetailBaseSchema(): DescriptionItemSchema[] { - return [ - { - field: 'name', - label: '商机名称', - }, - { - field: 'customerName', - label: '客户名称', - }, - { - field: 'totalPrice', - label: '商机金额(元)', - content: (data) => erpPriceInputFormatter(data.totalPrice), - }, - { - field: 'dealTime', - label: '预计成交日期', - content: (data) => formatDateTime(data?.dealTime) as string, - }, - { - field: 'contactNextTime', - label: '下次联系时间', - content: (data) => formatDateTime(data?.contactNextTime) as string, - }, - { - field: 'statusTypeName', - label: '商机状态组', - }, - { - field: 'statusName', - label: '商机阶段', - }, - { - field: 'remark', - label: '备注', - }, - ]; -} - -/** 详情列表的字段 */ -export function useDetailListColumns(): 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: 'formatNumber', - }, - { - field: 'dealTime', - title: '预计成交日期', - formatter: 'formatDate', - }, - { - field: 'ownerUserName', - title: '负责人', - }, - { - field: 'ownerUserDeptName', - title: '所属部门', - }, - { - field: 'statusTypeName', - title: '商机状态组', - fixed: 'right', - }, - { - field: 'statusName', - title: '商机阶段', - fixed: 'right', - }, - ]; -} diff --git a/apps/web-antd/src/views/crm/business/modules/detail-data.ts b/apps/web-antd/src/views/crm/business/modules/detail-data.ts new file mode 100644 index 000000000..cab0f65be --- /dev/null +++ b/apps/web-antd/src/views/crm/business/modules/detail-data.ts @@ -0,0 +1,126 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { formatDateTime } from '@vben/utils'; + +import { erpPriceInputFormatter } from '#/utils'; + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'totalPrice', + label: '商机金额(元)', + content: (data) => erpPriceInputFormatter(data.totalPrice), + }, + { + field: 'statusTypeName', + label: '商机组', + }, + { + field: 'ownerUserName', + label: '负责人', + }, + { + field: 'createTime', + label: '创建时间', + content: (data) => formatDateTime(data?.createTime) as string, + }, + ]; +} + +/** 详情页的基础字段 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'name', + label: '商机名称', + }, + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'totalPrice', + label: '商机金额(元)', + content: (data) => erpPriceInputFormatter(data.totalPrice), + }, + { + field: 'dealTime', + label: '预计成交日期', + content: (data) => formatDateTime(data?.dealTime) as string, + }, + { + field: 'contactNextTime', + label: '下次联系时间', + content: (data) => formatDateTime(data?.contactNextTime) as string, + }, + { + field: 'statusTypeName', + label: '商机状态组', + }, + { + field: 'statusName', + label: '商机阶段', + }, + { + field: 'remark', + label: '备注', + }, + ]; +} + +/** 详情列表的字段 */ +export function useDetailListColumns(): 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: 'formatNumber', + }, + { + field: 'dealTime', + title: '预计成交日期', + formatter: 'formatDate', + }, + { + field: 'ownerUserName', + title: '负责人', + }, + { + field: 'ownerUserDeptName', + title: '所属部门', + }, + { + field: 'statusTypeName', + title: '商机状态组', + fixed: 'right', + }, + { + field: 'statusName', + title: '商机阶段', + fixed: 'right', + }, + ]; +} diff --git a/apps/web-antd/src/views/crm/business/modules/detail-info.vue b/apps/web-antd/src/views/crm/business/modules/detail-info.vue index 2728d32c3..64e4a9fe7 100644 --- a/apps/web-antd/src/views/crm/business/modules/detail-info.vue +++ b/apps/web-antd/src/views/crm/business/modules/detail-info.vue @@ -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 '../data'; +import { useDetailBaseSchema } from './detail-data'; defineProps<{ business: CrmBusinessApi.Business; // 商机信息 diff --git a/apps/web-antd/src/views/crm/business/modules/detail-list-modal.vue b/apps/web-antd/src/views/crm/business/modules/detail-list-modal.vue index 61f21b893..902065437 100644 --- a/apps/web-antd/src/views/crm/business/modules/detail-list-modal.vue +++ b/apps/web-antd/src/views/crm/business/modules/detail-list-modal.vue @@ -13,7 +13,7 @@ import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { getBusinessPageByCustomer } from '#/api/crm/business'; import { $t } from '#/locales'; -import { useDetailListColumns } from '../data'; +import { useDetailListColumns } from './detail-data'; import Form from './form.vue'; const props = defineProps<{ diff --git a/apps/web-antd/src/views/crm/business/modules/detail-list.vue b/apps/web-antd/src/views/crm/business/modules/detail-list.vue index c4f41745d..81cbaa1ea 100644 --- a/apps/web-antd/src/views/crm/business/modules/detail-list.vue +++ b/apps/web-antd/src/views/crm/business/modules/detail-list.vue @@ -19,7 +19,7 @@ import { createContactBusinessList } from '#/api/crm/contact'; import { BizTypeEnum } from '#/api/crm/permission'; import { $t } from '#/locales'; -import { useDetailListColumns } from '../data'; +import { useDetailListColumns } from './detail-data'; import ListModal from './detail-list-modal.vue'; import Form from './form.vue'; diff --git a/apps/web-antd/src/views/crm/business/modules/detail.vue b/apps/web-antd/src/views/crm/business/modules/detail.vue index aecb77e03..418d80467 100644 --- a/apps/web-antd/src/views/crm/business/modules/detail.vue +++ b/apps/web-antd/src/views/crm/business/modules/detail.vue @@ -15,7 +15,7 @@ import { getOperateLogPage } from '#/api/crm/operateLog'; import { BizTypeEnum } from '#/api/crm/permission'; import { useDescription } from '#/components/description'; -import { useDetailSchema } from '../data'; +import { useDetailSchema } from './detail-data'; const BusinessDetailsInfo = defineAsyncComponent( () => import('./detail-info.vue'), diff --git a/apps/web-antd/src/views/crm/clue/data.ts b/apps/web-antd/src/views/crm/clue/data.ts index bd2924aad..ef35115ba 100644 --- a/apps/web-antd/src/views/crm/clue/data.ts +++ b/apps/web-antd/src/views/crm/clue/data.ts @@ -1,14 +1,8 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; -import type { DescriptionItemSchema } from '#/components/description'; - -import { h } from 'vue'; - -import { formatDateTime } from '@vben/utils'; import { getAreaTree } from '#/api/system/area'; import { getSimpleUserList } from '#/api/system/user'; -import { DictTag } from '#/components/dict-tag'; import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils'; /** 新增/修改的表单 */ @@ -254,102 +248,3 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { }, ]; } - -/** 详情头部的配置 */ -export function useDetailSchema(): DescriptionItemSchema[] { - return [ - { - field: 'source', - label: '线索来源', - content: (data) => - h(DictTag, { - type: DICT_TYPE.CRM_CUSTOMER_SOURCE, - value: data?.source, - }), - }, - { - field: 'mobile', - label: '手机', - }, - { - field: 'ownerUserName', - label: '负责人', - }, - { - field: 'createTime', - label: '创建时间', - content: (data) => formatDateTime(data?.createTime) as string, - }, - ]; -} - -/** 详情基本信息的配置 */ -export function useDetailBaseSchema(): DescriptionItemSchema[] { - return [ - { - field: 'name', - label: '线索名称', - }, - { - field: 'source', - label: '客户来源', - content: (data) => - h(DictTag, { - type: DICT_TYPE.CRM_CUSTOMER_SOURCE, - value: data?.source, - }), - }, - { - field: 'mobile', - label: '手机', - }, - { - field: 'telephone', - label: '电话', - }, - { - field: 'email', - label: '邮箱', - }, - { - field: 'areaName', - label: '地址', - content: (data) => data?.areaName + data?.detailAddress, - }, - { - field: 'qq', - label: 'QQ', - }, - { - field: 'wechat', - label: '微信', - }, - { - field: 'industryId', - label: '客户行业', - content: (data) => - h(DictTag, { - type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY, - value: data?.industryId, - }), - }, - { - field: 'level', - label: '客户级别', - content: (data) => - h(DictTag, { - type: DICT_TYPE.CRM_CUSTOMER_LEVEL, - value: data?.level, - }), - }, - { - field: 'contactNextTime', - label: '下次联系时间', - content: (data) => formatDateTime(data?.contactNextTime) as string, - }, - { - field: 'remark', - label: '备注', - }, - ]; -} diff --git a/apps/web-antd/src/views/crm/clue/modules/detail-data.ts b/apps/web-antd/src/views/crm/clue/modules/detail-data.ts new file mode 100644 index 000000000..73102e9b1 --- /dev/null +++ b/apps/web-antd/src/views/crm/clue/modules/detail-data.ts @@ -0,0 +1,107 @@ +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; +import { DICT_TYPE } from '#/utils'; + +/** 详情头部的配置 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'source', + label: '线索来源', + content: (data) => + h(DictTag, { + type: DICT_TYPE.CRM_CUSTOMER_SOURCE, + value: data?.source, + }), + }, + { + field: 'mobile', + label: '手机', + }, + { + field: 'ownerUserName', + label: '负责人', + }, + { + field: 'createTime', + label: '创建时间', + content: (data) => formatDateTime(data?.createTime) as string, + }, + ]; +} + +/** 详情基本信息的配置 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'name', + label: '线索名称', + }, + { + field: 'source', + label: '客户来源', + content: (data) => + h(DictTag, { + type: DICT_TYPE.CRM_CUSTOMER_SOURCE, + value: data?.source, + }), + }, + { + field: 'mobile', + label: '手机', + }, + { + field: 'telephone', + label: '电话', + }, + { + field: 'email', + label: '邮箱', + }, + { + field: 'areaName', + label: '地址', + content: (data) => data?.areaName + data?.detailAddress, + }, + { + field: 'qq', + label: 'QQ', + }, + { + field: 'wechat', + label: '微信', + }, + { + field: 'industryId', + label: '客户行业', + content: (data) => + h(DictTag, { + type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY, + value: data?.industryId, + }), + }, + { + field: 'level', + label: '客户级别', + content: (data) => + h(DictTag, { + type: DICT_TYPE.CRM_CUSTOMER_LEVEL, + value: data?.level, + }), + }, + { + field: 'contactNextTime', + label: '下次联系时间', + content: (data) => formatDateTime(data?.contactNextTime) as string, + }, + { + field: 'remark', + label: '备注', + }, + ]; +} diff --git a/apps/web-antd/src/views/crm/clue/modules/detail-info.vue b/apps/web-antd/src/views/crm/clue/modules/detail-info.vue index 2e9c68c00..fc9dbe3b4 100644 --- a/apps/web-antd/src/views/crm/clue/modules/detail-info.vue +++ b/apps/web-antd/src/views/crm/clue/modules/detail-info.vue @@ -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 '../data'; +import { useDetailBaseSchema } from './detail-data'; defineProps<{ clue: CrmClueApi.Clue; // 线索信息 diff --git a/apps/web-antd/src/views/crm/clue/modules/detail.vue b/apps/web-antd/src/views/crm/clue/modules/detail.vue index 012c97254..486da4c74 100644 --- a/apps/web-antd/src/views/crm/clue/modules/detail.vue +++ b/apps/web-antd/src/views/crm/clue/modules/detail.vue @@ -16,7 +16,7 @@ import { getOperateLogPage } from '#/api/crm/operateLog'; import { BizTypeEnum } from '#/api/crm/permission'; import { useDescription } from '#/components/description'; -import { useDetailSchema } from '../data'; +import { useDetailSchema } from './detail-data'; import ClueForm from './form.vue'; const FollowUp = defineAsyncComponent( diff --git a/apps/web-antd/src/views/crm/contact/data.ts b/apps/web-antd/src/views/crm/contact/data.ts index d6d9eaec8..31dee6a36 100644 --- a/apps/web-antd/src/views/crm/contact/data.ts +++ b/apps/web-antd/src/views/crm/contact/data.ts @@ -1,16 +1,10 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; -import type { DescriptionItemSchema } from '#/components/description'; - -import { h } from 'vue'; - -import { formatDateTime } from '@vben/utils'; import { getSimpleContactList } from '#/api/crm/contact'; import { getCustomerSimpleList } from '#/api/crm/customer'; import { getAreaTree } from '#/api/system/area'; import { getSimpleUserList } from '#/api/system/user'; -import { DictTag } from '#/components/dict-tag'; import { DICT_TYPE, getDictOptions } from '#/utils'; /** 新增/修改的表单 */ @@ -278,159 +272,3 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { }, ]; } - -/** 详情页的基础字段 */ -export function useDetailSchema(): DescriptionItemSchema[] { - return [ - { - field: 'name', - label: '客户名称', - }, - { - field: 'post', - label: '职务', - }, - { - field: 'mobile', - label: '手机', - }, - { - field: 'createTime', - label: '下次联系时间', - content: (data) => formatDateTime(data?.createTime) as string, - }, - ]; -} - -/** 详情页的基础字段 */ -export function useDetailBaseSchema(): DescriptionItemSchema[] { - return [ - { - field: 'name', - label: '姓名', - }, - { - field: 'customerName', - label: '客户名称', - }, - { - field: 'mobile', - label: '手机', - }, - { - field: 'telephone', - label: '电话', - }, - { - field: 'email', - label: '邮箱', - }, - { - field: 'qq', - label: 'QQ', - }, - { - field: 'wechat', - label: '微信', - }, - { - field: 'areaName', - label: '地址', - }, - { - field: 'detailAddress', - label: '详细地址', - }, - { - field: 'post', - label: '职务', - }, - { - field: 'parentName', - label: '直属上级', - }, - { - field: 'master', - label: '关键决策人', - content: (data) => - h(DictTag, { - type: DICT_TYPE.INFRA_BOOLEAN_STRING, - value: data?.master, - }), - }, - { - field: 'sex', - label: '性别', - content: (data) => - h(DictTag, { type: DICT_TYPE.SYSTEM_USER_SEX, value: data?.sex }), - }, - { - field: 'contactNextTime', - label: '下次联系时间', - content: (data) => formatDateTime(data?.contactNextTime) as string, - }, - { - field: 'remark', - label: '备注', - }, - ]; -} - -/** 详情列表的字段 */ -export function useDetailListColumns(): 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: 'sex', - title: '性别', - cellRender: { - name: 'CellDict', - props: { type: DICT_TYPE.SYSTEM_USER_SEX }, - }, - }, - { - field: 'mobile', - title: '手机', - }, - { - field: 'telephone', - title: '电话', - }, - { - field: 'email', - title: '邮箱', - }, - { - field: 'post', - title: '职位', - }, - { - field: 'detailAddress', - title: '地址', - }, - { - field: 'master', - title: '关键决策人', - cellRender: { - name: 'CellDict', - props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, - }, - }, - ]; -} diff --git a/apps/web-antd/src/views/crm/contact/modules/detail-data.ts b/apps/web-antd/src/views/crm/contact/modules/detail-data.ts new file mode 100644 index 000000000..e4de3e2ff --- /dev/null +++ b/apps/web-antd/src/views/crm/contact/modules/detail-data.ts @@ -0,0 +1,165 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; +import { DICT_TYPE } from '#/utils'; + +/** 详情页的基础字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'name', + label: '客户名称', + }, + { + field: 'post', + label: '职务', + }, + { + field: 'mobile', + label: '手机', + }, + { + field: 'createTime', + label: '下次联系时间', + content: (data) => formatDateTime(data?.createTime) as string, + }, + ]; +} + +/** 详情页的基础字段 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'name', + label: '姓名', + }, + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'mobile', + label: '手机', + }, + { + field: 'telephone', + label: '电话', + }, + { + field: 'email', + label: '邮箱', + }, + { + field: 'qq', + label: 'QQ', + }, + { + field: 'wechat', + label: '微信', + }, + { + field: 'areaName', + label: '地址', + }, + { + field: 'detailAddress', + label: '详细地址', + }, + { + field: 'post', + label: '职务', + }, + { + field: 'parentName', + label: '直属上级', + }, + { + field: 'master', + label: '关键决策人', + content: (data) => + h(DictTag, { + type: DICT_TYPE.INFRA_BOOLEAN_STRING, + value: data?.master, + }), + }, + { + field: 'sex', + label: '性别', + content: (data) => + h(DictTag, { type: DICT_TYPE.SYSTEM_USER_SEX, value: data?.sex }), + }, + { + field: 'contactNextTime', + label: '下次联系时间', + content: (data) => formatDateTime(data?.contactNextTime) as string, + }, + { + field: 'remark', + label: '备注', + }, + ]; +} + +/** 详情列表的字段 */ +export function useDetailListColumns(): 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: 'sex', + title: '性别', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_USER_SEX }, + }, + }, + { + field: 'mobile', + title: '手机', + }, + { + field: 'telephone', + title: '电话', + }, + { + field: 'email', + title: '邮箱', + }, + { + field: 'post', + title: '职位', + }, + { + field: 'detailAddress', + title: '地址', + }, + { + field: 'master', + title: '关键决策人', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + ]; +} diff --git a/apps/web-antd/src/views/crm/contact/modules/detail-info.vue b/apps/web-antd/src/views/crm/contact/modules/detail-info.vue index 9ca08aaa7..9e71bd593 100644 --- a/apps/web-antd/src/views/crm/contact/modules/detail-info.vue +++ b/apps/web-antd/src/views/crm/contact/modules/detail-info.vue @@ -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 '../data'; +import { useDetailBaseSchema } from './detail-data'; defineProps<{ contact: CrmContactApi.Contact; // 联系人信息 diff --git a/apps/web-antd/src/views/crm/contact/modules/detail-list-modal.vue b/apps/web-antd/src/views/crm/contact/modules/detail-list-modal.vue index f816d64a4..8fc58057f 100644 --- a/apps/web-antd/src/views/crm/contact/modules/detail-list-modal.vue +++ b/apps/web-antd/src/views/crm/contact/modules/detail-list-modal.vue @@ -13,7 +13,7 @@ import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { getContactPageByCustomer } from '#/api/crm/contact'; import { $t } from '#/locales'; -import { useDetailListColumns } from '../data'; +import { useDetailListColumns } from './detail-data'; import Form from './form.vue'; const props = defineProps<{ diff --git a/apps/web-antd/src/views/crm/contact/modules/detail-list.vue b/apps/web-antd/src/views/crm/contact/modules/detail-list.vue index a68a7129d..2ad66d514 100644 --- a/apps/web-antd/src/views/crm/contact/modules/detail-list.vue +++ b/apps/web-antd/src/views/crm/contact/modules/detail-list.vue @@ -19,7 +19,7 @@ import { import { BizTypeEnum } from '#/api/crm/permission'; import { $t } from '#/locales'; -import { useDetailListColumns } from '../data'; +import { useDetailListColumns } from './detail-data'; import ListModal from './detail-list-modal.vue'; import Form from './form.vue'; diff --git a/apps/web-antd/src/views/crm/contact/modules/detail.vue b/apps/web-antd/src/views/crm/contact/modules/detail.vue index b7c6e0498..4f20a5607 100644 --- a/apps/web-antd/src/views/crm/contact/modules/detail.vue +++ b/apps/web-antd/src/views/crm/contact/modules/detail.vue @@ -15,7 +15,7 @@ import { getOperateLogPage } from '#/api/crm/operateLog'; import { BizTypeEnum } from '#/api/crm/permission'; import { useDescription } from '#/components/description'; -import { useDetailSchema } from '../data'; +import { useDetailSchema } from './detail-data'; const ContactDetailsInfo = defineAsyncComponent( () => import('./detail-info.vue'), diff --git a/apps/web-antd/src/views/crm/contract/data.ts b/apps/web-antd/src/views/crm/contract/data.ts index 626ae073e..79118dd3f 100644 --- a/apps/web-antd/src/views/crm/contract/data.ts +++ b/apps/web-antd/src/views/crm/contract/data.ts @@ -1,16 +1,10 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; -import type { DescriptionItemSchema } from '#/components/description'; - -import { h } from 'vue'; - -import { formatDateTime } from '@vben/utils'; import { getSimpleBusinessList } from '#/api/crm/business'; import { getSimpleContactList } from '#/api/crm/contact'; import { getCustomerSimpleList } from '#/api/crm/customer'; -import { DictTag } from '#/components/dict-tag'; -import { erpPriceInputFormatter, floatToFixed2 } from '#/utils'; +import { floatToFixed2 } from '#/utils'; import { DICT_TYPE } from '#/utils/dict'; /** 新增/修改的表单 */ @@ -280,182 +274,3 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { }, ]; } - -/** 详情头部的配置 */ -export function useDetailSchema(): DescriptionItemSchema[] { - return [ - { - field: 'customerName', - label: '客户名称', - }, - { - field: 'totalPrice', - label: '合同金额(元)', - content: (data) => erpPriceInputFormatter(data?.totalPrice) as string, - }, - { - field: 'orderDate', - label: '下单时间', - content: (data) => formatDateTime(data?.orderDate) as string, - }, - { - field: 'totalReceivablePrice', - label: '回款金额(元)', - content: (data) => - erpPriceInputFormatter(data?.totalReceivablePrice) as string, - }, - { - field: 'ownerUserName', - label: '负责人', - }, - ]; -} - -/** 详情基本信息的配置 */ -export function useDetailBaseSchema(): DescriptionItemSchema[] { - return [ - { - field: 'no', - label: '合同编号', - }, - { - field: 'name', - label: '合同名称', - }, - { - field: 'customerName', - label: '客户名称', - }, - { - field: 'businessName', - label: '商机名称', - }, - { - field: 'totalPrice', - label: '合同金额(元)', - content: (data) => erpPriceInputFormatter(data?.totalPrice) as string, - }, - { - field: 'orderDate', - label: '下单时间', - content: (data) => formatDateTime(data?.orderDate) as string, - }, - { - field: 'startTime', - label: '合同开始时间', - content: (data) => formatDateTime(data?.startTime) as string, - }, - { - field: 'endTime', - label: '合同结束时间', - content: (data) => formatDateTime(data?.endTime) as string, - }, - { - field: 'signContactName', - label: '客户签约人', - }, - { - field: 'signUserName', - label: '公司签约人', - }, - { - field: 'remark', - label: '备注', - }, - { - field: 'auditStatus', - label: '合同状态', - content: (data) => - h(DictTag, { - type: DICT_TYPE.CRM_AUDIT_STATUS, - value: data?.auditStatus, - }), - }, - ]; -} - -export function useDetailListColumns(): VxeTableGridOptions['columns'] { - return [ - { - title: '合同编号', - field: 'no', - minWidth: 150, - fixed: 'left', - }, - { - title: '合同名称', - field: 'name', - minWidth: 150, - fixed: 'left', - slots: { default: 'name' }, - }, - { - title: '合同金额(元)', - field: 'totalPrice', - minWidth: 150, - formatter: 'formatNumber', - }, - { - title: '合同开始时间', - field: 'startTime', - minWidth: 150, - formatter: 'formatDateTime', - }, - { - title: '合同结束时间', - field: 'endTime', - minWidth: 150, - formatter: 'formatDateTime', - }, - { - title: '已回款金额(元)', - field: 'totalReceivablePrice', - minWidth: 150, - formatter: 'formatNumber', - }, - { - title: '未回款金额(元)', - field: 'unpaidPrice', - minWidth: 150, - formatter: ({ row }) => { - return floatToFixed2(row.totalPrice - row.totalReceivablePrice); - }, - }, - { - title: '负责人', - field: 'ownerUserName', - minWidth: 150, - }, - { - title: '所属部门', - field: 'ownerUserDeptName', - minWidth: 150, - }, - { - title: '创建时间', - field: 'createTime', - minWidth: 150, - formatter: 'formatDateTime', - }, - { - title: '创建人', - field: 'creatorName', - minWidth: 150, - }, - { - title: '备注', - field: 'remark', - minWidth: 150, - }, - { - title: '合同状态', - field: 'auditStatus', - fixed: 'right', - minWidth: 100, - cellRender: { - name: 'CellDict', - props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, - }, - }, - ]; -} diff --git a/apps/web-antd/src/views/crm/contract/modules/detail-data.ts b/apps/web-antd/src/views/crm/contract/modules/detail-data.ts new file mode 100644 index 000000000..cffb31427 --- /dev/null +++ b/apps/web-antd/src/views/crm/contract/modules/detail-data.ts @@ -0,0 +1,188 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; +import { DICT_TYPE, erpPriceInputFormatter, floatToFixed2 } from '#/utils'; + +/** 详情头部的配置 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'totalPrice', + label: '合同金额(元)', + content: (data) => erpPriceInputFormatter(data?.totalPrice) as string, + }, + { + field: 'orderDate', + label: '下单时间', + content: (data) => formatDateTime(data?.orderDate) as string, + }, + { + field: 'totalReceivablePrice', + label: '回款金额(元)', + content: (data) => + erpPriceInputFormatter(data?.totalReceivablePrice) as string, + }, + { + field: 'ownerUserName', + label: '负责人', + }, + ]; +} + +/** 详情基本信息的配置 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'no', + label: '合同编号', + }, + { + field: 'name', + label: '合同名称', + }, + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'businessName', + label: '商机名称', + }, + { + field: 'totalPrice', + label: '合同金额(元)', + content: (data) => erpPriceInputFormatter(data?.totalPrice) as string, + }, + { + field: 'orderDate', + label: '下单时间', + content: (data) => formatDateTime(data?.orderDate) as string, + }, + { + field: 'startTime', + label: '合同开始时间', + content: (data) => formatDateTime(data?.startTime) as string, + }, + { + field: 'endTime', + label: '合同结束时间', + content: (data) => formatDateTime(data?.endTime) as string, + }, + { + field: 'signContactName', + label: '客户签约人', + }, + { + field: 'signUserName', + label: '公司签约人', + }, + { + field: 'remark', + label: '备注', + }, + { + field: 'auditStatus', + label: '合同状态', + content: (data) => + h(DictTag, { + type: DICT_TYPE.CRM_AUDIT_STATUS, + value: data?.auditStatus, + }), + }, + ]; +} + +export function useDetailListColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '合同编号', + field: 'no', + minWidth: 150, + fixed: 'left', + }, + { + title: '合同名称', + field: 'name', + minWidth: 150, + fixed: 'left', + slots: { default: 'name' }, + }, + { + title: '合同金额(元)', + field: 'totalPrice', + minWidth: 150, + formatter: 'formatNumber', + }, + { + title: '合同开始时间', + field: 'startTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '合同结束时间', + field: 'endTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '已回款金额(元)', + field: 'totalReceivablePrice', + minWidth: 150, + formatter: 'formatNumber', + }, + { + title: '未回款金额(元)', + field: 'unpaidPrice', + minWidth: 150, + formatter: ({ row }) => { + return floatToFixed2(row.totalPrice - row.totalReceivablePrice); + }, + }, + { + title: '负责人', + field: 'ownerUserName', + minWidth: 150, + }, + { + title: '所属部门', + field: 'ownerUserDeptName', + minWidth: 150, + }, + { + title: '创建时间', + field: 'createTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '创建人', + field: 'creatorName', + minWidth: 150, + }, + { + title: '备注', + field: 'remark', + minWidth: 150, + }, + { + title: '合同状态', + field: 'auditStatus', + fixed: 'right', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, + }, + }, + ]; +} diff --git a/apps/web-antd/src/views/crm/contract/modules/detail-info.vue b/apps/web-antd/src/views/crm/contract/modules/detail-info.vue index f3b879888..7968e6dad 100644 --- a/apps/web-antd/src/views/crm/contract/modules/detail-info.vue +++ b/apps/web-antd/src/views/crm/contract/modules/detail-info.vue @@ -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 '../data'; +import { useDetailBaseSchema } from './detail-data'; defineProps<{ contract: CrmContractApi.Contract; // 合同信息 diff --git a/apps/web-antd/src/views/crm/contract/modules/detail-list.vue b/apps/web-antd/src/views/crm/contract/modules/detail-list.vue index ee8637eb5..d6ceee285 100644 --- a/apps/web-antd/src/views/crm/contract/modules/detail-list.vue +++ b/apps/web-antd/src/views/crm/contract/modules/detail-list.vue @@ -18,7 +18,7 @@ import { import { BizTypeEnum } from '#/api/crm/permission'; import { $t } from '#/locales'; -import { useDetailListColumns } from '../data'; +import { useDetailListColumns } from './detail-data'; import Form from './form.vue'; const props = defineProps<{ diff --git a/apps/web-antd/src/views/crm/contract/modules/detail.vue b/apps/web-antd/src/views/crm/contract/modules/detail.vue index 384400703..301cb4d0c 100644 --- a/apps/web-antd/src/views/crm/contract/modules/detail.vue +++ b/apps/web-antd/src/views/crm/contract/modules/detail.vue @@ -16,7 +16,7 @@ import { getOperateLogPage } from '#/api/crm/operateLog'; import { BizTypeEnum } from '#/api/crm/permission'; import { useDescription } from '#/components/description'; -import { useDetailSchema } from '../data'; +import { useDetailSchema } from './detail-data'; import ClueForm from './form.vue'; const FollowUp = defineAsyncComponent( diff --git a/apps/web-antd/src/views/crm/customer/data.ts b/apps/web-antd/src/views/crm/customer/data.ts index 840d56636..61731e20b 100644 --- a/apps/web-antd/src/views/crm/customer/data.ts +++ b/apps/web-antd/src/views/crm/customer/data.ts @@ -1,14 +1,8 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; -import type { DescriptionItemSchema } from '#/components/description'; - -import { h } from 'vue'; - -import { formatDateTime } from '@vben/utils'; import { getAreaTree } from '#/api/system/area'; import { getSimpleUserList } from '#/api/system/user'; -import { DictTag } from '#/components/dict-tag'; import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils'; /** 新增/修改的表单 */ @@ -240,96 +234,3 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { }, ]; } - -/** 详情页的字段 */ -export function useDetailSchema(): DescriptionItemSchema[] { - return [ - { - field: 'level', - label: '客户级别', - content: (data) => - h(DictTag, { type: DICT_TYPE.CRM_CUSTOMER_LEVEL, value: data?.level }), - }, - { - field: 'dealStatus', - label: '成交状态', - content: (data) => (data.dealStatus ? '已成交' : '未成交'), - }, - { - field: 'createTime', - label: '创建时间', - content: (data) => formatDateTime(data?.createTime) as string, - }, - ]; -} - -/** 详情页的基础字段 */ -export function useDetailBaseSchema(): DescriptionItemSchema[] { - return [ - { - field: 'name', - label: '客户名称', - }, - { - field: 'source', - label: '客户来源', - content: (data) => - h(DictTag, { - type: DICT_TYPE.CRM_CUSTOMER_SOURCE, - value: data?.source, - }), - }, - { - field: 'mobile', - label: '手机', - }, - { - field: 'telephone', - label: '电话', - }, - { - field: 'email', - label: '邮箱', - }, - { - field: 'areaName', - label: '地址', - }, - { - field: 'detailAddress', - label: '详细地址', - }, - { - field: 'qq', - label: 'QQ', - }, - { - field: 'wechat', - label: '微信', - }, - { - field: 'industryId', - label: '客户行业', - content: (data) => - h(DictTag, { - type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY, - value: data?.industryId, - }), - }, - { - field: 'level', - label: '客户级别', - content: (data) => - h(DictTag, { type: DICT_TYPE.CRM_CUSTOMER_LEVEL, value: data?.level }), - }, - { - field: 'contactNextTime', - label: '下次联系时间', - content: (data) => formatDateTime(data?.contactNextTime) as string, - }, - { - field: 'remark', - label: '备注', - }, - ]; -} diff --git a/apps/web-antd/src/views/crm/customer/modules/detail-data.ts b/apps/web-antd/src/views/crm/customer/modules/detail-data.ts new file mode 100644 index 000000000..75a71b01d --- /dev/null +++ b/apps/web-antd/src/views/crm/customer/modules/detail-data.ts @@ -0,0 +1,101 @@ +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; +import { DICT_TYPE } from '#/utils'; + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'level', + label: '客户级别', + content: (data) => + h(DictTag, { type: DICT_TYPE.CRM_CUSTOMER_LEVEL, value: data?.level }), + }, + { + field: 'dealStatus', + label: '成交状态', + content: (data) => (data.dealStatus ? '已成交' : '未成交'), + }, + { + field: 'createTime', + label: '创建时间', + content: (data) => formatDateTime(data?.createTime) as string, + }, + ]; +} + +/** 详情页的基础字段 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'name', + label: '客户名称', + }, + { + field: 'source', + label: '客户来源', + content: (data) => + h(DictTag, { + type: DICT_TYPE.CRM_CUSTOMER_SOURCE, + value: data?.source, + }), + }, + { + field: 'mobile', + label: '手机', + }, + { + field: 'telephone', + label: '电话', + }, + { + field: 'email', + label: '邮箱', + }, + { + field: 'areaName', + label: '地址', + }, + { + field: 'detailAddress', + label: '详细地址', + }, + { + field: 'qq', + label: 'QQ', + }, + { + field: 'wechat', + label: '微信', + }, + { + field: 'industryId', + label: '客户行业', + content: (data) => + h(DictTag, { + type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY, + value: data?.industryId, + }), + }, + { + field: 'level', + label: '客户级别', + content: (data) => + h(DictTag, { type: DICT_TYPE.CRM_CUSTOMER_LEVEL, value: data?.level }), + }, + { + field: 'contactNextTime', + label: '下次联系时间', + content: (data) => formatDateTime(data?.contactNextTime) as string, + }, + { + field: 'remark', + label: '备注', + }, + ]; +} diff --git a/apps/web-antd/src/views/crm/product/data.ts b/apps/web-antd/src/views/crm/product/data.ts index ff5b0903d..348afb372 100644 --- a/apps/web-antd/src/views/crm/product/data.ts +++ b/apps/web-antd/src/views/crm/product/data.ts @@ -1,20 +1,11 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; -import type { DescriptionItemSchema } from '#/components/description'; - -import { h } from 'vue'; import { handleTree } from '@vben/utils'; import { z } from '#/adapter/form'; import { getProductCategoryList } from '#/api/crm/product/category'; -import { DictTag } from '#/components/dict-tag'; -import { - CommonStatusEnum, - DICT_TYPE, - erpPriceInputFormatter, - getDictOptions, -} from '#/utils'; +import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; /** 新增/修改的表单 */ export function useFormSchema(): VbenFormSchema[] { @@ -183,112 +174,3 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { }, ]; } - -/** 详情页的字段 */ -export function useDetailSchema(): DescriptionItemSchema[] { - return [ - { - field: 'categoryName', - label: '产品类别', - }, - { - field: 'unit', - label: '产品单位', - content: (data) => - h(DictTag, { type: DICT_TYPE.CRM_PRODUCT_UNIT, value: data?.unit }), - }, - { - field: 'price', - label: '产品价格', - content: (data) => erpPriceInputFormatter(data.price), - }, - { - field: 'no', - label: '产品编码', - }, - ]; -} - -/** 详情页的基础字段 */ -export function useDetailBaseSchema(): DescriptionItemSchema[] { - return [ - { - field: 'name', - label: '产品名称', - }, - { - field: 'no', - label: '产品编码', - }, - { - field: 'price', - label: '价格(元)', - content: (data) => erpPriceInputFormatter(data.price), - }, - { - field: 'description', - label: '产品描述', - }, - { - field: 'categoryName', - label: '产品类型', - }, - { - field: 'status', - label: '是否上下架', - content: (data) => - h(DictTag, { type: DICT_TYPE.CRM_PRODUCT_STATUS, value: data?.status }), - }, - { - field: 'unit', - label: '产品单位', - content: (data) => - h(DictTag, { type: DICT_TYPE.CRM_PRODUCT_UNIT, value: data?.unit }), - }, - ]; -} - -/** 详情列表的字段 */ -export function useDetailListColumns( - showBussinePrice: boolean, -): VxeTableGridOptions['columns'] { - return [ - { - field: 'productName', - title: '产品名称', - }, - { - field: 'productNo', - title: '产品条码', - }, - { - field: 'productUnit', - title: '产品单位', - cellRender: { - name: 'CellDict', - props: { type: DICT_TYPE.CRM_PRODUCT_UNIT }, - }, - }, - { - field: 'productPrice', - title: '产品价格(元)', - formatter: 'formatNumber', - }, - { - field: 'businessPrice', - title: '商机价格(元)', - formatter: 'formatNumber', - visible: showBussinePrice, - }, - { - field: 'count', - title: '数量', - formatter: 'formatNumber', - }, - { - field: 'totalPrice', - title: '合计金额(元)', - formatter: 'formatNumber', - }, - ]; -} diff --git a/apps/web-antd/src/views/crm/product/modules/detail-data.ts b/apps/web-antd/src/views/crm/product/modules/detail-data.ts new file mode 100644 index 000000000..a8c0a10e1 --- /dev/null +++ b/apps/web-antd/src/views/crm/product/modules/detail-data.ts @@ -0,0 +1,116 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DictTag } from '#/components/dict-tag'; +import { DICT_TYPE, erpPriceInputFormatter } from '#/utils'; + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'categoryName', + label: '产品类别', + }, + { + field: 'unit', + label: '产品单位', + content: (data) => + h(DictTag, { type: DICT_TYPE.CRM_PRODUCT_UNIT, value: data?.unit }), + }, + { + field: 'price', + label: '产品价格', + content: (data) => erpPriceInputFormatter(data.price), + }, + { + field: 'no', + label: '产品编码', + }, + ]; +} + +/** 详情页的基础字段 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'name', + label: '产品名称', + }, + { + field: 'no', + label: '产品编码', + }, + { + field: 'price', + label: '价格(元)', + content: (data) => erpPriceInputFormatter(data.price), + }, + { + field: 'description', + label: '产品描述', + }, + { + field: 'categoryName', + label: '产品类型', + }, + { + field: 'status', + label: '是否上下架', + content: (data) => + h(DictTag, { type: DICT_TYPE.CRM_PRODUCT_STATUS, value: data?.status }), + }, + { + field: 'unit', + label: '产品单位', + content: (data) => + h(DictTag, { type: DICT_TYPE.CRM_PRODUCT_UNIT, value: data?.unit }), + }, + ]; +} + +/** 详情列表的字段 */ +export function useDetailListColumns( + showBussinePrice: boolean, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'productName', + title: '产品名称', + }, + { + field: 'productNo', + title: '产品条码', + }, + { + field: 'productUnit', + title: '产品单位', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_PRODUCT_UNIT }, + }, + }, + { + field: 'productPrice', + title: '产品价格(元)', + formatter: 'formatNumber', + }, + { + field: 'businessPrice', + title: '商机价格(元)', + formatter: 'formatNumber', + visible: showBussinePrice, + }, + { + field: 'count', + title: '数量', + formatter: 'formatNumber', + }, + { + field: 'totalPrice', + title: '合计金额(元)', + formatter: 'formatNumber', + }, + ]; +} diff --git a/apps/web-antd/src/views/crm/product/modules/detail-info.vue b/apps/web-antd/src/views/crm/product/modules/detail-info.vue index 2d902c41f..a7868f05d 100644 --- a/apps/web-antd/src/views/crm/product/modules/detail-info.vue +++ b/apps/web-antd/src/views/crm/product/modules/detail-info.vue @@ -3,7 +3,7 @@ import type { CrmProductApi } from '#/api/crm/product'; import { useDescription } from '#/components/description'; -import { useDetailBaseSchema } from '../data'; +import { useDetailBaseSchema } from './detail-data'; defineProps<{ product: CrmProductApi.Product; // 产品信息 diff --git a/apps/web-antd/src/views/crm/product/modules/detail-list.vue b/apps/web-antd/src/views/crm/product/modules/detail-list.vue index a2fac8d21..9dfb52ed6 100644 --- a/apps/web-antd/src/views/crm/product/modules/detail-list.vue +++ b/apps/web-antd/src/views/crm/product/modules/detail-list.vue @@ -10,7 +10,7 @@ import { getContract } from '#/api/crm/contract'; import { BizTypeEnum } from '#/api/crm/permission'; import { erpPriceInputFormatter } from '#/utils'; -import { useDetailListColumns } from '../data'; +import { useDetailListColumns } from './detail-data'; const props = defineProps<{ bizId: number; diff --git a/apps/web-antd/src/views/crm/product/modules/detail.vue b/apps/web-antd/src/views/crm/product/modules/detail.vue index ffe2f79e9..71f756098 100644 --- a/apps/web-antd/src/views/crm/product/modules/detail.vue +++ b/apps/web-antd/src/views/crm/product/modules/detail.vue @@ -15,7 +15,7 @@ import { BizTypeEnum } from '#/api/crm/permission'; import { getProduct } from '#/api/crm/product'; import { useDescription } from '#/components/description'; -import { useDetailSchema } from '../data'; +import { useDetailSchema } from './detail-data'; const ProductDetailsInfo = defineAsyncComponent( () => import('./detail-info.vue'), diff --git a/apps/web-antd/src/views/crm/receivable/data.ts b/apps/web-antd/src/views/crm/receivable/data.ts index d6132faf1..1c33d1aec 100644 --- a/apps/web-antd/src/views/crm/receivable/data.ts +++ b/apps/web-antd/src/views/crm/receivable/data.ts @@ -1,17 +1,10 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; -import type { DescriptionItemSchema } from '#/components/description'; - -import { h } from 'vue'; - -import { formatDateTime } from '@vben/utils'; import { getContractSimpleList } from '#/api/crm/contract'; import { getCustomerSimpleList } from '#/api/crm/customer'; import { getReceivablePlanSimpleList } from '#/api/crm/receivable/plan'; import { getSimpleUserList } from '#/api/system/user'; -import { DictTag } from '#/components/dict-tag'; -import { erpPriceInputFormatter } from '#/utils'; import { DICT_TYPE, getDictOptions } from '#/utils/dict'; /** 新增/修改的表单 */ @@ -274,144 +267,3 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { }, ]; } - -/** 详情页的字段 */ -export function useDetailSchema(): DescriptionItemSchema[] { - return [ - { - field: 'customerName', - label: '客户名称', - }, - { - field: 'totalPrice', - label: '合同金额', - content: (data) => erpPriceInputFormatter(data.totalPrice), - }, - { - field: 'returnTime', - label: '回款日期', - content: (data) => formatDateTime(data?.returnTime) as string, - }, - { - field: 'price', - label: '回款金额', - content: (data) => erpPriceInputFormatter(data.price), - }, - { - field: 'ownerUserName', - label: '负责人', - }, - ]; -} - -/** 详情页的基础字段 */ -export function useDetailBaseSchema(): DescriptionItemSchema[] { - return [ - { - field: 'no', - label: '回款编号', - }, - { - field: 'customerName', - label: '客户名称', - }, - { - field: 'contract', - label: '合同编号', - content: (data) => data?.contract?.no, - }, - { - field: 'returnTime', - label: '回款日期', - content: (data) => formatDateTime(data?.returnTime) as string, - }, - { - field: 'price', - label: '回款金额', - content: (data) => erpPriceInputFormatter(data.price), - }, - { - field: 'returnType', - label: '回款方式', - content: (data) => - h(DictTag, { - type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE, - value: data?.returnType, - }), - }, - { - field: 'remark', - label: '备注', - }, - ]; -} - -/** 详情列表的字段 */ -export function useDetailListColumns(): VxeTableGridOptions['columns'] { - return [ - { - title: '回款编号', - field: 'no', - minWidth: 150, - fixed: 'left', - }, - { - title: '客户名称', - field: 'customerName', - minWidth: 150, - }, - { - title: '合同编号', - field: 'contract.no', - minWidth: 150, - }, - { - title: '回款日期', - field: 'returnTime', - minWidth: 150, - formatter: 'formatDateTime', - }, - { - title: '回款金额(元)', - field: 'price', - minWidth: 150, - formatter: 'formatNumber', - }, - { - title: '回款方式', - field: 'returnType', - minWidth: 150, - cellRender: { - name: 'CellDict', - props: { type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE }, - }, - }, - { - title: '负责人', - field: 'ownerUserName', - minWidth: 150, - }, - { - title: '备注', - field: 'remark', - minWidth: 150, - }, - { - title: '回款状态', - field: 'auditStatus', - minWidth: 100, - fixed: 'right', - cellRender: { - name: 'CellDict', - props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, - }, - }, - { - title: '操作', - field: 'actions', - width: 130, - fixed: 'right', - slots: { default: 'actions' }, - }, - ]; -} diff --git a/apps/web-antd/src/views/crm/receivable/modules/detail-data.ts b/apps/web-antd/src/views/crm/receivable/modules/detail-data.ts new file mode 100644 index 000000000..744a74e0b --- /dev/null +++ b/apps/web-antd/src/views/crm/receivable/modules/detail-data.ts @@ -0,0 +1,150 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; +import { DICT_TYPE, erpPriceInputFormatter } from '#/utils'; + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'totalPrice', + label: '合同金额', + content: (data) => erpPriceInputFormatter(data.totalPrice), + }, + { + field: 'returnTime', + label: '回款日期', + content: (data) => formatDateTime(data?.returnTime) as string, + }, + { + field: 'price', + label: '回款金额', + content: (data) => erpPriceInputFormatter(data.price), + }, + { + field: 'ownerUserName', + label: '负责人', + }, + ]; +} + +/** 详情页的基础字段 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'no', + label: '回款编号', + }, + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'contract', + label: '合同编号', + content: (data) => data?.contract?.no, + }, + { + field: 'returnTime', + label: '回款日期', + content: (data) => formatDateTime(data?.returnTime) as string, + }, + { + field: 'price', + label: '回款金额', + content: (data) => erpPriceInputFormatter(data.price), + }, + { + field: 'returnType', + label: '回款方式', + content: (data) => + h(DictTag, { + type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE, + value: data?.returnType, + }), + }, + { + field: 'remark', + label: '备注', + }, + ]; +} + +/** 详情列表的字段 */ +export function useDetailListColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '回款编号', + field: 'no', + minWidth: 150, + fixed: 'left', + }, + { + title: '客户名称', + field: 'customerName', + minWidth: 150, + }, + { + title: '合同编号', + field: 'contract.no', + minWidth: 150, + }, + { + title: '回款日期', + field: 'returnTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '回款金额(元)', + field: 'price', + minWidth: 150, + formatter: 'formatNumber', + }, + { + title: '回款方式', + field: 'returnType', + minWidth: 150, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE }, + }, + }, + { + title: '负责人', + field: 'ownerUserName', + minWidth: 150, + }, + { + title: '备注', + field: 'remark', + minWidth: 150, + }, + { + title: '回款状态', + field: 'auditStatus', + minWidth: 100, + fixed: 'right', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, + }, + }, + { + title: '操作', + field: 'actions', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/crm/receivable/modules/detail-info.vue b/apps/web-antd/src/views/crm/receivable/modules/detail-info.vue index b22365cbc..884a1ce49 100644 --- a/apps/web-antd/src/views/crm/receivable/modules/detail-info.vue +++ b/apps/web-antd/src/views/crm/receivable/modules/detail-info.vue @@ -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 '../data'; +import { useDetailBaseSchema } from './detail-data'; defineProps<{ receivable: CrmReceivableApi.Receivable; // 收款信息 diff --git a/apps/web-antd/src/views/crm/receivable/modules/detail-list.vue b/apps/web-antd/src/views/crm/receivable/modules/detail-list.vue index 9b0679e99..a892185cb 100644 --- a/apps/web-antd/src/views/crm/receivable/modules/detail-list.vue +++ b/apps/web-antd/src/views/crm/receivable/modules/detail-list.vue @@ -13,7 +13,7 @@ import { } from '#/api/crm/receivable'; import { $t } from '#/locales'; -import { useDetailListColumns } from '../data'; +import { useDetailListColumns } from './detail-data'; import Form from './form.vue'; const props = defineProps<{ diff --git a/apps/web-antd/src/views/crm/receivable/modules/detail.vue b/apps/web-antd/src/views/crm/receivable/modules/detail.vue index 24f95b16e..b249fb332 100644 --- a/apps/web-antd/src/views/crm/receivable/modules/detail.vue +++ b/apps/web-antd/src/views/crm/receivable/modules/detail.vue @@ -16,7 +16,7 @@ import { BizTypeEnum } from '#/api/crm/permission'; import { getReceivable } from '#/api/crm/receivable'; import { useDescription } from '#/components/description'; -import { useDetailSchema } from '../data'; +import { useDetailSchema } from './detail-data'; import ReceivableForm from './form.vue'; const PermissionList = defineAsyncComponent( diff --git a/apps/web-antd/src/views/crm/receivable/plan/data.ts b/apps/web-antd/src/views/crm/receivable/plan/data.ts index 26453cb39..519c41f13 100644 --- a/apps/web-antd/src/views/crm/receivable/plan/data.ts +++ b/apps/web-antd/src/views/crm/receivable/plan/data.ts @@ -1,14 +1,7 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; -import type { DescriptionItemSchema } from '#/components/description'; - -import { h } from 'vue'; - -import { formatDateTime } from '@vben/utils'; import { getCustomerSimpleList } from '#/api/crm/customer'; -import { DictTag } from '#/components/dict-tag'; -import { erpPriceInputFormatter } from '#/utils'; import { DICT_TYPE, getDictOptions } from '#/utils/dict'; /** 新增/修改的表单 */ @@ -235,135 +228,3 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { }, ]; } - -/** 详情页的字段 */ -export function useDetailSchema(): DescriptionItemSchema[] { - return [ - { - field: 'customerName', - label: '客户名称', - }, - { - field: 'totalPrice', - label: '合同金额', - content: (data) => erpPriceInputFormatter(data.totalPrice), - }, - { - field: 'returnTime', - label: '回款日期', - content: (data) => formatDateTime(data?.returnTime) as string, - }, - { - field: 'price', - label: '回款金额', - content: (data) => erpPriceInputFormatter(data.price), - }, - { - field: 'ownerUserName', - label: '负责人', - }, - ]; -} - -/** 详情页的基础字段 */ -export function useDetailBaseSchema(): DescriptionItemSchema[] { - return [ - { - field: 'no', - label: '回款编号', - }, - { - field: 'customerName', - label: '客户名称', - }, - { - field: 'contract', - label: '合同编号', - content: (data) => data?.contract?.no, - }, - { - field: 'returnTime', - label: '回款日期', - content: (data) => formatDateTime(data?.returnTime) as string, - }, - { - field: 'price', - label: '回款金额', - content: (data) => erpPriceInputFormatter(data.price), - }, - { - field: 'returnType', - label: '回款方式', - content: (data) => - h(DictTag, { - type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE, - value: data?.returnType, - }), - }, - { - field: 'remark', - label: '备注', - }, - ]; -} - -/** 详情列表的字段 */ -export function useDetailListColumns(): VxeTableGridOptions['columns'] { - return [ - { - title: '客户名称', - field: 'customerName', - minWidth: 150, - }, - { - title: '合同编号', - field: 'contractNo', - minWidth: 150, - }, - { - title: '期数', - field: 'period', - minWidth: 150, - }, - { - title: '计划回款(元)', - field: 'price', - minWidth: 150, - formatter: 'formatNumber', - }, - { - title: '计划回款日期', - field: 'returnTime', - minWidth: 150, - formatter: 'formatDateTime', - }, - { - title: '提前几天提醒', - field: 'remindDays', - minWidth: 150, - }, - { - title: '提醒日期', - field: 'remindTime', - minWidth: 150, - formatter: 'formatDateTime', - }, - { - title: '负责人', - field: 'ownerUserName', - minWidth: 150, - }, - { - title: '备注', - field: 'remark', - minWidth: 150, - }, - { - title: '操作', - field: 'actions', - width: 240, - fixed: 'right', - slots: { default: 'actions' }, - }, - ]; -} diff --git a/apps/web-antd/src/views/crm/receivable/plan/modules/detail-data.ts b/apps/web-antd/src/views/crm/receivable/plan/modules/detail-data.ts new file mode 100644 index 000000000..8947dcef0 --- /dev/null +++ b/apps/web-antd/src/views/crm/receivable/plan/modules/detail-data.ts @@ -0,0 +1,141 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; +import { DICT_TYPE, erpPriceInputFormatter } from '#/utils'; + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'totalPrice', + label: '合同金额', + content: (data) => erpPriceInputFormatter(data.totalPrice), + }, + { + field: 'returnTime', + label: '回款日期', + content: (data) => formatDateTime(data?.returnTime) as string, + }, + { + field: 'price', + label: '回款金额', + content: (data) => erpPriceInputFormatter(data.price), + }, + { + field: 'ownerUserName', + label: '负责人', + }, + ]; +} + +/** 详情页的基础字段 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'no', + label: '回款编号', + }, + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'contract', + label: '合同编号', + content: (data) => data?.contract?.no, + }, + { + field: 'returnTime', + label: '回款日期', + content: (data) => formatDateTime(data?.returnTime) as string, + }, + { + field: 'price', + label: '回款金额', + content: (data) => erpPriceInputFormatter(data.price), + }, + { + field: 'returnType', + label: '回款方式', + content: (data) => + h(DictTag, { + type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE, + value: data?.returnType, + }), + }, + { + field: 'remark', + label: '备注', + }, + ]; +} + +/** 详情列表的字段 */ +export function useDetailListColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '客户名称', + field: 'customerName', + minWidth: 150, + }, + { + title: '合同编号', + field: 'contractNo', + minWidth: 150, + }, + { + title: '期数', + field: 'period', + minWidth: 150, + }, + { + title: '计划回款(元)', + field: 'price', + minWidth: 150, + formatter: 'formatNumber', + }, + { + title: '计划回款日期', + field: 'returnTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '提前几天提醒', + field: 'remindDays', + minWidth: 150, + }, + { + title: '提醒日期', + field: 'remindTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '负责人', + field: 'ownerUserName', + minWidth: 150, + }, + { + title: '备注', + field: 'remark', + minWidth: 150, + }, + { + title: '操作', + field: 'actions', + width: 240, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/crm/receivable/plan/modules/detail-info.vue b/apps/web-antd/src/views/crm/receivable/plan/modules/detail-info.vue index b22c59016..530b4e1a6 100644 --- a/apps/web-antd/src/views/crm/receivable/plan/modules/detail-info.vue +++ b/apps/web-antd/src/views/crm/receivable/plan/modules/detail-info.vue @@ -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 '../data'; +import { useDetailBaseSchema } from './detail-data'; defineProps<{ receivablePlan: CrmReceivablePlanApi.Plan; // 收款计划信息 diff --git a/apps/web-antd/src/views/crm/receivable/plan/modules/detail-list.vue b/apps/web-antd/src/views/crm/receivable/plan/modules/detail-list.vue index 06cae9bd2..8929bb491 100644 --- a/apps/web-antd/src/views/crm/receivable/plan/modules/detail-list.vue +++ b/apps/web-antd/src/views/crm/receivable/plan/modules/detail-list.vue @@ -15,7 +15,7 @@ import { import { $t } from '#/locales'; import ReceivableForm from '../../modules/form.vue'; -import { useDetailListColumns } from '../data'; +import { useDetailListColumns } from './detail-data'; import Form from './form.vue'; const props = defineProps<{ diff --git a/apps/web-antd/src/views/crm/receivable/plan/modules/detail.vue b/apps/web-antd/src/views/crm/receivable/plan/modules/detail.vue index edc8eefdb..04be42a3e 100644 --- a/apps/web-antd/src/views/crm/receivable/plan/modules/detail.vue +++ b/apps/web-antd/src/views/crm/receivable/plan/modules/detail.vue @@ -16,7 +16,7 @@ import { BizTypeEnum } from '#/api/crm/permission'; import { getReceivablePlan } from '#/api/crm/receivable/plan'; import { useDescription } from '#/components/description'; -import { useDetailSchema } from '../data'; +import { useDetailSchema } from './detail-data'; import ReceivablePlanForm from './form.vue'; const PermissionList = defineAsyncComponent( From 231de628d3f7ceae8e257fee88a6eca83e6b0e34 Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Fri, 6 Jun 2025 12:33:02 +0800 Subject: [PATCH 03/59] perf: operateLog --- .../src/components/operate-log/index.ts | 6 ++++++ .../components/operate-log/operate-log.vue | 21 ++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/apps/web-antd/src/components/operate-log/index.ts b/apps/web-antd/src/components/operate-log/index.ts index cf38b5e70..a134eaaab 100644 --- a/apps/web-antd/src/components/operate-log/index.ts +++ b/apps/web-antd/src/components/operate-log/index.ts @@ -1,3 +1,9 @@ +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'; diff --git a/apps/web-antd/src/components/operate-log/operate-log.vue b/apps/web-antd/src/components/operate-log/operate-log.vue index e1e9eb448..2493bc5d5 100644 --- a/apps/web-antd/src/components/operate-log/operate-log.vue +++ b/apps/web-antd/src/components/operate-log/operate-log.vue @@ -1,7 +1,9 @@ - diff --git a/apps/web-antd/src/views/crm/customer/modules/detail.vue b/apps/web-antd/src/views/crm/customer/modules/detail.vue index 1ee9c00db..9a3452ae9 100644 --- a/apps/web-antd/src/views/crm/customer/modules/detail.vue +++ b/apps/web-antd/src/views/crm/customer/modules/detail.vue @@ -2,7 +2,7 @@ import type { CrmCustomerApi } from '#/api/crm/customer'; import type { SystemOperateLogApi } from '#/api/system/operate-log'; -import { defineAsyncComponent, onMounted, ref } from 'vue'; +import { onMounted, ref } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { confirm, Page, useVbenModal } from '@vben/common-ui'; @@ -14,56 +14,23 @@ import { getCustomer, updateCustomerDealStatus } from '#/api/crm/customer'; import { getOperateLogPage } from '#/api/crm/operateLog'; import { BizTypeEnum } from '#/api/crm/permission'; import { useDescription } from '#/components/description'; +import { AsyncOperateLog } from '#/components/operate-log'; +import { BusinessDetailsList } from '#/views/crm/business'; +import { ContactDetailsList } from '#/views/crm/contact'; +import { ContractDetailsList } from '#/views/crm/contract'; +import { + CustomerDetailsInfo, + CustomerForm, + DistributeForm, +} from '#/views/crm/customer'; +import { FollowUp } from '#/views/crm/followup'; +import { PermissionList, TransferForm } from '#/views/crm/permission'; +import { + ReceivableDetailsList, + ReceivablePlanDetailsList, +} from '#/views/crm/receivable'; -import { useDetailSchema } from '../data'; - -const BusinessList = defineAsyncComponent( - () => import('#/views/crm/business/modules/detail-list.vue'), -); - -const CustomerDetailsInfo = defineAsyncComponent( - () => import('./detail-info.vue'), -); - -const ContactDetailsList = defineAsyncComponent( - () => import('#/views/crm/contact/modules/detail-list.vue'), -); - -const ContractDetailsList = defineAsyncComponent( - () => import('#/views/crm/contract/modules/detail-list.vue'), -); - -const CustomerForm = defineAsyncComponent( - () => import('#/views/crm/customer/modules/form.vue'), -); - -const DistributeForm = defineAsyncComponent( - () => import('#/views/crm/customer/poolConfig/distribute-form.vue'), -); - -const FollowUp = defineAsyncComponent( - () => import('#/views/crm/followup/index.vue'), -); - -const PermissionList = defineAsyncComponent( - () => import('#/views/crm/permission/modules/permission-list.vue'), -); - -const TransferForm = defineAsyncComponent( - () => import('#/views/crm/permission/modules/transfer-form.vue'), -); - -const OperateLog = defineAsyncComponent( - () => import('#/components/operate-log'), -); - -const ReceivableDetailsList = defineAsyncComponent( - () => import('#/views/crm/receivable/modules/detail-list.vue'), -); - -const ReceivablePlanDetailsList = defineAsyncComponent( - () => import('#/views/crm/receivable/plan/modules/detail-list.vue'), -); +import { useDetailSchema } from './detail-data'; const loading = ref(false); @@ -278,7 +245,7 @@ onMounted(() => { /> - { - + diff --git a/apps/web-antd/src/views/crm/followup/index.ts b/apps/web-antd/src/views/crm/followup/index.ts new file mode 100644 index 000000000..560abe8c7 --- /dev/null +++ b/apps/web-antd/src/views/crm/followup/index.ts @@ -0,0 +1,3 @@ +import { defineAsyncComponent } from 'vue'; + +export const FollowUp = defineAsyncComponent(() => import('./index.vue')); diff --git a/apps/web-antd/src/views/crm/permission/index.ts b/apps/web-antd/src/views/crm/permission/index.ts index a175bff47..4d9f097cd 100644 --- a/apps/web-antd/src/views/crm/permission/index.ts +++ b/apps/web-antd/src/views/crm/permission/index.ts @@ -1,2 +1,13 @@ -export { default as PermissionList } from './modules/permission-list.vue'; -export { default as TransferForm } from './modules/transfer-form.vue'; +import { defineAsyncComponent } from 'vue'; + +export const PermissionList = defineAsyncComponent( + () => import('./modules/permission-list.vue'), +); + +export const PermissionForm = defineAsyncComponent( + () => import('./modules/permission-form.vue'), +); + +export const TransferForm = defineAsyncComponent( + () => import('./modules/transfer-form.vue'), +); diff --git a/apps/web-antd/src/views/crm/product/index.ts b/apps/web-antd/src/views/crm/product/index.ts new file mode 100644 index 000000000..09e7043b1 --- /dev/null +++ b/apps/web-antd/src/views/crm/product/index.ts @@ -0,0 +1,17 @@ +import { defineAsyncComponent } from 'vue'; + +export const ProductDetailsInfo = defineAsyncComponent( + () => import('./modules/detail-info.vue'), +); + +export const ProductForm = defineAsyncComponent( + () => import('./modules/form.vue'), +); + +export const ProductDetails = defineAsyncComponent( + () => import('./modules/detail.vue'), +); + +export const ProductDetailsList = defineAsyncComponent( + () => import('./modules/detail-list.vue'), +); diff --git a/apps/web-antd/src/views/crm/product/modules/detail.vue b/apps/web-antd/src/views/crm/product/modules/detail.vue index 71f756098..1255e4671 100644 --- a/apps/web-antd/src/views/crm/product/modules/detail.vue +++ b/apps/web-antd/src/views/crm/product/modules/detail.vue @@ -2,7 +2,7 @@ import type { CrmProductApi } from '#/api/crm/product'; import type { SystemOperateLogApi } from '#/api/system/operate-log'; -import { defineAsyncComponent, onMounted, ref } from 'vue'; +import { onMounted, ref } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { Page } from '@vben/common-ui'; @@ -14,17 +14,11 @@ import { getOperateLogPage } from '#/api/crm/operateLog'; import { BizTypeEnum } from '#/api/crm/permission'; import { getProduct } from '#/api/crm/product'; import { useDescription } from '#/components/description'; +import { AsyncOperateLog } from '#/components/operate-log'; +import { ProductDetailsInfo } from '#/views/crm/product'; import { useDetailSchema } from './detail-data'; -const ProductDetailsInfo = defineAsyncComponent( - () => import('./detail-info.vue'), -); - -const OperateLog = defineAsyncComponent( - () => import('#/components/operate-log'), -); - const loading = ref(false); const route = useRoute(); @@ -87,7 +81,7 @@ onMounted(() => { - + diff --git a/apps/web-antd/src/views/crm/receivable/index.ts b/apps/web-antd/src/views/crm/receivable/index.ts new file mode 100644 index 000000000..bd8a2aed4 --- /dev/null +++ b/apps/web-antd/src/views/crm/receivable/index.ts @@ -0,0 +1,29 @@ +import { defineAsyncComponent } from 'vue'; + +export const ReceivableDetailsInfo = defineAsyncComponent( + () => import('./modules/detail-info.vue'), +); + +export const ReceivableForm = defineAsyncComponent( + () => import('./modules/form.vue'), +); + +export const ReceivableDetails = defineAsyncComponent( + () => import('./modules/detail.vue'), +); + +export const ReceivableDetailsList = defineAsyncComponent( + () => import('./modules/detail-list.vue'), +); + +export const ReceivablePlanDetailsInfo = defineAsyncComponent( + () => import('./plan/modules/detail-info.vue'), +); + +export const ReceivablePlanDetailsList = defineAsyncComponent( + () => import('./plan/modules/detail-list.vue'), +); + +export const ReceivablePlanDetails = defineAsyncComponent( + () => import('./plan/modules/detail.vue'), +); diff --git a/apps/web-antd/src/views/crm/receivable/modules/detail.vue b/apps/web-antd/src/views/crm/receivable/modules/detail.vue index b249fb332..8fddd38b5 100644 --- a/apps/web-antd/src/views/crm/receivable/modules/detail.vue +++ b/apps/web-antd/src/views/crm/receivable/modules/detail.vue @@ -2,7 +2,7 @@ import type { CrmReceivableApi } from '#/api/crm/receivable'; import type { SystemOperateLogApi } from '#/api/system/operate-log'; -import { computed, defineAsyncComponent, onMounted, ref } from 'vue'; +import { computed, onMounted, ref } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { Page, useVbenModal } from '@vben/common-ui'; @@ -15,22 +15,13 @@ import { getOperateLogPage } from '#/api/crm/operateLog'; import { BizTypeEnum } from '#/api/crm/permission'; import { getReceivable } from '#/api/crm/receivable'; import { useDescription } from '#/components/description'; +import { AsyncOperateLog } from '#/components/operate-log'; +import { PermissionList } from '#/views/crm/permission'; +import { ReceivableDetailsInfo } from '#/views/crm/receivable'; import { useDetailSchema } from './detail-data'; import ReceivableForm from './form.vue'; -const PermissionList = defineAsyncComponent( - () => import('#/views/crm/permission/modules/permission-list.vue'), -); - -const OperateLog = defineAsyncComponent( - () => import('#/components/operate-log'), -); - -const ReceivableDetailsInfo = defineAsyncComponent( - () => import('./detail-info.vue'), -); - const loading = ref(false); const route = useRoute(); @@ -131,7 +122,7 @@ onMounted(() => { /> - + diff --git a/apps/web-antd/src/views/crm/receivable/modules/form.vue b/apps/web-antd/src/views/crm/receivable/modules/form.vue index e92610c25..287804065 100644 --- a/apps/web-antd/src/views/crm/receivable/modules/form.vue +++ b/apps/web-antd/src/views/crm/receivable/modules/form.vue @@ -64,7 +64,7 @@ const [Modal, modalApi] = useVbenModal({ return; } // 加载数据 - const data = modalApi.getData(); + const data = modalApi.getData(); if (!data) { return; } diff --git a/apps/web-antd/src/views/crm/receivable/plan/modules/detail.vue b/apps/web-antd/src/views/crm/receivable/plan/modules/detail.vue index 04be42a3e..573f97cb4 100644 --- a/apps/web-antd/src/views/crm/receivable/plan/modules/detail.vue +++ b/apps/web-antd/src/views/crm/receivable/plan/modules/detail.vue @@ -2,7 +2,7 @@ import type { CrmReceivablePlanApi } from '#/api/crm/receivable/plan'; import type { SystemOperateLogApi } from '#/api/system/operate-log'; -import { computed, defineAsyncComponent, onMounted, ref } from 'vue'; +import { computed, onMounted, ref } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { Page, useVbenModal } from '@vben/common-ui'; @@ -15,22 +15,13 @@ import { getOperateLogPage } from '#/api/crm/operateLog'; import { BizTypeEnum } from '#/api/crm/permission'; import { getReceivablePlan } from '#/api/crm/receivable/plan'; import { useDescription } from '#/components/description'; +import { AsyncOperateLog } from '#/components/operate-log'; +import { PermissionList } from '#/views/crm/permission'; +import { ReceivablePlanDetailsInfo } from '#/views/crm/receivable'; import { useDetailSchema } from './detail-data'; import ReceivablePlanForm from './form.vue'; -const PermissionList = defineAsyncComponent( - () => import('#/views/crm/permission/modules/permission-list.vue'), -); - -const OperateLog = defineAsyncComponent( - () => import('#/components/operate-log'), -); - -const ReceivablePlanDetailsInfo = defineAsyncComponent( - () => import('./detail-info.vue'), -); - const loading = ref(false); const route = useRoute(); @@ -135,7 +126,7 @@ onMounted(() => { /> - + From 082bb7167fe791488cddc4fe4eacc0d9f4cca7f9 Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Fri, 6 Jun 2025 14:35:02 +0800 Subject: [PATCH 05/59] fix: system dept --- apps/web-antd/src/views/system/dept/index.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web-antd/src/views/system/dept/index.vue b/apps/web-antd/src/views/system/dept/index.vue index db28fffce..f62e6db7a 100644 --- a/apps/web-antd/src/views/system/dept/index.vue +++ b/apps/web-antd/src/views/system/dept/index.vue @@ -119,7 +119,7 @@ onMounted(async () => { diff --git a/apps/web-antd/src/views/infra/demo/demo03/erp/modules/demo03-course-list.vue b/apps/web-antd/src/views/infra/demo/demo03/erp/modules/demo03-course-list.vue index 86cc2b468..c92e81003 100644 --- a/apps/web-antd/src/views/infra/demo/demo03/erp/modules/demo03-course-list.vue +++ b/apps/web-antd/src/views/infra/demo/demo03/erp/modules/demo03-course-list.vue @@ -74,7 +74,7 @@ async function onDeleteBatch() { key: 'action_process_msg', }); try { - await deleteDemo03CourseList(deleteIds.value); + await deleteDemo03CourseList(checkedIds.value); message.success($t('ui.actionMessage.deleteSuccess')); onRefresh(); } finally { @@ -82,13 +82,13 @@ async function onDeleteBatch() { } } -const deleteIds = ref([]); // 待删除学生课程 ID -function setDeleteIds({ +const checkedIds = ref([]); +function handleRowCheckboxChange({ records, }: { records: Demo03StudentApi.Demo03Course[]; }) { - deleteIds.value = records.map((item) => item.id); + checkedIds.value = records.map((item) => item.id); } /** 表格操作按钮的回调函数 */ @@ -143,8 +143,8 @@ const [Grid, gridApi] = useVbenVxeGrid({ }, } as VxeTableGridOptions, gridEvents: { - checkboxAll: setDeleteIds, - checkboxChange: setDeleteIds, + checkboxAll: handleRowCheckboxChange, + checkboxChange: handleRowCheckboxChange, }, }); @@ -184,7 +184,7 @@ watch( type="primary" danger class="ml-2" - :disabled="isEmpty(deleteIds)" + :disabled="isEmpty(checkedIds)" @click="onDeleteBatch" v-access:code="['infra:demo03-student:delete']" > diff --git a/apps/web-antd/src/views/infra/demo/demo03/erp/modules/demo03-grade-list.vue b/apps/web-antd/src/views/infra/demo/demo03/erp/modules/demo03-grade-list.vue index 25664ba4e..a752b5991 100644 --- a/apps/web-antd/src/views/infra/demo/demo03/erp/modules/demo03-grade-list.vue +++ b/apps/web-antd/src/views/infra/demo/demo03/erp/modules/demo03-grade-list.vue @@ -74,7 +74,7 @@ async function onDeleteBatch() { key: 'action_process_msg', }); try { - await deleteDemo03GradeList(deleteIds.value); + await deleteDemo03GradeList(checkedIds.value); message.success($t('ui.actionMessage.deleteSuccess')); onRefresh(); } finally { @@ -82,13 +82,13 @@ async function onDeleteBatch() { } } -const deleteIds = ref([]); // 待删除学生班级 ID -function setDeleteIds({ +const checkedIds = ref([]); +function handleRowCheckboxChange({ records, }: { records: Demo03StudentApi.Demo03Grade[]; }) { - deleteIds.value = records.map((item) => item.id); + checkedIds.value = records.map((item) => item.id); } /** 表格操作按钮的回调函数 */ @@ -143,8 +143,8 @@ const [Grid, gridApi] = useVbenVxeGrid({ }, } as VxeTableGridOptions, gridEvents: { - checkboxAll: setDeleteIds, - checkboxChange: setDeleteIds, + checkboxAll: handleRowCheckboxChange, + checkboxChange: handleRowCheckboxChange, }, }); @@ -184,7 +184,7 @@ watch( type="primary" danger class="ml-2" - :disabled="isEmpty(deleteIds)" + :disabled="isEmpty(checkedIds)" @click="onDeleteBatch" v-access:code="['infra:demo03-student:delete']" > diff --git a/apps/web-antd/src/views/infra/demo/demo03/inner/index.vue b/apps/web-antd/src/views/infra/demo/demo03/inner/index.vue index 24d768b26..f003d8c9c 100644 --- a/apps/web-antd/src/views/infra/demo/demo03/inner/index.vue +++ b/apps/web-antd/src/views/infra/demo/demo03/inner/index.vue @@ -74,7 +74,7 @@ async function onDeleteBatch() { key: 'action_process_msg', }); try { - await deleteDemo03StudentList(deleteIds.value); + await deleteDemo03StudentList(checkedIds.value); message.success($t('ui.actionMessage.deleteSuccess')); onRefresh(); } finally { @@ -82,13 +82,13 @@ async function onDeleteBatch() { } } -const deleteIds = ref([]); // 待删除学生 ID -function setDeleteIds({ +const checkedIds = ref([]); +function handleRowCheckboxChange({ records, }: { records: Demo03StudentApi.Demo03Student[]; }) { - deleteIds.value = records.map((item) => item.id); + checkedIds.value = records.map((item) => item.id); } /** 导出表格 */ @@ -145,8 +145,8 @@ const [Grid, gridApi] = useVbenVxeGrid({ }, } as VxeTableGridOptions, gridEvents: { - checkboxAll: setDeleteIds, - checkboxChange: setDeleteIds, + checkboxAll: handleRowCheckboxChange, + checkboxChange: handleRowCheckboxChange, }, }); @@ -190,7 +190,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ type="primary" danger class="ml-2" - :disabled="isEmpty(deleteIds)" + :disabled="isEmpty(checkedIds)" @click="onDeleteBatch" v-access:code="['infra:demo03-student:delete']" > diff --git a/apps/web-antd/src/views/infra/demo/demo03/normal/index.vue b/apps/web-antd/src/views/infra/demo/demo03/normal/index.vue index 0f2fa9492..85d19daa2 100644 --- a/apps/web-antd/src/views/infra/demo/demo03/normal/index.vue +++ b/apps/web-antd/src/views/infra/demo/demo03/normal/index.vue @@ -61,13 +61,13 @@ async function onDelete(row: Demo03StudentApi.Demo03Student) { } } -const deleteIds = ref([]); // 待删除学生 ID -function setDeleteIds({ +const checkedIds = ref([]); +function handleRowCheckboxChange({ records, }: { records: Demo03StudentApi.Demo03Student[]; }) { - deleteIds.value = records.map((item) => item.id); + checkedIds.value = records.map((item) => item.id); } /** 批量删除学生 */ async function onDeleteBatch() { @@ -77,7 +77,7 @@ async function onDeleteBatch() { key: 'action_process_msg', }); try { - await deleteDemo03StudentList(deleteIds.value); + await deleteDemo03StudentList(checkedIds.value); message.success($t('ui.actionMessage.deleteSuccess')); onRefresh(); } finally { @@ -139,8 +139,8 @@ const [Grid, gridApi] = useVbenVxeGrid({ }, } as VxeTableGridOptions, gridEvents: { - checkboxAll: setDeleteIds, - checkboxChange: setDeleteIds, + checkboxAll: handleRowCheckboxChange, + checkboxChange: handleRowCheckboxChange, }, }); @@ -173,7 +173,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ type="primary" danger class="ml-2" - :disabled="isEmpty(deleteIds)" + :disabled="isEmpty(checkedIds)" @click="onDeleteBatch" v-access:code="['infra:demo03-student:delete']" > diff --git a/apps/web-antd/src/views/infra/demo/general/demo01/index.vue b/apps/web-antd/src/views/infra/demo/general/demo01/index.vue index 3684e3b2c..b76e8f0db 100644 --- a/apps/web-antd/src/views/infra/demo/general/demo01/index.vue +++ b/apps/web-antd/src/views/infra/demo/general/demo01/index.vue @@ -122,7 +122,7 @@ async function onDeleteBatch() { key: 'action_process_msg', }); try { - await deleteDemo01ContactList(deleteIds.value); + await deleteDemo01ContactList(checkedIds.value); message.success($t('ui.actionMessage.deleteSuccess')); await getList(); } finally { @@ -130,13 +130,13 @@ async function onDeleteBatch() { } } -const deleteIds = ref([]); // 待删除示例联系人 ID -function setDeleteIds({ +const checkedIds = ref([]); +function handleRowCheckboxChange({ records, }: { records: Demo01ContactApi.Demo01Contact[]; }) { - deleteIds.value = records.map((item) => item.id); + checkedIds.value = records.map((item) => item.id); } /** 导出表格 */ @@ -241,7 +241,7 @@ onMounted(() => { type="primary" danger class="ml-2" - :disabled="isEmpty(deleteIds)" + :disabled="isEmpty(checkedIds)" @click="onDeleteBatch" v-access:code="['infra:demo01-contact:delete']" > @@ -254,8 +254,8 @@ onMounted(() => { :data="list" show-overflow :loading="loading" - @checkbox-all="setDeleteIds" - @checkbox-change="setDeleteIds" + @checkbox-all="handleRowCheckboxChange" + @checkbox-change="handleRowCheckboxChange" > diff --git a/apps/web-antd/src/views/infra/demo/general/demo03/erp/index.vue b/apps/web-antd/src/views/infra/demo/general/demo03/erp/index.vue index 2a92757a0..62385532b 100644 --- a/apps/web-antd/src/views/infra/demo/general/demo03/erp/index.vue +++ b/apps/web-antd/src/views/infra/demo/general/demo03/erp/index.vue @@ -133,7 +133,7 @@ async function onDeleteBatch() { key: 'action_process_msg', }); try { - await deleteDemo03StudentList(deleteIds.value); + await deleteDemo03StudentList(checkedIds.value); message.success($t('ui.actionMessage.deleteSuccess')); await getList(); } finally { @@ -141,13 +141,13 @@ async function onDeleteBatch() { } } -const deleteIds = ref([]); // 待删除学生 ID -function setDeleteIds({ +const checkedIds = ref([]); +function handleRowCheckboxChange({ records, }: { records: Demo03StudentApi.Demo03Student[]; }) { - deleteIds.value = records.map((item) => item.id); + checkedIds.value = records.map((item) => item.id); } /** 导出表格 */ @@ -250,7 +250,7 @@ onMounted(() => { type="primary" danger class="ml-2" - :disabled="isEmpty(deleteIds)" + :disabled="isEmpty(checkedIds)" @click="onDeleteBatch" v-access:code="['infra:demo03-student:delete']" > @@ -269,8 +269,8 @@ onMounted(() => { }" show-overflow :loading="loading" - @checkbox-all="setDeleteIds" - @checkbox-change="setDeleteIds" + @checkbox-all="handleRowCheckboxChange" + @checkbox-change="handleRowCheckboxChange" > diff --git a/apps/web-antd/src/views/infra/demo/general/demo03/erp/modules/demo03-course-list.vue b/apps/web-antd/src/views/infra/demo/general/demo03/erp/modules/demo03-course-list.vue index 1a2a8a273..47ed647c8 100644 --- a/apps/web-antd/src/views/infra/demo/general/demo03/erp/modules/demo03-course-list.vue +++ b/apps/web-antd/src/views/infra/demo/general/demo03/erp/modules/demo03-course-list.vue @@ -80,7 +80,7 @@ async function onDeleteBatch() { key: 'action_process_msg', }); try { - await deleteDemo03CourseList(deleteIds.value); + await deleteDemo03CourseList(checkedIds.value); message.success($t('ui.actionMessage.deleteSuccess')); await getList(); } finally { @@ -88,13 +88,13 @@ async function onDeleteBatch() { } } -const deleteIds = ref([]); // 待删除学生课程 ID -function setDeleteIds({ +const checkedIds = ref([]); +function handleRowCheckboxChange({ records, }: { records: Demo03StudentApi.Demo03Course[]; }) { - deleteIds.value = records.map((item) => item.id); + checkedIds.value = records.map((item) => item.id); } const loading = ref(true); // 列表的加载中 @@ -231,7 +231,7 @@ onMounted(() => { type="primary" danger class="ml-2" - :disabled="isEmpty(deleteIds)" + :disabled="isEmpty(checkedIds)" @click="onDeleteBatch" v-access:code="['infra:demo03-student:delete']" > @@ -244,8 +244,8 @@ onMounted(() => { :data="list" show-overflow :loading="loading" - @checkbox-all="setDeleteIds" - @checkbox-change="setDeleteIds" + @checkbox-all="handleRowCheckboxChange" + @checkbox-change="handleRowCheckboxChange" > diff --git a/apps/web-antd/src/views/infra/demo/general/demo03/erp/modules/demo03-grade-list.vue b/apps/web-antd/src/views/infra/demo/general/demo03/erp/modules/demo03-grade-list.vue index d3bf48fc1..0f8435c4b 100644 --- a/apps/web-antd/src/views/infra/demo/general/demo03/erp/modules/demo03-grade-list.vue +++ b/apps/web-antd/src/views/infra/demo/general/demo03/erp/modules/demo03-grade-list.vue @@ -80,7 +80,7 @@ async function onDeleteBatch() { key: 'action_process_msg', }); try { - await deleteDemo03GradeList(deleteIds.value); + await deleteDemo03GradeList(checkedIds.value); message.success($t('ui.actionMessage.deleteSuccess')); await getList(); } finally { @@ -88,13 +88,13 @@ async function onDeleteBatch() { } } -const deleteIds = ref([]); // 待删除学生班级 ID -function setDeleteIds({ +const checkedIds = ref([]); +function handleRowCheckboxChange({ records, }: { records: Demo03StudentApi.Demo03Grade[]; }) { - deleteIds.value = records.map((item) => item.id); + checkedIds.value = records.map((item) => item.id); } const loading = ref(true); // 列表的加载中 @@ -231,7 +231,7 @@ onMounted(() => { type="primary" danger class="ml-2" - :disabled="isEmpty(deleteIds)" + :disabled="isEmpty(checkedIds)" @click="onDeleteBatch" v-access:code="['infra:demo03-student:delete']" > @@ -244,8 +244,8 @@ onMounted(() => { :data="list" show-overflow :loading="loading" - @checkbox-all="setDeleteIds" - @checkbox-change="setDeleteIds" + @checkbox-all="handleRowCheckboxChange" + @checkbox-change="handleRowCheckboxChange" > diff --git a/apps/web-antd/src/views/infra/demo/general/demo03/inner/index.vue b/apps/web-antd/src/views/infra/demo/general/demo03/inner/index.vue index cd635dcef..b093a83de 100644 --- a/apps/web-antd/src/views/infra/demo/general/demo03/inner/index.vue +++ b/apps/web-antd/src/views/infra/demo/general/demo03/inner/index.vue @@ -129,7 +129,7 @@ async function onDeleteBatch() { key: 'action_process_msg', }); try { - await deleteDemo03StudentList(deleteIds.value); + await deleteDemo03StudentList(checkedIds.value); message.success($t('ui.actionMessage.deleteSuccess')); await getList(); } finally { @@ -137,13 +137,13 @@ async function onDeleteBatch() { } } -const deleteIds = ref([]); // 待删除学生 ID -function setDeleteIds({ +const checkedIds = ref([]); +function handleRowCheckboxChange({ records, }: { records: Demo03StudentApi.Demo03Student[]; }) { - deleteIds.value = records.map((item) => item.id); + checkedIds.value = records.map((item) => item.id); } /** 导出表格 */ @@ -246,7 +246,7 @@ onMounted(() => { type="primary" danger class="ml-2" - :disabled="isEmpty(deleteIds)" + :disabled="isEmpty(checkedIds)" @click="onDeleteBatch" v-access:code="['infra:demo03-student:delete']" > @@ -259,8 +259,8 @@ onMounted(() => { :data="list" show-overflow :loading="loading" - @checkbox-all="setDeleteIds" - @checkbox-change="setDeleteIds" + @checkbox-all="handleRowCheckboxChange" + @checkbox-change="handleRowCheckboxChange" > diff --git a/apps/web-antd/src/views/infra/demo/general/demo03/normal/index.vue b/apps/web-antd/src/views/infra/demo/general/demo03/normal/index.vue index efa97f4d4..75db2f158 100644 --- a/apps/web-antd/src/views/infra/demo/general/demo03/normal/index.vue +++ b/apps/web-antd/src/views/infra/demo/general/demo03/normal/index.vue @@ -123,7 +123,7 @@ async function onDeleteBatch() { key: 'action_process_msg', }); try { - await deleteDemo03StudentList(deleteIds.value); + await deleteDemo03StudentList(checkedIds.value); message.success($t('ui.actionMessage.deleteSuccess')); await getList(); } finally { @@ -131,13 +131,13 @@ async function onDeleteBatch() { } } -const deleteIds = ref([]); // 待删除学生 ID -function setDeleteIds({ +const checkedIds = ref([]); +function handleRowCheckboxChange({ records, }: { records: Demo03StudentApi.Demo03Student[]; }) { - deleteIds.value = records.map((item) => item.id); + checkedIds.value = records.map((item) => item.id); } /** 导出表格 */ @@ -240,7 +240,7 @@ onMounted(() => { type="primary" danger class="ml-2" - :disabled="isEmpty(deleteIds)" + :disabled="isEmpty(checkedIds)" @click="onDeleteBatch" v-access:code="['infra:demo03-student:delete']" > @@ -253,8 +253,8 @@ onMounted(() => { :data="list" show-overflow :loading="loading" - @checkbox-all="setDeleteIds" - @checkbox-change="setDeleteIds" + @checkbox-all="handleRowCheckboxChange" + @checkbox-change="handleRowCheckboxChange" > From ffdccfb19b80277de10f618c8137b37fa67e5134 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 6 Jun 2025 16:12:11 +0800 Subject: [PATCH 08/59] =?UTF-8?q?fix:=20TableAction=20=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=88=A0=E9=99=A4=20disabled=20=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/views/infra/demo/demo01/index.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web-antd/src/views/infra/demo/demo01/index.vue b/apps/web-antd/src/views/infra/demo/demo01/index.vue index f3f988055..7ac328ea8 100644 --- a/apps/web-antd/src/views/infra/demo/demo01/index.vue +++ b/apps/web-antd/src/views/infra/demo/demo01/index.vue @@ -5,7 +5,7 @@ import type { Demo01ContactApi } from '#/api/infra/demo/demo01'; import { ref } from 'vue'; import { Page, useVbenModal } from '@vben/common-ui'; -import { downloadFileFromBlobPart } from '@vben/utils'; +import {downloadFileFromBlobPart, isEmpty} from '@vben/utils'; import { message } from 'ant-design-vue'; @@ -151,6 +151,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ label: '批量删除', type: 'primary', danger: true, + disabled: isEmpty(checkedIds), icon: ACTION_ICON.DELETE, auth: ['infra:demo01-contact:delete'], onClick: handleDeleteBatch, From acd0677f817fe724188b6ba03d5490dcfb148834 Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Fri, 6 Jun 2025 16:24:43 +0800 Subject: [PATCH 09/59] feat: product list --- apps/web-antd/src/views/crm/business/data.ts | 6 + .../src/views/crm/business/modules/form.vue | 44 ++++- apps/web-antd/src/views/crm/contract/data.ts | 118 ++++++++--- .../src/views/crm/contract/modules/form.vue | 46 ++++- apps/web-antd/src/views/crm/product/data.ts | 57 ++++++ apps/web-antd/src/views/crm/product/index.ts | 4 + .../crm/product/modules/product-table.vue | 183 ++++++++++++++++++ 7 files changed, 425 insertions(+), 33 deletions(-) create mode 100644 apps/web-antd/src/views/crm/product/modules/product-table.vue diff --git a/apps/web-antd/src/views/crm/business/data.ts b/apps/web-antd/src/views/crm/business/data.ts index f2e5fa0e5..0cfd32130 100644 --- a/apps/web-antd/src/views/crm/business/data.ts +++ b/apps/web-antd/src/views/crm/business/data.ts @@ -90,6 +90,12 @@ export function useFormSchema(): VbenFormSchema[] { valueFormat: 'x', }, }, + { + fieldName: 'product', + label: '产品清单', + component: 'Input', + formItemClass: 'col-span-3', + }, { fieldName: 'totalProductPrice', label: '产品总金额', diff --git a/apps/web-antd/src/views/crm/business/modules/form.vue b/apps/web-antd/src/views/crm/business/modules/form.vue index 8d3b0d553..3a25fb663 100644 --- a/apps/web-antd/src/views/crm/business/modules/form.vue +++ b/apps/web-antd/src/views/crm/business/modules/form.vue @@ -13,7 +13,10 @@ import { getBusiness, updateBusiness, } from '#/api/crm/business'; +import { BizTypeEnum } from '#/api/crm/permission'; import { $t } from '#/locales'; +import { erpPriceMultiply } from '#/utils'; +import { ProductEditTable } from '#/views/crm/product'; import { useFormSchema } from '../data'; @@ -25,15 +28,37 @@ const getTitle = computed(() => { : $t('ui.actionTitle.create', ['商机']); }); +function handleUpdateProducts(products: any) { + formData.value = modalApi.getData(); + formData.value!.products = products; + if (formData.value) { + const totalProductPrice = + formData.value.products?.reduce( + (prev, curr) => prev + curr.totalPrice, + 0, + ) ?? 0; + const discountPercent = formData.value.discountPercent; + const discountPrice = + discountPercent === null + ? 0 + : erpPriceMultiply(totalProductPrice, discountPercent / 100); + const totalPrice = totalProductPrice - (discountPrice ?? 0); + formData.value!.totalProductPrice = totalProductPrice; + formData.value!.totalPrice = totalPrice; + formApi.setValues(formData.value!); + } +} + const [Form, formApi] = useVbenForm({ commonConfig: { componentProps: { class: 'w-full', }, - formItemClass: 'col-span-2', labelWidth: 120, }, - layout: 'horizontal', + // 一共3列 + wrapperClass: 'grid-cols-3', + layout: 'vertical', schema: useFormSchema(), showDefaultActions: false, }); @@ -47,6 +72,7 @@ const [Modal, modalApi] = useVbenModal({ modalApi.lock(); // 提交表单 const data = (await formApi.getValues()) as CrmBusinessApi.Business; + data.products = formData.value?.products; try { await (formData.value?.id ? updateBusiness(data) : createBusiness(data)); // 关闭并提示 @@ -80,7 +106,17 @@ const [Modal, modalApi] = useVbenModal({ diff --git a/apps/web-antd/src/views/crm/contract/data.ts b/apps/web-antd/src/views/crm/contract/data.ts index 79118dd3f..2c20e7a71 100644 --- a/apps/web-antd/src/views/crm/contract/data.ts +++ b/apps/web-antd/src/views/crm/contract/data.ts @@ -1,22 +1,32 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import { z } from '#/adapter/form'; import { getSimpleBusinessList } from '#/api/crm/business'; import { getSimpleContactList } from '#/api/crm/contact'; import { getCustomerSimpleList } from '#/api/crm/customer'; -import { floatToFixed2 } from '#/utils'; +import { getSimpleUserList } from '#/api/system/user'; +import { erpPriceMultiply, floatToFixed2 } from '#/utils'; import { DICT_TYPE } from '#/utils/dict'; /** 新增/修改的表单 */ export function useFormSchema(): VbenFormSchema[] { return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, { fieldName: 'no', label: '合同编号', component: 'Input', - rules: 'required', componentProps: { - placeholder: '请输入合同编号', + placeholder: '保存时自动生成', + disabled: () => true, }, }, { @@ -28,9 +38,22 @@ export function useFormSchema(): VbenFormSchema[] { placeholder: '请输入合同名称', }, }, + { + fieldName: 'ownerUserId', + label: '负责人', + component: 'ApiSelect', + componentProps: { + api: () => getSimpleUserList(), + fieldNames: { + label: 'nickname', + value: 'id', + }, + }, + rules: 'required', + }, { fieldName: 'customerId', - label: '客户', + label: '客户名称', component: 'ApiSelect', rules: 'required', componentProps: { @@ -42,9 +65,8 @@ export function useFormSchema(): VbenFormSchema[] { }, { fieldName: 'businessId', - label: '商机', + label: '商机名称', component: 'ApiSelect', - rules: 'required', componentProps: { api: getSimpleBusinessList, labelField: 'name', @@ -52,49 +74,53 @@ export function useFormSchema(): VbenFormSchema[] { placeholder: '请选择商机', }, }, - { - fieldName: 'totalPrice', - label: '合同金额', - component: 'InputNumber', - rules: 'required', - componentProps: { - placeholder: '请输入合同金额', - min: 0, - precision: 2, - }, - }, { fieldName: 'orderDate', - label: '下单时间', + label: '下单日期', component: 'DatePicker', rules: 'required', componentProps: { - placeholder: '请选择下单时间', + showTime: false, + format: 'YYYY-MM-DD', + valueFormat: 'x', }, }, { fieldName: 'startTime', label: '合同开始时间', component: 'DatePicker', - rules: 'required', componentProps: { - placeholder: '请选择合同开始时间', + showTime: false, + format: 'YYYY-MM-DD', + valueFormat: 'x', }, }, { fieldName: 'endTime', label: '合同结束时间', component: 'DatePicker', - rules: 'required', componentProps: { - placeholder: '请选择合同结束时间', + showTime: false, + format: 'YYYY-MM-DD', + valueFormat: 'x', + }, + }, + { + fieldName: 'signUserId', + label: '公司签约人', + component: 'ApiSelect', + componentProps: { + api: () => getSimpleUserList(), + fieldNames: { + label: 'nickname', + value: 'id', + }, }, }, { fieldName: 'signContactId', label: '客户签约人', component: 'ApiSelect', - rules: 'required', componentProps: { api: getSimpleContactList, labelField: 'name', @@ -111,6 +137,50 @@ export function useFormSchema(): VbenFormSchema[] { rows: 4, }, }, + { + fieldName: 'product', + label: '产品清单', + component: 'Input', + formItemClass: 'col-span-3', + }, + { + fieldName: 'totalProductPrice', + label: '产品总金额', + component: 'InputNumber', + componentProps: { + min: 0, + }, + }, + { + fieldName: 'discountPercent', + label: '整单折扣(%)', + component: 'InputNumber', + rules: z.number().min(0).max(100).default(0), + componentProps: { + min: 0, + precision: 2, + }, + }, + { + fieldName: 'totalPrice', + label: '折扣后金额', + component: 'InputNumber', + dependencies: { + triggerFields: ['totalProductPrice', 'discountPercent'], + disabled: () => true, + trigger(values, form) { + const discountPrice = + erpPriceMultiply( + values.totalProductPrice, + values.discountPercent / 100, + ) ?? 0; + form.setFieldValue( + 'totalPrice', + values.totalProductPrice - discountPrice, + ); + }, + }, + }, ]; } diff --git a/apps/web-antd/src/views/crm/contract/modules/form.vue b/apps/web-antd/src/views/crm/contract/modules/form.vue index 5d2b0b78a..b96f22c02 100644 --- a/apps/web-antd/src/views/crm/contract/modules/form.vue +++ b/apps/web-antd/src/views/crm/contract/modules/form.vue @@ -12,7 +12,10 @@ import { getContract, updateContract, } from '#/api/crm/contract'; +import { BizTypeEnum } from '#/api/crm/permission'; import { $t } from '#/locales'; +import { erpPriceMultiply } from '#/utils'; +import { ProductEditTable } from '#/views/crm/product'; import { useFormSchema } from '../data'; @@ -24,15 +27,37 @@ const getTitle = computed(() => { : $t('ui.actionTitle.create', ['合同']); }); +function handleUpdateProducts(products: any) { + formData.value = modalApi.getData(); + formData.value!.products = products; + if (formData.value) { + const totalProductPrice = + formData.value.products?.reduce( + (prev, curr) => prev + curr.totalPrice, + 0, + ) ?? 0; + const discountPercent = formData.value.discountPercent; + const discountPrice = + discountPercent === null + ? 0 + : erpPriceMultiply(totalProductPrice, discountPercent / 100); + const totalPrice = totalProductPrice - (discountPrice ?? 0); + formData.value!.totalProductPrice = totalProductPrice; + formData.value!.totalPrice = totalPrice; + formApi.setValues(formData.value!); + } +} + const [Form, formApi] = useVbenForm({ commonConfig: { componentProps: { class: 'w-full', }, + labelWidth: 120, }, - // 一共2列 - wrapperClass: 'grid-cols-2', - layout: 'horizontal', + // 一共3列 + wrapperClass: 'grid-cols-3', + layout: 'vertical', schema: useFormSchema(), showDefaultActions: false, }); @@ -46,6 +71,7 @@ const [Modal, modalApi] = useVbenModal({ modalApi.lock(); // 提交表单 const data = (await formApi.getValues()) as CrmContractApi.Contract; + data.products = formData.value?.products; try { await (formData.value?.id ? updateContract(data) : createContract(data)); // 关闭并提示 @@ -79,7 +105,17 @@ const [Modal, modalApi] = useVbenModal({ diff --git a/apps/web-antd/src/views/crm/product/data.ts b/apps/web-antd/src/views/crm/product/data.ts index 348afb372..fa56e96b6 100644 --- a/apps/web-antd/src/views/crm/product/data.ts +++ b/apps/web-antd/src/views/crm/product/data.ts @@ -174,3 +174,60 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { }, ]; } + +/** 代码生成表格列定义 */ +export function useProductEditTableColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'seq', title: '序号', minWidth: 50 }, + { + field: 'productId', + title: '产品名称', + minWidth: 100, + slots: { default: 'productId' }, + }, + { + field: 'productNo', + title: '条码', + minWidth: 150, + }, + { + field: 'productUnit', + title: '单位', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_PRODUCT_UNIT }, + }, + }, + { + field: 'productPrice', + title: '价格(元)', + minWidth: 100, + formatter: 'formatNumber', + }, + { + field: 'sellingPrice', + title: '售价(元)', + minWidth: 100, + slots: { default: 'sellingPrice' }, + }, + { + field: 'count', + title: '数量', + minWidth: 100, + slots: { default: 'count' }, + }, + { + field: 'totalPrice', + title: '合计', + minWidth: 100, + formatter: 'formatNumber', + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/crm/product/index.ts b/apps/web-antd/src/views/crm/product/index.ts index 09e7043b1..a9038b467 100644 --- a/apps/web-antd/src/views/crm/product/index.ts +++ b/apps/web-antd/src/views/crm/product/index.ts @@ -15,3 +15,7 @@ export const ProductDetails = defineAsyncComponent( export const ProductDetailsList = defineAsyncComponent( () => import('./modules/detail-list.vue'), ); + +export const ProductEditTable = defineAsyncComponent( + () => import('./modules/product-table.vue'), +); diff --git a/apps/web-antd/src/views/crm/product/modules/product-table.vue b/apps/web-antd/src/views/crm/product/modules/product-table.vue new file mode 100644 index 000000000..6ef206355 --- /dev/null +++ b/apps/web-antd/src/views/crm/product/modules/product-table.vue @@ -0,0 +1,183 @@ + + + From 95e7ffafe0bd5c238bfb973e5e782fb80b6f0ab5 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 6 Jun 2025 17:03:02 +0800 Subject: [PATCH 10/59] =?UTF-8?q?fix:=20TableAction=20=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=88=A0=E9=99=A4=20disabled=20=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/views/infra/demo/demo01/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web-antd/src/views/infra/demo/demo01/index.vue b/apps/web-antd/src/views/infra/demo/demo01/index.vue index 7ac328ea8..48eed5386 100644 --- a/apps/web-antd/src/views/infra/demo/demo01/index.vue +++ b/apps/web-antd/src/views/infra/demo/demo01/index.vue @@ -5,7 +5,7 @@ import type { Demo01ContactApi } from '#/api/infra/demo/demo01'; import { ref } from 'vue'; import { Page, useVbenModal } from '@vben/common-ui'; -import {downloadFileFromBlobPart, isEmpty} from '@vben/utils'; +import { downloadFileFromBlobPart, isEmpty } from '@vben/utils'; import { message } from 'ant-design-vue'; From 5e77558efd5513b2a88823289fc960d67b2b3ff6 Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Fri, 6 Jun 2025 20:44:39 +0800 Subject: [PATCH 11/59] feat: vxe add formatPast2 --- apps/web-antd/src/adapter/vxe-table.ts | 32 ++++++++++++++++++++++++++ apps/web-antd/src/utils/formatTime.ts | 32 -------------------------- apps/web-antd/src/utils/index.ts | 1 - 3 files changed, 32 insertions(+), 33 deletions(-) delete mode 100644 apps/web-antd/src/utils/formatTime.ts diff --git a/apps/web-antd/src/adapter/vxe-table.ts b/apps/web-antd/src/adapter/vxe-table.ts index c4dcb2a86..78b0d0d08 100644 --- a/apps/web-antd/src/adapter/vxe-table.ts +++ b/apps/web-antd/src/adapter/vxe-table.ts @@ -267,6 +267,38 @@ setupVbenVxeTable({ // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 // vxeUI.formats.add + + vxeUI.formats.add('formatPast2', { + tableCellFormatMethod({ cellValue }) { + if (cellValue === null || cellValue === undefined) { + return ''; + } + // 定义时间单位常量,便于维护 + const SECOND = 1000; + const MINUTE = 60 * SECOND; + const HOUR = 60 * MINUTE; + const DAY = 24 * HOUR; + + // 计算各时间单位 + const day = Math.floor(cellValue / DAY); + const hour = Math.floor((cellValue % DAY) / HOUR); + const minute = Math.floor((cellValue % HOUR) / MINUTE); + const second = Math.floor((cellValue % MINUTE) / SECOND); + + // 根据时间长短返回不同格式 + if (day > 0) { + return `${day} 天${hour} 小时 ${minute} 分钟`; + } + if (hour > 0) { + return `${hour} 小时 ${minute} 分钟`; + } + if (minute > 0) { + return `${minute} 分钟`; + } + return second > 0 ? `${second} 秒` : `${0} 秒`; + }, + }); + // add by 星语:数量格式化,例如说:金额 vxeUI.formats.add('formatNumber', { tableCellFormatMethod({ cellValue }, digits = 2) { diff --git a/apps/web-antd/src/utils/formatTime.ts b/apps/web-antd/src/utils/formatTime.ts deleted file mode 100644 index 45fe64421..000000000 --- a/apps/web-antd/src/utils/formatTime.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * 将毫秒,转换成时间字符串。例如说,xx 分钟 - * - * @param ms 毫秒 - * @returns {string} 字符串 - */ -// TODO @xingyu:这个要融合到哪里去 date 么? -export function formatPast2(ms: number): string { - // 定义时间单位常量,便于维护 - const SECOND = 1000; - const MINUTE = 60 * SECOND; - const HOUR = 60 * MINUTE; - const DAY = 24 * HOUR; - - // 计算各时间单位 - const day = Math.floor(ms / DAY); - const hour = Math.floor((ms % DAY) / HOUR); - const minute = Math.floor((ms % HOUR) / MINUTE); - const second = Math.floor((ms % MINUTE) / SECOND); - - // 根据时间长短返回不同格式 - if (day > 0) { - return `${day} 天${hour} 小时 ${minute} 分钟`; - } - if (hour > 0) { - return `${hour} 小时 ${minute} 分钟`; - } - if (minute > 0) { - return `${minute} 分钟`; - } - return second > 0 ? `${second} 秒` : `${0} 秒`; -} diff --git a/apps/web-antd/src/utils/index.ts b/apps/web-antd/src/utils/index.ts index b33984b78..eb173b636 100644 --- a/apps/web-antd/src/utils/index.ts +++ b/apps/web-antd/src/utils/index.ts @@ -2,7 +2,6 @@ export * from './constants'; export * from './dict'; export * from './download'; export * from './formatNumber'; -export * from './formatTime'; export * from './formCreate'; export * from './rangePickerProps'; export * from './routerHelper'; From 7e8f2a1328cd3a9e8e80b82816c79a908f8d19fe Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Fri, 6 Jun 2025 20:45:27 +0800 Subject: [PATCH 12/59] refactor: modal select --- .../select-modal/dept-select-modal.vue | 120 +++---- .../select-modal/user-select-modal.vue | 304 +++++++++--------- 2 files changed, 198 insertions(+), 226 deletions(-) diff --git a/apps/web-antd/src/components/select-modal/dept-select-modal.vue b/apps/web-antd/src/components/select-modal/dept-select-modal.vue index 8fc25665e..d3f469f58 100644 --- a/apps/web-antd/src/components/select-modal/dept-select-modal.vue +++ b/apps/web-antd/src/components/select-modal/dept-select-modal.vue @@ -9,7 +9,7 @@ import { ref } from 'vue'; import { useVbenModal } from '@vben/common-ui'; import { handleTree } from '@vben/utils'; -import { Button, Card, Col, Row, Tree } from 'ant-design-vue'; +import { Card, Col, Row, Tree } from 'ant-design-vue'; import { getSimpleDeptList } from '#/api/system/dept'; @@ -41,24 +41,6 @@ const emit = defineEmits<{ confirm: [deptList: SystemDeptApi.Dept[]]; }>(); -// 对话框配置 -const [Modal, modalApi] = useVbenModal({ - title: props.title, - async onOpenChange(isOpen: boolean) { - if (!isOpen) { - resetData(); - return; - } - modalApi.setState({ loading: true }); - try { - deptData.value = await getSimpleDeptList(); - deptTree.value = handleTree(deptData.value) as DataNode[]; - } finally { - modalApi.setState({ loading: false }); - } - }, - destroyOnClose: true, -}); type checkedKeys = number[] | { checked: number[]; halfChecked: number[] }; // 部门树形结构 const deptTree = ref([]); @@ -67,25 +49,56 @@ const selectedDeptIds = ref([]); // 部门数据 const deptData = ref([]); -/** 打开对话框 */ -const open = async (selectedList?: SystemDeptApi.Dept[]) => { - modalApi.open(); - // // 设置已选择的部门 - if (selectedList?.length) { - const selectedIds = selectedList - .map((dept) => dept.id) - .filter((id): id is number => id !== undefined); - selectedDeptIds.value = props.checkStrictly - ? { - checked: selectedIds, - halfChecked: [], - } - : selectedIds; - } -}; +// 对话框配置 +const [Modal, modalApi] = useVbenModal({ + async onConfirm() { + // 获取选中的部门ID + const selectedIds: number[] = Array.isArray(selectedDeptIds.value) + ? selectedDeptIds.value + : selectedDeptIds.value.checked || []; + const deptArray = deptData.value.filter((dept) => + selectedIds.includes(dept.id!), + ); + emit('confirm', deptArray); + // 关闭并提示 + await modalApi.close(); + }, + async onOpenChange(isOpen: boolean) { + if (!isOpen) { + deptTree.value = []; + selectedDeptIds.value = []; + return; + } + // 加载数据 + const data = modalApi.getData(); + if (!data) { + return; + } + modalApi.lock(); + try { + deptData.value = await getSimpleDeptList(); + deptTree.value = handleTree(deptData.value) as DataNode[]; + // // 设置已选择的部门 + if (data.selectedList?.length) { + const selectedIds = data.selectedList + .map((dept: SystemDeptApi.Dept) => dept.id) + .filter((id: number) => id !== undefined); + selectedDeptIds.value = props.checkStrictly + ? { + checked: selectedIds, + halfChecked: [], + } + : selectedIds; + } + } finally { + modalApi.unlock(); + } + }, + destroyOnClose: true, +}); /** 处理选中状态变化 */ -const handleCheck = () => { +function handleCheck() { if (!props.multiple) { // 单选模式下,只保留最后选择的节点 if (Array.isArray(selectedDeptIds.value)) { @@ -106,37 +119,10 @@ const handleCheck = () => { } } } -}; - -/** 提交选择 */ -const handleConfirm = async () => { - // 获取选中的部门ID - const selectedIds: number[] = Array.isArray(selectedDeptIds.value) - ? selectedDeptIds.value - : selectedDeptIds.value.checked || []; - const deptArray = deptData.value.filter((dept) => - selectedIds.includes(dept.id!), - ); - // 关闭并提示 - await modalApi.close(); - emit('confirm', deptArray); -}; - -const handleCancel = () => { - modalApi.close(); -}; - -/** 重置数据 */ -const resetData = () => { - deptTree.value = []; - selectedDeptIds.value = []; -}; - -/** 提供 open 方法,用于打开对话框 */ -defineExpose({ open }); +} diff --git a/apps/web-antd/src/components/select-modal/user-select-modal.vue b/apps/web-antd/src/components/select-modal/user-select-modal.vue index e55cca14c..23fce5aec 100644 --- a/apps/web-antd/src/components/select-modal/user-select-modal.vue +++ b/apps/web-antd/src/components/select-modal/user-select-modal.vue @@ -17,7 +17,6 @@ import { message, Pagination, Row, - Spin, Transfer, Tree, } from 'ant-design-vue'; @@ -66,16 +65,66 @@ const expandedKeys = ref([]); const selectedDeptId = ref(); const deptSearchKeys = ref(''); -// 加载状态 -const loading = ref(false); - // 用户数据管理 const userList = ref([]); // 存储所有已知用户 const selectedUserIds = ref([]); +// 弹窗配置 +const [Modal, modalApi] = useVbenModal({ + onCancel: handleCancel, + onClosed: handleClosed, + async onOpenChange(isOpen: boolean) { + if (!isOpen) { + resetData(); + return; + } + // 加载数据 + const data = modalApi.getData(); + if (!data) { + return; + } + modalApi.lock(); + try { + // 加载部门数据 + const deptData = await getSimpleDeptList(); + deptList.value = deptData; + const treeData = handleTree(deptData); + deptTree.value = treeData.map((node) => processDeptNode(node)); + expandedKeys.value = deptTree.value.map((node) => node.key); + + // 加载初始用户数据 + await loadUserData(1, leftListState.value.pagination.pageSize); + + // 设置已选用户 + if (data.userIds?.length) { + selectedUserIds.value = data.userIds.map(String); + // 加载已选用户的完整信息 TODO 目前接口暂不支持 多个用户ID 查询, 需要后端支持 + const { list } = await getUserPage({ + pageNo: 1, + pageSize: 100, // 临时使用固定值确保能加载所有已选用户 + userIds: data.userIds, + }); + // 使用 Map 来去重,以用户 ID 为 key + const userMap = new Map(userList.value.map((user) => [user.id, user])); + list.forEach((user) => { + if (!userMap.has(user.id)) { + userMap.set(user.id, user); + } + }); + userList.value = [...userMap.values()]; + updateRightListData(); + } + + modalApi.open(); + } finally { + modalApi.unlock(); + } + }, + destroyOnClose: true, +}); + // 左侧列表状态 const leftListState = ref({ - loading: false, searchValue: '', dataSource: [] as SystemUserApi.User[], pagination: { @@ -145,8 +194,7 @@ const filteredDeptTree = computed(() => { }); // 加载用户数据 -const loadUserData = async (pageNo: number, pageSize: number) => { - leftListState.value.loading = true; +async function loadUserData(pageNo: number, pageSize: number) { try { const { list, total } = await getUserPage({ pageNo, @@ -167,13 +215,11 @@ const loadUserData = async (pageNo: number, pageSize: number) => { if (newUsers.length > 0) { userList.value.push(...newUsers); } - } finally { - leftListState.value.loading = false; - } -}; + } finally {} +} // 更新右侧列表数据 -const updateRightListData = () => { +function updateRightListData() { // 使用 Set 来去重选中的用户ID const uniqueSelectedIds = new Set(selectedUserIds.value); @@ -202,22 +248,22 @@ const updateRightListData = () => { const endIndex = startIndex + pageSize; rightListState.value.dataSource = filteredUsers.slice(startIndex, endIndex); -}; +} // 处理左侧分页变化 -const handleLeftPaginationChange = async (page: number, pageSize: number) => { +async function handleLeftPaginationChange(page: number, pageSize: number) { await loadUserData(page, pageSize); -}; +} // 处理右侧分页变化 -const handleRightPaginationChange = (page: number, pageSize: number) => { +function handleRightPaginationChange(page: number, pageSize: number) { rightListState.value.pagination.current = page; rightListState.value.pagination.pageSize = pageSize; updateRightListData(); -}; +} // 处理用户搜索 -const handleUserSearch = async (direction: string, value: string) => { +async function handleUserSearch(direction: string, value: string) { if (direction === 'left') { leftListState.value.searchValue = value; leftListState.value.pagination.current = 1; @@ -227,18 +273,18 @@ const handleUserSearch = async (direction: string, value: string) => { rightListState.value.pagination.current = 1; updateRightListData(); } -}; +} // 处理用户选择变化 -const handleUserChange = (targetKeys: string[]) => { +function handleUserChange(targetKeys: string[]) { // 使用 Set 来去重选中的用户ID selectedUserIds.value = [...new Set(targetKeys)]; emit('update:value', selectedUserIds.value.map(Number)); updateRightListData(); -}; +} // 重置数据 -const resetData = () => { +function resetData() { userList.value = []; selectedUserIds.value = []; @@ -249,7 +295,6 @@ const resetData = () => { selectedUserIds.value = []; leftListState.value = { - loading: false, searchValue: '', dataSource: [], pagination: { @@ -268,61 +313,20 @@ const resetData = () => { total: 0, }, }; -}; - -// 打开弹窗 -const open = async (userIds: string[]) => { - resetData(); - loading.value = true; - try { - // 加载部门数据 - const deptData = await getSimpleDeptList(); - deptList.value = deptData; - const treeData = handleTree(deptData); - deptTree.value = treeData.map((node) => processDeptNode(node)); - expandedKeys.value = deptTree.value.map((node) => node.key); - - // 加载初始用户数据 - await loadUserData(1, leftListState.value.pagination.pageSize); - - // 设置已选用户 - if (userIds?.length) { - selectedUserIds.value = userIds.map(String); - // 加载已选用户的完整信息 TODO 目前接口暂不支持 多个用户ID 查询, 需要后端支持 - const { list } = await getUserPage({ - pageNo: 1, - pageSize: 100, // 临时使用固定值确保能加载所有已选用户 - userIds, - }); - // 使用 Map 来去重,以用户 ID 为 key - const userMap = new Map(userList.value.map((user) => [user.id, user])); - list.forEach((user) => { - if (!userMap.has(user.id)) { - userMap.set(user.id, user); - } - }); - userList.value = [...userMap.values()]; - updateRightListData(); - } - - modalApi.open(); - } finally { - loading.value = false; - } -}; +} // TODO 后端接口目前仅支持 username 检索, 筛选条件需要跟后端请求参数保持一致。 -const filterOption = (inputValue: string, option: any) => { +function filterOption(inputValue: string, option: any) { return option.username.toLowerCase().includes(inputValue.toLowerCase()); -}; +} // 处理部门树展开/折叠 -const handleExpand = (keys: Key[]) => { +function handleExpand(keys: Key[]) { expandedKeys.value = keys; -}; +} // 处理部门搜索 -const handleDeptSearch = (value: string) => { +function handleDeptSearch(value: string) { deptSearchKeys.value = value; // 如果有搜索结果,自动展开所有节点 @@ -342,10 +346,10 @@ const handleDeptSearch = (value: string) => { // 清空搜索时,只展开第一级节点 expandedKeys.value = deptTree.value.map((node) => node.key); } -}; +} // 处理部门选择 -const handleDeptSelect = async (selectedKeys: Key[], _info: any) => { +async function handleDeptSelect(selectedKeys: Key[], _info: any) { // 更新选中的部门ID const newDeptId = selectedKeys.length > 0 ? Number(selectedKeys[0]) : undefined; @@ -356,10 +360,10 @@ const handleDeptSelect = async (selectedKeys: Key[], _info: any) => { const { pageSize } = leftListState.value.pagination; leftListState.value.pagination.current = 1; await loadUserData(1, pageSize); -}; +} // 确认选择 -const handleConfirm = () => { +function handleConfirm() { if (selectedUserIds.value.length === 0) { message.warning('请选择用户'); return; @@ -371,115 +375,101 @@ const handleConfirm = () => { ), ); modalApi.close(); -}; +} // 取消选择 -const handleCancel = () => { +function handleCancel() { emit('cancel'); modalApi.close(); // 确保在动画结束后再重置数据 setTimeout(() => { resetData(); }, 300); -}; +} // 关闭弹窗 -const handleClosed = () => { +function handleClosed() { emit('closed'); resetData(); -}; - -// 弹窗配置 -const [ModalComponent, modalApi] = useVbenModal({ - title: props.title, - onCancel: handleCancel, - onClosed: handleClosed, - destroyOnClose: true, -}); +} // 递归处理部门树节点 -const processDeptNode = (node: any): DeptTreeNode => { +function processDeptNode(node: any): DeptTreeNode { return { key: String(node.id), title: `${node.name} (${node.id})`, name: node.name, children: node.children?.map((child: any) => processDeptNode(child)), }; -}; - -defineExpose({ - open, -}); +} diff --git a/apps/web-ele/src/components/table-action/typing.ts b/apps/web-ele/src/components/table-action/typing.ts new file mode 100644 index 000000000..7d41b1a23 --- /dev/null +++ b/apps/web-ele/src/components/table-action/typing.ts @@ -0,0 +1,36 @@ +import type { ButtonProps } from 'element-plus'; + +export type ButtonType = + | 'danger' + | 'default' + | 'info' + | 'primary' + | 'success' + | 'text' + | 'warning'; + +export interface PopConfirm { + title: string; + okText?: string; + cancelText?: string; + confirm: () => void; + cancel?: () => void; + icon?: string; + disabled?: boolean; +} + +export interface ActionItem extends Partial { + onClick?: () => void; + type?: ButtonType; + label?: string; + color?: 'error' | 'success' | 'warning'; + icon?: string; + popConfirm?: PopConfirm; + disabled?: boolean; + divider?: boolean; + // 权限编码控制是否显示 + auth?: string[]; + // 业务控制是否显示 + ifShow?: ((action: ActionItem) => boolean) | boolean; + tooltip?: string | { [key: string]: any; content?: string }; +} diff --git a/apps/web-ele/src/locales/langs/en-US/page.json b/apps/web-ele/src/locales/langs/en-US/page.json index e889bfc10..00a8c90b2 100644 --- a/apps/web-ele/src/locales/langs/en-US/page.json +++ b/apps/web-ele/src/locales/langs/en-US/page.json @@ -23,7 +23,8 @@ "cancel": "Cancel", "confirm": "Confirm", "reset": "Reset", - "search": "Search" + "search": "Search", + "more": "More" }, "tenant": { "placeholder": "Please select tenant", diff --git a/apps/web-ele/src/locales/langs/zh-CN/page.json b/apps/web-ele/src/locales/langs/zh-CN/page.json index a7781960b..eefc4924b 100644 --- a/apps/web-ele/src/locales/langs/zh-CN/page.json +++ b/apps/web-ele/src/locales/langs/zh-CN/page.json @@ -23,7 +23,8 @@ "cancel": "取消", "confirm": "确认", "reset": "重置", - "search": "搜索" + "search": "搜索", + "more": "更多" }, "tenant": { "placeholder": "请选择租户", diff --git a/apps/web-ele/src/views/system/user/data.ts b/apps/web-ele/src/views/system/user/data.ts index c209c8ced..b2cbd00bd 100644 --- a/apps/web-ele/src/views/system/user/data.ts +++ b/apps/web-ele/src/views/system/user/data.ts @@ -1,8 +1,7 @@ import type { VbenFormSchema } from '#/adapter/form'; -import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { SystemUserApi } from '#/api/system/user'; -import { useAccess } from '@vben/access'; import { $t } from '@vben/locales'; import { handleTree } from '@vben/utils'; @@ -17,8 +16,6 @@ import { getRangePickerDefaultProps, } from '#/utils'; -const { hasAccessByCodes } = useAccess(); - /** 新增/修改的表单 */ export function useFormSchema(): VbenFormSchema[] { return [ @@ -282,7 +279,6 @@ export function useGridFormSchema(): VbenFormSchema[] { /** 列表的字段 */ export function useGridColumns( - onActionClick: OnActionClickFn, onStatusChange?: ( newStatus: number, row: T, @@ -335,41 +331,10 @@ export function useGridColumns( formatter: 'formatDateTime', }, { - field: 'operation', title: '操作', - minWidth: 130, + width: 180, fixed: 'right', - align: 'center', - cellRender: { - attrs: { - nameField: 'username', - nameTitle: '用户', - onClick: onActionClick, - }, - name: 'CellOperation', - // TODO @芋艿:后续把 delete、assign-role、reset-password 搞成"更多" - options: [ - { - code: 'edit', - show: hasAccessByCodes(['system:user:update']), - }, - { - code: 'delete', - show: hasAccessByCodes(['system:user:delete']), - }, - { - code: 'assign-role', - text: '分配角色', - show: hasAccessByCodes(['system:permission:assign-user-role']), - 'v-access:code': 'system:user:assign-role1', - }, - { - code: 'reset-password', - text: '重置密码', - show: hasAccessByCodes(['system:user:update-password']), - }, - ], - }, + slots: { default: 'actions' }, }, ]; } diff --git a/apps/web-ele/src/views/system/user/index.vue b/apps/web-ele/src/views/system/user/index.vue index ed8b01c05..e7b293716 100644 --- a/apps/web-ele/src/views/system/user/index.vue +++ b/apps/web-ele/src/views/system/user/index.vue @@ -9,10 +9,9 @@ import type { SystemUserApi } from '#/api/system/user'; import { ref } from 'vue'; import { confirm, Page, useVbenModal } from '@vben/common-ui'; -import { Download, Plus, Upload } from '@vben/icons'; import { downloadFileFromBlobPart } from '@vben/utils'; -import { ElButton, ElLoading, ElMessage } from 'element-plus'; +import { ElLoading, ElMessage } from 'element-plus'; import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { @@ -22,6 +21,7 @@ import { updateUserStatus, } from '#/api/system/user'; import { DocAlert } from '#/components/doc-alert'; +import { ACTION_ICON, TableAction } from '#/components/table-action'; import { $t } from '#/locales'; import { DICT_TYPE, getDictLabel } from '#/utils'; @@ -216,32 +216,69 @@ const [Grid, gridApi] = useVbenVxeGrid({
+
From 9258716ba7097f294c7f9141afce053053c12424 Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Sat, 7 Jun 2025 11:53:06 +0800 Subject: [PATCH 23/59] feat: vxe add CellImages renderer --- apps/web-antd/src/adapter/vxe-table.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/web-antd/src/adapter/vxe-table.ts b/apps/web-antd/src/adapter/vxe-table.ts index 78b0d0d08..cff57ed94 100644 --- a/apps/web-antd/src/adapter/vxe-table.ts +++ b/apps/web-antd/src/adapter/vxe-table.ts @@ -74,6 +74,16 @@ setupVbenVxeTable({ }, }); + vxeUI.renderer.add('CellImages', { + renderTableDefault(_renderOpts, params) { + const { column, row } = params; + if (column && column.field && row[column.field]) { + return row[column.field].map((item: any) => h(Image, { src: item })); + } + return ''; + }, + }); + // 表格配置项可以用 cellRender: { name: 'CellLink' }, vxeUI.renderer.add('CellLink', { renderTableDefault(renderOpts) { From 1b3e2eef812b6f5e55c40eb2cc8b8fadbc6af4d7 Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Sat, 7 Jun 2025 11:53:32 +0800 Subject: [PATCH 24/59] feat: mall comment --- apps/web-antd/src/api/mall/product/comment.ts | 10 +- .../src/views/mall/product/comment/data.ts | 205 ++++++++++++++++++ .../src/views/mall/product/comment/index.vue | 173 ++++++++++++--- .../mall/product/comment/modules/form.vue | 83 +++++++ 4 files changed, 445 insertions(+), 26 deletions(-) create mode 100644 apps/web-antd/src/views/mall/product/comment/data.ts create mode 100644 apps/web-antd/src/views/mall/product/comment/modules/form.vue diff --git a/apps/web-antd/src/api/mall/product/comment.ts b/apps/web-antd/src/api/mall/product/comment.ts index 7ca1e45fa..8990c2048 100644 --- a/apps/web-antd/src/api/mall/product/comment.ts +++ b/apps/web-antd/src/api/mall/product/comment.ts @@ -3,6 +3,12 @@ import type { PageParam, PageResult } from '@vben/request'; import { requestClient } from '#/api/request'; export namespace MallCommentApi { + export interface Property { + propertyId: number; + propertyName: string; + valueId: number; + valueName: string; + } /** 商品评论 */ export interface Comment { id: number; @@ -20,11 +26,13 @@ export namespace MallCommentApi { descriptionScores: number; benefitScores: number; content: string; - picUrls: string; + picUrls: string[]; replyStatus: boolean; replyUserId: number; replyContent: string; replyTime: Date; + createTime: Date; + skuProperties: Property[]; } /** 评论可见性更新 */ diff --git a/apps/web-antd/src/views/mall/product/comment/data.ts b/apps/web-antd/src/views/mall/product/comment/data.ts new file mode 100644 index 000000000..f422155d6 --- /dev/null +++ b/apps/web-antd/src/views/mall/product/comment/data.ts @@ -0,0 +1,205 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeGridPropTypes } from '#/adapter/vxe-table'; +import type { MallCommentApi } from '#/api/mall/product/comment'; + +import { getSpuSimpleList } from '#/api/mall/product/spu'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'spuId', + label: '商品', + component: 'ApiSelect', + componentProps: { + api: getSpuSimpleList, + labelField: 'name', + valueField: 'id', + }, + rules: 'required', + }, + { + fieldName: 'userAvatar', + label: '用户头像', + component: 'ImageUpload', + componentProps: { + maxSize: 1, + }, + rules: 'required', + }, + { + fieldName: 'userNickname', + label: '用户名称', + component: 'Input', + rules: 'required', + }, + { + fieldName: 'content', + label: '评论内容', + component: 'Textarea', + rules: 'required', + }, + { + fieldName: 'descriptionScores', + label: '描述星级', + component: 'Rate', + rules: 'required', + }, + { + fieldName: 'benefitScores', + label: '服务星级', + component: 'Rate', + rules: 'required', + }, + { + fieldName: 'picUrls', + label: '评论图片', + component: 'ImageUpload', + componentProps: { + maxSize: 9, + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'replyStatus', + label: '回复状态', + component: 'Select', + componentProps: { + options: [ + { label: '已回复', value: true }, + { label: '未回复', value: false }, + ], + }, + }, + { + fieldName: 'spuName', + label: '商品名称', + component: 'Input', + }, + { + fieldName: 'userNickname', + label: '用户名称', + component: 'Input', + }, + { + fieldName: 'orderId', + label: '订单编号', + component: 'Input', + }, + { + fieldName: 'createTime', + label: '评论时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 表格列配置 */ +export function useGridColumns( + onStatusChange?: ( + newStatus: boolean, + row: T, + ) => PromiseLike, +): VxeGridPropTypes.Columns { + return [ + { + field: 'id', + title: '评论编号', + fixed: 'left', + }, + { + field: 'skuPicUrl', + title: '商品图片', + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'spuName', + title: '商品名称', + minWidth: 200, + }, + { + field: 'skuProperties', + title: '商品属性', + minWidth: 200, + formatter: ({ cellValue }) => { + return cellValue && cellValue.length > 0 + ? cellValue + .map((item: any) => `${item.propertyName} : ${item.valueName}`) + .join('\n') + : '-'; + }, + }, + { + field: 'userNickname', + title: '用户名称', + }, + { + field: 'descriptionScores', + title: '商品评分', + }, + { + field: 'benefitScores', + title: '服务评分', + }, + { + field: 'content', + title: '评论内容', + }, + { + field: 'picUrls', + title: '评论图片', + cellRender: { + name: 'CellImages', + }, + }, + { + field: 'replyContent', + title: '回复内容', + }, + { + field: 'createTime', + title: '评论时间', + formatter: 'formatDateTime', + }, + { + field: 'visible', + title: '是否展示', + align: 'center', + cellRender: { + attrs: { beforeChange: onStatusChange }, + name: 'CellSwitch', + props: { + checkedValue: true, + unCheckedValue: false, + }, + }, + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/mall/product/comment/index.vue b/apps/web-antd/src/views/mall/product/comment/index.vue index 1a59c945a..8511c5a00 100644 --- a/apps/web-antd/src/views/mall/product/comment/index.vue +++ b/apps/web-antd/src/views/mall/product/comment/index.vue @@ -1,34 +1,157 @@ diff --git a/apps/web-antd/src/views/mall/product/comment/modules/form.vue b/apps/web-antd/src/views/mall/product/comment/modules/form.vue new file mode 100644 index 000000000..71352fa82 --- /dev/null +++ b/apps/web-antd/src/views/mall/product/comment/modules/form.vue @@ -0,0 +1,83 @@ + + + From e95c01d8cc08c31c8ee7b2f441ef7ed5cd00c9c1 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sat, 7 Jun 2025 12:08:21 +0800 Subject: [PATCH 25/59] =?UTF-8?q?fix:=20=E3=80=90ele=E3=80=91user=20remove?= =?UTF-8?q?=20onActionClick?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-ele/src/views/system/user/index.vue | 29 ++------------------ 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/apps/web-ele/src/views/system/user/index.vue b/apps/web-ele/src/views/system/user/index.vue index e7b293716..7b0417e36 100644 --- a/apps/web-ele/src/views/system/user/index.vue +++ b/apps/web-ele/src/views/system/user/index.vue @@ -1,8 +1,5 @@ + + diff --git a/apps/web-ele/src/views/infra/demo/demo01/modules/form.vue b/apps/web-ele/src/views/infra/demo/demo01/modules/form.vue new file mode 100644 index 000000000..943b97698 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo01/modules/form.vue @@ -0,0 +1,91 @@ + + + diff --git a/apps/web-ele/src/views/system/user/index.vue b/apps/web-ele/src/views/system/user/index.vue index 7b0417e36..51776e637 100644 --- a/apps/web-ele/src/views/system/user/index.vue +++ b/apps/web-ele/src/views/system/user/index.vue @@ -10,7 +10,7 @@ import { downloadFileFromBlobPart } from '@vben/utils'; import { ElLoading, ElMessage } from 'element-plus'; -import { useVbenVxeGrid } from '#/adapter/vxe-table'; +import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { deleteUser, exportUser, @@ -18,7 +18,6 @@ import { updateUserStatus, } from '#/api/system/user'; import { DocAlert } from '#/components/doc-alert'; -import { ACTION_ICON, TableAction } from '#/components/table-action'; import { $t } from '#/locales'; import { DICT_TYPE, getDictLabel } from '#/utils'; From b28476d0c8c457cd8766261281aa9eea32ae651d Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sat, 7 Jun 2025 12:28:59 +0800 Subject: [PATCH 27/59] =?UTF-8?q?feat:=20=E3=80=90ele=E3=80=91=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=A0=91=E8=A1=A8=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/infra/demo/demo02/data.ts | 120 ++++++++++++ .../src/views/infra/demo/demo02/index.vue | 176 ++++++++++++++++++ .../views/infra/demo/demo02/modules/form.vue | 92 +++++++++ 3 files changed, 388 insertions(+) create mode 100644 apps/web-ele/src/views/infra/demo/demo02/data.ts create mode 100644 apps/web-ele/src/views/infra/demo/demo02/index.vue create mode 100644 apps/web-ele/src/views/infra/demo/demo02/modules/form.vue diff --git a/apps/web-ele/src/views/infra/demo/demo02/data.ts b/apps/web-ele/src/views/infra/demo/demo02/data.ts new file mode 100644 index 000000000..74430fb28 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo02/data.ts @@ -0,0 +1,120 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { Demo02CategoryApi } from '#/api/infra/demo/demo02'; + +import { handleTree } from '@vben/utils'; + +import { getDemo02CategoryList } from '#/api/infra/demo/demo02'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'parentId', + label: '上级示例分类', + component: 'ApiTreeSelect', + componentProps: { + allowClear: true, + api: async () => { + const data = await getDemo02CategoryList({}); + data.unshift({ + id: 0, + name: '顶级示例分类', + }); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择上级示例分类', + treeDefaultExpandAll: true, + }, + rules: 'selectRequired', + }, + { + fieldName: 'name', + label: '名字', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '名字', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入名字', + }, + }, + { + fieldName: 'parentId', + label: '父级编号', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入父级编号', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 120, + }, + { + field: 'name', + title: '名字', + minWidth: 120, + treeNode: true, + }, + { + field: 'parentId', + title: '父级编号', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/demo/demo02/index.vue b/apps/web-ele/src/views/infra/demo/demo02/index.vue new file mode 100644 index 000000000..e92e9aeea --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo02/index.vue @@ -0,0 +1,176 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo02/modules/form.vue b/apps/web-ele/src/views/infra/demo/demo02/modules/form.vue new file mode 100644 index 000000000..bfabb2601 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo02/modules/form.vue @@ -0,0 +1,92 @@ + + + From 759c6b975f331504dab3a1fbeedf3a00a235c600 Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Sat, 7 Jun 2025 12:35:07 +0800 Subject: [PATCH 28/59] feat: product list --- .../src/views/mall/product/spu/data.ts | 122 +++++++ .../src/views/mall/product/spu/index.vue | 328 ++++++++++++++++-- .../views/mall/product/spu/modules/detail.vue | 3 + .../views/mall/product/spu/modules/form.vue | 3 + 4 files changed, 431 insertions(+), 25 deletions(-) create mode 100644 apps/web-antd/src/views/mall/product/spu/data.ts create mode 100644 apps/web-antd/src/views/mall/product/spu/modules/detail.vue create mode 100644 apps/web-antd/src/views/mall/product/spu/modules/form.vue diff --git a/apps/web-antd/src/views/mall/product/spu/data.ts b/apps/web-antd/src/views/mall/product/spu/data.ts new file mode 100644 index 000000000..fa55151dd --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/data.ts @@ -0,0 +1,122 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { MallSpuApi } from '#/api/mall/product/spu'; + +import { getCategoryList } from '#/api/mall/product/category'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '商品名称', + component: 'Input', + }, + { + fieldName: 'categoryId', + label: '商品分类', + component: 'ApiTreeSelect', + componentProps: { + api: () => getCategoryList({}), + fieldNames: { label: 'name', value: 'id', children: 'children' }, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onStatusChange?: ( + newStatus: number, + row: T, + ) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { + type: 'expand', + width: 80, + slots: { content: 'expand_content' }, + fixed: 'left', + }, + { + field: 'id', + title: '商品编号', + fixed: 'left', + }, + { + field: 'name', + title: '商品名称', + fixed: 'left', + minWidth: 200, + }, + { + field: 'picUrl', + title: '商品图片', + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'price', + title: '价格', + formatter: 'formatFraction', + }, + { + field: 'marketPrice', + title: '市场价', + formatter: 'formatFraction', + }, + { + field: 'costPrice', + title: '成本价', + formatter: 'formatFraction', + }, + { + field: 'salesCount', + title: '销量', + }, + { + field: 'stock', + title: '库存', + }, + { + field: 'sort', + title: '排序', + }, + { + field: 'status', + title: '销售状态', + cellRender: { + attrs: { beforeChange: onStatusChange }, + name: 'CellSwitch', + props: { + checkedValue: 1, + checkedChildren: '上架', + unCheckedValue: 0, + unCheckedChildren: '下架', + }, + }, + }, + { + field: 'createTime', + title: '创建时间', + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 300, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/mall/product/spu/index.vue b/apps/web-antd/src/views/mall/product/spu/index.vue index 761da1334..393320652 100644 --- a/apps/web-antd/src/views/mall/product/spu/index.vue +++ b/apps/web-antd/src/views/mall/product/spu/index.vue @@ -1,34 +1,312 @@ diff --git a/apps/web-antd/src/views/mall/product/spu/modules/detail.vue b/apps/web-antd/src/views/mall/product/spu/modules/detail.vue new file mode 100644 index 000000000..c28ebc684 --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/modules/detail.vue @@ -0,0 +1,3 @@ + + + diff --git a/apps/web-antd/src/views/mall/product/spu/modules/form.vue b/apps/web-antd/src/views/mall/product/spu/modules/form.vue new file mode 100644 index 000000000..5b6413575 --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/modules/form.vue @@ -0,0 +1,3 @@ + + + From 7dced16ca664f0f712be88f3533340fe8ecfc5d2 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sat, 7 Jun 2025 12:50:57 +0800 Subject: [PATCH 29/59] =?UTF-8?q?fix:=20=E3=80=90ele=E3=80=91=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=94=9F=E6=88=90=E5=99=A8=E8=A1=A8=E5=8D=95=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-ele/src/views/infra/codegen/data.ts | 2 +- apps/web-ele/src/views/infra/codegen/edit/index.vue | 3 ++- .../src/views/infra/codegen/modules/generation-info.vue | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/web-ele/src/views/infra/codegen/data.ts b/apps/web-ele/src/views/infra/codegen/data.ts index b9b354a2a..45c0adb3c 100644 --- a/apps/web-ele/src/views/infra/codegen/data.ts +++ b/apps/web-ele/src/views/infra/codegen/data.ts @@ -350,7 +350,7 @@ export function useGenerationInfoSubTableFormSchema( }, { label: '一对一', - value: 'false', + value: false, }, ], }, diff --git a/apps/web-ele/src/views/infra/codegen/edit/index.vue b/apps/web-ele/src/views/infra/codegen/edit/index.vue index e338334cd..ec090172f 100644 --- a/apps/web-ele/src/views/infra/codegen/edit/index.vue +++ b/apps/web-ele/src/views/infra/codegen/edit/index.vue @@ -131,11 +131,12 @@ getDetail(); v-for="(step, index) in steps" :key="index" :title="step.title" + class="cursor-pointer" + @click="() => (currentStep = index)" />
- Date: Sat, 7 Jun 2025 12:53:03 +0800 Subject: [PATCH 30/59] =?UTF-8?q?fix:=20=E3=80=90antd=E3=80=91=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=94=9F=E6=88=90=E5=99=A8=E8=A1=A8=E5=8D=95=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/infra/codegen/modules/generation-info.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/web-antd/src/views/infra/codegen/modules/generation-info.vue b/apps/web-antd/src/views/infra/codegen/modules/generation-info.vue index da859ef2f..3d89656f8 100644 --- a/apps/web-antd/src/views/infra/codegen/modules/generation-info.vue +++ b/apps/web-antd/src/views/infra/codegen/modules/generation-info.vue @@ -69,6 +69,8 @@ function updateTreeSchema(): void { treeFormApi.setState({ schema: useGenerationInfoTreeFormSchema(props.columns), }); + // 树表信息回显 + treeFormApi.setValues(props.table as any); } /** 更新主子表信息表单 schema */ @@ -76,6 +78,8 @@ function updateSubSchema(): void { subFormApi.setState({ schema: useGenerationInfoSubTableFormSchema(props.columns, tables.value), }); + // 主子表信息回显 + subFormApi.setValues(props.table as any); } /** 获取合并的表单值 */ From c7013a030ef8d782a86f1203a8aa0ead09e13beb Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Sat, 7 Jun 2025 12:54:50 +0800 Subject: [PATCH 31/59] feat: treeToString --- packages/@core/base/shared/src/utils/tree.ts | 46 +++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/packages/@core/base/shared/src/utils/tree.ts b/packages/@core/base/shared/src/utils/tree.ts index ca2a6765b..468d204f7 100644 --- a/packages/@core/base/shared/src/utils/tree.ts +++ b/packages/@core/base/shared/src/utils/tree.ts @@ -164,4 +164,48 @@ function handleTree( return tree; } -export { filterTree, handleTree, mapTree, traverseTreeValues }; +/** + * 获取节点的完整结构 + * @param tree 树数据 + * @param nodeId 节点 id + */ +function treeToString(tree: any[], nodeId: number | string) { + if (tree === undefined || !Array.isArray(tree) || tree.length === 0) { + console.warn('tree must be an array'); + return ''; + } + // 校验是否是一级节点 + const node = tree.find((item) => item.id === nodeId); + if (node !== undefined) { + return node.name; + } + let str = ''; + + function performAThoroughValidation(arr: any[]) { + if (arr === undefined || !Array.isArray(arr) || arr.length === 0) { + return false; + } + for (const item of arr) { + if (item.id === nodeId) { + str += ` / ${item.name}`; + return true; + } else if (item.children !== undefined && item.children.length > 0) { + str += ` / ${item.name}`; + if (performAThoroughValidation(item.children)) { + return true; + } + } + } + return false; + } + + for (const item of tree) { + str = `${item.name}`; + if (performAThoroughValidation(item.children)) { + break; + } + } + return str; +} + +export { filterTree, handleTree, mapTree, traverseTreeValues, treeToString }; From e3429f644b741c6a9b8b62a471bfbe00930a414b Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Sat, 7 Jun 2025 12:55:04 +0800 Subject: [PATCH 32/59] feat: spu expant --- .../src/views/mall/product/spu/data.ts | 17 +++--- .../src/views/mall/product/spu/index.vue | 52 ++++++++++++++++--- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/apps/web-antd/src/views/mall/product/spu/data.ts b/apps/web-antd/src/views/mall/product/spu/data.ts index fa55151dd..bd1050fee 100644 --- a/apps/web-antd/src/views/mall/product/spu/data.ts +++ b/apps/web-antd/src/views/mall/product/spu/data.ts @@ -2,6 +2,8 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { MallSpuApi } from '#/api/mall/product/spu'; +import { handleTree } from '@vben/utils'; + import { getCategoryList } from '#/api/mall/product/category'; import { getRangePickerDefaultProps } from '#/utils'; @@ -18,7 +20,10 @@ export function useGridFormSchema(): VbenFormSchema[] { label: '商品分类', component: 'ApiTreeSelect', componentProps: { - api: () => getCategoryList({}), + api: async () => { + const res = await getCategoryList({}); + return handleTree(res, 'id', 'parentId', 'children'); + }, fieldNames: { label: 'name', value: 'id', children: 'children' }, }, }, @@ -71,16 +76,6 @@ export function useGridColumns( title: '价格', formatter: 'formatFraction', }, - { - field: 'marketPrice', - title: '市场价', - formatter: 'formatFraction', - }, - { - field: 'costPrice', - title: '成本价', - formatter: 'formatFraction', - }, { field: 'salesCount', title: '销量', diff --git a/apps/web-antd/src/views/mall/product/spu/index.vue b/apps/web-antd/src/views/mall/product/spu/index.vue index 393320652..1bf26e5de 100644 --- a/apps/web-antd/src/views/mall/product/spu/index.vue +++ b/apps/web-antd/src/views/mall/product/spu/index.vue @@ -6,11 +6,16 @@ import { onMounted, ref } from 'vue'; import { useRouter } from 'vue-router'; import { confirm, Page } from '@vben/common-ui'; -import { downloadFileFromBlobPart } from '@vben/utils'; +import { + downloadFileFromBlobPart, + handleTree, + treeToString, +} from '@vben/utils'; -import { message, Tabs } from 'ant-design-vue'; +import { Descriptions, message, Tabs } from 'ant-design-vue'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; +import { getCategoryList } from '#/api/mall/product/category'; import { deleteSpu, exportSpu, @@ -20,13 +25,15 @@ import { } from '#/api/mall/product/spu'; import { DocAlert } from '#/components/doc-alert'; import { $t } from '#/locales'; -import { ProductSpuStatusEnum } from '#/utils'; +import { fenToYuan, ProductSpuStatusEnum } from '#/utils'; import { useGridColumns, useGridFormSchema } from './data'; const { push } = useRouter(); const tabType = ref(0); +const categoryList = ref(); + // tabs 数据 const tabsData = ref([ { @@ -171,7 +178,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ height: 80, }, expandConfig: { - height: 150, + height: 100, }, keepSource: true, proxyConfig: { @@ -204,6 +211,9 @@ function onChangeTab(key: any) { onMounted(() => { getTabCount(); + getCategoryList({}).then((res) => { + categoryList.value = handleTree(res, 'id', 'parentId', 'children'); + }); }); @@ -247,10 +257,36 @@ onMounted(() => { />