amis icon indicating copy to clipboard operation
amis copied to clipboard

fix setPages: 确保在子节点没有设置url的时候,正确拼接父级路径。

Open aynakeya opened this issue 9 months ago • 3 comments

What

修复 setPages 中页面路径生成逻辑,确保在子节点没有设置url的时候,正确拼接父级路径。

Why

当前状态下,如果父节点设置了url但是字节点没有设置,那么子节点不会使用父节点的url,而会变成page-n/page-n的格式

[
    {
        "label": "分组2",
        "url": "/dashboard",
        "children": [
            {
                "label": "用户管理",
                "schema": {
                    "type": "page",
                    "title": "用户管理",
                    "body": "页面C"
                }
            },
            {
                "label": "部门管理",
                "url": "3",
                "schemaApi": "${API_HOST}/api/amis-mock/mock2/service/form?tpl=tpl3"
            },
        ]
    }
]

如上的的用户管理会返回 page-1/page-1 而不是/dashboard/page-1

How

修改

         path =
            item.url ||
            `/${paths
              .map(item => item.index)
              .concat(index)
              .map(index => `page-${index + 1}`)
              .join('/')}`;

          path = item.url || `page-${index + 1}`;

Test Script

function mapTree<T extends TreeItem>(
    tree: Array<T>,
    iterator: (
        item: T,
        key: number,
        level: number,
        paths: Array<T>,
        indexes: Array<number>
    ) => T,
    level: number = 1,
    depthFirst: boolean = false,
    paths: Array<T> = [],
    indexes: Array<number> = []
) {
    return tree.map((item: any, index) => {
        if (depthFirst) {
            let children: TreeArray | undefined = item.children
                ? mapTree(
                    item.children,
                    iterator,
                    level + 1,
                    depthFirst,
                    paths.concat(item),
                    indexes.concat(index)
                )
                : undefined;
            children && (item = {...item, children: children});
            item = iterator(item, index, level, paths, indexes.concat(index)) || {
                ...(item as object)
            };
            return item;
        }

        item = iterator(item, index, level, paths, indexes.concat(index)) || {
            ...(item as object)
        };

        if (item.children && item.children.splice) {
            item.children = mapTree(
                item.children,
                iterator,
                level + 1,
                depthFirst,
                paths.concat(item),
                indexes.concat(index)
            );
        }

        return item;
    });
}

let setPages = (pages: any) => {
    if (pages && !Array.isArray(pages)) {
        pages = [pages];
    } else if (!Array.isArray(pages)) {
        return;
    }

    pages = mapTree(pages, (item, index, level, paths) => {
        let path = item.link || item.url;

        if (item.schema || item.schemaApi) {
            path = item.url || `page-${index + 1}`;
            // path =
            //     item.url ||
            //     `/${paths
            //         .map(item => item.index)
            //         .concat(index)
            //         .map(index => `page-${index + 1}`)
            //         .join('/')}`;

            // path exists and start with /
            if (path && path[0] !== '/') {
                let parentPath = '/';
                let index = paths.length;
                while (index > 0) {
                    const item = paths[index - 1];

                    if (item?.path) {
                        parentPath = item.path + '/';
                        break;
                    }
                    index--;
                }

                path = parentPath + path;
            }
        }

        return {
            ...item,
            index,
            id: item.id ,
            label: item.label,
            icon: item.icon,
            path
        };
    });
    return pages;
}

