From 1196dab9e4fef656c1703e52653b7081b82abee8 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Wed, 28 Jan 2026 17:23:39 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(form-create):=20=E3=80=90antd/ele?= =?UTF-8?q?=E3=80=91=E8=A1=A8=E5=8D=95=E9=80=89=E6=8B=A9=E5=99=A8=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=BB=98=E8=AE=A4=E9=80=89=E4=B8=AD=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E7=94=A8=E6=88=B7/=E9=83=A8=E9=97=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UserSelect 组件新增 defaultCurrentUser 配置,支持默认选中当前登录用户 - DeptSelect 组件重构为独立的树形选择器,支持 defaultCurrentDept 配置 - DeptSelect 支持 returnType 配置,可返回部门 ID 或部门名称 - 修复 useSelectRule 未将自定义 props 默认值传递给组件的问题 - 修复 DeptSelect 数据加载完成前回显失败的问题 - 同时支持 web-antd 和 web-ele 两个应用 --- .../form-create/components/dept-select.vue | 210 +++++++++++++++++ .../form-create/components/use-api-select.tsx | 39 +++- .../src/components/form-create/helpers.ts | 14 ++ .../form-create/rules/use-select-rule.ts | 13 +- .../web-antd/src/plugins/form-create/index.ts | 7 +- .../form-create/components/dept-select.vue | 218 ++++++++++++++++++ .../form-create/components/use-api-select.tsx | 39 +++- .../src/components/form-create/helpers.ts | 14 ++ .../form-create/rules/use-select-rule.ts | 13 +- apps/web-ele/src/plugins/form-create/index.ts | 7 +- 10 files changed, 558 insertions(+), 16 deletions(-) create mode 100644 apps/web-antd/src/components/form-create/components/dept-select.vue create mode 100644 apps/web-ele/src/components/form-create/components/dept-select.vue diff --git a/apps/web-antd/src/components/form-create/components/dept-select.vue b/apps/web-antd/src/components/form-create/components/dept-select.vue new file mode 100644 index 000000000..9f70ff0aa --- /dev/null +++ b/apps/web-antd/src/components/form-create/components/dept-select.vue @@ -0,0 +1,210 @@ + + + + diff --git a/apps/web-antd/src/components/form-create/components/use-api-select.tsx b/apps/web-antd/src/components/form-create/components/use-api-select.tsx index c63e2cd1e..dba12f326 100644 --- a/apps/web-antd/src/components/form-create/components/use-api-select.tsx +++ b/apps/web-antd/src/components/form-create/components/use-api-select.tsx @@ -2,6 +2,7 @@ import type { ApiSelectProps } from '#/components/form-create/typing'; import { defineComponent, onMounted, ref, useAttrs } from 'vue'; +import { useUserStore } from '@vben/stores'; import { isEmpty } from '@vben/utils'; import { @@ -74,12 +75,46 @@ export function useApiSelect(option: ApiSelectProps) { type: String, default: 'id', }, + // 是否默认选中当前用户(仅用于 UserSelect) + defaultCurrentUser: { + type: Boolean, + default: false, + }, }, - setup(props) { + setup(props, { emit }) { const attrs = useAttrs(); const options = ref([]); // 下拉数据 const loading = ref(false); // 是否正在从远程获取数据 const queryParam = ref(); // 当前输入的值 + + // 检查是否有有效的预设值 + function hasValidPresetValue(): boolean { + const value = attrs.modelValue; + if (value === undefined || value === null || value === '') { + return false; + } + if (Array.isArray(value)) { + return value.length > 0; + } + return true; + } + + // 设置默认当前用户 + function setDefaultCurrentUser(): void { + if (option.name !== 'UserSelect' || !props.defaultCurrentUser) { + return; + } + if (hasValidPresetValue()) { + return; + } + const userStore = useUserStore(); + const currentUserId = userStore.userInfo?.id; + if (currentUserId) { + const defaultValue = props.multiple ? [currentUserId] : currentUserId; + emit('update:modelValue', defaultValue); + } + } + const getOptions = async () => { options.value = []; // 接口选择器 @@ -199,6 +234,8 @@ export function useApiSelect(option: ApiSelectProps) { onMounted(async () => { await getOptions(); + // 设置默认当前用户(仅用于 UserSelect) + setDefaultCurrentUser(); }); const buildSelect = () => { diff --git a/apps/web-antd/src/components/form-create/helpers.ts b/apps/web-antd/src/components/form-create/helpers.ts index f43b1ce6d..f04638945 100644 --- a/apps/web-antd/src/components/form-create/helpers.ts +++ b/apps/web-antd/src/components/form-create/helpers.ts @@ -189,6 +189,14 @@ export async function useFormCreateDesigner(designer: Ref) { name: 'UserSelect', label: '用户选择器', icon: 'icon-eye', + props: [ + { + type: 'switch', + field: 'defaultCurrentUser', + title: '默认选中当前用户', + value: true, + }, + ], }); const deptSelectRule = useSelectRule({ name: 'DeptSelect', @@ -205,6 +213,12 @@ export async function useFormCreateDesigner(designer: Ref) { { label: '部门名称', value: 'name' }, ], }, + { + type: 'switch', + field: 'defaultCurrentDept', + title: '默认选中当前部门', + value: true, + }, ], }); const dictSelectRule = useDictSelectRule(); diff --git a/apps/web-antd/src/components/form-create/rules/use-select-rule.ts b/apps/web-antd/src/components/form-create/rules/use-select-rule.ts index 3efd8c9d5..fc89b9a24 100644 --- a/apps/web-antd/src/components/form-create/rules/use-select-rule.ts +++ b/apps/web-antd/src/components/form-create/rules/use-select-rule.ts @@ -23,13 +23,24 @@ export function useSelectRule(option: SelectRuleOption) { name, event: option.event, rule() { - return { + // 构建基础规则 + const baseRule: any = { type: name, field: buildUUID(), title: label, info: '', $required: false, }; + // 将自定义 props 的默认值添加到 rule 的 props 中 + if (option.props && option.props.length > 0) { + baseRule.props = {}; + option.props.forEach((prop: any) => { + if (prop.field && prop.value !== undefined) { + baseRule.props[prop.field] = prop.value; + } + }); + } + return baseRule; }, props(_: any, { t }: any) { if (!option.props) { diff --git a/apps/web-antd/src/plugins/form-create/index.ts b/apps/web-antd/src/plugins/form-create/index.ts index db60c0f51..024aae834 100644 --- a/apps/web-antd/src/plugins/form-create/index.ts +++ b/apps/web-antd/src/plugins/form-create/index.ts @@ -34,6 +34,7 @@ import { // ======================= 自定义组件 ======================= import { useApiSelect } from '#/components/form-create'; +import DeptSelect from '#/components/form-create/components/dept-select.vue'; import DictSelect from '#/components/form-create/components/dict-select.vue'; import { useImagesUpload } from '#/components/form-create/components/use-images-upload'; import { Tinymce } from '#/components/tinymce'; @@ -45,12 +46,6 @@ const UserSelect = useApiSelect({ valueField: 'id', url: '/system/user/simple-list', }); -const DeptSelect = useApiSelect({ - name: 'DeptSelect', - labelField: 'name', - valueField: 'id', - url: '/system/dept/simple-list', -}); const ApiSelect = useApiSelect({ name: 'ApiSelect', }); diff --git a/apps/web-ele/src/components/form-create/components/dept-select.vue b/apps/web-ele/src/components/form-create/components/dept-select.vue new file mode 100644 index 000000000..ffe3a7c75 --- /dev/null +++ b/apps/web-ele/src/components/form-create/components/dept-select.vue @@ -0,0 +1,218 @@ + + + + diff --git a/apps/web-ele/src/components/form-create/components/use-api-select.tsx b/apps/web-ele/src/components/form-create/components/use-api-select.tsx index 3041e796a..accd8190b 100644 --- a/apps/web-ele/src/components/form-create/components/use-api-select.tsx +++ b/apps/web-ele/src/components/form-create/components/use-api-select.tsx @@ -2,6 +2,7 @@ import type { ApiSelectProps } from '#/components/form-create/typing'; import { defineComponent, onMounted, ref, useAttrs } from 'vue'; +import { useUserStore } from '@vben/stores'; import { isEmpty } from '@vben/utils'; import { @@ -74,12 +75,46 @@ export function useApiSelect(option: ApiSelectProps) { type: String, default: 'id', }, + // 是否默认选中当前用户(仅用于 UserSelect) + defaultCurrentUser: { + type: Boolean, + default: false, + }, }, - setup(props) { + setup(props, { emit }) { const attrs = useAttrs(); const options = ref([]); // 下拉数据 const loading = ref(false); // 是否正在从远程获取数据 const queryParam = ref(); // 当前输入的值 + + // 检查是否有有效的预设值 + function hasValidPresetValue(): boolean { + const value = attrs.modelValue; + if (value === undefined || value === null || value === '') { + return false; + } + if (Array.isArray(value)) { + return value.length > 0; + } + return true; + } + + // 设置默认当前用户 + function setDefaultCurrentUser(): void { + if (option.name !== 'UserSelect' || !props.defaultCurrentUser) { + return; + } + if (hasValidPresetValue()) { + return; + } + const userStore = useUserStore(); + const currentUserId = userStore.userInfo?.id; + if (currentUserId) { + const defaultValue = props.multiple ? [currentUserId] : currentUserId; + emit('update:modelValue', defaultValue); + } + } + const getOptions = async () => { options.value = []; // 接口选择器 @@ -199,6 +234,8 @@ export function useApiSelect(option: ApiSelectProps) { onMounted(async () => { await getOptions(); + // 设置默认当前用户(仅用于 UserSelect) + setDefaultCurrentUser(); }); const buildSelect = () => { diff --git a/apps/web-ele/src/components/form-create/helpers.ts b/apps/web-ele/src/components/form-create/helpers.ts index a5b123225..dbe938a5e 100644 --- a/apps/web-ele/src/components/form-create/helpers.ts +++ b/apps/web-ele/src/components/form-create/helpers.ts @@ -189,6 +189,14 @@ export async function useFormCreateDesigner(designer: Ref) { name: 'UserSelect', label: '用户选择器', icon: 'icon-eye', + props: [ + { + type: 'switch', + field: 'defaultCurrentUser', + title: '默认选中当前用户', + value: true, + }, + ], }); const deptSelectRule = useSelectRule({ name: 'DeptSelect', @@ -205,6 +213,12 @@ export async function useFormCreateDesigner(designer: Ref) { { label: '部门名称', value: 'name' }, ], }, + { + type: 'switch', + field: 'defaultCurrentDept', + title: '默认选中当前部门', + value: true, + }, ], }); const dictSelectRule = useDictSelectRule(); diff --git a/apps/web-ele/src/components/form-create/rules/use-select-rule.ts b/apps/web-ele/src/components/form-create/rules/use-select-rule.ts index 3efd8c9d5..fc89b9a24 100644 --- a/apps/web-ele/src/components/form-create/rules/use-select-rule.ts +++ b/apps/web-ele/src/components/form-create/rules/use-select-rule.ts @@ -23,13 +23,24 @@ export function useSelectRule(option: SelectRuleOption) { name, event: option.event, rule() { - return { + // 构建基础规则 + const baseRule: any = { type: name, field: buildUUID(), title: label, info: '', $required: false, }; + // 将自定义 props 的默认值添加到 rule 的 props 中 + if (option.props && option.props.length > 0) { + baseRule.props = {}; + option.props.forEach((prop: any) => { + if (prop.field && prop.value !== undefined) { + baseRule.props[prop.field] = prop.value; + } + }); + } + return baseRule; }, props(_: any, { t }: any) { if (!option.props) { diff --git a/apps/web-ele/src/plugins/form-create/index.ts b/apps/web-ele/src/plugins/form-create/index.ts index f4578b4f2..785c901de 100644 --- a/apps/web-ele/src/plugins/form-create/index.ts +++ b/apps/web-ele/src/plugins/form-create/index.ts @@ -34,6 +34,7 @@ import { // ======================= 自定义组件 ======================= import { useApiSelect } from '#/components/form-create'; +import DeptSelect from '#/components/form-create/components/dept-select.vue'; import DictSelect from '#/components/form-create/components/dict-select.vue'; import { useImagesUpload } from '#/components/form-create/components/use-images-upload'; import { Tinymce } from '#/components/tinymce'; @@ -45,12 +46,6 @@ const UserSelect = useApiSelect({ valueField: 'id', url: '/system/user/simple-list', }); -const DeptSelect = useApiSelect({ - name: 'DeptSelect', - labelField: 'name', - valueField: 'id', - url: '/system/dept/simple-list', -}); const ApiSelect = useApiSelect({ name: 'ApiSelect', }); From 548da70f9fc157e33a2cba00a5c57a207ffa3742 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Wed, 28 Jan 2026 17:45:20 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(mall):=20=E3=80=90antd/ele=E3=80=91?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=95=86=E5=93=81=20SKU=20=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前端创建/编辑商品时,SKU 对象缺少 name 字段初始化,导致提交时后端校验 "商品 SKU 名字不能为空" 失败。 修改内容: - form/index.vue: 初始化 SKU 添加 name 字段,提交前校验商品名称并赋值给 SKU - sku-list.vue: createEmptySku 函数添加 name 字段 影响范围:web-antd、web-ele 两个版本 --- .../views/mall/product/spu/components/sku-list.vue | 1 + .../src/views/mall/product/spu/form/index.vue | 9 +++++++++ .../views/mall/product/spu/components/sku-list.vue | 1 + .../src/views/mall/product/spu/form/index.vue | 14 +++++++++++--- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/web-antd/src/views/mall/product/spu/components/sku-list.vue b/apps/web-antd/src/views/mall/product/spu/components/sku-list.vue index a5f6f383a..383cda1cd 100644 --- a/apps/web-antd/src/views/mall/product/spu/components/sku-list.vue +++ b/apps/web-antd/src/views/mall/product/spu/components/sku-list.vue @@ -56,6 +56,7 @@ const tableHeaders = ref<{ label: string; prop: string }[]>([]); /** 创建空 SKU 数据 */ function createEmptySku(): MallSpuApi.Sku { return { + name: '', // SKU 名称,提交时会自动使用 SPU 名称 price: 0, marketPrice: 0, costPrice: 0, diff --git a/apps/web-antd/src/views/mall/product/spu/form/index.vue b/apps/web-antd/src/views/mall/product/spu/form/index.vue index 0d654b318..e8aa3ea3a 100644 --- a/apps/web-antd/src/views/mall/product/spu/form/index.vue +++ b/apps/web-antd/src/views/mall/product/spu/form/index.vue @@ -50,6 +50,7 @@ const formData = ref({ subCommissionType: false, skus: [ { + name: '', // SKU 名称,提交时会自动使用 SPU 名称 price: 0, marketPrice: 0, costPrice: 0, @@ -181,6 +182,11 @@ async function handleSubmit() { .merge(otherFormApi) .submitAllForm(true); values.skus = formData.value.skus; + // 校验商品名称不能为空(用于 SKU name) + if (!values.name || values.name.trim() === '') { + message.error('商品名称不能为空'); + return; + } if (values.skus) { try { // 校验 sku @@ -190,6 +196,8 @@ async function handleSubmit() { return; } values.skus.forEach((item) => { + // 给 sku name 赋值(使用商品名称作为 SKU 名称) + item.name = values.name; // 金额转换:元转分 item.price = convertToInteger(item.price); item.marketPrice = convertToInteger(item.marketPrice); @@ -277,6 +285,7 @@ function handleChangeSpec() { // 重置 sku 列表 formData.value.skus = [ { + name: '', // SKU 名称,提交时会自动使用 SPU 名称 price: 0, marketPrice: 0, costPrice: 0, diff --git a/apps/web-ele/src/views/mall/product/spu/components/sku-list.vue b/apps/web-ele/src/views/mall/product/spu/components/sku-list.vue index 06dc595b9..bcf6eb617 100644 --- a/apps/web-ele/src/views/mall/product/spu/components/sku-list.vue +++ b/apps/web-ele/src/views/mall/product/spu/components/sku-list.vue @@ -62,6 +62,7 @@ const tableHeaders = ref<{ label: string; prop: string }[]>([]); /** 创建空 SKU 数据 */ function createEmptySku(): MallSpuApi.Sku { return { + name: '', // SKU 名称,提交时会自动使用 SPU 名称 price: 0, marketPrice: 0, costPrice: 0, diff --git a/apps/web-ele/src/views/mall/product/spu/form/index.vue b/apps/web-ele/src/views/mall/product/spu/form/index.vue index 5f8a53d66..b2d65e30e 100644 --- a/apps/web-ele/src/views/mall/product/spu/form/index.vue +++ b/apps/web-ele/src/views/mall/product/spu/form/index.vue @@ -50,6 +50,7 @@ const formData = ref({ subCommissionType: false, skus: [ { + name: '', // SKU 名称,提交时会自动使用 SPU 名称 price: 0, marketPrice: 0, costPrice: 0, @@ -168,8 +169,8 @@ const [OtherForm, otherFormApi] = useVbenForm({ }); /** tab 切换 */ -function handleTabChange(key: string) { - activeTabName.value = key; +function handleTabChange(key: number | string) { + activeTabName.value = key as string; } /** 提交表单 */ @@ -181,6 +182,11 @@ async function handleSubmit() { .merge(otherFormApi) .submitAllForm(true); values.skus = formData.value.skus; + // 校验商品名称不能为空(用于 SKU name) + if (!values.name || values.name.trim() === '') { + ElMessage.error('商品名称不能为空'); + return; + } if (values.skus) { try { // 校验 sku @@ -190,6 +196,8 @@ async function handleSubmit() { return; } values.skus.forEach((item) => { + // 给 sku name 赋值(使用商品名称作为 SKU 名称) + item.name = values.name; // 金额转换:元转分 item.price = convertToInteger(item.price); item.marketPrice = convertToInteger(item.marketPrice); @@ -277,6 +285,7 @@ function handleChangeSpec() { // 重置 sku 列表 formData.value.skus = [ { + name: '', // SKU 名称,提交时会自动使用 SPU 名称 price: 0, marketPrice: 0, costPrice: 0, @@ -320,7 +329,6 @@ onMounted(async () => {