Merge pull request !207 from xingyu/dev
master
xingyu 2025-08-26 09:14:39 +00:00 committed by Gitee
commit 8e4f52c8aa
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
12 changed files with 82 additions and 365 deletions

View File

@ -1,2 +1,3 @@
export { default as Tinyflow } from './Tinyflow.vue'; export { default as Tinyflow } from './tinyflow.vue';
export * from './ui/typing'; export * from './ui/typing';

View File

@ -4,6 +4,7 @@
// TODO @芋艿:后续这些 form-create 的优化;另外需要使用 form-create-helper 会好点 // TODO @芋艿:后续这些 form-create 的优化;另外需要使用 form-create-helper 会好点
import { isRef } from 'vue'; import { isRef } from 'vue';
import formCreate from '@form-create/ant-design-vue';
// 编码表单 Conf // 编码表单 Conf
export const encodeConf = (designerRef: any) => { export const encodeConf = (designerRef: any) => {
return JSON.stringify(designerRef.value.getOption()); return JSON.stringify(designerRef.value.getOption());
@ -23,7 +24,7 @@ export const encodeFields = (designerRef: any) => {
export const decodeFields = (fields: string[]) => { export const decodeFields = (fields: string[]) => {
const rule: object[] = []; const rule: object[] = [];
fields.forEach((item) => { fields.forEach((item) => {
rule.push(JSON.parse(item)); rule.push(formCreate.parseJson(item));
}); });
return rule; return rule;
}; };
@ -34,7 +35,7 @@ export const setConfAndFields = (
conf: string, conf: string,
fields: string | string[], fields: string | string[],
) => { ) => {
designerRef.value.setOption(JSON.parse(conf)); designerRef.value.setOption(formCreate.parseJson(conf));
// 处理 fields 参数类型,确保传入 decodeFields 的是 string[] 类型 // 处理 fields 参数类型,确保传入 decodeFields 的是 string[] 类型
const fieldsArray = Array.isArray(fields) ? fields : [fields]; const fieldsArray = Array.isArray(fields) ? fields : [fields];
designerRef.value.setRule(decodeFields(fieldsArray)); designerRef.value.setRule(decodeFields(fieldsArray));
@ -50,7 +51,7 @@ export const setConfAndFields2 = (
if (isRef(detailPreview)) { if (isRef(detailPreview)) {
detailPreview = detailPreview.value; detailPreview = detailPreview.value;
} }
detailPreview.option = JSON.parse(conf); detailPreview.option = formCreate.parseJson(conf);
detailPreview.rule = decodeFields(fields); detailPreview.rule = decodeFields(fields);
if (value) { if (value) {
detailPreview.value = value; detailPreview.value = value;

View File

@ -10,7 +10,7 @@ import { isNumber } from '@vben/utils';
import { Button, Input, Select } from 'ant-design-vue'; import { Button, Input, Select } from 'ant-design-vue';
import { testWorkflow } from '#/api/ai/workflow'; import { testWorkflow } from '#/api/ai/workflow';
import { Tinyflow } from '#/components/Tinyflow'; import { Tinyflow } from '#/components/tinyflow';
defineProps<{ defineProps<{
provider: any; provider: any;

View File

@ -7,6 +7,7 @@ import { computed, nextTick, ref, watch } from 'vue';
import { useTabs } from '@vben/hooks'; import { useTabs } from '@vben/hooks';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import formCreate from '@form-create/ant-design-vue';
import { Button, Card, Col, message, Row, Space, Tabs } from 'ant-design-vue'; import { Button, Card, Col, message, Row, Space, Tabs } from 'ant-design-vue';
import { getProcessDefinition } from '#/api/bpm/definition'; import { getProcessDefinition } from '#/api/bpm/definition';
@ -51,7 +52,7 @@ const props = defineProps({
const emit = defineEmits(['cancel']); const emit = defineEmits(['cancel']);
// form-create // form-create
const isFormReady = ref(false) const isFormReady = ref(false);
const { closeCurrentTab } = useTabs(); const { closeCurrentTab } = useTabs();
@ -129,18 +130,17 @@ async function initProcessInfo(row: any, formVariables?: any) {
// formVariables row.formFields // formVariables row.formFields
// formVariables // formVariables
// //
const allowedFields = new Set( const formApi = formCreate.create(decodeFields(row.formFields));
decodeFields(row.formFields).map((fieldObj: any) => fieldObj.field), const allowedFields = formApi.fields();
);
for (const key in formVariables) { for (const key in formVariables) {
if (!allowedFields.has(key)) { if (!allowedFields.includes(key)) {
delete formVariables[key]; delete formVariables[key];
} }
} }
setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables); setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables);
// //
isFormReady.value = true isFormReady.value = true;
await nextTick(); await nextTick();
fApi.value?.btn.show(false); // fApi.value?.btn.show(false); //

View File

@ -45,7 +45,8 @@ const props = defineProps({
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'selectionChange', value: MallSpuApi.Sku[]): void; (e: 'selectionChange', value: MallSpuApi.Sku[]): void;
}>(); const formData = ref<MallSpuApi.Spu>(); // }>();
const formData = ref<MallSpuApi.Spu>(); //
const skuList = ref<MallSpuApi.Sku[]>([ const skuList = ref<MallSpuApi.Sku[]>([
{ {
price: 0, // price: 0, //
@ -88,7 +89,7 @@ const deleteSku = (row: MallSpuApi.Sku) => {
); );
formData.value!.skus!.splice(index, 1); formData.value!.skus!.splice(index, 1);
}; };
const tableHeaders = ref<{ label: string; prop: string; }[]>([]); // const tableHeaders = ref<{ label: string; prop: string }[]>([]); //
/** /**
* 保存时每个商品规格的表单要校验下例如说销售金额最低是 0.01 这种 * 保存时每个商品规格的表单要校验下例如说销售金额最低是 0.01 这种
*/ */
@ -204,7 +205,7 @@ const validateData = (propertyList: any[]) => {
sku.properties sku.properties
?.map((property: any) => property.propertyId) ?.map((property: any) => property.propertyId)
?.forEach((propertyId: number) => { ?.forEach((propertyId: number) => {
if (!skuPropertyIds.indexOf(propertyId!) === -1) { if (!skuPropertyIds.includes(propertyId!)) {
skuPropertyIds.push(propertyId!); skuPropertyIds.push(propertyId!);
} }
}), }),
@ -463,333 +464,9 @@ defineExpose({ generateTableData, validateSku, getSkuTableRef });
size="small" size="small"
type="primary" type="primary"
@click="deleteSku(row)" @click="deleteSku(row)"
>
删除
</el-button>
</template>
</el-table-column>
</ElTable>
<!-- 情况二作为活动组件 -->
<ElTable
v-if="isActivityComponent"
:data="formData!.skus!"
border
max-height="500"
size="small"
style="width: 99%"
>
<el-table-column v-if="isComponent" type="selection" width="45" />
<el-table-column align="center" label="图片" min-width="80">
<template #default="{ row }">
<el-image :src="row.picUrl" class="h-60px w-60px" />
</template>
</el-table-column>
<template v-if="formData!.specType">
<!-- 根据商品属性动态添加 -->
<el-table-column
v-for="(item, index) in tableHeaders"
:key="index"
:label="item.label"
align="center"
min-width="80"
>
<template #default="{ row }">
<span style="font-weight: bold; color: #40aaff">
{{ row.properties?.[index]?.valueName }}
</span>
</template>
</el-table-column>
</template>
<el-table-column align="center" label="商品条码" min-width="100">
<template #default="{ row }">
{{ row.barCode }}
</template>
</el-table-column>
<el-table-column align="center" label="销售价(元)" min-width="80">
<template #default="{ row }">
{{ formatToFraction(row.price) }}
</template>
</el-table-column>
<el-table-column align="center" label="市场价(元)" min-width="80">
<template #default="{ row }">
{{ formatToFraction(row.marketPrice) }}
</template>
</el-table-column>
<el-table-column align="center" label="成本价(元)" min-width="80">
<template #default="{ row }">
{{ formatToFraction(row.costPrice) }}
</template>
</el-table-column>
<el-table-column align="center" label="库存" min-width="80">
<template #default="{ row }">
{{ row.stock }}
</template>
</el-table-column>
<!-- 方便扩展每个活动配置的属性不一样 -->
<slot name="extension"></slot>
</ElTable>
</template>.includes(propertyId!)) {
skuPropertyIds.push(propertyId!);
}
}),
);
const propertyIds = propertyList.map((item) => item.id);
return skuPropertyIds.length === propertyIds.length;
};
/** 构建所有排列组合 */
const build = (
propertyValuesList: MallSpuApi.Property[][],
): MallSpuApi.Property[] | MallSpuApi.Property[][] => {
if (!propertyValuesList || propertyValuesList.length === 0) {
return [];
} else if (propertyValuesList.length === 1) {
return propertyValuesList[0] || [];
} else {
const result: MallSpuApi.Property[][] = [];
const rest = build(propertyValuesList.slice(1));
if (propertyValuesList[0] && Array.isArray(rest)) {
for (let i = 0; i < propertyValuesList[0].length; i++) {
for (const restItem of rest) {
const currentItem = propertyValuesList[0][i];
//
if (Array.isArray(restItem)) {
result.push([currentItem!, ...restItem]);
} else if (restItem) {
// restItemundefined
result.push([currentItem!, restItem as MallSpuApi.Property]);
}
}
}
}
return result;
}
};
/** 监听属性列表,生成相关参数和表头 */
watch(
() => props.propertyList,
(propertyList: PropertyAndValues[]) => {
//
if (!formData.value!.specType) {
return;
}
// 使
if (props.isBatch) {
skuList.value = [
{
price: 0,
marketPrice: 0,
costPrice: 0,
barCode: '',
picUrl: '',
stock: 0,
weight: 0,
volume: 0,
firstBrokeragePrice: 0,
secondBrokeragePrice: 0,
},
];
}
//
if (JSON.stringify(propertyList) === '[]') {
return;
}
//
tableHeaders.value = [];
//
propertyList.forEach((item, index) => {
// nameindex
tableHeaders.value.push({ prop: `name${index}`, label: item.name });
});
// sku
if (validateData(propertyList)) {
return;
}
//
if (propertyList.some((item) => !item.values || isEmpty(item.values))) {
return;
}
// table sku
generateTableData(propertyList);
},
{
deep: true,
immediate: true,
},
);
const activitySkuListRef = ref<InstanceType<typeof ElTable>>();
const getSkuTableRef = () => {
return activitySkuListRef.value;
};
// sku
defineExpose({ generateTableData, validateSku, getSkuTableRef });
</script>
<template>
<!-- 情况一添加/修改 -->
<ElTable
v-if="!isActivityComponent"
:data="isBatch ? skuList : formData!.skus!"
border
class="tabNumWidth"
max-height="500"
size="small"
>
<el-table-column align="center" label="图片" min-width="120">
<template #default="{ row }">
<UploadImg
v-model="row.picUrl"
height="50px"
width="50px"
:show-description="false"
/>
</template>
</el-table-column>
<template v-if="formData!.specType && !isBatch">
<!-- 根据商品属性动态添加 -->
<el-table-column
v-for="(item, index) in tableHeaders"
:key="index"
:label="item.label"
align="center"
min-width="120"
>
<template #default="{ row }">
<span style="font-weight: bold; color: #40aaff">
{{ row.properties?.[index]?.valueName }}
</span>
</template>
</el-table-column>
</template>
<el-table-column align="center" label="商品条码" min-width="168">
<template #default="{ row }">
<el-input v-model="row.barCode" class="w-100%" />
</template>
</el-table-column>
<el-table-column align="center" label="销售价" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.price"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
<el-table-column align="center" label="市场价" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.marketPrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
<el-table-column align="center" label="成本价" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.costPrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
<el-table-column align="center" label="库存" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.stock"
:min="0"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
<el-table-column align="center" label="重量(kg)" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.weight"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
<el-table-column align="center" label="体积(m^3)" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.volume"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
<template v-if="formData!.subCommissionType">
<el-table-column align="center" label="一级返佣(元)" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.firstBrokeragePrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
<el-table-column align="center" label="二级返佣(元)" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.secondBrokeragePrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
controls-position="right"
/>
</template>
</el-table-column>
</template>
<el-table-column
v-if="formData?.specType"
align="center"
fixed="right"
label="操作"
width="80"
>
<template #default="{ row }">
<el-button
v-if="isBatch"
link
size="small"
type="primary"
@click="batchAdd"
> >
批量添加 删除
</el-button> </el-button>
<el-button
v-else
link
size="small"
type="primary"
@click="deleteSku(row)"
>删除</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</ElTable> </ElTable>

View File

@ -43,12 +43,10 @@ const emits = defineEmits<{
const wrapperClass = computed(() => { const wrapperClass = computed(() => {
const cls = ['flex']; const cls = ['flex'];
if (props.layout === 'vertical') { if (props.layout === 'inline') {
cls.push(props.compact ? 'gap-x-2' : 'gap-x-4', 'flex-col grid'); cls.push('flex-wrap gap-x-2');
} else if (props.layout === 'inline') {
cls.push('flex-wrap gap-2');
} else { } else {
cls.push('gap-2 flex-col grid'); cls.push(props.compact ? 'gap-x-2' : 'gap-x-4', 'flex-col grid');
} }
return cn(...cls, props.wrapperClass); return cn(...cls, props.wrapperClass);
}); });

View File

@ -107,7 +107,6 @@ export class ModalApi {
this.store.setState((prev) => ({ this.store.setState((prev) => ({
...prev, ...prev,
isOpen: false, isOpen: false,
submitting: false,
})); }));
} }
} }
@ -162,7 +161,11 @@ export class ModalApi {
} }
open() { open() {
this.store.setState((prev) => ({ ...prev, isOpen: true })); this.store.setState((prev) => ({
...prev,
isOpen: true,
submitting: false,
}));
} }
setData<T>(payload: T) { setData<T>(payload: T) {

View File

@ -8,12 +8,7 @@ import { computed, ref } from 'vue';
import { cn } from '@vben-core/shared/utils'; import { cn } from '@vben-core/shared/utils';
import { X } from 'lucide-vue-next'; import { X } from 'lucide-vue-next';
import { import { DialogClose, DialogContent, useForwardPropsEmits } from 'radix-vue';
DialogClose,
DialogContent,
DialogPortal,
useForwardPropsEmits,
} from 'radix-vue';
import DialogOverlay from './DialogOverlay.vue'; import DialogOverlay from './DialogOverlay.vue';
@ -87,7 +82,7 @@ defineExpose({
</script> </script>
<template> <template>
<DialogPortal :to="appendTo"> <Teleport defer :to="appendTo">
<Transition name="fade"> <Transition name="fade">
<DialogOverlay <DialogOverlay
v-if="open && modal" v-if="open && modal"
@ -132,5 +127,5 @@ defineExpose({
<X class="h-4 w-4" /> <X class="h-4 w-4" />
</DialogClose> </DialogClose>
</DialogContent> </DialogContent>
</DialogPortal> </Teleport>
</template> </template>

View File

@ -7,7 +7,7 @@ import { computed, ref } from 'vue';
import { cn } from '@vben-core/shared/utils'; import { cn } from '@vben-core/shared/utils';
import { DialogContent, DialogPortal, useForwardPropsEmits } from 'radix-vue'; import { DialogContent, useForwardPropsEmits } from 'radix-vue';
import { sheetVariants } from './sheet'; import { sheetVariants } from './sheet';
import SheetOverlay from './SheetOverlay.vue'; import SheetOverlay from './SheetOverlay.vue';
@ -73,7 +73,7 @@ function onAnimationEnd(event: AnimationEvent) {
</script> </script>
<template> <template>
<DialogPortal :to="appendTo"> <Teleport defer :to="appendTo">
<Transition name="fade"> <Transition name="fade">
<SheetOverlay <SheetOverlay
v-if="open && modal" v-if="open && modal"
@ -103,5 +103,5 @@ function onAnimationEnd(event: AnimationEvent) {
<Cross2Icon class="h-5 w-" /> <Cross2Icon class="h-5 w-" />
</DialogClose> --> </DialogClose> -->
</DialogContent> </DialogContent>
</DialogPortal> </Teleport>
</template> </template>

View File

@ -103,10 +103,15 @@ function updateTreeValue() {
treeValue.value = undefined; treeValue.value = undefined;
} else { } else {
if (Array.isArray(val)) { if (Array.isArray(val)) {
const filteredValues = val.filter((v) => { let filteredValues = val.filter((v) => {
const item = getItemByValue(v); const item = getItemByValue(v);
return item && !get(item, props.disabledField); return item && !get(item, props.disabledField);
}); });
if (!props.checkStrictly && props.autoCheckParent) {
filteredValues = processParentSelection(filteredValues);
}
treeValue.value = filteredValues.map((v) => getItemByValue(v)); treeValue.value = filteredValues.map((v) => getItemByValue(v));
if (filteredValues.length !== val.length) { if (filteredValues.length !== val.length) {
@ -123,7 +128,35 @@ function updateTreeValue() {
} }
} }
} }
function processParentSelection(
selectedValues: Array<number | string>,
): Array<number | string> {
if (props.checkStrictly) return selectedValues;
const result = [...selectedValues];
for (let i = result.length - 1; i >= 0; i--) {
const currentValue = result[i];
if (currentValue === undefined) continue;
const currentItem = getItemByValue(currentValue);
if (!currentItem) continue;
const children = get(currentItem, props.childrenField);
if (Array.isArray(children) && children.length > 0) {
const hasSelectedChildren = children.some((child) => {
const childValue = get(child, props.valueField);
return result.includes(childValue);
});
if (!hasSelectedChildren) {
result.splice(i, 1);
}
}
}
return result;
}
function updateModelValue(val: Arrayable<Recordable<any>>) { function updateModelValue(val: Arrayable<Recordable<any>>) {
if (Array.isArray(val)) { if (Array.isArray(val)) {
const filteredVal = val.filter((v) => !get(v, props.disabledField)); const filteredVal = val.filter((v) => !get(v, props.disabledField));

View File

@ -104,7 +104,7 @@ function selectColor() {
watch( watch(
() => [modelValue.value, props.isDark] as [BuiltinThemeType, boolean], () => [modelValue.value, props.isDark] as [BuiltinThemeType, boolean],
([themeType, isDark]) => { ([themeType, isDark], [_, isDarkPrev]) => {
const theme = builtinThemePresets.value.find( const theme = builtinThemePresets.value.find(
(item) => item.type === themeType, (item) => item.type === themeType,
); );
@ -113,7 +113,9 @@ watch(
? theme.darkPrimaryColor || theme.primaryColor ? theme.darkPrimaryColor || theme.primaryColor
: theme.primaryColor; : theme.primaryColor;
themeColorPrimary.value = primaryColor || theme.color; if (!(theme.type === 'custom' && isDark !== isDarkPrev)) {
themeColorPrimary.value = primaryColor || theme.color;
}
} }
}, },
); );

View File

@ -5,7 +5,7 @@ import type { Recordable } from '@vben/types';
import type { SystemRoleApi } from '#/api/system/role'; import type { SystemRoleApi } from '#/api/system/role';
import { computed, ref } from 'vue'; import { computed, nextTick, ref } from 'vue';
import { useVbenDrawer, VbenTree } from '@vben/common-ui'; import { useVbenDrawer, VbenTree } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
@ -47,20 +47,27 @@ const [Drawer, drawerApi] = useVbenDrawer({
drawerApi.unlock(); drawerApi.unlock();
}); });
}, },
async onOpenChange(isOpen) { async onOpenChange(isOpen) {
if (isOpen) { if (isOpen) {
const data = drawerApi.getData<SystemRoleApi.SystemRole>(); const data = drawerApi.getData<SystemRoleApi.SystemRole>();
formApi.resetForm(); formApi.resetForm();
if (permissions.value.length === 0) {
await loadPermissions();
}
if (data) { if (data) {
formData.value = data; formData.value = data;
id.value = data.id; id.value = data.id;
formApi.setValues(data);
} else { } else {
id.value = undefined; id.value = undefined;
} }
if (permissions.value.length === 0) {
await loadPermissions();
}
// Wait for Vue to flush DOM updates (form fields mounted)
await nextTick();
if (data) {
formApi.setValues(data);
}
} }
}, },
}); });