UNPKG

mm_os

Version:

MM_OS服务端架构,用于快速构建应用程序,支持网站建设、小程序后台、AI应用、物联网(IOT/AIOT)、游戏服务端等多种场景。

702 lines (650 loc) 17.3 kB
const fs = require('fs'); const { Tpl } = require('mm_tpl'); const Item = require('mm_machine').Drive; const Model = require('./viewmodel.js'); let tpl = new Tpl({ extname: '.vue', chache_extname: '.cache.vue' }); tpl.dir = __dirname; const viewModel = new Model(); /** * 导航驱动类 * @augments {Item} * @class */ class Drive extends Item { static config = { // 路由 'routes': [ /* { // 名称 "name": "root", // 路由路径 "path": "/", // 组件文件路径 "component": "", // 页面显示级别 "level": "", // 身份验证 "oauth": { // 是否登录 "sign_in": false, // 要求会员级别 "vip": 0, // 要求管理级别 "gm": 0, // 要求商户级别 "mc": 0, // 要求用户组 "user_group": [], // 要求管理组 "user_admin": [] } } */ ], // 顶部导航 'top_nav': [], // 左侧导航 'left_nav': [], // 底部导航 'bottom_nav': [], // 右侧导航 'right_nav': [], // 桌面导航 'desktop_nav': [], // 快捷导航 'quick_nav': [], // 主导航 'main_nav': [ /* { // 标题 "title": "", // 路径 "url": "", // 在没有权限下是否显示 "show": false, // 显示顺序 "display": 3, // 子导航 "sub": [ { // 标题 "title": "", // 路径 "url": "", // 在没有权限下是否显示 "show": false } ] } */ ], // 主程序文件 - 默认为空 'main': '' }; /** * 构造函数 * @param {object} config 配置参数 * @param {object} parent 父对象 * @class */ constructor(config, parent) { super({ ...Drive.config, ...config }, parent); // 模板路径 this.tpl_path = './tpl/'; // 默认启用热更新 this.mode = 3; } }; /** * 调用函数 * @param {object} type 导航类型 * @returns {object} 执行结果 */ Drive.prototype.run = function (type) { var cg = { routes: this.config.routes }; cg[type] = this.config[type]; return cg; }; /** * 合并项 * @param {object[]} arr 目标数组 * @param {object[]} lt 导航项 */ Drive.prototype.mergeSub = function (arr, lt) { if (lt) { var len = lt.length; for (var i = 0; i < len; i++) { var o = lt[i]; if (o.name) { var item = arr.getMatch(o.name, 'name'); if (item) { $.push(item, o, true); } else { arr.push(o); } } } } }; /** * 执行导航 * @param {string} type 导航类型 * @returns {object|string} 执行结果 */ Drive.prototype.exec = function (type) { var cg = this.config; if (cg[type]) { return this.run(type); } return ''; }; /** * 排序 */ Drive.prototype.sort = async function () { var cg = this.config; cg.top.sortBy('asc', 'display'); cg.left.sortBy('asc', 'display'); cg.bottom.sortBy('asc', 'display'); cg.right.sortBy('asc', 'display'); cg.desktop.sortBy('asc', 'display'); cg.quick.sortBy('asc', 'display'); cg.main.sortBy('asc', 'display'); }; /** * 合并配置 * @param {object} o 配置 */ Drive.prototype.merge = function (o) { var cg = this.config; cg.name = o.name; cg.sort = o.sort; this.mergeSub(cg.routes, o.routes); this.mergeSub(cg.top, o.top); this.mergeSub(cg.left, o.left); this.mergeSub(cg.bottom, o.bottom); this.mergeSub(cg.right, o.right); this.mergeSub(cg.desktop, o.desktop); this.mergeSub(cg.quick, o.quick); this.mergeSub(cg.main, o.main); }; /** * 新建导航 * @param {string} title 标题 * @param {string} name 名称 * @param {string} url 路径 * @param {number} display 显示顺序 * @returns {object} 导航对象 */ Drive.prototype.newNav = function (title, name, url, display = 100) { var obj = { 'title': title, // 在没有权限下是否显示 'show': false, 'name': name, 'url': url, 'display': display }; return obj; }; /** * 新建路由 * @param {string} app 应用名 * @param {string} plugin 插件名 * @param {string} name 接口名 * @param {number} group 权限组 * @param {object} oauth 身份验证 * @returns {object} 路由对象 */ Drive.prototype.newRoutes = function (app, plugin, name, group, oauth) { var pn = plugin === 'pc' ? '' : plugin; var n = name.replace(app + '_', ''); var obj = { 'name': name, 'path': '/' + app + '/' + n, 'component': '/' + app + (pn ? '/' + pn : '') + '/src/pages/' + n + '.vue', 'level': n.replace('_form', '').replace('_view', '').indexOf('_') === -1 ? 3 : 2, 'oauth': oauth }; if (!oauth) { if (!group) { // 不验证身份 obj.oauth = { 'sign_in': false }; } else if (group === 1) { // 验证身份为管理员 obj.oauth = { 'sign_in': true, 'gm': 2, 'user_admin': [] }; } else { // 验证身份为商户 obj.oauth = { 'sign_in': true, 'mc': 1, 'user_group': [] }; } } return obj; }; /** * 处理管理端API配置 * @param {string} dir 目录路径 * @param {string} app 应用名 * @param {string} plugin 插件名 * @param {Array} nav 导航数组 * @param {object} cg 配置对象 * @param {object} _this 当前对象 */ Drive.prototype._processManageApis = function (dir, app, plugin, nav, cg, _this) { var d = dir + 'api_manage'; if (!d.hasDir()) { d = dir + `api_${app}_manage`; } if (!d.hasDir()) { return; } var list = $.file.getAll(d, '*api.json'); list.map((f) => { var o = f.loadJson(); if (o) { delete o.oauth.scope; var name = o.name.replace('_manage', ''); // 添加一个列表页 var obj = _this.newRoutes(app, plugin, name, 1, o.oauth); //添加一个详情页 var obj2 = _this.newRoutes(app, plugin, name + '_form', 1, o.oauth); obj2.level += 1; cg.routes.push(obj); cg.routes.push(obj2); nav.push(_this.newNav(o.title, name, obj.path)); } }); }; /** * 处理客户端API配置 * @param {string} dir 目录路径 * @param {string} app 应用名 * @param {string} plugin 插件名 * @param {Array} nav 导航数组 * @param {object} cg 配置对象 * @param {object} _this 当前对象 */ Drive.prototype._processClientApis = function (dir, app, plugin, nav, cg, _this) { var d = dir + 'api_client'; if (!d.hasDir()) { d = dir + `api_${app}_client`; } if (!d.hasDir()) { return; } var list = $.file.getAll(d, '*api.json'); list.map((f) => { var o = f.loadJson(); if (o && !Array.isArray(o)) { if (o.oauth) { delete o.oauth.scope; } var name = o.name; // 添加一个列表页 var obj = _this.newRoutes(app, plugin, name, 0, o.oauth); //添加一个详情页 var obj2 = _this.newRoutes(app, plugin, name + '_view', 0, o.oauth); obj2.level += 1; if (name.indexOf('_') === -1) { obj.level += 1; obj2.level += 1; } cg.routes.push(obj); cg.routes.push(obj2); nav.push(_this.newNav(o.title, name, obj.path)); } }); }; /** * 获取应用标题 * @param {string} app 应用名 * @param {string} file 文件路径 * @returns {string} 应用标题 */ Drive.prototype._getAppTitle = function (app, file) { var title = '未命名'; var app_file = ('/app/' + app + '/app.json').fullname(); if (app_file.hasFile()) { var jobj = app_file.loadJson(); if (jobj) { title = jobj.title; } } var app_config = (file + '').left('plugin') + 'app.json'; if (app_config.hasFile()) { var oj = app_config.loadJson(); title = oj.title; } return title; }; /** * 设置默认路由 * @param {object} cg 配置对象 * @param {string} app 应用名 */ Drive.prototype._setDefaultRoute = function (cg, app) { if (cg.routes.length > 0) { cg.routes.splice(0, 0, { name: app, path: '/' + app, redirect: cg.routes[0].path }); } }; /** * 新建配置 * @param {string} file 文件路径 */ Drive.prototype.newConfig = function (file) { var cg = this.config; var plugin = (file + '').right('plugin' + $.slash).left($.slash, true); var dir = (file + '').left(plugin) + 'server' + $.slash; var _this = this; var app = (file + '').right('app' + $.slash).left($.slash, true); var nav = []; if (plugin.indexOf('admin') !== -1) { this._processManageApis(dir, app, plugin, nav, cg, _this); } else { this._processClientApis(dir, app, plugin, nav, cg, _this); } cg.name = app + '_' + plugin; var title = this._getAppTitle(app, file); this._setDefaultRoute(cg, app); cg.main = [{ 'title': title, 'name': app, 'icon': '<i class="fa-cog"></i>', 'url': '/' + app, 'display': 0, 'sub': nav }]; file.saveText(JSON.stringify(cg, null, 4)); }; /** * 获取API作用域和路由 * @param {string} app 应用域名 * @param {object} route 路由配置 * @returns {object} 返回作用域和路由 */ Drive.prototype._getApiScope = function (app, route) { var { path, component } = route; var scope = app; var p = path.replace('_table', '').replace('_list', '').replace('_view', '').replace('_form', ''); var api_route; if (component.indexOf('/admin') !== -1) { scope += '_manage'; api_route = '/apis' + p; } else { scope += '_client'; api_route = '/api' + p; } return { scope, api_route }; }; /** * 获取API配置 * @param {string} app 应用域名 * @param {object} route 路由配置 * @returns {object} 返回api配置 */ Drive.prototype.getApi = function (app, route) { var { scope, api_route } = this._getApiScope(app, route); var api = $.pool.api[scope]; if (!api) { scope = scope.replace(app + '_', ''); api = $.pool.api[scope]; } if (!api) { return null; } return this._findApiConfig(api.list, scope, api_route); }; /** * 查找API配置 * @param {Array} list API列表 * @param {string} scope 作用域 * @param {string} api_route API路由 * @returns {object} 返回api配置 */ Drive.prototype._findApiConfig = function (list, scope, api_route) { var config = { api: {}, param: {}, sql: {} }; for (var i = 0; i < list.length; i++) { var o = list[i]; if (o.config.path === api_route) { return { scope, api: o.config, param: o.param.config, sql: o.sql.config }; } } return config; }; /** * 获取模板文件路径 * @param {string} name 文件名 * @param {string} plugin 插件名 * @param {string} file 文件路径 * @returns {string} 模板文件路径 */ Drive.prototype._getTplPath = function (name, plugin, file) { var f = this.tpl_path; var l = $.slash; f += this._getSubDir(plugin, file, l); f += this._getPageFile(name); return f.fullname(__dirname); }; /** * 获取子目录 * @param {string} plugin 插件名 * @param {string} file 文件路径 * @param {string} l 分隔符 * @returns {string} 子目录 * @private */ Drive.prototype._getSubDir = function (plugin, file, l) { if (plugin) { return plugin + '/'; } var subDirs = { 'mobile': 'mobile/', 'admin': 'admin/', 'pc': 'pc/' }; for (var k in subDirs) { if (file.indexOf(k + l) !== -1) { return subDirs[k]; } } return ''; }; /** * 获取页面文件名 * @param {string} name 文件名 * @returns {string} 页面文件名 * @private */ Drive.prototype._getPageFile = function (name) { var pageMap = [ { suffix: '_config_form', file: 'page_config_form.vue' }, { suffix: '_form', file: 'page_form.vue' }, { suffix: '_view', file: 'page_view.vue' }, { suffix: '_table', file: 'page_table.vue' }, { suffix: '_list', file: 'page_list.vue' }, { suffix: 'channel', file: 'page_channel.vue' }, { suffix: 'type', file: 'page_type.vue' }, { suffix: 'nav', file: 'page_nav.vue' }, { suffix: 'lang', file: 'page_lang.vue' }, { suffix: 'config', file: 'page_config.vue' } ]; for (var i = 0; i < pageMap.length; i++) { if (name.ends(pageMap[i].suffix)) { return pageMap[i].file; } } return 'page_default.vue'; }; /** * 创建视图模型 * @param {string} name 文件名 * @param {string} app 应用名 * @param {string} plugin 插件名 * @param {string} file 文件路径 * @param {Array} arr 路径数组 * @param {object} route 路由配置 * @returns {object} 视图模型 */ Drive.prototype._createViewModel = function (name, app, plugin, file, arr, route) { var model = { id: app + '_' + name, name: name, app, plugin, file, group: arr[arr.length - 2], nav_config: this.config, route, scope: '', api: {}, param: {}, sql: {} }; return Object.assign(model, this.getApi(app, route)); }; /** * 渲染并保存vue文件 * @param {string} tpl_file 模板文件路径 * @param {object} vm 视图模型 * @param {string} file 目标文件路径 */ Drive.prototype._renderAndSaveVue = function (tpl_file, vm, file) { try { var vue = tpl.view(tpl_file, vm); if (vue) { file.saveText(vue); } else { this.log('error', '更新模板失败', tpl.error); } } catch (err) { this.log('error', '更新模板失败', err); } }; /** * 创建vue文件 * @param {string} filename 文件保存路径 * @param {object} route 路由配置 */ Drive.prototype.createVue = async function (filename, route) { var file = filename; var l = $.slash; file = file.replace($.run_path, '/'); var arr = file.substring(file.indexOf('app')).split(l); var name = arr[arr.length - 1].replace('.vue', ''); var plugin = ''; var app = ''; if (arr.length > 3) { plugin = arr[3]; app = arr[1]; } var tpl_file = this._getTplPath(name, plugin, file); var model = this._createViewModel(name, app, plugin, file, arr, route); var vm = await viewModel.run(model); vm.JSON = JSON; this._renderAndSaveVue(tpl_file, vm, file); }; /** * 创建路径 * @param {string} filepath 文件路径 */ Drive.prototype.mkdir = function (filepath) { var l = $.slash; var arr = filepath.split(l); var dir = arr[0]; for (var i = 1; i < arr.length; i++) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } dir = dir + l + arr[i]; } }; /** * 获取Vue文件目录 * @param {Array} routes 路由列表 * @returns {object} 返回路径信息 */ Drive.prototype._getVueDirInfo = function (routes) { for (var i = 0; i < routes.length; i++) { var f = routes[i].component; if (f) { var p = f.dirname().replace(/\\/g, '/'); var fl = f.replace('/', '').replace('/', '\\plugin\\').replace('/', '\\static\\'); var dir = ('./app/' + fl).fullname().dirname(); return { p, dir }; } } return { p: '', dir: '' }; }; /** * 创建Vue文件 * @param {object} route 路由配置 * @param {string} p 原始路径 * @param {string} dir 目标目录 * @param {boolean} cover 是否覆盖 */ Drive.prototype._createVueFile = async function (route, p, dir, cover) { var f = route.component; if (!f) return; var file = f.replace(p, dir).fullname(); if (cover || !file.hasFile()) { this.mkdir(file); await this.createVue(file, route); } }; /** * 更新路由vue文件 * @param {string} route_path 路由路径 * @param {boolean} cover 是否覆盖文件 */ Drive.prototype.updateVue = async function (route_path, cover) { var lt = this.config.routes; var { p, dir } = this._getVueDirInfo(lt); if (!dir) return; for (var i = 0; i < lt.length; i++) { if (route_path && lt[i].path.indexOf(route_path) === -1) continue; await this._createVueFile(lt[i], p, dir, cover); } }; /** * 获取模型 * @param {string} type 模型类型 * @returns {object} 返回获取到的模型 */ Drive.prototype.getModel = function (type) { let model = { ...this.config }; let dir = this.getDir(); let l = $.slash; let app_name = dir.between('app' + l, l); let plugin_name = dir.between('plugin' + l, l); let name = dir.basename(); model.app = app_name; model.plugin = plugin_name; model.name = model.name || name; return model; }; module.exports = Drive;