mm_os
Version:
MM_OS服务端架构,用于快速构建应用程序,支持网站建设、小程序后台、AI应用、物联网(IOT/AIOT)、游戏服务端等多种场景。
702 lines (650 loc) • 17.3 kB
JavaScript
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;