commit
8e4f52c8aa
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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); // 隐藏提交按钮
|
||||||
|
|
|
@ -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) {
|
|
||||||
// 确保restItem不是undefined,并进行类型断言
|
|
||||||
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) => {
|
|
||||||
// name加属性项index区分属性值
|
|
||||||
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>
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue