diff --git a/apps/web-ele/src/components/form-create/components/use-images-upload.tsx b/apps/web-ele/src/components/form-create/components/use-images-upload.tsx
index a9d0572b5..a57cbaa4b 100644
--- a/apps/web-ele/src/components/form-create/components/use-images-upload.tsx
+++ b/apps/web-ele/src/components/form-create/components/use-images-upload.tsx
@@ -15,9 +15,8 @@ export function useImagesUpload() {
default: 5,
},
},
- setup() {
- // TODO: @puhui999:@dhb52 其实还是靠 props 默认参数起作用,没能从 formCreate 传递
- return (props: { maxNumber?: number; multiple?: boolean }) => (
+ setup(props) {
+ return () => (
);
},
diff --git a/apps/web-ele/src/utils/tree.ts b/apps/web-ele/src/utils/tree.ts
deleted file mode 100644
index 172ed2ba1..000000000
--- a/apps/web-ele/src/utils/tree.ts
+++ /dev/null
@@ -1,442 +0,0 @@
-interface TreeHelperConfig {
- id: string;
- children: string;
- pid: string;
-}
-
-const DEFAULT_CONFIG: TreeHelperConfig = {
- id: 'id',
- children: 'children',
- pid: 'pid',
-};
-export const defaultProps = {
- children: 'children',
- label: 'name',
- value: 'id',
- isLeaf: 'leaf',
- emitPath: false, // 用于 cascader 组件:在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false,则只返回该节点的值
-};
-
-const getConfig = (config: Partial
) =>
- Object.assign({}, DEFAULT_CONFIG, config);
-
-// tree from list
-export const listToTree = (
- list: any[],
- config: Partial = {},
-): T[] => {
- const conf = getConfig(config) as TreeHelperConfig;
- const nodeMap = new Map();
- const result: T[] = [];
- const { id, children, pid } = conf;
-
- for (const node of list) {
- node[children] = node[children] || [];
- nodeMap.set(node[id], node);
- }
- for (const node of list) {
- const parent = nodeMap.get(node[pid]);
- (parent ? parent.children : result).push(node);
- }
- return result;
-};
-
-export const treeToList = (
- tree: any,
- config: Partial = {},
-): T => {
- config = getConfig(config);
- const { children } = config;
- const result: any = [...tree];
- for (let i = 0; i < result.length; i++) {
- const childNodes = result[i][children];
- if (!childNodes) continue;
- result.splice(i + 1, 0, ...childNodes);
- }
- return result;
-};
-
-export const findNode = (
- tree: any,
- func: Fn,
- config: Partial = {},
-): null | T => {
- config = getConfig(config);
- const { children } = config;
- const list = [...tree];
- for (const node of list) {
- if (func(node)) return node;
- const childNodes = node[children];
- if (childNodes) {
- list.push(...childNodes);
- }
- }
- return null;
-};
-
-export const findNodeAll = (
- tree: any,
- func: Fn,
- config: Partial = {},
-): T[] => {
- config = getConfig(config);
- const { children } = config;
- const list = [...tree];
- const result: T[] = [];
- for (const node of list) {
- func(node) && result.push(node);
- const childNodes = node[children];
- if (childNodes) {
- list.push(...childNodes);
- }
- }
- return result;
-};
-
-export const findPath = (
- tree: any,
- func: Fn,
- config: Partial = {},
-): null | T | T[] => {
- config = getConfig(config);
- const path: T[] = [];
- const list = [...tree];
- const visitedSet = new Set();
- const { children } = config;
- while (list.length > 0) {
- const node = list[0];
- if (visitedSet.has(node)) {
- path.pop();
- list.shift();
- } else {
- visitedSet.add(node);
- const childNodes = node[children];
- if (childNodes) {
- list.unshift(...childNodes);
- }
- path.push(node);
- if (func(node)) {
- return path;
- }
- }
- }
- return null;
-};
-
-export const findPathAll = (
- tree: any,
- func: Fn,
- config: Partial = {},
-) => {
- config = getConfig(config);
- const path: any[] = [];
- const list = [...tree];
- const result: any[] = [];
- const visitedSet = new Set();
- const { children } = config;
- while (list.length > 0) {
- const node = list[0];
- if (visitedSet.has(node)) {
- path.pop();
- list.shift();
- } else {
- visitedSet.add(node);
- const childNodes = node[children];
- if (childNodes) {
- list.unshift(...childNodes);
- }
- path.push(node);
- func(node) && result.push([...path]);
- }
- }
- return result;
-};
-
-export const filter = (
- tree: T[],
- func: (n: T) => boolean,
- config: Partial = {},
-): T[] => {
- config = getConfig(config);
- const children = config.children as string;
-
- function listFilter(list: T[]) {
- return list
- .map((node: any) => ({ ...node }))
- .filter((node) => {
- node[children] = node[children] && listFilter(node[children]);
- return func(node) || node[children]?.length > 0;
- });
- }
-
- return listFilter(tree);
-};
-
-export const forEach = (
- tree: T[],
- func: (n: T) => any,
- config: Partial = {},
-): void => {
- config = getConfig(config);
- const list: any[] = [...tree];
- const { children } = config;
- for (let i = 0; i < list.length; i++) {
- // func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿
- if (func(list[i])) {
- return;
- }
- children &&
- list[i][children] &&
- list.splice(i + 1, 0, ...list[i][children]);
- }
-};
-
-/**
- * @description: Extract tree specified structure
- */
-export const treeMap = (
- treeData: T[],
- opt: { children?: string; conversion: Fn },
-): T[] => {
- return treeData.map((item) => treeMapEach(item, opt));
-};
-
-/**
- * @description: Extract tree specified structure
- */
-export const treeMapEach = (
- data: any,
- { children = 'children', conversion }: { children?: string; conversion: Fn },
-) => {
- const haveChildren =
- Array.isArray(data[children]) && data[children].length > 0;
- const conversionData = conversion(data) || {};
- return haveChildren
- ? {
- ...conversionData,
- [children]: data[children].map((i: number) =>
- treeMapEach(i, {
- children,
- conversion,
- }),
- ),
- }
- : {
- ...conversionData,
- };
-};
-
-/**
- * 递归遍历树结构
- * @param treeDatas 树
- * @param callBack 回调
- * @param parentNode 父节点
- */
-export const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => {
- treeDatas.forEach((element) => {
- const newNode = callBack(element, parentNode) || element;
- if (element.children) {
- eachTree(element.children, callBack, newNode);
- }
- });
-};
-
-/**
- * 构造树型结构数据
- * @param {*} data 数据源
- * @param {*} id id字段 默认 'id'
- * @param {*} parentId 父节点字段 默认 'parentId'
- * @param {*} children 孩子节点字段 默认 'children'
- */
-export const handleTree = (
- data: any[],
- id?: string,
- parentId?: string,
- children?: string,
-) => {
- if (!Array.isArray(data)) {
- console.warn('data must be an array');
- return [];
- }
- const config = {
- id: id || 'id',
- parentId: parentId || 'parentId',
- childrenList: children || 'children',
- };
-
- const childrenListMap = {};
- const nodeIds = {};
- const tree: any[] = [];
-
- for (const d of data) {
- const parentId = d[config.parentId];
- if (
- childrenListMap[parentId] === null ||
- childrenListMap[parentId] === undefined
- ) {
- childrenListMap[parentId] = [];
- }
- nodeIds[d[config.id]] = d;
- childrenListMap[parentId].push(d);
- }
-
- for (const d of data) {
- const parentId = d[config.parentId];
- if (nodeIds[parentId] === null || nodeIds[parentId] === undefined) {
- tree.push(d);
- }
- }
-
- for (const t of tree) {
- adaptToChildrenList(t);
- }
-
- function adaptToChildrenList(o) {
- if (childrenListMap[o[config.id]] !== null) {
- o[config.childrenList] = childrenListMap[o[config.id]];
- }
- if (o[config.childrenList]) {
- for (const c of o[config.childrenList]) {
- adaptToChildrenList(c);
- }
- }
- }
-
- return tree;
-};
-
-/**
- * 构造树型结构数据
- * @param {*} data 数据源
- * @param {*} id id字段 默认 'id'
- * @param {*} parentId 父节点字段 默认 'parentId'
- * @param {*} children 孩子节点字段 默认 'children'
- * @param {*} rootId 根Id 默认 0
- */
-// @ts-ignore: 遗留函数,保持原有逻辑不变
-export const handleTree2 = (data, id, parentId, children, rootId) => {
- id = id || 'id';
- parentId = parentId || 'parentId';
- // children = children || 'children'
- rootId =
- rootId ||
- Math.min(
- ...data.map((item) => {
- return item[parentId];
- }),
- ) ||
- 0;
- // 对源数据深度克隆
- const cloneData = structuredClone(data);
- // 循环所有项
- const treeData = cloneData.filter((father) => {
- const branchArr = cloneData.filter((child) => {
- // 返回每一项的子级数组
- return father[id] === child[parentId];
- });
- branchArr.length > 0 ? (father.children = branchArr) : '';
- // 返回第一层
- return father[parentId] === rootId;
- });
- return treeData === '' ? data : treeData;
-};
-
-/**
- * 校验选中的节点,是否为指定 level
- *
- * @param tree 要操作的树结构数据
- * @param nodeId 需要判断在什么层级的数据
- * @param level 检查的级别, 默认检查到二级
- * @return true 是;false 否
- */
-export const checkSelectedNode = (
- tree: any[],
- nodeId: any,
- level = 2,
-): boolean => {
- if (tree === undefined || !Array.isArray(tree) || tree.length === 0) {
- console.warn('tree must be an array');
- return false;
- }
-
- // 校验是否是一级节点
- if (tree.some((item) => item.id === nodeId)) {
- return false;
- }
-
- // 递归计数
- let count = 1;
-
- // 深层次校验
- function performAThoroughValidation(arr: any[]): boolean {
- count += 1;
- for (const item of arr) {
- if (item.id === nodeId) {
- return true;
- } else if (
- item.children !== undefined &&
- item.children.length > 0 &&
- performAThoroughValidation(item.children)
- ) {
- return true;
- }
- }
- return false;
- }
-
- for (const item of tree) {
- count = 1;
- if (
- performAThoroughValidation(item.children) && // 找到后对比是否是期望的层级
- count >= level
- ) {
- return true;
- }
- }
-
- return false;
-};
-
-/**
- * 获取节点的完整结构
- * @param tree 树数据
- * @param nodeId 节点 id
- */
-export const treeToString = (tree: any[], nodeId) => {
- if (tree === undefined || !Array.isArray(tree) || tree.length === 0) {
- console.warn('tree must be an array');
- return '';
- }
- // 校验是否是一级节点
- const node = tree.find((item) => item.id === nodeId);
- if (node !== undefined) {
- return node.name;
- }
- let str = '';
-
- function performAThoroughValidation(arr) {
- if (arr === undefined || !Array.isArray(arr) || arr.length === 0) {
- return false;
- }
- for (const item of arr) {
- if (item.id === nodeId) {
- str += ` / ${item.name}`;
- return true;
- } else if (item.children !== undefined && item.children.length > 0) {
- str += ` / ${item.name}`;
- if (performAThoroughValidation(item.children)) {
- return true;
- }
- }
- }
- return false;
- }
-
- for (const item of tree) {
- str = `${item.name}`;
- if (performAThoroughValidation(item.children)) {
- break;
- }
- }
- return str;
-};
diff --git a/apps/web-ele/src/views/mall/promotion/article/data.ts b/apps/web-ele/src/views/mall/promotion/article/data.ts
index 487fa0e0f..b4e4bb614 100644
--- a/apps/web-ele/src/views/mall/promotion/article/data.ts
+++ b/apps/web-ele/src/views/mall/promotion/article/data.ts
@@ -92,14 +92,10 @@ export function useFormSchema(): VbenFormSchema[] {
defaultValue: true,
},
{
- // TODO: @puhui999:商品关联
fieldName: 'spuId',
label: '商品关联',
component: 'Input',
formItemClass: 'col-span-2',
- componentProps: {
- placeholder: '请输入商品 SPU 编号',
- },
},
{
fieldName: 'sort',
diff --git a/apps/web-ele/src/views/mall/promotion/article/modules/form.vue b/apps/web-ele/src/views/mall/promotion/article/modules/form.vue
index fd3aad79d..754ba967a 100644
--- a/apps/web-ele/src/views/mall/promotion/article/modules/form.vue
+++ b/apps/web-ele/src/views/mall/promotion/article/modules/form.vue
@@ -14,6 +14,7 @@ import {
updateArticle,
} from '#/api/mall/promotion/article';
import { $t } from '#/locales';
+import { SpuShowcase } from '#/views/mall/product/spu/components';
import { useFormSchema } from '../data';
@@ -41,6 +42,10 @@ const [Form, formApi] = useVbenForm({
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
+ // 同步商品选择到表单,确保验证时能获取到值
+ if (formData.value?.spuId) {
+ await formApi.setFieldValue('spuId', formData.value.spuId);
+ }
const { valid } = await formApi.validate();
if (!valid) {
return;
@@ -82,6 +87,11 @@ const [Modal, modalApi] = useVbenModal({
-
+
diff --git a/apps/web-ele/src/views/mall/promotion/coupon/template/modules/form.vue b/apps/web-ele/src/views/mall/promotion/coupon/template/modules/form.vue
index 13e4c7535..a7f4996da 100644
--- a/apps/web-ele/src/views/mall/promotion/coupon/template/modules/form.vue
+++ b/apps/web-ele/src/views/mall/promotion/coupon/template/modules/form.vue
@@ -49,6 +49,19 @@ const [Form, formApi] = useVbenForm({
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
+ // 同步商品/分类选择到表单,确保验证时能获取到值
+ if (formData.value.productSpuIds) {
+ await formApi.setFieldValue(
+ 'productSpuIds',
+ formData.value.productSpuIds,
+ );
+ }
+ if (formData.value.productCategoryIds) {
+ await formApi.setFieldValue(
+ 'productCategoryIds',
+ formData.value.productCategoryIds,
+ );
+ }
const { valid } = await formApi.validate();
if (!valid) {
return;
diff --git a/apps/web-ele/src/views/mall/promotion/discountActivity/modules/form.vue b/apps/web-ele/src/views/mall/promotion/discountActivity/modules/form.vue
index e51dfe8f7..69f09011f 100644
--- a/apps/web-ele/src/views/mall/promotion/discountActivity/modules/form.vue
+++ b/apps/web-ele/src/views/mall/promotion/discountActivity/modules/form.vue
@@ -10,7 +10,9 @@ import type {
import { computed, nextTick, ref } from 'vue';
import { useVbenForm, useVbenModal } from '@vben/common-ui';
+import { PromotionDiscountTypeEnum } from '@vben/constants';
import {
+ cloneDeep,
convertToInteger,
erpCalculatePercentage,
formatToFraction,
@@ -37,12 +39,6 @@ import { useFormSchema } from '../data';
defineOptions({ name: 'DiscountActivityForm' });
-/** 折扣类型枚举 */
-const PromotionDiscountTypeEnum = {
- PRICE: { type: 1 }, // 满减
- PERCENT: { type: 2 }, // 折扣
-};
-
const emit = defineEmits(['success']);
// ================= 表单相关 =================
@@ -243,7 +239,7 @@ const [Modal, modalApi] = useVbenModal({
modalApi.lock();
try {
// 获取折扣商品配置
- const products = structuredClone(
+ const products = cloneDeep(
spuAndSkuListRef.value?.getSkuConfigs('productConfig') || [],
) as MallDiscountActivityApi.DiscountProduct[];
@@ -253,7 +249,7 @@ const [Modal, modalApi] = useVbenModal({
item.discountPrice = convertToInteger(item.discountPrice);
});
- const data = structuredClone(
+ const data = cloneDeep(
await formApi.getValues(),
) as MallDiscountActivityApi.DiscountActivity;
data.products = products;
diff --git a/apps/web-ele/src/views/mall/promotion/rewardActivity/modules/form.vue b/apps/web-ele/src/views/mall/promotion/rewardActivity/modules/form.vue
index 4ade74808..f210f4953 100644
--- a/apps/web-ele/src/views/mall/promotion/rewardActivity/modules/form.vue
+++ b/apps/web-ele/src/views/mall/promotion/rewardActivity/modules/form.vue
@@ -52,8 +52,21 @@ const [Form, formApi] = useVbenForm({
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
- // 在验证前同步 formData.rules 到表单中
+ // 在验证前同步 formData 中的值到表单中
await formApi.setFieldValue('rules', formData.value.rules || []);
+ // 同步商品/分类选择到表单,确保验证时能获取到值
+ if (formData.value.productSpuIds) {
+ await formApi.setFieldValue(
+ 'productSpuIds',
+ formData.value.productSpuIds,
+ );
+ }
+ if (formData.value.productCategoryIds) {
+ await formApi.setFieldValue(
+ 'productCategoryIds',
+ formData.value.productCategoryIds,
+ );
+ }
const { valid } = await formApi.validate();
if (!valid) {
return;
@@ -65,9 +78,8 @@ const [Modal, modalApi] = useVbenModal({
// 使用 formData.value 作为基础,确保 rules 来自 formData
const data = { ...values, ...formData.value };
if (data.startAndEndTime && Array.isArray(data.startAndEndTime)) {
- // TODO @puhui999:这里 ele 会告警;antd 不会告警,可能要看看;
- data.startTime = Number(data.startAndEndTime[0]);
- data.endTime = Number(data.startAndEndTime[1]);
+ data.startTime = data.startAndEndTime[0];
+ data.endTime = data.startAndEndTime[1];
delete data.startAndEndTime;
}
// 深拷贝 rules 避免修改原始数据