[大前端全栈] elpis框架核心设计

75 阅读5分钟

前端-CRUD战神? 工程化思想?

一、架构工程化思想

  1. 对于一个后台管理系统来说,大部分是crud一小部分是定制化开发
  2. 那么我们将大部分的curd开发抽象化为一份JSON配置通过解析器,再用我们封装好的组件渲染出我们想要的效果就不用再重复的去干那些体力活。
  3. 像我一样的新手总会去想vue好还是react好,webpack好还是vite好,koa好还是express好
  4. 这个elpis是使用vue3 + webpack5 + element-plus + koa(解析器) + express(实现热更新)
  5. 学完elpis争辩那些发现是没有意义的只要我们有工程化思想其实 使用vue还是react,webpack还是vite、koa还是express其实都一样,他们只是工具,只要我们会一个另一个就大差不差看看文档就能上手使用这才是核心。
  6. 比如webpack 热更新配置 vite热更新配置 都只是为了实现热更新而已。
  7. DSL(领域模型):假如有多个购物系统且模块大同小异,那么可以配置一份基类,各个项目再自己单独配置,再通过解析器去覆盖、新增得到各自系统的JSON配置,然后对于这份JSON配置落地为真实项目。
  8. 先设计一个糙版本配置
export default {
    mode: 'dashboard', // 模板类型 , 不同模板类型对应不一样的模板数据结构
    name: '', // 名称
    desc: '', // 描述
    icon: '', // icon
    homePage: '', // 首页(项目配置)
    // 头部菜单
    menu: [{
        key: '', //菜单唯一描述
        name: '', // 菜单名称
        menuType: '', // 枚举值, group(菜单组) | module(页面)

        // 当 menuType == group 时, 才可填
        subMenu: [{
            // 可递归menuItem
        },],

        // 当 menuType == module 时, 才可填
        moduleType: '', // 枚举值: sider/iframe/custom/schema

        // 当 moduleType == sider 时, 才可填 第三方页面(其他项目)
        siderConfig: {
            menu: [{
                // 可递归menuItem(除 moduleType === sider )
            }]
        },

        // 当 moduleType == iframe 时, 才可填 第三方页面(其他项目)
        iframeConfig: {
            path: '', // iframe 路径
        },

        // 当 moduleType == custom 时, 才可填 自定义定制化页面
        customConfig: {
            path: '', // 自定义路由
        },

        // 当 moduleType == schema 时, 才可填 模板页面
        schemaConfig: {
            api: '', // 数据源API(遵循 RESTFUL 规范)
            schema: { // 板块数据结构
                type: 'object',
                properties: {
                    key: {
                        ...schema, // 标准 schema 配置
                        type: '', // 字段类型
                        label: '', // 字段的中文名
                        // 字段在 table 中的相关配置
                        tableOption: {
                            ...elTableColumnConfig, // 标准 el-table-column 配置
                            toFixed: 0, // 保留的小数点后几位
                            visiable: true, // 默认为 true (false 时,表示不在表单中显示)
                        },
                        // 字段在 search-bar 中的相关配置
                        searchOption: {
                            ...eleComponentConfig, // 标准 el-component-column 配置
                            comType: '',// 配置组件类型 input/select/...
                            default: '', // 默认值

                            // comType === 'select'
                            enumList: [], // 下拉框可选项

                            // comType === 'dynamicSelect'
                            api: '',
                        }
                    },
                    // ...
                }
            },
            // table 相关配置
            tableConfig: {
                headerButtons: [{
                    label: '', // 按钮中文名
                    eventKey: '', // 按钮事件名
                    eventOption: {}, // 按钮事件具体配置
                    ...elButtonConfig, //标准 el-button 配置
                }],
                rowButtons: [{
                    label: '', // 按钮中文名
                    eventKey: '', // 按钮事件名
                    eventOption: {
                        // 当 eventKey === 'remove'
                        params: {
                            // parmaKey = 参数的键值
                            // rowValueKey = 参数值 格式为 schema::tableKey 的时候, 到 table 中找相应的字段
                            // 比如 user_id: 'schema::user_id'
                            paramKey: rowValueKey,
                        }
                    }, // 按钮事件具体配置
                    ...elButtonConfig, //标准 el-button 配置
                }]
            },
            searchConfig: { // search-bar 相关配置
            },
            components: { // 模块组件
            }
        },
    },]
}
  1. 实际模板