let pages = [
    {
        "label": "示例",
        "url": "/dashboard",
        "children": [
            {
                "label": "页面A",
                "url": "index",
                "schema": {
                    "type": "page",
                    "title": "页面A",
                    "body": "页面A"
                },
                "children": [
                    {
                        "label": "页面A-1",
                        "url": "1",
                        "schema": {
                            "type": "page",
                            "title": "页面A-1",
                            "body": "页面A-1"
                        }
                    },
                ]
            },
            {
                "label": "页面B",
                "badge": 3,
                "badgeClassName": "bg-info",
                "schema": {
                    "type": "page",
                    "title": "页面B",
                    "body": "页面B"
                },
                "children": [
                    {
                        "label": "页面bbb-1",
                        "url": "aaa",
                        "schema": {
                            "type": "page",
                            "title": "页面A-1",
                            "body": "页面A-1"
                        }
                    },
                    {
                        "label": "页面bbb-1",
                        "url": "/aaaa",
                        "schema": {
                            "type": "page",
                            "title": "页面A-1",
                            "body": "页面A-1"
                        }
                    },
                ]
            },
            {
                "label": "列表示例",
                "url": "/crud",
                "rewrite": "/crud/list",
                "icon": "fa fa-cube",
                "children": [
                    {
                        "label": "列表",
                        "url": "/crud/list",
                        "icon": "fa fa-list",
                        "schemaApi": "get:/pages/crud-list.json"
                    },
                    {
                        "label": "新增",
                        "url": "new",
                        "icon": "fa fa-plus",
                        "schemaApi": "get:/pages/crud-new.json"
                    }
                ]
            }
        ]
    },
    {
        "label": "分组2",
        "url": "/dashboard",
        "children": [
            {
                "label": "用户管理",
                "schema": {
                    "type": "page",
                    "title": "用户管理",
                    "body": "页面C"
                }
            },
            {
                "label": "部门管理",
                "url": "3",
                "schemaApi": "${API_HOST}/api/amis-mock/mock2/service/form?tpl=tpl3"
            },
        ]
    }
]

console.log(JSON.stringify(setPages(pages), null, 4));

aynakeya avatar Jul 11 '25 13:07 aynakeya

👍 Thanks for this! 🏷 I have applied any labels matching special text in your issue.

Please review the labels and make any necessary changes.

github-actions[bot] avatar Jul 11 '25 13:07 github-actions[bot]

你好,之前时故意这么设定的,如果不带上层级,index 可能会冲突

2betop avatar Jul 18 '25 06:07 2betop

你好,之前时故意这么设定的,如果不带上层级,index 可能会冲突

你好,我修改了代码,这样还有冲突么。

测试代码

function mapTree<T extends TreeItem>(
    tree: Array<T>,
    iterator: (
        item: T,
        key: number,
        level: number,
        paths: Array<T>,
        indexes: Array<number>
    ) => T,
    level: number = 1,
    depthFirst: boolean = false,
    paths: Array<T> = [],
    indexes: Array<number> = []
) {
    return tree.map((item: any, index) => {
        if (depthFirst) {
            let children: TreeArray | undefined = item.children
                ? mapTree(
                    item.children,
                    iterator,
                    level + 1,
                    depthFirst,
                    paths.concat(item),
                    indexes.concat(index)
                )
                : undefined;
            children && (item = {...item, children: children});
            item = iterator(item, index, level, paths, indexes.concat(index)) || {
                ...(item as object)
            };
            return item;
        }

        item = iterator(item, index, level, paths, indexes.concat(index)) || {
            ...(item as object)
        };

        if (item.children && item.children.splice) {
            item.children = mapTree(
                item.children,
                iterator,
                level + 1,
                depthFirst,
                paths.concat(item),
                indexes.concat(index)
            );
        }

        return item;
    });
}

