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';

View File

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

View File

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

View File

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

View File

@ -45,7 +45,8 @@ const props = defineProps({
const emit = defineEmits<{
(e: 'selectionChange', value: MallSpuApi.Sku[]): void;
}>(); const formData = ref<MallSpuApi.Spu>(); //
}>();
const formData = ref<MallSpuApi.Spu>(); //
const skuList = ref<MallSpuApi.Sku[]>([
{
price: 0, //
@ -88,7 +89,7 @@ const deleteSku = (row: MallSpuApi.Sku) => {
);
formData.value!.skus!.splice(index, 1);
};
const tableHeaders = ref<{ label: string; prop: string; }[]>([]); //
const tableHeaders = ref<{ label: string; prop: string }[]>([]); //
/**
* 保存时每个商品规格的表单要校验下例如说销售金额最低是 0.01 这种
*/
@ -204,7 +205,7 @@ const validateData = (propertyList: any[]) => {
sku.properties
?.map((property: any) => property.propertyId)
?.forEach((propertyId: number) => {
if (!skuPropertyIds.indexOf(propertyId!) === -1) {
if (!skuPropertyIds.includes(propertyId!)) {
skuPropertyIds.push(propertyId!);
}
}),
@ -463,333 +464,9 @@ defineExpose({ generateTableData, validateSku, getSkuTableRef });
size="small"
type="primary"
@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
v-else
link
size="small"
type="primary"
@click="deleteSku(row)"
>删除</el-button
>
</template>
</el-table-column>
</ElTable>

View File

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

View File

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

View File

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

View File

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

View File

@ -103,10 +103,15 @@ function updateTreeValue() {
treeValue.value = undefined;
} else {
if (Array.isArray(val)) {
const filteredValues = val.filter((v) => {
let filteredValues = val.filter((v) => {
const item = getItemByValue(v);
return item && !get(item, props.disabledField);
});
if (!props.checkStrictly && props.autoCheckParent) {
filteredValues = processParentSelection(filteredValues);
}
treeValue.value = filteredValues.map((v) => getItemByValue(v));
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>>) {
if (Array.isArray(val)) {
const filteredVal = val.filter((v) => !get(v, props.disabledField));

View File

@ -104,7 +104,7 @@ function selectColor() {
watch(
() => [modelValue.value, props.isDark] as [BuiltinThemeType, boolean],
([themeType, isDark]) => {
([themeType, isDark], [_, isDarkPrev]) => {
const theme = builtinThemePresets.value.find(
(item) => item.type === themeType,
);
@ -113,7 +113,9 @@ watch(
? theme.darkPrimaryColor || 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 { computed, ref } from 'vue';
import { computed, nextTick, ref } from 'vue';
import { useVbenDrawer, VbenTree } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
@ -47,20 +47,27 @@ const [Drawer, drawerApi] = useVbenDrawer({
drawerApi.unlock();
});
},
async onOpenChange(isOpen) {
if (isOpen) {
const data = drawerApi.getData<SystemRoleApi.SystemRole>();
formApi.resetForm();
if (permissions.value.length === 0) {
await loadPermissions();
}
if (data) {
formData.value = data;
id.value = data.id;
formApi.setValues(data);
} else {
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);
}
}
},
});