From 041e3f6e2d054a55367da0d08d99341f3442dbb5 Mon Sep 17 00:00:00 2001 From: XuZhiqiang Date: Wed, 17 Jun 2026 11:25:07 +0800 Subject: [PATCH 1/9] =?UTF-8?q?fix(@vben/web-antdv-next):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20Tree=20=E9=80=89=E4=B8=AD=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=E6=97=A7=20dataRef=20=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E7=AD=9B=E9=80=89=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/mes/md/item/type/components/tree.vue | 13 +++++++++++-- .../views/system/dept/components/tree-select.vue | 8 ++++++-- .../src/views/system/user/modules/dept-tree.vue | 8 ++++++-- .../views/wms/md/item/category/components/tree.vue | 13 +++++++++++-- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/apps/web-antdv-next/src/views/mes/md/item/type/components/tree.vue b/apps/web-antdv-next/src/views/mes/md/item/type/components/tree.vue index 347e87b6a..acb3ad22b 100644 --- a/apps/web-antdv-next/src/views/mes/md/item/type/components/tree.vue +++ b/apps/web-antdv-next/src/views/mes/md/item/type/components/tree.vue @@ -54,8 +54,17 @@ function handleSearch(value: string) { } /** 处理节点点击:支持点击同一节点取消选中 */ -function handleSelect(_selectedKeys: any[], info: any) { - const row = info.node.dataRef as MesMdItemTypeApi.ItemType; +function handleSelect(selectedNodeKeys: any[], info: any) { + const selectedKey = selectedNodeKeys[0] ?? info.node?.id ?? info.node?.key; + const row = itemTypeList.value.find( + (item) => String(item.id) === String(selectedKey), + ); + if (!row) { + currentNodeId.value = undefined; + selectedKeys.value = []; + emit('nodeClick', undefined); + return; + } if (currentNodeId.value === row.id) { currentNodeId.value = undefined; selectedKeys.value = []; diff --git a/apps/web-antdv-next/src/views/system/dept/components/tree-select.vue b/apps/web-antdv-next/src/views/system/dept/components/tree-select.vue index cf66a0733..7054d3c82 100644 --- a/apps/web-antdv-next/src/views/system/dept/components/tree-select.vue +++ b/apps/web-antdv-next/src/views/system/dept/components/tree-select.vue @@ -38,8 +38,12 @@ function handleSearch(e: any) { } /** 选中部门:点击已选中的节点时取消选中 */ -function handleSelect(_selectedKeys: any[], info: any) { - emit('select', info.selected ? info.node.dataRef : undefined); +function handleSelect(selectedNodeKeys: any[], info: any) { + const selectedKey = selectedNodeKeys[0]; + const dept = info.selected + ? deptList.value.find((item) => String(item.id) === String(selectedKey)) + : undefined; + emit('select', dept); } /** 重置选中状态(供外部重置按钮调用) */ diff --git a/apps/web-antdv-next/src/views/system/user/modules/dept-tree.vue b/apps/web-antdv-next/src/views/system/user/modules/dept-tree.vue index beea3f314..48ffe69c4 100644 --- a/apps/web-antdv-next/src/views/system/user/modules/dept-tree.vue +++ b/apps/web-antdv-next/src/views/system/user/modules/dept-tree.vue @@ -32,8 +32,12 @@ function handleSearch(e: any) { } /** 选中部门 */ -function handleSelect(_selectedKeys: any[], info: any) { - emit('select', info.node.dataRef); +function handleSelect(selectedNodeKeys: any[], info: any) { + const selectedKey = selectedNodeKeys[0] ?? info.node?.id ?? info.node?.key; + const dept = info.selected + ? deptList.value.find((item) => String(item.id) === String(selectedKey)) + : undefined; + emit('select', dept); } /** 初始化 */ diff --git a/apps/web-antdv-next/src/views/wms/md/item/category/components/tree.vue b/apps/web-antdv-next/src/views/wms/md/item/category/components/tree.vue index 7954aea91..e682d58c9 100644 --- a/apps/web-antdv-next/src/views/wms/md/item/category/components/tree.vue +++ b/apps/web-antdv-next/src/views/wms/md/item/category/components/tree.vue @@ -55,8 +55,17 @@ function handleSearch(value: string) { } /** 处理节点点击:支持点击同一节点取消选中 */ -function handleSelect(_selectedKeys: any[], info: any) { - const row = info.node.dataRef as WmsItemCategoryApi.ItemCategory; +function handleSelect(selectedNodeKeys: any[], info: any) { + const selectedKey = selectedNodeKeys[0] ?? info.node?.id ?? info.node?.key; + const row = categoryList.value.find( + (item) => String(item.id) === String(selectedKey), + ); + if (!row) { + currentNodeId.value = undefined; + selectedKeys.value = []; + emit('nodeClick', undefined); + return; + } if (currentNodeId.value === row.id) { currentNodeId.value = undefined; selectedKeys.value = []; From 6951bd68a12d8473ca419ff1ef41f4f227e59358 Mon Sep 17 00:00:00 2001 From: XuZhiqiang Date: Wed, 17 Jun 2026 13:24:57 +0800 Subject: [PATCH 2/9] =?UTF-8?q?feat(@vben/web-antdv-next):=20BPM=E6=8B=92?= =?UTF-8?q?=E7=BB=9D=E5=8F=AF=E4=BB=A5=E6=B7=BB=E5=8A=A0=E9=99=84=E4=BB=B6?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD=E8=87=B3=E5=AE=A1=E6=89=B9?= =?UTF-8?q?=E8=A1=A8=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/api/bpm/processInstance/index.ts | 1 + .../detail/modules/operation-button.vue | 80 ++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/apps/web-antdv-next/src/api/bpm/processInstance/index.ts b/apps/web-antdv-next/src/api/bpm/processInstance/index.ts index 7ee5421f8..f8442c35c 100644 --- a/apps/web-antdv-next/src/api/bpm/processInstance/index.ts +++ b/apps/web-antdv-next/src/api/bpm/processInstance/index.ts @@ -83,6 +83,7 @@ export namespace BpmProcessInstanceApi { reason: string; signPicUrl: string; status: number; + attachments?: string[]; } /** 抄送流程实例 */ diff --git a/apps/web-antdv-next/src/views/bpm/processInstance/detail/modules/operation-button.vue b/apps/web-antdv-next/src/views/bpm/processInstance/detail/modules/operation-button.vue index 423cd8daf..8afd72618 100644 --- a/apps/web-antdv-next/src/views/bpm/processInstance/detail/modules/operation-button.vue +++ b/apps/web-antdv-next/src/views/bpm/processInstance/detail/modules/operation-button.vue @@ -55,6 +55,7 @@ import { transferTask, } from '#/api/bpm/task'; import { setConfAndFields2 } from '#/components/form-create'; +import { FileUpload } from '#/components/upload'; import { $t } from '#/locales'; import Signature from './signature.vue'; @@ -120,6 +121,7 @@ const approveReasonForm: any = reactive({ reason: '', signPicUrl: '', nextAssignees: {}, + attachments: [], }); const approveReasonRule: Record = computed(() => { return { @@ -140,7 +142,8 @@ const approveReasonRule: Record = computed(() => { }); const rejectFormRef = ref(); -const rejectReasonForm = reactive({ +const rejectReasonForm = reactive<{ attachments: string[]; reason: string }>({ + attachments: [], reason: '', }); // 拒绝表单 const rejectReasonRule: any = computed(() => { @@ -290,6 +293,14 @@ function closePopover(type: string, formRef: any | FormInstance) { if (formRef) { formRef.resetFields(); } + if (type === 'approve') { + approveReasonForm.reason = ''; + approveReasonForm.attachments = []; + approveReasonForm.signPicUrl = ''; + } else if (type === 'reject') { + rejectReasonForm.reason = ''; + rejectReasonForm.attachments = []; + } if (popOverVisible.value[type]) popOverVisible.value[type] = false; nextAssigneesActivityNode.value = []; // 清理 Timeline 组件中的自定义审批人数据 @@ -401,6 +412,7 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) { const data = { id: runningTask.value.id, reason: approveReasonForm.reason, + attachments: approveReasonForm.attachments, variables, // 审批通过, 把修改的字段值赋于流程实例变量 nextAssignees: approveReasonForm.nextAssignees, // 下个自选节点选择的审批人信息 } as any; @@ -414,6 +426,9 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) { await formCreateApi.validate(); } await approveTask(data); + approveReasonForm.reason = ''; + approveReasonForm.attachments = []; + approveReasonForm.signPicUrl = ''; popOverVisible.value.approve = false; nextAssigneesActivityNode.value = []; // 清理 Timeline 组件中的自定义审批人数据 @@ -425,9 +440,12 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) { // 审批不通过数据 const data = { id: runningTask.value.id, + attachments: rejectReasonForm.attachments, reason: rejectReasonForm.reason, }; await rejectTask(data); + rejectReasonForm.reason = ''; + rejectReasonForm.attachments = []; popOverVisible.value.reject = false; message.success('审批不通过成功'); } @@ -748,6 +766,38 @@ function handleSignFinish(url: string) { approveFormRef.value?.validateFields(['signPicUrl']); } +/** 附件图片预览 */ +const imagePreviewOpen = ref(false); +const imagePreviewUrl = ref(''); + +/** 判断文件是否为图片类型 */ +function isImageUrl(url: string) { + return /\.(bmp|gif|jpe?g|png|svg|webp)$/i.test(url); +} + +/** 处理文件预览 */ +function handleFilePreview(file: any) { + if (!file?.url && !file?.response) { + message.warning('文件地址不存在,无法预览'); + return; + } + const url = file.url || file?.response?.url || file?.response; + if (!url) { + message.warning('文件地址不存在,无法预览'); + return; + } + if (isImageUrl(url)) { + imagePreviewUrl.value = url; + imagePreviewOpen.value = true; + } else { + window.open(url, '_blank'); + } +} + +function handleImagePreviewOpenChange(open: boolean) { + imagePreviewOpen.value = open; +} + /** 处理弹窗可见性 */ function handlePopoverVisible(visible: boolean) { if (!visible) { @@ -843,6 +893,15 @@ defineExpose({ loadTodoTask }); :rows="4" /> + + + From 2fc5575c304a3646e4e83b04a407baefe8e1beec Mon Sep 17 00:00:00 2001 From: XuZhiqiang Date: Wed, 17 Jun 2026 21:28:10 +0800 Subject: [PATCH 8/9] =?UTF-8?q?fix(web-antdv-next):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=20DateRangePicker=20=E5=85=B1=E4=BA=AB=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E7=9A=84=E4=BA=8C=E5=85=83=E7=BB=84=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/utils/rangePickerProps.ts | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/apps/web-antdv-next/src/utils/rangePickerProps.ts b/apps/web-antdv-next/src/utils/rangePickerProps.ts index 2ba4be294..df52a2dc1 100644 --- a/apps/web-antdv-next/src/utils/rangePickerProps.ts +++ b/apps/web-antdv-next/src/utils/rangePickerProps.ts @@ -1,7 +1,16 @@ +import type { Dayjs } from 'dayjs'; + import dayjs from 'dayjs'; import { $t } from '#/locales'; +type DateRangeTuple = [Dayjs, Dayjs]; +type StringRangeTuple = [string, string]; + +function dateRange(start: Dayjs, end: Dayjs): DateRangeTuple { + return [start, end]; +} + /** 时间段选择器拓展 */ export function getRangePickerDefaultProps() { return { @@ -13,55 +22,55 @@ export function getRangePickerDefaultProps() { placeholder: [ $t('utils.rangePicker.beginTime'), $t('utils.rangePicker.endTime'), - ], + ] as StringRangeTuple, // 快捷时间范围 presets: [ { label: $t('utils.rangePicker.today'), - value: [dayjs().startOf('day'), dayjs().endOf('day')], + value: dateRange(dayjs().startOf('day'), dayjs().endOf('day')), }, { label: $t('utils.rangePicker.last7Days'), - value: [ + value: dateRange( dayjs().subtract(7, 'day').startOf('day'), dayjs().endOf('day'), - ], + ), }, { label: $t('utils.rangePicker.last30Days'), - value: [ + value: dateRange( dayjs().subtract(30, 'day').startOf('day'), dayjs().endOf('day'), - ], + ), }, { label: $t('utils.rangePicker.yesterday'), - value: [ + value: dateRange( dayjs().subtract(1, 'day').startOf('day'), dayjs().subtract(1, 'day').endOf('day'), - ], + ), }, { label: $t('utils.rangePicker.thisWeek'), - value: [dayjs().startOf('week'), dayjs().endOf('day')], + value: dateRange(dayjs().startOf('week'), dayjs().endOf('day')), }, { label: $t('utils.rangePicker.thisMonth'), - value: [dayjs().startOf('month'), dayjs().endOf('day')], + value: dateRange(dayjs().startOf('month'), dayjs().endOf('day')), }, { label: $t('utils.rangePicker.lastWeek'), - value: [ + value: dateRange( dayjs().subtract(1, 'week').startOf('day'), dayjs().endOf('day'), - ], + ), }, ], showTime: { - defaultValue: [ + defaultValue: dateRange( dayjs('00:00:00', 'HH:mm:ss'), dayjs('23:59:59', 'HH:mm:ss'), - ], + ), format: 'HH:mm:ss', }, }; From f152217c3cf13ae0fb1fded2d8a8fd3eae8178ef Mon Sep 17 00:00:00 2001 From: XuZhiqiang Date: Thu, 18 Jun 2026 14:21:12 +0800 Subject: [PATCH 9/9] fix(web-antdv-next): normalize date picker timestamp handling Add DatePicker and RangePicker adapter wrappers for numeric timestamp values, route direct value-format="x" usages through the adapter, and keep ShortcutDateRangePicker values as Dayjs. --- .../src/adapter/component/date-picker.ts | 86 +++++++++++++++++++ .../src/adapter/component/index.ts | 13 +-- .../shortcut-date-range-picker.vue | 1 - .../demo/general/demo01/modules/form.vue | 2 +- .../demo/general/demo03/erp/modules/form.vue | 2 +- .../general/demo03/inner/modules/form.vue | 2 +- .../general/demo03/normal/modules/form.vue | 2 +- 7 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 apps/web-antdv-next/src/adapter/component/date-picker.ts diff --git a/apps/web-antdv-next/src/adapter/component/date-picker.ts b/apps/web-antdv-next/src/adapter/component/date-picker.ts new file mode 100644 index 000000000..2428cd4fb --- /dev/null +++ b/apps/web-antdv-next/src/adapter/component/date-picker.ts @@ -0,0 +1,86 @@ +import type { Component } from 'vue'; + +import type { Recordable } from '@vben/types'; + +import { defineAsyncComponent, defineComponent, h, ref } from 'vue'; + +const RawDatePicker = defineAsyncComponent( + () => import('antdv-next/dist/date-picker/index'), +); +const RawRangePicker = defineAsyncComponent(() => + import('antdv-next/dist/date-picker/index').then( + (res) => res.DateRangePicker, + ), +); + +const TIMESTAMP_VALUE_FORMATS = new Set(['x', 'X']); + +function isTimestampValueFormat(valueFormat: unknown) { + return ( + typeof valueFormat === 'string' && TIMESTAMP_VALUE_FORMATS.has(valueFormat) + ); +} + +function normalizeTimestampPickerValue(value: any, valueFormat: unknown): any { + if (!isTimestampValueFormat(valueFormat)) { + return value; + } + if (Array.isArray(value)) { + return value.map((item) => normalizeTimestampPickerValue(item, valueFormat)); + } + return typeof value === 'number' ? String(value) : value; +} + +function withTimestampValueFormat( + component: Component, + name: 'DatePicker' | 'RangePicker', +) { + return defineComponent({ + name, + inheritAttrs: false, + setup(_, { attrs, expose, slots }) { + const innerRef = ref(); + expose( + new Proxy( + {}, + { + get: (_target, key) => innerRef.value?.[key], + has: (_target, key) => key in (innerRef.value || {}), + }, + ), + ); + + return () => { + const pickerAttrs: Recordable = { ...attrs }; + if ( + 'value-format' in pickerAttrs && + !Reflect.has(pickerAttrs, 'valueFormat') + ) { + pickerAttrs.valueFormat = pickerAttrs['value-format']; + } + const valueFormat = pickerAttrs.valueFormat; + + for (const key of [ + 'value', + 'defaultValue', + 'pickerValue', + 'defaultPickerValue', + ]) { + if (Reflect.has(pickerAttrs, key)) { + pickerAttrs[key] = normalizeTimestampPickerValue( + pickerAttrs[key], + valueFormat, + ); + } + } + + return h(component, { ...pickerAttrs, ref: innerRef }, slots); + }; + }, + }); +} + +const DatePicker = withTimestampValueFormat(RawDatePicker, 'DatePicker'); +const RangePicker = withTimestampValueFormat(RawRangePicker, 'RangePicker'); + +export { DatePicker, RangePicker }; diff --git a/apps/web-antdv-next/src/adapter/component/index.ts b/apps/web-antdv-next/src/adapter/component/index.ts index 9a0897d5b..846473793 100644 --- a/apps/web-antdv-next/src/adapter/component/index.ts +++ b/apps/web-antdv-next/src/adapter/component/index.ts @@ -75,6 +75,9 @@ import { message, Modal, notification } from 'antdv-next'; import { uploadFile as uploadFileApi } from '#/api/infra/file'; import { Tinymce as RichTextarea } from '#/components/tinymce'; import { FileUpload, ImageUpload } from '#/components/upload'; + +import { DatePicker, RangePicker } from './date-picker'; + type AdapterUploadProps = UploadProps & { aspectRatio?: string; crop?: boolean; @@ -97,9 +100,6 @@ const Checkbox = defineAsyncComponent( const CheckboxGroup = defineAsyncComponent(() => import('antdv-next/dist/checkbox/index').then((res) => res.CheckboxGroup), ); -const DatePicker = defineAsyncComponent( - () => import('antdv-next/dist/date-picker/index'), -); const Divider = defineAsyncComponent( () => import('antdv-next/dist/divider/index'), ); @@ -117,11 +117,6 @@ const Radio = defineAsyncComponent(() => import('antdv-next/dist/radio/index')); const RadioGroup = defineAsyncComponent(() => import('antdv-next/dist/radio/index').then((res) => res.RadioGroup), ); -const RangePicker = defineAsyncComponent(() => - import('antdv-next/dist/date-picker/index').then( - (res) => res.DateRangePicker, - ), -); const Rate = defineAsyncComponent(() => import('antdv-next/dist/rate/index')); const Select = defineAsyncComponent( () => import('antdv-next/dist/select/index'), @@ -794,4 +789,4 @@ async function initComponentAdapter() { }); } -export { initComponentAdapter }; +export { DatePicker, initComponentAdapter, RangePicker }; diff --git a/apps/web-antdv-next/src/components/shortcut-date-range-picker/shortcut-date-range-picker.vue b/apps/web-antdv-next/src/components/shortcut-date-range-picker/shortcut-date-range-picker.vue index cb27a8e00..a6ec00a83 100644 --- a/apps/web-antdv-next/src/components/shortcut-date-range-picker/shortcut-date-range-picker.vue +++ b/apps/web-antdv-next/src/components/shortcut-date-range-picker/shortcut-date-range-picker.vue @@ -91,7 +91,6 @@ onMounted(() => {