module.exports = {
    model: 'dashboard',
    name: '电商系统',
    menu: [{
        key: 'product',
        name: '商品管理',
        menuType: 'module',
        moduleType: 'schema',
        schemaConfig: {
            api: '/api/proj/product',
            schema: {
                type: 'object',
                properties: {
                    product_id: {
                        type: 'string',
                        label: '商品ID',
                        tableOption: {
                            width: 300,
                            'show-overflow-tooltip': true
                        }
                    },
                    product_name: {
                        type: 'string',
                        label: '商品名称',
                        tableOption: {
                            width: 200,
                            'show-overflow-tooltip': true
                        },
                        searchOption: {
                            comType: 'dynamicSelect',
                            api: '/api/proj/product_enum/list',
                        }
                    },
                    price: {
                        type: 'number',
                        label: '价格',
                        tableOption: {
                            width: 200,
                        },
                        searchOption: {
                            comType: 'select',
                            enumList: [{
                                label: '全部',
                                value: -999
                            }, {
                                label: '¥39.9',
                                value: 39.9
                            }, {
                                label: '¥199',
                                value: 199
                            }, {
                                label: '¥899',
                                value: 899
                            }]
                        }
                    },
                    inventory: {
                        type: 'number',
                        label: '库存',
                        tableOption: {
                            width: 200,
                        },
                        searchOption: {
                            comType: 'input',
                        }
                    },
                    create_time: {
                        type: 'string',
                        label: '创建时间',
                        tableOption: {},
                        searchOption: {
                            comType: 'dateRange',
                        }
                    }
                }
            },
            tableConfig: {
                headerButtons: [{
                    label: '新增商品',
                    eventKey: 'showComponent',
                    type: 'primary',
                    plain: true,
                }],
                rowButtons: [{
                    label: '修改',
                    eventKey: 'showComponent',
                    type: "warning",
                }, {
                    label: '删除',
                    eventKey: 'remove',
                    eventOption: {
                        params: {
                            product_id: 'schema::product_id',
                        }
                    },
                    type: "danger",
                }]
            }
        },

    }, {
        key: 'order',
        name: '订单管理',
        menuType: 'module',
        moduleType: 'custom',
        customConfig: {
            path: '/todo'
        }
    }, {
        key: 'client',
        name: '客户管理',
        menuType: 'module',
        moduleType: 'custom',
        customConfig: {
            path: '/todo'
        }
    }]
}
  1. 再根据单独的项目配置
    name: '京东',
    desc: '京东电商系统',
    homePage: '/schema?proj_key=jd&&key=product',
    menu: [{
        key: 'shop-setting',
        name: '店铺设置',
        menuType: 'group',
        subMenu: [{
            key: 'info-setting',
            name: '店铺信息',
            menuType: 'module',
            moduleType: 'custom',
            customConfig: {
                path: '/todo'
            }
        }, {
            key: 'quality-setting',
            name: '店铺资质',
            menuType: 'module',
            moduleType: 'iframe',
            iframeConfig: {
                path: 'http://www.baidu.com'
            }
        }]
    }]
}
  1. 构造不同领域下的不同项目配置
const glob = require('glob');
const path = require('path');
const { sep } = path;

// project 继承 model
const projectExtendModel = (model, project) => {
    return _.mergeWith({}, model, project, (modelValue, projValue) => {
        // 处理数组合并的特殊情况
        if (Array.isArray(modelValue) && Array.isArray(projValue)) {
            let result = [];

            // 因为 project 继承 model, 所以需要处理修改和新增内容的情况
            // project有的键值, model也有 => 修改 (重载)
            // project有的键值, model没有 => 新增 (拓展)
            // model有的键值, project没有 => 保留 (继承)

            // 处理 修改 和 保留
            for (let i = 0; i < modelValue.length; ++i) {
                let modelItem = modelValue[i];
                const projItem = projValue.find(projItem => projItem.key === modelItem.key);
                // project 有的键值, model也有, 则递归调用 projectExtendModel 方法 覆盖修改
                result.push(projItem ? projectExtendModel(modelItem, projItem) : modelItem)
            }
            // 处理 新增
            for (let i = 0; i < projValue.length; ++i) {
                const projItem = projValue[i];
                const modelItem = modelValue.find(modelItem => modelItem.key === projItem.key);
                if (!modelItem) {
                    result.push(projItem)
                }
            }

            return result
        }
    })
}

/**
 * 解析 model 配置, 并返回组织且继承后的数据结构
 * 案例
 [{
    model:${model},
    project:{
        proj1Key:${proj1},
        proj2Key:${proj2}
    }
 },...]
 * */
module.exports = (app) => {
    const modelList = []

    // 遍历当前文件夹,构造模型数据结构, 挂载到 modelList 上
    const modelPath = path.resolve(app.baseDir, `.${sep}model`);
    const fileList = glob.sync(path.resolve(modelPath, `.${sep}**${sep}**.js`));
    fileList.forEach(file => {
        // 提取文件名称
        let name = path.resolve(file)

        if (name.indexOf('index.js') > -1) { return; }

        // 区分配置类型(model / project)
        const type = name.indexOf(`${sep}project${sep}`) > -1 ? 'project' : 'model';
        if (type === 'project') {
            const modelKey = file.match(/\/model\/(.*?)\/project/)?.[1];
            const projKey = file.match(/\/project\/(.*?)\.js/)?.[1];
            let modelItem = modelList.find(item => item.model?.key === modelKey);
            if (!modelItem) { // 初始化 model 数据结构
                modelItem = {};
                modelList.push(modelItem);
            }
            if (!modelItem.project) { // 初始化 project 数据结构
                modelItem.project = {};
            }
            modelItem.project[projKey] = require(name);
            modelItem.project[projKey].key = projKey; // 注入 projKey
            modelItem.project[projKey].modelKey = modelKey; // 注入 modelKey
        }

        if (type === 'model') {
            const modelKey = file.match(/\/model\/(.*?)\/model\.js/)?.[1];
            let modelItem = modelList.find(item => item.model?.key === modelKey);
            if (!modelItem) { // 初始化 model 数据结构
                modelItem = {};
                modelList.push(modelItem);
            }
            modelItem.model = require(name);
            modelItem.model.key = modelKey; // 注入 modelKey
        }
    });

    // 数据进一步整理: project => 继承 model
    modelList.forEach(item => {
        const { model, project } = item;
        for (const key in project) {
            project[key] = projectExtendModel(model, project[key])
        }
    });

    return modelList
}
  1. 再根据 Restful API规范实现一个接口不同请求方式实现不同功能
  2. schema就是我们解决重复性crud的配置我们去封装一些表格组件 和搜索组件去解析我们这份配置生成页面即可
  3. 额外的定制化需求可以是 iframe 还是custom去自定义一些组件开发大大减少了体力
  4. 学习地址--> 抖音搜索 <哲玄前端>