@@ -0,0 +1,205 @@
|
|||||||
|
<!-- 部门选择器 - 树形结构显示 -->
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { useUserStore } from '@vben/stores';
|
||||||
|
import { handleTree } from '@vben/utils';
|
||||||
|
|
||||||
|
import { TreeSelect } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
defineOptions({ name: 'DeptSelect' });
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
multiple: false,
|
||||||
|
returnType: 'id',
|
||||||
|
defaultCurrentDept: false,
|
||||||
|
disabled: false,
|
||||||
|
placeholder: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(
|
||||||
|
e: 'update:modelValue',
|
||||||
|
value: number | number[] | string | string[] | undefined,
|
||||||
|
): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// todo @puhui999:是不是可以简化,使用 api 的;
|
||||||
|
/** 部门数据接口 */
|
||||||
|
interface DeptVO {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
parentId: number;
|
||||||
|
sort?: number;
|
||||||
|
leaderUserId?: number;
|
||||||
|
phone?: string;
|
||||||
|
email?: string;
|
||||||
|
status?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO @puhui999:linter 报错;
|
||||||
|
/** 接受父组件参数 */
|
||||||
|
interface Props {
|
||||||
|
modelValue?: number | number[] | string | string[];
|
||||||
|
multiple?: boolean;
|
||||||
|
returnType?: 'id' | 'name';
|
||||||
|
defaultCurrentDept?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
placeholder?: string;
|
||||||
|
formCreateInject?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deptTree = ref<any[]>([]); // 部门树形数据
|
||||||
|
const deptList = ref<DeptVO[]>([]); // 原始部门列表(用于 returnType='name' 时查找名称)
|
||||||
|
const selectedValue = ref<number | number[] | undefined>(); // 当前选中值
|
||||||
|
|
||||||
|
/** 加载部门树形数据 */
|
||||||
|
async function loadDeptTree(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const data = await requestClient.get<DeptVO[]>('/system/dept/simple-list');
|
||||||
|
deptList.value = data;
|
||||||
|
deptTree.value = handleTree(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[DeptSelect] 加载部门数据失败:', error);
|
||||||
|
deptTree.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据 ID 获取部门名称 */
|
||||||
|
function getDeptNameById(id: number): string | undefined {
|
||||||
|
const dept = deptList.value.find((item: DeptVO) => item.id === id);
|
||||||
|
if (!dept) {
|
||||||
|
console.warn(`[DeptSelect] 未找到 ID 为 ${id} 的部门`);
|
||||||
|
}
|
||||||
|
return dept?.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据名称获取部门 ID */
|
||||||
|
function getDeptIdByName(name: string): number | undefined {
|
||||||
|
const dept = deptList.value.find((item: DeptVO) => item.name === name);
|
||||||
|
return dept?.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理选中值变化 */
|
||||||
|
function handleChange(value: number | number[] | undefined): void {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 树节点过滤方法(支持搜索过滤) */
|
||||||
|
function filterTreeNode(inputValue: string, treeNode: any): boolean {
|
||||||
|
if (!inputValue) return true;
|
||||||
|
return treeNode.name?.toLowerCase().includes(inputValue.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 同步 modelValue 到内部选中值 */
|
||||||
|
function syncSelectedValue(): void {
|
||||||
|
const newValue = props.modelValue;
|
||||||
|
if (newValue === undefined || newValue === null) {
|
||||||
|
selectedValue.value = props.multiple ? [] : undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 returnType 是 'name',需要将名称转换为 ID 用于树选择器显示
|
||||||
|
if (props.returnType === 'name') {
|
||||||
|
// 只有在 deptList 加载完成后才能进行转换
|
||||||
|
if (deptList.value.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.multiple && Array.isArray(newValue)) {
|
||||||
|
selectedValue.value = (newValue as string[])
|
||||||
|
.map((name) => getDeptIdByName(name))
|
||||||
|
.filter(Boolean) as number[];
|
||||||
|
} else if (!props.multiple && typeof newValue === 'string') {
|
||||||
|
selectedValue.value = getDeptIdByName(newValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedValue.value = newValue as number | number[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 监听 modelValue 变化,同步到内部选中值 */
|
||||||
|
watch(() => props.modelValue, syncSelectedValue, { immediate: true });
|
||||||
|
|
||||||
|
/** 监听 deptList 变化,重新同步选中值(解决数据加载完成后的回显问题) */
|
||||||
|
watch(() => deptList.value, syncSelectedValue);
|
||||||
|
|
||||||
|
/** 检查是否有有效的预设值 */
|
||||||
|
function hasValidPresetValue(): boolean {
|
||||||
|
const value = props.modelValue;
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.length > 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 设置默认值(当前用户部门) */
|
||||||
|
function setDefaultValue(): void {
|
||||||
|
// 仅当 defaultCurrentDept 为 true 时处理
|
||||||
|
if (!props.defaultCurrentDept) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 检查是否已有预设值(预设值优先级高于默认当前部门)
|
||||||
|
if (hasValidPresetValue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前用户的部门 ID
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const deptId = userStore.userInfo?.deptId as number | undefined;
|
||||||
|
// 处理 deptId 为空或 0 的边界情况
|
||||||
|
if (!deptId || deptId === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据多选模式决定默认值格式
|
||||||
|
const defaultValue = props.multiple ? [deptId] : deptId;
|
||||||
|
emit('update:modelValue', defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 组件挂载时加载数据并设置默认值 */
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadDeptTree();
|
||||||
|
// 数据加载完成后设置默认值
|
||||||
|
setDefaultValue();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TreeSelect
|
||||||
|
v-model:value="selectedValue"
|
||||||
|
class="w-full"
|
||||||
|
:tree-data="deptTree"
|
||||||
|
:field-names="{ label: 'name', value: 'id', children: 'children' }"
|
||||||
|
:multiple="multiple"
|
||||||
|
:disabled="disabled"
|
||||||
|
:placeholder="placeholder || '请选择部门'"
|
||||||
|
:tree-checkable="multiple"
|
||||||
|
:show-search="true"
|
||||||
|
:filter-tree-node="filterTreeNode"
|
||||||
|
:allow-clear="true"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -2,6 +2,7 @@ import type { ApiSelectProps } from '#/components/form-create/typing';
|
|||||||
|
|
||||||
import { defineComponent, onMounted, ref, useAttrs } from 'vue';
|
import { defineComponent, onMounted, ref, useAttrs } from 'vue';
|
||||||
|
|
||||||
|
import { useUserStore } from '@vben/stores';
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -74,12 +75,46 @@ export function useApiSelect(option: ApiSelectProps) {
|
|||||||
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>(); // 当前输入的值
|
||||||
|
|
||||||
|
// 检查是否有有效的预设值
|
||||||
|
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 () => {
|
const getOptions = async () => {
|
||||||
options.value = [];
|
options.value = [];
|
||||||
// 接口选择器
|
// 接口选择器
|
||||||
@@ -199,6 +234,8 @@ export function useApiSelect(option: ApiSelectProps) {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getOptions();
|
await getOptions();
|
||||||
|
// 设置默认当前用户(仅用于 UserSelect)
|
||||||
|
setDefaultCurrentUser();
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildSelect = () => {
|
const buildSelect = () => {
|
||||||
|
|||||||
@@ -189,6 +189,14 @@ export async function useFormCreateDesigner(designer: Ref) {
|
|||||||
name: 'UserSelect',
|
name: 'UserSelect',
|
||||||
label: '用户选择器',
|
label: '用户选择器',
|
||||||
icon: 'icon-eye',
|
icon: 'icon-eye',
|
||||||
|
props: [
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'defaultCurrentUser',
|
||||||
|
title: '默认选中当前用户',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
const deptSelectRule = useSelectRule({
|
const deptSelectRule = useSelectRule({
|
||||||
name: 'DeptSelect',
|
name: 'DeptSelect',
|
||||||
@@ -205,6 +213,12 @@ export async function useFormCreateDesigner(designer: Ref) {
|
|||||||
{ label: '部门名称', value: 'name' },
|
{ label: '部门名称', value: 'name' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'defaultCurrentDept',
|
||||||
|
title: '默认选中当前部门',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const dictSelectRule = useDictSelectRule();
|
const dictSelectRule = useDictSelectRule();
|
||||||
|
|||||||
@@ -23,13 +23,24 @@ export function useSelectRule(option: SelectRuleOption) {
|
|||||||
name,
|
name,
|
||||||
event: option.event,
|
event: option.event,
|
||||||
rule() {
|
rule() {
|
||||||
return {
|
// 构建基础规则
|
||||||
|
const baseRule: any = {
|
||||||
type: name,
|
type: name,
|
||||||
field: buildUUID(),
|
field: buildUUID(),
|
||||||
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(_: any, { t }: any) {
|
props(_: any, { t }: any) {
|
||||||
if (!option.props) {
|
if (!option.props) {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
|
|
||||||
// ======================= 自定义组件 =======================
|
// ======================= 自定义组件 =======================
|
||||||
import { useApiSelect } from '#/components/form-create';
|
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 DictSelect from '#/components/form-create/components/dict-select.vue';
|
||||||
import { useImagesUpload } from '#/components/form-create/components/use-images-upload';
|
import { useImagesUpload } from '#/components/form-create/components/use-images-upload';
|
||||||
import { Tinymce } from '#/components/tinymce';
|
import { Tinymce } from '#/components/tinymce';
|
||||||
@@ -45,12 +46,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',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ const tableHeaders = ref<{ label: string; prop: string }[]>([]);
|
|||||||
/** 创建空 SKU 数据 */
|
/** 创建空 SKU 数据 */
|
||||||
function createEmptySku(): MallSpuApi.Sku {
|
function createEmptySku(): MallSpuApi.Sku {
|
||||||
return {
|
return {
|
||||||
|
name: '', // SKU 名称,提交时会自动使用 SPU 名称
|
||||||
price: 0,
|
price: 0,
|
||||||
marketPrice: 0,
|
marketPrice: 0,
|
||||||
costPrice: 0,
|
costPrice: 0,
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ const formData = ref<MallSpuApi.Spu>({
|
|||||||
subCommissionType: false,
|
subCommissionType: false,
|
||||||
skus: [
|
skus: [
|
||||||
{
|
{
|
||||||
|
name: '', // SKU 名称,提交时会自动使用 SPU 名称
|
||||||
price: 0,
|
price: 0,
|
||||||
marketPrice: 0,
|
marketPrice: 0,
|
||||||
costPrice: 0,
|
costPrice: 0,
|
||||||
@@ -181,6 +182,11 @@ async function handleSubmit() {
|
|||||||
.merge(otherFormApi)
|
.merge(otherFormApi)
|
||||||
.submitAllForm(true);
|
.submitAllForm(true);
|
||||||
values.skus = formData.value.skus;
|
values.skus = formData.value.skus;
|
||||||
|
// 校验商品名称不能为空(用于 SKU name)
|
||||||
|
if (!values.name || values.name.trim() === '') {
|
||||||
|
message.error('商品名称不能为空');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (values.skus) {
|
if (values.skus) {
|
||||||
try {
|
try {
|
||||||
// 校验 sku
|
// 校验 sku
|
||||||
@@ -190,6 +196,8 @@ async function handleSubmit() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
values.skus.forEach((item) => {
|
values.skus.forEach((item) => {
|
||||||
|
// 给 sku name 赋值(使用商品名称作为 SKU 名称)
|
||||||
|
item.name = values.name;
|
||||||
// 金额转换:元转分
|
// 金额转换:元转分
|
||||||
item.price = convertToInteger(item.price);
|
item.price = convertToInteger(item.price);
|
||||||
item.marketPrice = convertToInteger(item.marketPrice);
|
item.marketPrice = convertToInteger(item.marketPrice);
|
||||||
@@ -277,6 +285,7 @@ function handleChangeSpec() {
|
|||||||
// 重置 sku 列表
|
// 重置 sku 列表
|
||||||
formData.value.skus = [
|
formData.value.skus = [
|
||||||
{
|
{
|
||||||
|
name: '', // SKU 名称,提交时会自动使用 SPU 名称
|
||||||
price: 0,
|
price: 0,
|
||||||
marketPrice: 0,
|
marketPrice: 0,
|
||||||
costPrice: 0,
|
costPrice: 0,
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
<!-- 部门选择器 - 树形结构显示 -->
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { useUserStore } from '@vben/stores';
|
||||||
|
import { handleTree } from '@vben/utils';
|
||||||
|
|
||||||
|
import { ElTreeSelect } from 'element-plus';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
defineOptions({ name: 'DeptSelect' });
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
multiple: false,
|
||||||
|
returnType: 'id',
|
||||||
|
defaultCurrentDept: false,
|
||||||
|
disabled: false,
|
||||||
|
placeholder: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(
|
||||||
|
e: 'update:modelValue',
|
||||||
|
value: number | number[] | string | string[] | undefined,
|
||||||
|
): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// todo @puhui999:是不是可以简化,使用 api 的;
|
||||||
|
/** 部门数据接口 */
|
||||||
|
interface DeptVO {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
parentId: number;
|
||||||
|
sort?: number;
|
||||||
|
leaderUserId?: number;
|
||||||
|
phone?: string;
|
||||||
|
email?: string;
|
||||||
|
status?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO @puhui999:linter 报错;
|
||||||
|
/** 接受父组件参数 */
|
||||||
|
interface Props {
|
||||||
|
modelValue?: number | number[] | string | string[];
|
||||||
|
multiple?: boolean;
|
||||||
|
returnType?: 'id' | 'name';
|
||||||
|
defaultCurrentDept?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
placeholder?: string;
|
||||||
|
formCreateInject?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO @AI:可以使用 element plus 自带的对象么?
|
||||||
|
// Element Plus TreeSelect 的 props 配置
|
||||||
|
const treeProps = {
|
||||||
|
label: 'name',
|
||||||
|
value: 'id',
|
||||||
|
children: 'children',
|
||||||
|
};
|
||||||
|
|
||||||
|
const deptTree = ref<any[]>([]); // 部门树形数据
|
||||||
|
const deptList = ref<DeptVO[]>([]); // 原始部门列表(用于 returnType='name' 时查找名称)
|
||||||
|
const selectedValue = ref<number | number[] | undefined>(); // 当前选中值
|
||||||
|
|
||||||
|
/** 加载部门树形数据 */
|
||||||
|
async function loadDeptTree(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const data = await requestClient.get<DeptVO[]>('/system/dept/simple-list');
|
||||||
|
deptList.value = data;
|
||||||
|
deptTree.value = handleTree(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[DeptSelect] 加载部门数据失败:', error);
|
||||||
|
deptTree.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据 ID 获取部门名称 */
|
||||||
|
function getDeptNameById(id: number): string | undefined {
|
||||||
|
const dept = deptList.value.find((item: DeptVO) => item.id === id);
|
||||||
|
if (!dept) {
|
||||||
|
console.warn(`[DeptSelect] 未找到 ID 为 ${id} 的部门`);
|
||||||
|
}
|
||||||
|
return dept?.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据名称获取部门 ID */
|
||||||
|
function getDeptIdByName(name: string): number | undefined {
|
||||||
|
const dept = deptList.value.find((item: DeptVO) => item.name === name);
|
||||||
|
return dept?.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理选中值变化 */
|
||||||
|
function handleChange(value: number | number[] | undefined): void {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 树节点过滤方法(支持搜索过滤) */
|
||||||
|
function filterNode(value: string, data: any): boolean {
|
||||||
|
if (!value) return true;
|
||||||
|
return data.name?.toLowerCase().includes(value.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 同步 modelValue 到内部选中值 */
|
||||||
|
function syncSelectedValue(): void {
|
||||||
|
const newValue = props.modelValue;
|
||||||
|
if (newValue === undefined || newValue === null) {
|
||||||
|
selectedValue.value = props.multiple ? [] : undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 returnType 是 'name',需要将名称转换为 ID 用于树选择器显示
|
||||||
|
if (props.returnType === 'name') {
|
||||||
|
// 只有在 deptList 加载完成后才能进行转换
|
||||||
|
if (deptList.value.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.multiple && Array.isArray(newValue)) {
|
||||||
|
selectedValue.value = (newValue as string[])
|
||||||
|
.map((name) => getDeptIdByName(name))
|
||||||
|
.filter(Boolean) as number[];
|
||||||
|
} else if (!props.multiple && typeof newValue === 'string') {
|
||||||
|
selectedValue.value = getDeptIdByName(newValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedValue.value = newValue as number | number[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 监听 modelValue 变化,同步到内部选中值 */
|
||||||
|
watch(() => props.modelValue, syncSelectedValue, { immediate: true });
|
||||||
|
|
||||||
|
/** 监听 deptList 变化,重新同步选中值(解决数据加载完成后的回显问题) */
|
||||||
|
watch(() => deptList.value, syncSelectedValue);
|
||||||
|
|
||||||
|
/** 检查是否有有效的预设值 */
|
||||||
|
function hasValidPresetValue(): boolean {
|
||||||
|
const value = props.modelValue;
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.length > 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 设置默认值(当前用户部门) */
|
||||||
|
function setDefaultValue(): void {
|
||||||
|
// 仅当 defaultCurrentDept 为 true 时处理
|
||||||
|
if (!props.defaultCurrentDept) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 检查是否已有预设值(预设值优先级高于默认当前部门)
|
||||||
|
if (hasValidPresetValue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前用户的部门 ID
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const deptId = userStore.userInfo?.deptId as number | undefined;
|
||||||
|
// 处理 deptId 为空或 0 的边界情况
|
||||||
|
if (!deptId || deptId === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据多选模式决定默认值格式
|
||||||
|
const defaultValue = props.multiple ? [deptId] : deptId;
|
||||||
|
emit('update:modelValue', defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 组件挂载时加载数据并设置默认值 */
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadDeptTree();
|
||||||
|
// 数据加载完成后设置默认值
|
||||||
|
setDefaultValue();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ElTreeSelect
|
||||||
|
v-model="selectedValue"
|
||||||
|
class="w-full"
|
||||||
|
:data="deptTree"
|
||||||
|
:props="treeProps"
|
||||||
|
:multiple="multiple"
|
||||||
|
:disabled="disabled"
|
||||||
|
:placeholder="placeholder || '请选择部门'"
|
||||||
|
:check-strictly="true"
|
||||||
|
:filterable="true"
|
||||||
|
:filter-node-method="filterNode"
|
||||||
|
:clearable="true"
|
||||||
|
node-key="id"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -2,6 +2,7 @@ import type { ApiSelectProps } from '#/components/form-create/typing';
|
|||||||
|
|
||||||
import { defineComponent, onMounted, ref, useAttrs } from 'vue';
|
import { defineComponent, onMounted, ref, useAttrs } from 'vue';
|
||||||
|
|
||||||
|
import { useUserStore } from '@vben/stores';
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -74,12 +75,46 @@ export function useApiSelect(option: ApiSelectProps) {
|
|||||||
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>(); // 当前输入的值
|
||||||
|
|
||||||
|
// 检查是否有有效的预设值
|
||||||
|
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 () => {
|
const getOptions = async () => {
|
||||||
options.value = [];
|
options.value = [];
|
||||||
// 接口选择器
|
// 接口选择器
|
||||||
@@ -199,6 +234,8 @@ export function useApiSelect(option: ApiSelectProps) {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getOptions();
|
await getOptions();
|
||||||
|
// 设置默认当前用户(仅用于 UserSelect)
|
||||||
|
setDefaultCurrentUser();
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildSelect = () => {
|
const buildSelect = () => {
|
||||||
|
|||||||
@@ -189,6 +189,14 @@ export async function useFormCreateDesigner(designer: Ref) {
|
|||||||
name: 'UserSelect',
|
name: 'UserSelect',
|
||||||
label: '用户选择器',
|
label: '用户选择器',
|
||||||
icon: 'icon-eye',
|
icon: 'icon-eye',
|
||||||
|
props: [
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'defaultCurrentUser',
|
||||||
|
title: '默认选中当前用户',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
const deptSelectRule = useSelectRule({
|
const deptSelectRule = useSelectRule({
|
||||||
name: 'DeptSelect',
|
name: 'DeptSelect',
|
||||||
@@ -205,6 +213,12 @@ export async function useFormCreateDesigner(designer: Ref) {
|
|||||||
{ label: '部门名称', value: 'name' },
|
{ label: '部门名称', value: 'name' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'defaultCurrentDept',
|
||||||
|
title: '默认选中当前部门',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const dictSelectRule = useDictSelectRule();
|
const dictSelectRule = useDictSelectRule();
|
||||||
|
|||||||
@@ -23,13 +23,24 @@ export function useSelectRule(option: SelectRuleOption) {
|
|||||||
name,
|
name,
|
||||||
event: option.event,
|
event: option.event,
|
||||||
rule() {
|
rule() {
|
||||||
return {
|
// 构建基础规则
|
||||||
|
const baseRule: any = {
|
||||||
type: name,
|
type: name,
|
||||||
field: buildUUID(),
|
field: buildUUID(),
|
||||||
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(_: any, { t }: any) {
|
props(_: any, { t }: any) {
|
||||||
if (!option.props) {
|
if (!option.props) {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
|
|
||||||
// ======================= 自定义组件 =======================
|
// ======================= 自定义组件 =======================
|
||||||
import { useApiSelect } from '#/components/form-create';
|
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 DictSelect from '#/components/form-create/components/dict-select.vue';
|
||||||
import { useImagesUpload } from '#/components/form-create/components/use-images-upload';
|
import { useImagesUpload } from '#/components/form-create/components/use-images-upload';
|
||||||
import { Tinymce } from '#/components/tinymce';
|
import { Tinymce } from '#/components/tinymce';
|
||||||
@@ -45,12 +46,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',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import {
|
|||||||
MULTI_LEVEL_DEPT,
|
MULTI_LEVEL_DEPT,
|
||||||
} from '#/views/bpm/components/simple-process-design/consts';
|
} from '#/views/bpm/components/simple-process-design/consts';
|
||||||
import { useFormFieldsPermission } from '#/views/bpm/components/simple-process-design/helpers';
|
import { useFormFieldsPermission } from '#/views/bpm/components/simple-process-design/helpers';
|
||||||
// TODO @jason:antd 是不是导入对齐这个?
|
|
||||||
import { ProcessExpressionSelectModal } from '#/views/bpm/processExpression/components';
|
import { ProcessExpressionSelectModal } from '#/views/bpm/processExpression/components';
|
||||||
|
|
||||||
defineOptions({ name: 'UserTask' });
|
defineOptions({ name: 'UserTask' });
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO @jason:和 antd 对应的文件,逻辑有点不一样;
|
||||||
|
|
||||||
// 节点名称配置
|
// 节点名称配置
|
||||||
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
|
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
|
||||||
useNodeName(BpmNodeTypeEnum.USER_TASK_NODE);
|
useNodeName(BpmNodeTypeEnum.USER_TASK_NODE);
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export function useWatchNode(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 解析 formCreate 所有表单字段, 并返回
|
// 解析 formCreate 所有表单字段, 并返回
|
||||||
|
// TODO @jason:这个逻辑,和 antd 对应的逻辑不太一致;
|
||||||
function parseFormCreateFields(formFields?: string[]) {
|
function parseFormCreateFields(formFields?: string[]) {
|
||||||
const result: Array<Record<string, any>> = [];
|
const result: Array<Record<string, any>> = [];
|
||||||
if (formFields) {
|
if (formFields) {
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ export { default as SimpleProcessViewer } from './components/simple-process-view
|
|||||||
|
|
||||||
export type { SimpleFlowNode } from './consts';
|
export type { SimpleFlowNode } from './consts';
|
||||||
|
|
||||||
|
// TODO @jaosn:和 antd 对应的文件,不太一样
|
||||||
export { parseFormFields } from './helpers';
|
export { parseFormFields } from './helpers';
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const xmlString = inject('processData') as Ref; // 注入流程数据
|
|||||||
const modelData = inject('modelData') as Ref; // 注入模型数据
|
const modelData = inject('modelData') as Ref; // 注入模型数据
|
||||||
|
|
||||||
const modeler = shallowRef(); // BPMN Modeler
|
const modeler = shallowRef(); // BPMN Modeler
|
||||||
|
// TODO @AI:和对应的 antd 文件,略微有点不一样;
|
||||||
const processDesigner = ref();
|
const processDesigner = ref();
|
||||||
const controlForm = ref({
|
const controlForm = ref({
|
||||||
simulation: true,
|
simulation: true,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const emit = defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
// 查询参数
|
// 查询参数
|
||||||
|
// TODO @jason:这里的风格,和 antd 对应的不一致;
|
||||||
const queryParams = ref({
|
const queryParams = ref({
|
||||||
status: CommonStatusEnum.ENABLE,
|
status: CommonStatusEnum.ENABLE,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|||||||
import { DICT_TYPE } from '@vben/constants';
|
import { DICT_TYPE } from '@vben/constants';
|
||||||
|
|
||||||
/** 选择监听器弹窗的列表字段 */
|
/** 选择监听器弹窗的列表字段 */
|
||||||
|
// TODO @jason:和 antd 对应的,不太一致;
|
||||||
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
return [
|
return [
|
||||||
{ field: 'name', title: '名字', minWidth: 120 },
|
{ field: 'name', title: '名字', minWidth: 120 },
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const emit = defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
// 查询参数
|
// 查询参数
|
||||||
|
// TODO @jason:这里的风格,和 antd 对应的不一致;
|
||||||
const queryParams = ref({
|
const queryParams = ref({
|
||||||
type: '',
|
type: '',
|
||||||
status: CommonStatusEnum.ENABLE,
|
status: CommonStatusEnum.ENABLE,
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ const tableHeaders = ref<{ label: string; prop: string }[]>([]);
|
|||||||
/** 创建空 SKU 数据 */
|
/** 创建空 SKU 数据 */
|
||||||
function createEmptySku(): MallSpuApi.Sku {
|
function createEmptySku(): MallSpuApi.Sku {
|
||||||
return {
|
return {
|
||||||
|
name: '', // SKU 名称,提交时会自动使用 SPU 名称
|
||||||
price: 0,
|
price: 0,
|
||||||
marketPrice: 0,
|
marketPrice: 0,
|
||||||
costPrice: 0,
|
costPrice: 0,
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ const formData = ref<MallSpuApi.Spu>({
|
|||||||
subCommissionType: false,
|
subCommissionType: false,
|
||||||
skus: [
|
skus: [
|
||||||
{
|
{
|
||||||
|
name: '', // SKU 名称,提交时会自动使用 SPU 名称
|
||||||
price: 0,
|
price: 0,
|
||||||
marketPrice: 0,
|
marketPrice: 0,
|
||||||
costPrice: 0,
|
costPrice: 0,
|
||||||
@@ -168,8 +169,8 @@ const [OtherForm, otherFormApi] = useVbenForm({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** tab 切换 */
|
/** tab 切换 */
|
||||||
function handleTabChange(key: string) {
|
function handleTabChange(key: number | string) {
|
||||||
activeTabName.value = key;
|
activeTabName.value = key as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 提交表单 */
|
/** 提交表单 */
|
||||||
@@ -181,6 +182,11 @@ async function handleSubmit() {
|
|||||||
.merge(otherFormApi)
|
.merge(otherFormApi)
|
||||||
.submitAllForm(true);
|
.submitAllForm(true);
|
||||||
values.skus = formData.value.skus;
|
values.skus = formData.value.skus;
|
||||||
|
// 校验商品名称不能为空(用于 SKU name)
|
||||||
|
if (!values.name || values.name.trim() === '') {
|
||||||
|
ElMessage.error('商品名称不能为空');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (values.skus) {
|
if (values.skus) {
|
||||||
try {
|
try {
|
||||||
// 校验 sku
|
// 校验 sku
|
||||||
@@ -190,6 +196,8 @@ async function handleSubmit() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
values.skus.forEach((item) => {
|
values.skus.forEach((item) => {
|
||||||
|
// 给 sku name 赋值(使用商品名称作为 SKU 名称)
|
||||||
|
item.name = values.name;
|
||||||
// 金额转换:元转分
|
// 金额转换:元转分
|
||||||
item.price = convertToInteger(item.price);
|
item.price = convertToInteger(item.price);
|
||||||
item.marketPrice = convertToInteger(item.marketPrice);
|
item.marketPrice = convertToInteger(item.marketPrice);
|
||||||
@@ -277,6 +285,7 @@ function handleChangeSpec() {
|
|||||||
// 重置 sku 列表
|
// 重置 sku 列表
|
||||||
formData.value.skus = [
|
formData.value.skus = [
|
||||||
{
|
{
|
||||||
|
name: '', // SKU 名称,提交时会自动使用 SPU 名称
|
||||||
price: 0,
|
price: 0,
|
||||||
marketPrice: 0,
|
marketPrice: 0,
|
||||||
costPrice: 0,
|
costPrice: 0,
|
||||||
@@ -320,7 +329,6 @@ onMounted(async () => {
|
|||||||
<ElCard class="h-full w-full" v-loading="formLoading">
|
<ElCard class="h-full w-full" v-loading="formLoading">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<!-- @puhui999:idea 这边会有告警 -->
|
|
||||||
<ElTabs v-model="activeTabName" @tab-change="handleTabChange">
|
<ElTabs v-model="activeTabName" @tab-change="handleTabChange">
|
||||||
<ElTabPane label="基础设置" name="info" />
|
<ElTabPane label="基础设置" name="info" />
|
||||||
<ElTabPane label="价格库存" name="sku" />
|
<ElTabPane label="价格库存" name="sku" />
|
||||||
|
|||||||
Reference in New Issue
Block a user