!860 表单设计器 UserSelect/DeptSelect 支持默认选中当前用户/部门、修复商品 SKU 名称校验失败的问题

Merge pull request !860 from puhui999/master-dev
pull/861/head^2
芋道源码 2026-01-29 12:13:04 +00:00 committed by Gitee
commit a10d564f9b
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
8 changed files with 289 additions and 10 deletions

View File

@ -0,0 +1,196 @@
<!-- 部门选择器 - 树形结构显示 -->
<template>
<el-tree-select
v-model="selectedValue"
class="w-1/1"
:data="deptTree"
:props="treeProps"
:multiple="multiple"
:disabled="disabled"
:placeholder="placeholder || '请选择部门'"
:check-strictly="true"
:filterable="true"
:filter-node-method="filterNode"
:clearable="true"
:render-after-expand="false"
node-key="id"
@change="handleChange"
/>
</template>
<script lang="ts" setup>
import { handleTree } from '@/utils/tree'
import { getSimpleDeptList, type DeptVO } from '@/api/system/dept'
import { useUserStoreWithOut } from '@/store/modules/user'
defineOptions({ name: 'DeptSelect' })
//
interface Props {
modelValue?: number | string | number[] | string[]
multiple?: boolean
returnType?: 'id' | 'name'
defaultCurrentDept?: boolean
disabled?: boolean
placeholder?: string
formCreateInject?: any
}
const props = withDefaults(defineProps<Props>(), {
multiple: false,
returnType: 'id',
defaultCurrentDept: false,
disabled: false,
placeholder: ''
})
const emit = defineEmits<{
(e: 'update:modelValue', value: number | string | number[] | string[] | undefined): void
}>()
//
const treeProps = {
label: 'name',
value: 'id',
children: 'children'
}
//
const deptTree = ref<any[]>([])
// returnType='name'
const deptList = ref<DeptVO[]>([])
//
const selectedValue = ref<number | string | number[] | string[] | undefined>()
//
const loadDeptTree = async () => {
try {
const data = await getSimpleDeptList()
deptList.value = data
deptTree.value = handleTree(data)
} catch (error) {
console.warn('加载部门数据失败:', error)
deptTree.value = []
}
}
// ID
const getDeptNameById = (id: number): string | undefined => {
const dept = deptList.value.find((item) => item.id === id)
return dept?.name
}
// ID
const getDeptIdByName = (name: string): number | undefined => {
const dept = deptList.value.find((item) => item.name === name)
return dept?.id
}
//
const handleChange = (value: number | number[] | undefined) => {
if (value === undefined || value === null) {
emit('update:modelValue', props.multiple ? [] : undefined)
return
}
// returnType
if (props.returnType === 'name') {
if (props.multiple && Array.isArray(value)) {
const names = value.map((id) => getDeptNameById(id)).filter(Boolean) as string[]
emit('update:modelValue', names)
} else if (!props.multiple && typeof value === 'number') {
const name = getDeptNameById(value)
emit('update:modelValue', name)
}
} else {
emit('update:modelValue', value)
}
}
//
const filterNode = (value: string, data: any) => {
if (!value) return true
return data.name.includes(value)
}
// modelValue
watch(
() => props.modelValue,
(newValue) => {
if (newValue === undefined || newValue === null) {
selectedValue.value = props.multiple ? [] : undefined
return
}
// returnType 'name' ID
if (props.returnType === 'name') {
if (props.multiple && Array.isArray(newValue)) {
const ids = (newValue as string[])
.map((name) => getDeptIdByName(name))
.filter(Boolean) as number[]
selectedValue.value = ids
} else if (!props.multiple && typeof newValue === 'string') {
const id = getDeptIdByName(newValue)
selectedValue.value = id
}
} else {
selectedValue.value = newValue as number | number[]
}
},
{ immediate: true }
)
//
const hasValidPresetValue = (): boolean => {
const value = props.modelValue
if (value === undefined || value === null || value === '') {
return false
}
if (Array.isArray(value)) {
return value.length > 0
}
return true
}
//
const setDefaultValue = () => {
console.log('[DeptSelect] setDefaultValue called, defaultCurrentDept:', props.defaultCurrentDept)
// defaultCurrentDept true
if (!props.defaultCurrentDept) {
console.log('[DeptSelect] defaultCurrentDept is false, skip')
return
}
//
if (hasValidPresetValue()) {
console.log('[DeptSelect] has preset value, skip:', props.modelValue)
return
}
// ID
const userStore = useUserStoreWithOut()
const user = userStore.getUser
const deptId = user?.deptId
console.log('[DeptSelect] current user:', user, 'deptId:', deptId)
// deptId 0
if (!deptId || deptId === 0) {
console.log('[DeptSelect] deptId is invalid, skip')
return
}
//
const defaultValue = props.multiple ? [deptId] : deptId
console.log('[DeptSelect] setting default value:', defaultValue)
emit('update:modelValue', defaultValue)
}
//
onMounted(async () => {
await loadDeptTree()
//
setDefaultValue()
})
</script>

View File