let setPages = (pages: any) => {
    if (pages && !Array.isArray(pages)) {
        pages = [pages];
    } else if (!Array.isArray(pages)) {
        return;
    }

    pages = mapTree(pages, (item, index, level, paths, indexes) => {
        let path = item.link || item.url;

        if (item.schema || item.schemaApi) {
            // get current path either url exist or generated url page-index
            let currentPath = item.url || `page-${index + 1}`;


            // if start with '/', absolute path, return directly
            if (currentPath.startsWith('/')) {
                path = item.url;
            } else {
                let closestIndex = paths.length - 1;
                let fullPaths: string[] = [currentPath];
                while (closestIndex >= 0) {
                    const item = paths[closestIndex];

                    // if parent path exists
                    if (item?.path) {
                        fullPaths = fullPaths.concat(item.path);
                        // if its a scheme, the path might not be generated, so keep moving upwards
                        if (item.schema || item.schemaApi) {
                            break;
                        }
                    }else {
                        fullPaths = fullPaths.concat(`page-${indexes[closestIndex] + 1}`);
                    }
                    closestIndex--;
                }

                path = fullPaths.reverse().join("/");
                // maybe add /??,
                // if (path && path[0] !== '/') {
                //     path = `/${path}`;
                // }
            }
        }else {
            // the above code would be lot cleaner if we set path for no schema node here.
            // but there might be other issue. keep as is.
        }

        return {
            ...item,
            index,
            id: item.id,
            label: item.label,
            icon: item.icon,
            path
        };
    });
    return pages;
}

let pages = [
    {
        "label": "1 顶层有url",
        "url": "/dashboard",
        "children": [
            {
                "label": "1.1 相对路径",
                "url": "relative-1-1",
                "schema": {
                    "type": "page"
                },
                "children": [
                    {
                        "label": "1.1.1 无url",
                        "schema": {
                            "type": "page"
                        }
                    },
                    {
                        "label": "1.1.2 相对路径",
                        "url": "relative-1-1-2",
                        "schema": {
                            "type": "page"
                        }
                    }
                ]
            },
            {
                "label": "1.2 绝对路径",
                "url": "/page-b",
                "schema": {
                    "type": "page"
                }
            },
            {
                "label": "1.3 无url",
                "schema": {
                    "type": "page"
                },
                children: [
                    {
                        "label": "1.3.1 相对",
                        "url": "relative-1-3-1",
                        "schema": {
                            "type": "page"
                        }
                    },
                ]
            }
        ]
    },
    {
        "label": "2 顶层带url 不带 /",
        "url": "features",
        "children": [
            {
                "label": "2.1 重复相对路径",
                "url": "page-a",
                "schemaApi": "get:/api/feature1"
            },
            {
                "label": "2.2 无schema 绝对路径",
                "url": "/external-link",
                "children": [
                    {
                        "label": "2.2.1 子功能 绝对路径覆盖",
                        "url": "/sub-absolute-override",
                        "schema": {
                            "type": "page"
                        }
                    },
                    {
                        "label": "2.2.1 相对路径",
                        "url": "sub-relative",
                        "schema": {
                            "type": "page"
                        }
                    },
                ]
            }, {
                "label": "2.3 无schema 无url",
                "children": [
                    {
                        "label": "2.3.1 绝对路径覆盖",
                        "url": "/abosolute-2-3-1",
                        "schema": {
                            "type": "page"
                        }
                    },
                    {
                        "label": "2.3.2 相对路径 ",
                        "url": "relative-2-3-2",
                        "schema": {
                            "type": "page"
                        }
                    },
                    {
                        "label": "2.3.3 相对路径 无schema",
                        "url": "relative-2-3-3",
                        "children": [
                            {
                                "label": "2.3.3.1 无url",
                                "schema": {
                                    "type": "page"
                                }
                            },
                        ]
                    },
                ]
            }
        ]
    },
    {
        "label": "3 无URL的顶层",
        "children": [
            {
                "label": "3.1 无url",
                "schema": {
                    "type": "page"
                }
            },
            {
                "label": "3.2 无url",
                "children": [
                    {
                        "label": "3.2.1 相对",
                        "url": "relative-3-2-1",
                        "schema": {
                            "type": "page"
                        }
                    },
                ]
            }
        ]
    },
    {
        "label": "4 另一个无URL的顶层",
        "children": [
            {
                "label": "4.1 子页面(无url,依赖父索引)",
                "schema": {
                    "type": "page"
                }
            },
            {
                "label": "4.2 有url",
                "url": "relative-4-2",
                "schema": {
                    "type": "page"
                }
            },
            {
                "label": "4.3 有url",
                "url": "relative-4-3",
                "children": [
                    {
                        "label": "4.3.1 无url",
                        "schema": {
                            "type": "page"
                        }
                    },
                ]
            },
        ]
    }
]

