mirror of
https://github.com/yudaocode/yudao-ui-admin-vue3.git
synced 2026-04-19 13:38:39 +00:00
!860 表单设计器 UserSelect/DeptSelect 支持默认选中当前用户/部门、修复商品 SKU 名称校验失败的问题
Merge pull request !860 from puhui999/master-dev
This commit is contained in:
196
src/components/FormCreate/src/components/DeptSelect.vue
Normal file
196
src/components/FormCreate/src/components/DeptSelect.vue
Normal 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>
|
||||||
@@ -2,6 +2,7 @@ import request from '@/config/axios'
|
|||||||
import { isEmpty } from '@/utils/is'
|
import { isEmpty } from '@/utils/is'
|
||||||
import { ApiSelectProps } from '@/components/FormCreate/src/type'
|
import { ApiSelectProps } from '@/components/FormCreate/src/type'
|
||||||
import { jsonParse } from '@/utils'
|
import { jsonParse } from '@/utils'
|
||||||
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
|
||||||
export const useApiSelect = (option: ApiSelectProps) => {
|
export const useApiSelect = (option: ApiSelectProps) => {
|
||||||
return defineComponent({
|
return defineComponent({
|
||||||
@@ -61,13 +62,62 @@ export const useApiSelect = (option: ApiSelectProps) => {
|
|||||||
returnType: {
|
returnType: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'id'
|
default: 'id'
|
||||||
|
},
|
||||||
|
// 是否默认选中当前用户(仅 UserSelect 使用)
|
||||||
|
defaultCurrentUser: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props, { emit }) {
|
||||||
const attrs = useAttrs()
|
const attrs = useAttrs()
|
||||||
const options = ref<any[]>([]) // 下拉数据
|
const options = ref<any[]>([]) // 下拉数据
|
||||||
const loading = ref(false) // 是否正在从远程获取数据
|
const loading = ref(false) // 是否正在从远程获取数据
|
||||||
const queryParam = ref<any>() // 当前输入的值
|
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 () => {
|
const getOptions = async () => {
|
||||||
options.value = []
|
options.value = []
|
||||||
// 接口选择器
|
// 接口选择器
|
||||||
@@ -188,6 +238,8 @@ export const useApiSelect = (option: ApiSelectProps) => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getOptions()
|
await getOptions()
|
||||||
|
// 设置默认当前用户(在数据加载完成后)
|
||||||
|
setDefaultCurrentUser()
|
||||||
})
|
})
|
||||||
|
|
||||||
const buildSelect = () => {
|
const buildSelect = () => {
|
||||||
|
|||||||
@@ -19,13 +19,24 @@ export const useSelectRule = (option: SelectRuleOption) => {
|
|||||||
name,
|
name,
|
||||||
event: option.event,
|
event: option.event,
|
||||||
rule() {
|
rule() {
|
||||||
return {
|
// 构建基础规则
|
||||||
|
const baseRule: any = {
|
||||||
type: name,
|
type: name,
|
||||||
field: generateUUID(),
|
field: generateUUID(),
|
||||||
title: label,
|
title: label,
|
||||||
info: '',
|
info: '',
|
||||||
$required: false
|
$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 }) {
|
props(_, { t }) {
|
||||||
if (!option.props) {
|
if (!option.props) {
|
||||||
|
|||||||
@@ -52,7 +52,15 @@ export const useFormCreateDesigner = async (designer: Ref) => {
|
|||||||
const userSelectRule = useSelectRule({
|
const userSelectRule = useSelectRule({
|
||||||
name: 'UserSelect',
|
name: 'UserSelect',
|
||||||
label: '用户选择器',
|
label: '用户选择器',
|
||||||
icon: 'icon-user-o'
|
icon: 'icon-user-o',
|
||||||
|
props: [
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'defaultCurrentUser',
|
||||||
|
title: '默认选中当前用户',
|
||||||
|
value: true
|
||||||
|
}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
const deptSelectRule = useSelectRule({
|
const deptSelectRule = useSelectRule({
|
||||||
name: 'DeptSelect',
|
name: 'DeptSelect',
|
||||||
@@ -68,6 +76,12 @@ export const useFormCreateDesigner = async (designer: Ref) => {
|
|||||||
{ label: '部门编号', value: 'id' },
|
{ label: '部门编号', value: 'id' },
|
||||||
{ label: '部门名称', value: 'name' }
|
{ label: '部门名称', value: 'name' }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'defaultCurrentDept',
|
||||||
|
title: '默认选中当前部门',
|
||||||
|
value: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ import { UploadFile, UploadImg, UploadImgs } from '@/components/UploadFile'
|
|||||||
import { useApiSelect } from '@/components/FormCreate'
|
import { useApiSelect } from '@/components/FormCreate'
|
||||||
import { Editor } from '@/components/Editor'
|
import { Editor } from '@/components/Editor'
|
||||||
import DictSelect from '@/components/FormCreate/src/components/DictSelect.vue'
|
import DictSelect from '@/components/FormCreate/src/components/DictSelect.vue'
|
||||||
|
import DeptSelect from '@/components/FormCreate/src/components/DeptSelect.vue'
|
||||||
|
|
||||||
const UserSelect = useApiSelect({
|
const UserSelect = useApiSelect({
|
||||||
name: 'UserSelect',
|
name: 'UserSelect',
|
||||||
@@ -75,12 +76,6 @@ const UserSelect = useApiSelect({
|
|||||||
valueField: 'id',
|
valueField: 'id',
|
||||||
url: '/system/user/simple-list'
|
url: '/system/user/simple-list'
|
||||||
})
|
})
|
||||||
const DeptSelect = useApiSelect({
|
|
||||||
name: 'DeptSelect',
|
|
||||||
labelField: 'name',
|
|
||||||
valueField: 'id',
|
|
||||||
url: '/system/dept/simple-list'
|
|
||||||
})
|
|
||||||
const ApiSelect = useApiSelect({
|
const ApiSelect = useApiSelect({
|
||||||
name: 'ApiSelect'
|
name: 'ApiSelect'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -318,6 +318,7 @@ const props = defineProps({
|
|||||||
const formData: Ref<Spu | undefined> = ref<Spu>() // 表单数据
|
const formData: Ref<Spu | undefined> = ref<Spu>() // 表单数据
|
||||||
const skuList = ref<Sku[]>([
|
const skuList = ref<Sku[]>([
|
||||||
{
|
{
|
||||||
|
name: '', // SKU 名称
|
||||||
price: 0, // 商品价格
|
price: 0, // 商品价格
|
||||||
marketPrice: 0, // 市场价
|
marketPrice: 0, // 市场价
|
||||||
costPrice: 0, // 成本价
|
costPrice: 0, // 成本价
|
||||||
@@ -449,6 +450,7 @@ const generateTableData = (propertyList: any[]) => {
|
|||||||
}
|
}
|
||||||
for (const item of buildSkuList) {
|
for (const item of buildSkuList) {
|
||||||
const row = {
|
const row = {
|
||||||
|
name: '', // SKU 名称,提交时会自动使用 SPU 名称
|
||||||
properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象
|
properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象
|
||||||
price: 0,
|
price: 0,
|
||||||
marketPrice: 0,
|
marketPrice: 0,
|
||||||
@@ -525,6 +527,7 @@ watch(
|
|||||||
if (props.isBatch) {
|
if (props.isBatch) {
|
||||||
skuList.value = [
|
skuList.value = [
|
||||||
{
|
{
|
||||||
|
name: '', // SKU 名称
|
||||||
price: 0,
|
price: 0,
|
||||||
marketPrice: 0,
|
marketPrice: 0,
|
||||||
costPrice: 0,
|
costPrice: 0,
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ const onChangeSpec = () => {
|
|||||||
// 重置sku列表
|
// 重置sku列表
|
||||||
formData.skus = [
|
formData.skus = [
|
||||||
{
|
{
|
||||||
|
name: '', // SKU 名称,提交时会自动使用 SPU 名称
|
||||||
price: 0,
|
price: 0,
|
||||||
marketPrice: 0,
|
marketPrice: 0,
|
||||||
costPrice: 0,
|
costPrice: 0,
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import OtherForm from './OtherForm.vue'
|
|||||||
import SkuForm from './SkuForm.vue'
|
import SkuForm from './SkuForm.vue'
|
||||||
import DeliveryForm from './DeliveryForm.vue'
|
import DeliveryForm from './DeliveryForm.vue'
|
||||||
import { convertToInteger, floatToFixed2, formatToFraction } from '@/utils'
|
import { convertToInteger, floatToFixed2, formatToFraction } from '@/utils'
|
||||||
|
import { isEmpty } from '@/utils/is'
|
||||||
|
|
||||||
defineOptions({ name: 'ProductSpuAdd' })
|
defineOptions({ name: 'ProductSpuAdd' })
|
||||||
|
|
||||||
@@ -94,6 +95,7 @@ const formData = ref<ProductSpuApi.Spu>({
|
|||||||
subCommissionType: false, // 分销类型
|
subCommissionType: false, // 分销类型
|
||||||
skus: [
|
skus: [
|
||||||
{
|
{
|
||||||
|
name: '', // SKU 名称,提交时会自动使用 SPU 名称
|
||||||
price: 0, // 商品价格
|
price: 0, // 商品价格
|
||||||
marketPrice: 0, // 市场价
|
marketPrice: 0, // 市场价
|
||||||
costPrice: 0, // 成本价
|
costPrice: 0, // 成本价
|
||||||
@@ -158,8 +160,13 @@ const submitForm = async () => {
|
|||||||
await unref(otherRef)?.validate()
|
await unref(otherRef)?.validate()
|
||||||
// 深拷贝一份, 这样最终 server 端不满足,不需要影响原始数据
|
// 深拷贝一份, 这样最终 server 端不满足,不需要影响原始数据
|
||||||
const deepCopyFormData = cloneDeep(unref(formData.value)) as ProductSpuApi.Spu
|
const deepCopyFormData = cloneDeep(unref(formData.value)) as ProductSpuApi.Spu
|
||||||
|
// 校验商品名称不能为空(用于 SKU name)
|
||||||
|
if (isEmpty(deepCopyFormData.name)) {
|
||||||
|
message.error('商品名称不能为空')
|
||||||
|
return
|
||||||
|
}
|
||||||
deepCopyFormData.skus!.forEach((item) => {
|
deepCopyFormData.skus!.forEach((item) => {
|
||||||
// 给sku name赋值
|
// 给sku name赋值(使用商品名称作为 SKU 名称)
|
||||||
item.name = deepCopyFormData.name
|
item.name = deepCopyFormData.name
|
||||||
// sku相关价格元转分
|
// sku相关价格元转分
|
||||||
item.price = convertToInteger(item.price)
|
item.price = convertToInteger(item.price)
|
||||||
|
|||||||
Reference in New Issue
Block a user