@ -2,6 +2,7 @@ import request from '@/config/axios'
import { isEmpty } from '@/utils/is'
import { ApiSelectProps } from '@/components/FormCreate/src/type'
import { jsonParse } from '@/utils'
import { useUserStoreWithOut } from '@/store/modules/user'
export const useApiSelect = (option: ApiSelectProps) => {
return defineComponent({
@ -61,13 +62,62 @@ export const useApiSelect = (option: ApiSelectProps) => {
returnType: {
type: String,
default: 'id'
},
// 是否默认选中当前用户(仅 UserSelect 使用)
defaultCurrentUser: {
type: Boolean,
default: false
}
},
setup(props) {
setup(props, { emit }) {
const attrs = useAttrs()
const options = ref<any[]>([]) // 下拉数据
const loading = ref(false) // 是否正在从远程获取数据
const queryParam = ref<any>() // 当前输入的值
// 检查是否有有效的预设值
const hasValidPresetValue = (): boolean => {
const value = attrs.modelValue
if (value === undefined || value === null || value === '') {
return false
}
if (Array.isArray(value)) {
return value.length > 0
}
return true
}
// 设置默认当前用户(仅当 defaultCurrentUser 为 true 且无预设值时)
const setDefaultCurrentUser = () => {
console.log('[UserSelect] setDefaultCurrentUser called, defaultCurrentUser:', props.defaultCurrentUser)
// 仅当组件名为 UserSelect 且 defaultCurrentUser 为 true 时处理
if (option.name !== 'UserSelect' || !props.defaultCurrentUser) {
console.log('[UserSelect] skip - not UserSelect or defaultCurrentUser is false')
return
}
// 检查是否已有预设值(预设值优先级高于默认当前用户)
if (hasValidPresetValue()) {
console.log('[UserSelect] has preset value, skip:', attrs.modelValue)
return
}
// 获取当前用户 ID
const userStore = useUserStoreWithOut()
const user = userStore.getUser
const currentUserId = user?.id
console.log('[UserSelect] current user:', user, 'userId:', currentUserId)
if (currentUserId) {
// 根据多选/单选模式设置默认值
const defaultValue = props.multiple ? [currentUserId] : currentUserId
console.log('[UserSelect] setting default value:', defaultValue)
emit('update:modelValue', defaultValue)
}
}
const getOptions = async () => {
options.value = []
// 接口选择器
@ -188,6 +238,8 @@ export const useApiSelect = (option: ApiSelectProps) => {
onMounted(async () => {
await getOptions()
// 设置默认当前用户(在数据加载完成后)
setDefaultCurrentUser()
})
const buildSelect = () => {

View File

@ -19,13 +19,24 @@ export const useSelectRule = (option: SelectRuleOption) => {
name,
event: option.event,
rule() {
return {
// 构建基础规则
const baseRule: any = {
type: name,
field: generateUUID(),
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(_, { t }) {
if (!option.props) {

View File

@ -52,7 +52,15 @@ export const useFormCreateDesigner = async (designer: Ref) => {
const userSelectRule = useSelectRule({
name: 'UserSelect',
label: '用户选择器',
icon: 'icon-user-o'
icon: 'icon-user-o',
props: [
{
type: 'switch',
field: 'defaultCurrentUser',
title: '默认选中当前用户',
value: true
}
]
})
const deptSelectRule = useSelectRule({
name: 'DeptSelect',
@ -68,6 +76,12 @@ export const useFormCreateDesigner = async (designer: Ref) => {
{ label: '部门编号', value: 'id' },
{ label: '部门名称', value: 'name' }
]
},
{
type: 'switch',
field: 'defaultCurrentDept',
title: '默认选中当前部门',
value: true
}
]
})

View File

@ -68,6 +68,7 @@ import { UploadFile, UploadImg, UploadImgs } from '@/components/UploadFile'
import { useApiSelect } from '@/components/FormCreate'
import { Editor } from '@/components/Editor'
import DictSelect from '@/components/FormCreate/src/components/DictSelect.vue'
import DeptSelect from '@/components/FormCreate/src/components/DeptSelect.vue'
const UserSelect = useApiSelect({
name: 'UserSelect',
@ -75,12 +76,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'
})

View File

@ -318,6 +318,7 @@ const props = defineProps({
const formData: Ref<Spu | undefined> = ref<Spu>() //
const skuList = ref<Sku[]>([
{
name: '', // SKU
price: 0, //
marketPrice: 0, //
costPrice: 0, //
@ -449,6 +450,7 @@ const generateTableData = (propertyList: any[]) => {
}
for (const item of buildSkuList) {
const row = {
name: '', // SKU 使 SPU
properties: Array.isArray(item) ? item : [item], // property
price: 0,
marketPrice: 0,
@ -525,6 +527,7 @@ watch(
if (props.isBatch) {
skuList.value = [
{
name: '', // SKU
price: 0,
marketPrice: 0,
costPrice: 0,

View File

@ -173,6 +173,7 @@ const onChangeSpec = () => {
// sku
formData.skus = [
{
name: '', // SKU 使 SPU
price: 0,
marketPrice: 0,
costPrice: 0,

View File

@ -62,6 +62,7 @@ import OtherForm from './OtherForm.vue'
import SkuForm from './SkuForm.vue'
import DeliveryForm from './DeliveryForm.vue'
import { convertToInteger, floatToFixed2, formatToFraction } from '@/utils'
import { isEmpty } from '@/utils/is'
defineOptions({ name: 'ProductSpuAdd' })
@ -94,6 +95,7 @@ const formData = ref<ProductSpuApi.Spu>({
subCommissionType: false, //
skus: [
{
name: '', // SKU 使 SPU
price: 0, //
marketPrice: 0, //
costPrice: 0, //
@ -158,8 +160,13 @@ const submitForm = async () => {
await unref(otherRef)?.validate()
// , server
const deepCopyFormData = cloneDeep(unref(formData.value)) as ProductSpuApi.Spu
// SKU name
if (isEmpty(deepCopyFormData.name)) {
message.error('商品名称不能为空')
return
}
deepCopyFormData.skus!.forEach((item) => {
// sku name
// sku name使 SKU
item.name = deepCopyFormData.name
// sku
item.price = convertToInteger(item.price)