/** * 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); });