162 lines
6.9 KiB
JavaScript
162 lines
6.9 KiB
JavaScript
/**
|
||
* YwxApp 路由模块(ThinkPHP 风格 URL 生成)
|
||
* ------------------------------------------------------------------
|
||
* 通过后端模板注入的全局 layui.app 获取部署配置(root / module /
|
||
* routeBase / controller / action / assetUrl),对外提供 path / asset /
|
||
* api / route 四个方法,供任意 layui 模块以模块化方式调用:
|
||
*
|
||
* layui.use(['route'], function () {
|
||
* var route = layui.route;
|
||
* route.route('edit', 'user', null, { id: 1 }); // -> /admin/user/edit?id=1
|
||
* route.asset('ywxapp/css/style.css');
|
||
* });
|
||
*
|
||
* 同时本模块会把方法挂回 layui.app,保持对旧代码的向后兼容
|
||
* (旧的 layui.app.route(...) 仍可正常工作)。
|
||
*/
|
||
layui.define(function (exports) {
|
||
// 基础配置来自后端模板注入的全局 layui.app
|
||
// 注意:必须在每次调用时实时读取 layui.app,不能在此处缓存。
|
||
// 因为本模块(route.js)作为 layui 模块被异步加载,若此时 layui.app
|
||
// 尚未被 layout.html 的同步脚本赋值,缓存到的就是空对象,导致后续
|
||
// route() 生成的 URL 缺少应用前缀(如 /index/menu 而非 /admin/index/menu)。
|
||
var getConf = function () {
|
||
return (typeof layui !== 'undefined' && layui.app) || window.layui.app || {};
|
||
};
|
||
|
||
var trim = function (s) {
|
||
return String(s).replace(/(^\/+|\/+$)/g, '');
|
||
};
|
||
|
||
/**
|
||
* 页面路径拼接(ThinkPHP 风格:/模块/控制器/方法)
|
||
* 控制器、模块参数均可选,模块缺省使用当前模块(即后端注入的虚拟名):
|
||
* route.path('index') -> /<当前模块>/index
|
||
* route.path('index', 'user') -> /<当前模块>/user/index
|
||
* route.path('index', 'user', 'admin') -> /admin/user/index
|
||
*/
|
||
var path = function (method, controller, module) {
|
||
var conf = getConf();
|
||
var cfgRoot = conf.root || '/';
|
||
var cfgModule = conf.module || '';
|
||
var r = String(cfgRoot || '/').replace(/\/+$/, '');
|
||
if (module === undefined || module === null || module === '') {
|
||
module = cfgModule;
|
||
}
|
||
var parts = [];
|
||
if (module) parts.push(trim(module));
|
||
if (controller) parts.push(trim(controller));
|
||
if (method) parts.push(trim(method));
|
||
if (parts.length === 0) return r + '/';
|
||
return r + '/' + parts.join('/');
|
||
};
|
||
|
||
/**
|
||
* 静态资源路径拼接
|
||
* route.asset('ywxapp/css/style.css') -> /assets/ywxapp/css/style.css
|
||
*/
|
||
var asset = function (p) {
|
||
var conf = getConf();
|
||
var base = String(conf.assetUrl || '/assets').replace(/\/+$/, '');
|
||
p = String(p || '').replace(/^\/+/, '');
|
||
return base + '/' + p;
|
||
};
|
||
|
||
/**
|
||
* API 路径拼接,同 path()(控制器、模块均可选,模块缺省为当前模块)
|
||
* route.api('save') -> /<当前模块>/save
|
||
* route.api('save', 'user') -> /<当前模块>/user/save
|
||
* route.api('save', 'user', 'admin')-> /admin/user/save
|
||
*/
|
||
var api = function (method, controller, module) {
|
||
return path(method, controller, module);
|
||
};
|
||
|
||
/**
|
||
* 后端 Route::buildUrl() 的 JS 版:与后端路由规则完全一致
|
||
* 签名同 path():route(方法, 控制器, 模块),控制器/模块可选(缺省用当前模块)。
|
||
* 额外支持参数与锚点:route('edit','user', null, {id:5}, 'top')
|
||
* -> /admin/user/edit?id=5#top
|
||
*/
|
||
var route = function (method, controller, module, vars, anchor) {
|
||
// 实时读取后端注入的 layui.app(避免模块异步加载时缓存到空的 layui.app)
|
||
var conf = getConf();
|
||
var cfgRoot = conf.root || '/';
|
||
var cfgModule = conf.module || '';
|
||
var cfgRouteBase = conf.routeBase || '';
|
||
var cfgController = conf.controller || '';
|
||
var cfgAction = conf.action || '';
|
||
// 基准 URL:优先用后端注入的 cfgRouteBase(形如 /admin/{__CTRL__}/{__ACT__})。
|
||
// fallback(后端未注入时)基于 root+module 拼带占位符的合法基准,
|
||
// 绝不再用 path('{__CTRL__}','{__ACT__}') —— 那会产出裸 /{__CTRL__}/{__ACT__}。
|
||
var base;
|
||
if (cfgRouteBase) {
|
||
base = String(cfgRouteBase);
|
||
// 防御:若后端注入的 routeBase 不含占位符(异常),补上后缀,避免漏替换
|
||
if (base.indexOf('{__CTRL__}') === -1) {
|
||
base = base.replace(/\/+$/, '') + '/{__CTRL__}/{__ACT__}';
|
||
}
|
||
} else {
|
||
base = String(cfgRoot || '/').replace(/\/+$/, '')
|
||
+ (cfgModule ? '/' + trim(cfgModule) : '')
|
||
+ '/{__CTRL__}/{__ACT__}';
|
||
}
|
||
if (module === undefined || module === null || module === '') {
|
||
module = cfgModule;
|
||
}
|
||
// 未显式传 controller 时,优先用后端注入的 cfgController,再兜底默认 'index'
|
||
// (绝不再裸用字面量 '{__CTRL__}',否则会拼出 /{__CTRL__}/xxx 这类非法 URL)
|
||
var ctrl = controller ? trim(controller) : (cfgController || 'index');
|
||
var act = method ? trim(method) : (cfgAction || 'index');
|
||
// 若显式传了 module,则替换掉基准里的应用前缀
|
||
if (module) {
|
||
base = base.replace(/\/[^\/]+\/\{__CTRL__\}/, '/' + trim(module) + '/{__CTRL__}');
|
||
}
|
||
var url = base
|
||
.replace('{__CTRL__}', ctrl)
|
||
.replace('{__ACT__}', act);
|
||
// 拼装查询参数
|
||
if (vars && typeof vars === 'object') {
|
||
var q = [];
|
||
for (var k in vars) {
|
||
if (Object.prototype.hasOwnProperty.call(vars, k) && vars[k] !== '' && vars[k] !== null && vars[k] !== undefined) {
|
||
q.push(encodeURIComponent(k) + '=' + encodeURIComponent(vars[k]));
|
||
}
|
||
}
|
||
if (q.length) url += (url.indexOf('?') >= 0 ? '&' : '?') + q.join('&');
|
||
}
|
||
if (anchor) url += '#' + String(anchor).replace(/^#/, '');
|
||
return url;
|
||
};
|
||
|
||
var routeModule = {
|
||
// 配置快照(只读,便于调试)—— 实时读取 layui.app
|
||
config: function () {
|
||
var c = getConf();
|
||
return {
|
||
root: c.root || '/',
|
||
assetUrl: c.assetUrl || '/assets',
|
||
module: c.module || '',
|
||
routeBase: c.routeBase || '',
|
||
controller: c.controller || '',
|
||
action: c.action || ''
|
||
};
|
||
},
|
||
path: path,
|
||
asset: asset,
|
||
api: api,
|
||
route: route
|
||
};
|
||
|
||
// 向后兼容:把方法挂回全局 layui.app,旧代码 layui.app.route() 继续可用
|
||
if (layui.app) {
|
||
layui.app.path = path;
|
||
layui.app.asset = asset;
|
||
layui.app.api = api;
|
||
layui.app.route = route;
|
||
layui.app.config = routeModule.config;
|
||
}
|
||
|
||
exports('route', routeModule);
|
||
});
|