function printPathWithUrl(pages: any, prefix = '') {
    if (!Array.isArray(pages)) {
        return;
    }

    pages.forEach(page => {
        const originalUrl = page.url ? `url: ${page.url}` : 'url: null';
        const generatedPath = `path: ${page.path || '无'}`;
        const hasSchema = `schema: ${page.schema || page.schemaApi ? 'yes' : 'no'}`;

        console.log(`${prefix}├─ ${page.label} (${originalUrl}, ${generatedPath}, ${hasSchema})`);

        if (page.children && page.children.length > 0) {
            printPathWithUrl(page.children, prefix + '    ');
        }
    });
}

printPathWithUrl(setPages(pages));
├─ 1 顶层有url (url: /dashboard, path: /dashboard, schema: no)
    ├─ 1.1 相对路径 (url: relative-1-1, path: /dashboard/relative-1-1, schema: yes)
        ├─ 1.1.1 无url (url: null, path: /dashboard/relative-1-1/page-1, schema: yes)
        ├─ 1.1.2 相对路径 (url: relative-1-1-2, path: /dashboard/relative-1-1/relative-1-1-2, schema: yes)
    ├─ 1.2 绝对路径 (url: /page-b, path: /page-b, schema: yes)
    ├─ 1.3 无url (url: null, path: /dashboard/page-3, schema: yes)
        ├─ 1.3.1 相对 (url: relative-1-3-1, path: /dashboard/page-3/relative-1-3-1, schema: yes)
├─ 2 顶层带url 不带 / (url: features, path: features, schema: no)
    ├─ 2.1 重复相对路径 (url: page-a, path: features/page-a, schema: yes)
    ├─ 2.2 无schema 绝对路径 (url: /external-link, path: /external-link, schema: no)
        ├─ 2.2.1 子功能 绝对路径覆盖 (url: /sub-absolute-override, path: /sub-absolute-override, schema: yes)
        ├─ 2.2.1 相对路径 (url: sub-relative, path: features//external-link/sub-relative, schema: yes)
    ├─ 2.3 无schema 无url (url: null, path: 无, schema: no)
        ├─ 2.3.1 绝对路径覆盖 (url: /abosolute-2-3-1, path: /abosolute-2-3-1, schema: yes)
        ├─ 2.3.2 相对路径  (url: relative-2-3-2, path: features/page-3/relative-2-3-2, schema: yes)
        ├─ 2.3.3 相对路径 无schema (url: relative-2-3-3, path: relative-2-3-3, schema: no)
            ├─ 2.3.3.1 无url (url: null, path: features/page-3/relative-2-3-3/page-1, schema: yes)
├─ 3 无URL的顶层 (url: null, path: 无, schema: no)
    ├─ 3.1 无url (url: null, path: page-3/page-1, schema: yes)
    ├─ 3.2 无url (url: null, path: 无, schema: no)
        ├─ 3.2.1 相对 (url: relative-3-2-1, path: page-3/page-2/relative-3-2-1, schema: yes)
├─ 4 另一个无URL的顶层 (url: null, path: 无, schema: no)
    ├─ 4.1 子页面(无url,依赖父索引) (url: null, path: page-4/page-1, schema: yes)
    ├─ 4.2 有url (url: relative-4-2, path: page-4/relative-4-2, schema: yes)
    ├─ 4.3 有url (url: relative-4-3, path: relative-4-3, schema: no)
        ├─ 4.3.1 无url (url: null, path: page-4/relative-4-3/page-1, schema: yes)

aynakeya avatar Jul 18 '25 18:07 aynakeya