diff --git a/CHANGELOG.md b/CHANGELOG.md index c161308..325f3c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +* 0.2.1 + - 优化了样式 + - 增加了 `Sidebar` 的默认处理 + - 增加了 NMP 权限模块的支持 + - 增加了列表页的局部刷新功能和 `AuthPanel` 控件 + - 补充 `system/user` 模块的接口 + - 修复 `TableTip` 的一些问题 + - `navigator` 升级,修复子 Tab 的权限问题导致的跳转错误 + - 系统初始化时增加自定义接口的读取 + * 0.2.0 - 修复 merge 错误 - 跳升版本 diff --git a/package.json b/package.json index 19b7831..57d633c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bat-ria", - "version": "0.2.0", + "version": "0.2.1", "description": "RIA extension for Brand Ads Team", "main": "main.js", "scripts": { diff --git a/src/css/common.less b/src/css/common.less index a936698..c373347 100644 --- a/src/css/common.less +++ b/src/css/common.less @@ -4,20 +4,23 @@ // Basic status @bat-success-color: #008000; -@bat-warning-color: #FF9A00; -@bat-alert-color: #FF0000; -@bat-aux-color: #9C9A9C; +@bat-warning-color: #ff9a00; +@bat-alert-color: #ff0000; +@bat-aux-color: #9c9a9c; -// Link colors -@bat-link-color: #3377AA; -@bat-link-hover-color: #FF6600; +// link colors +@bat-link-color: #3377aa; +@bat-link-hover-color: #ff6600; -// Background & border color -@bat-border-blue: #BAD5F1; -@bat-border-light-blue: #DBEBFA; -@bat-bg-dark-blue: #B6D2F1; -@bat-bg-blue: #E7F1FB; -@bat-bg-light-blue: #F1F6FC; +// background & border color +@bat-border-blue: #bad5f1; +@bat-border-light-blue: #dbebfa; +@bat-bg-dark-blue: #b6d2f1; +@bat-bg-blue: #e7f1fb; +@bat-bg-light-blue: #f1f6fc; + +// text colors +@bat-text-blue: #0c3ca0; /** @@ -221,11 +224,23 @@ hr { &.nav-sub-current { display: block; } + + & > .nav-sub-separate:last-child { + display: none; + } } @sys-userinfo-color: #FFFFFF; .user-info { - color: darken(@sys-userinfo-color, 10); + color: darken(@sys-userinfo-color, 20); + + span { + color: #FFF; + } + + a:hover { + text-decoration: underline; + } } /** @@ -234,3 +249,34 @@ hr { .copyright { text-align: center; } + + +/** + * Forbidden / Not Found + */ +.forbidden-message, +.not-found-message { + width: 540px; + height: 131px; + margin: 100px auto; + padding-left: 110px; + font-size: 14px; + .clearfix(); + + .message-text { + margin-top: 10px; + margin-bottom: 10px; + .font-family(heading); + font-size: 32px; + line-height: 48px; + color: @bat-text-blue; + } +} + +.forbidden-message { + background: url(../img/coupui.png) no-repeat 0 -265px; +} + +.not-found-message { + background: url(../img/coupui.png) no-repeat 0 -133px; +} diff --git a/src/css/form.less b/src/css/form.less index ee5125e..02a5d9a 100644 --- a/src/css/form.less +++ b/src/css/form.less @@ -6,8 +6,9 @@ .form-header { background-color: @bat-bg-light-blue; padding: 8px 10px; + line-height: 26px; font-weight: 700; - + & + .form-body { border-top: 1px solid @bat-border-blue; } @@ -73,4 +74,4 @@ .form-segment + & { padding-left: 0; } -} \ No newline at end of file +} diff --git a/src/css/list.less b/src/css/list.less index aea433c..953b754 100644 --- a/src/css/list.less +++ b/src/css/list.less @@ -5,6 +5,11 @@ .list-header { border-bottom: none; + + h3 { + line-height: 26px; + font-weight: 700; + } } /* 列表头部 > 摘要数据区域 */ @@ -59,10 +64,27 @@ /* 列表头部 > 列表操作列区域 */ .list-operation { + position: relative; + margin-left: 12px; .bat-linkify(); + + &:before { + content: "|"; + display: inline; + position: absolute; + left: -8px; + color: @bat-aux-color; + } + + &:first-child { + margin-left: 0; + &:before { + display: none; + } + } } .list-operation-separator { margin: 0 5px; color: @bat-aux-color; -} \ No newline at end of file +} diff --git a/src/css/main.less b/src/css/main.less index f6fb0b9..b58903b 100644 --- a/src/css/main.less +++ b/src/css/main.less @@ -1,7 +1,6 @@ @import "./mixins.less"; @import "./common.less"; +@import "../ui/css/ui.less"; @import "./ui.less"; @import "./list.less"; @import "./form.less"; - -@import "../ui/css/ui.less"; \ No newline at end of file diff --git a/src/css/mixins.less b/src/css/mixins.less index c2ab4ce..ef2c971 100644 --- a/src/css/mixins.less +++ b/src/css/mixins.less @@ -8,4 +8,9 @@ &:hover { color: @bat-link-hover-color; } -} \ No newline at end of file +} + +.bat-disabled() { + color: @bat-aux-color; + cursor: not-allowed; +} diff --git a/src/css/ui.less b/src/css/ui.less index 8b0be66..fcf7009 100644 --- a/src/css/ui.less +++ b/src/css/ui.less @@ -53,10 +53,12 @@ } } +// esui/Pager .ui-pager-main li { font-family: inherit; } +// esui/Table .ui-table-cell-editable { .ellipsis(); } @@ -74,6 +76,7 @@ margin: 0 auto; } +// esui/Dialog .ui-dialog-title { font-size: 12px; } @@ -94,6 +97,7 @@ padding: 10px 0 10px 6px } +// esui/Button .skin-spring-button { height: 26px; line-height: 26px; @@ -112,6 +116,15 @@ button.skin-link-button:hover { height: 26px; } +.skin-spring-button-disabled { + &, + &:hover, + &:active { + .linear-gradient-background(top, #a9d796, #a9d796); + border-color: #8ac971; + } +} + .skin-spring-add-disabled { &, &:hover, @@ -133,20 +146,12 @@ button.skin-link-button:hover { top, #f6f6f6, #FFF, "../img/esui-btn-download.png", 9px, center, #f6f6f6); } +// esui/RangeCalendar .ui-rangecalendar { .linear-gradient-background (top, #ffffff, #f6f6f6, #ffffff); } -.ui-uploader { - display: inline-block; - *width: 150px; - white-space: normal; -} - -.ui-uploader-input-container { - *float: left; -} - +// esui/BoxGroup .ui-checkbox, .ui-radio, .ui-boxgroup-checkbox, @@ -156,6 +161,7 @@ button.skin-link-button:hover { } } +// esui/Textbox .ui-textbox { &, & input, @@ -164,6 +170,7 @@ button.skin-link-button:hover { } } +// esui/Tab .ui-tab-horizontal { .ui-tab-navigator { .clearfix(); @@ -190,6 +197,7 @@ button.skin-link-button:hover { } } +// esui/Toast .ui-toast-content { padding: 0 10px; font-size: 12px; @@ -215,6 +223,7 @@ button.skin-link-button:hover { } } +// esui/Select .ui-select-validity-label-invalid { display: inline-block; margin: 0; @@ -224,6 +233,7 @@ button.skin-link-button:hover { color: #C00; } +// esui/Wizard .skin-default-wizard { .ui-wizard-node { padding-right: 8px; @@ -267,5 +277,57 @@ button.skin-link-button:hover { .ui-wizard-node-done span { background-color: @bat-bg-blue; - } -} \ No newline at end of file + } +} + +// esui/Crumb +.ui-crumb-node-last { + font-weight: 700; +} + + +// bat-ria/Uploader +.ui-uploader { + display: inline-block; + *width: 150px; + white-space: normal; +} + +.ui-uploader-input-container { + *float: left; +} + +// bat-ria/RichSelector inside esui/Sidebar +.ui-sidebar { + .ui-richselector-empty-text { + padding: 0 10px; + line-height: 30px; + } + + .ui-tree-richselector { + + .ui-tree-node-indicator-level-0 { + display: none; + } + + .ui-richselector-query-list { + .ui-tree-item-content { + color: @bat-link-color; + } + + .ui-tree-content-wrapper-selected { + .ui-tree-item-content { + color: @bat-link-color; + font-weight: 700; + } + } + } + + .ui-richselector-query-list { + .ui-tree-content-wrapper-selected, + .ui-tree-content-wrapper:hover { + background: @bat-bg-light-blue; + } + } + } +} diff --git a/src/extension/hooks.js b/src/extension/hooks.js index 2169215..f00e37a 100644 --- a/src/extension/hooks.js +++ b/src/extension/hooks.js @@ -10,9 +10,7 @@ define(function (require) { function getAderArgMap() { var user = require('../system/user'); - var aderId = user.ader && user.ader.id - || URI.parseQuery(document.location.search).aderId - || user.visitor && user.visitor.id; + var aderId = user.getAderId(); return aderId ? { aderId: aderId } : {}; } diff --git a/src/extension/ui.js b/src/extension/ui.js index 8d8fb5c..c213a8a 100644 --- a/src/extension/ui.js +++ b/src/extension/ui.js @@ -10,6 +10,7 @@ define( function (require) { var u = require('underscore'); var lib = require('esui/lib'); + var Control = require('esui/Control'); /** * 加载并配置验证规则 @@ -18,8 +19,10 @@ define( */ function initializeValidationRules() { // 加载所有验证规则 - var MaxLengthRule = require('esui/validator/MaxLengthRule'); - var MinLengthRule = require('esui/validator/MinLengthRule'); + require('esui/validator/MaxByteLengthRule'); + require('esui/validator/MinByteLengthRule'); + require('esui/validator/MaxLengthRule'); + require('esui/validator/MinLengthRule'); var RequiredRule = require('esui/validator/RequiredRule'); var PatternRule = require('esui/validator/PatternRule'); var MaxRule = require('esui/validator/MaxRule'); @@ -67,68 +70,32 @@ define( } var Rule = require('esui/validator/Rule'); + var defaultGetErrorMessage = Rule.prototype.getErrorMessage; - MaxLengthRule.prototype.getErrorMessage = function (control) { - if (control.get('maxErrorMessage')) { - var getErrorMessage = Rule.prototype.getErrorMessage; - getErrorMessage.apply(this, arguments); + function getErrorMessage(control) { + if (control.get(this.type + 'ErrorMessage')) { + return defaultGetErrorMessage.apply(this, arguments); } var rangeErrorMessage = getRangeErrorMessage(control); if (rangeErrorMessage) { return rangeErrorMessage; } - return Rule.prototype.getErrorMessage.apply(this, arguments); - }; - MinLengthRule.prototype.getErrorMessage = function (control) { - if (control.get('maxErrorMessage')) { - var getErrorMessage = Rule.prototype.getErrorMessage; - getErrorMessage.apply(this, arguments); - } - var rangeErrorMessage = getRangeErrorMessage(control); - if (rangeErrorMessage) { - return rangeErrorMessage; - } - return Rule.prototype.getErrorMessage.apply(this, arguments); - }; - - MaxRule.prototype.getErrorMessage = function (control) { - if (control.get('maxErrorMessage')) { - var getErrorMessage = Rule.prototype.getErrorMessage; - getErrorMessage.apply(this, arguments); - } - var rangeErrorMessage = getRangeErrorMessage(control); - if (rangeErrorMessage) { - return rangeErrorMessage; - } - return Rule.prototype.getErrorMessage.apply(this, arguments); - }; - - MinRule.prototype.getErrorMessage = function (control) { - if (control.get('maxErrorMessage')) { - var getErrorMessage = Rule.prototype.getErrorMessage; - getErrorMessage.apply(this, arguments); - } - var rangeErrorMessage = getRangeErrorMessage(control); - if (rangeErrorMessage) { - return rangeErrorMessage; - } - return Rule.prototype.getErrorMessage.apply(this, arguments); - }; + return defaultGetErrorMessage.apply(this, arguments); + } + MaxRule.prototype.getErrorMessage = getErrorMessage; + MinRule.prototype.getErrorMessage = getErrorMessage; PatternRule.prototype.getErrorMessage = function (control) { var pattern = control.get('pattern') + ''; - if (control.get('patternErrorMessage') - || !NUMBER_REGEX.hasOwnProperty(pattern) - ) { - var getErrorMessage = Rule.prototype.getErrorMessage; - getErrorMessage.apply(this, arguments); + if (control.get('patternErrorMessage') || !NUMBER_REGEX.hasOwnProperty(pattern)) { + return defaultGetErrorMessage.apply(this, arguments); } var rangeErrorMessage = getRangeErrorMessage(control); if (rangeErrorMessage) { return rangeErrorMessage; } - return Rule.prototype.getErrorMessage.apply(this, arguments); + return defaultGetErrorMessage.apply(this, arguments); }; } @@ -179,7 +146,7 @@ define( function initializeGlobalExtensions() { var ui = require('esui'); var globalExtensions = [ - { type: 'CustomData', options: {} } + // { type: 'CustomData', options: {} } ]; u.each(globalExtensions, function (extension) { @@ -264,12 +231,47 @@ define( }; } + function addTreeNodeTitle() { + var Tree = require('esui/Tree'); + + Tree.prototype.itemTemplate = '${text}'; + } + + function fixSidebarHide() { + var Sidebar = require('esui/Sidebar'); + + /** + * 隐藏控件 + * + * @return {boolean} + */ + Sidebar.prototype.hide = function () { + Control.prototype.hide.call(this); + + var mat = lib.g(this.helper.getId('mat')); + if (mat) { + mat.style.display = 'none'; + } + + // 隐藏主区域 + this.main.style.display = 'none'; + + // minibar + var miniBar = lib.g(this.helper.getId('minibar')); + if (miniBar) { + miniBar.style.display = 'none'; + } + }; + } + function activate() { initializeValidationRules(); addControlLinkMode(); initializeGlobalExtensions(); addRegionExtension(); addCrumbGlobalRedirect(); + addTreeNodeTitle(); + fixSidebarHide(); } return { diff --git a/src/img/coupui.png b/src/img/coupui.png new file mode 100644 index 0000000..60c1e3f Binary files /dev/null and b/src/img/coupui.png differ diff --git a/src/io/serverIO.js b/src/io/serverIO.js index 3f135c6..614803d 100644 --- a/src/io/serverIO.js +++ b/src/io/serverIO.js @@ -81,7 +81,8 @@ define(function (require) { if (status === 'SUCCESS') { return { success: true, - result: data.result + message: data.message, + result: data.result || data.page }; } else { @@ -207,7 +208,8 @@ define(function (require) { var defaults = { url: url, data: data, - dataType: 'json' + dataType: 'json', + charset: 'utf-8' }; options = options diff --git a/src/main.js b/src/main.js index e95951a..7cc84f4 100644 --- a/src/main.js +++ b/src/main.js @@ -9,6 +9,7 @@ define( var config = {}; var u = require('underscore'); var util = require('./util'); + var Deferred = require('er/Deferred'); /** * 初始化API请求器 @@ -28,14 +29,27 @@ define( /** * 初始化系统启动 * + * @param {Array} [extra] 额外的请求发送器 + * * @ignore */ - function loadData() { - var Deferred = require('er/Deferred'); - - return Deferred.all( - Deferred.when(config.api.user()), - Deferred.when(config.api.constants()) + function loadData(extra) { + extra = extra ? u.map(extra, function (api) { + if (typeof api === 'string') { + return util.genRequesters(api); + } + else { + return api; + } + }) : []; + + var requests = [ config.api.user, config.api.constants ].concat(extra || []); + + return Deferred.all.apply( + Deferred, + u.map(requests, function (requester) { + return Deferred.when(requester()); + }) ); } @@ -53,6 +67,15 @@ define( var consts = require('./system/constants'); var localConstants = require('common/constants'); consts.init(u.extend(localConstants, constants)); + + // 返回其余请求结果 + var extra = [].slice.call(arguments).slice(2); + return Deferred.all.apply( + Deferred, + u.map(extra, function (result) { + return Deferred.resolved(result); + }) + ); } /** @@ -71,9 +94,12 @@ define( /** * RIA启动入口 * + * @param {Object} riaConfig RIA配置 + * @param {Array} requesters 初始化数据需要的请求发送器 + * @param {Function} callback 初始化请求返回后的回调函数 * @ignore */ - function start(riaConfig) { + function start(riaConfig, requesters, callback) { config = riaConfig; @@ -95,8 +121,9 @@ define( initApiConfig(); // 读取必要信息后初始化系统 - return loadData() + return loadData(requesters) .then(initData) + .then(callback) .then(init); } diff --git a/src/mvc/BaseModel.js b/src/mvc/BaseModel.js index b73af58..794152f 100644 --- a/src/mvc/BaseModel.js +++ b/src/mvc/BaseModel.js @@ -22,8 +22,8 @@ define(function (require) { util.inherits(BaseModel, UIModel); /** - * 合并默认数据源 - */ + * 合并默认数据源 + */ BaseModel.prototype.mergeDefaultDatasource = function() { if (!this.datasource) { this.datasource = this.defaultDatasource; diff --git a/src/mvc/BaseView.js b/src/mvc/BaseView.js index ee72b41..1f7ef4d 100644 --- a/src/mvc/BaseView.js +++ b/src/mvc/BaseView.js @@ -189,7 +189,7 @@ define(function (require) { * 2. showDialog(dialog, options) */ BaseView.prototype.showDialog = function (dialog, options) { - if (!dialog instanceof Dialog) { + if (!(dialog instanceof Dialog)) { options = dialog; dialog = this.popDialog.call(this, options); } @@ -289,7 +289,7 @@ define(function (require) { */ BaseView.prototype.waitActionDialog = function () { var dialog = this.popActionDialog.apply(this, arguments); - + var deferred = new Deferred(); dialog.on('actionloaded', deferred.resolver.resolve); @@ -299,5 +299,15 @@ define(function (require) { return deferred.promise; }; + /** + * 刷新权限设置,在Action加载过新内容时使用 + */ + BaseView.prototype.refreshAuth = function () { + var authPanel = this.get('authPanel'); + if (authPanel) { + authPanel.initAuth(); + } + }; + return BaseView; }); diff --git a/src/mvc/ForbiddenAction.js b/src/mvc/ForbiddenAction.js new file mode 100644 index 0000000..5fb1db2 --- /dev/null +++ b/src/mvc/ForbiddenAction.js @@ -0,0 +1,27 @@ +/** + * @file 403页 Action + * @author Justineo(justice360@gmail.com) + */ + +define(function (require) { + var util = require('er/util'); + var Action = require('er/Action'); + + /** + * 403页 Action + * + * @extends er/Action + * @constructor + */ + function ForbiddenAction() { + Action.apply(this, arguments); + } + + util.inherits(ForbiddenAction, Action); + + ForbiddenAction.prototype.modelType = require('./ForbiddenModel'); + ForbiddenAction.prototype.viewType = require('./ForbiddenView'); + + return ForbiddenAction; +}); + diff --git a/src/mvc/ForbiddenModel.js b/src/mvc/ForbiddenModel.js new file mode 100644 index 0000000..8b22e93 --- /dev/null +++ b/src/mvc/ForbiddenModel.js @@ -0,0 +1,24 @@ +/** + * @file 403页 Model + * @author Justineo(justice360@gmail.com) + */ + +define(function (require) { + var util = require('er/util'); + var Model = require('er/Model'); + + /** + * 403页 Model + * + * @param {Object=} context 初始化时的数据 + * + * @constructor + * @extends er/Model + */ + function ForbiddenModel(context) { + Model.call(this, context); + } + util.inherits(ForbiddenModel, Model); + + return ForbiddenModel; +}); diff --git a/src/mvc/ForbiddenView.js b/src/mvc/ForbiddenView.js new file mode 100644 index 0000000..c93bf8e --- /dev/null +++ b/src/mvc/ForbiddenView.js @@ -0,0 +1,31 @@ +/** + * @file 403页 View + * @author Justineo(justice360@gmail.com) + */ + +define(function (require) { + // require template + require('../tpl!../tpl/forbidden.tpl.html'); + + var util = require('er/util'); + var View = require('er/View'); + + /** + * 403页 View + * + * @extends er.View + * @constructor + */ + function ForbiddenView() { + View.apply(this, arguments); + } + + util.inherits(ForbiddenView, View); + + /** + * @inheritDoc + */ + ForbiddenView.prototype.template = 'TPL_forbidden'; + + return ForbiddenView; +}); diff --git a/src/mvc/FormAction.js b/src/mvc/FormAction.js index 681e8be..8f89df1 100644 --- a/src/mvc/FormAction.js +++ b/src/mvc/FormAction.js @@ -219,7 +219,7 @@ define(function (require) { * @return {meta.Promise} */ FormAction.prototype.submit = function (submitData) { - var handleBeforeSubmit = this.fire('beforesubmit'); + var handleBeforeSubmit = this.fire('beforesubmit', { submitData: submitData }); if (!handleBeforeSubmit.isDefaultPrevented()) { try { var submitRequester = this.model.submitRequester; @@ -286,7 +286,7 @@ define(function (require) { * beforeValidate -> * validate -> * afterValidate -> - * sumbmit -> + * submit -> * enableSubmit * * 可针对业务需求扩展beforeValidate、afterValidate @@ -299,9 +299,9 @@ define(function (require) { require('er/Deferred') .when(this.beforeValidate(submitData)) - .then(u.bind(u.partial(this.validate, submitData), this)) - .then(u.bind(u.partial(this.afterValidate, submitData), this)) - .then(u.bind(u.partial(this.submit, submitData), this)) + .then(u.bind(this.validate, this, submitData)) + .then(u.bind(this.afterValidate, this, submitData)) + .then(u.bind(this.submit, this, submitData)) .ensure(u.bind(this.view.enableSubmit, this.view)); }; diff --git a/src/mvc/FormView.js b/src/mvc/FormView.js index b2076fa..58a0ce3 100644 --- a/src/mvc/FormView.js +++ b/src/mvc/FormView.js @@ -172,7 +172,7 @@ define(function (require) { function scrollTo(element) { var offset = lib.getOffset(element); if (lib.page.getScrollTop() > offset.top) { - document.body.scrollTop = document.documentElement.scrollTop = offset.top - 10; + element.scrollIntoView(true); } } @@ -186,7 +186,7 @@ define(function (require) { FormView.prototype.handleValidateInvalid = function () { var me = this; var form = this.get('form'); - u.some(form.getInputControls(), function (input, index) { + u.some(form.getInputControls(), function (input) { if (input.hasState('validity-invalid')) { var e = me.fire('scrolltofirsterror', { firstErrValidity: input }); if (!e.isDefaultPrevented()) { diff --git a/src/mvc/ListAction.js b/src/mvc/ListAction.js index fa99444..8ff62ed 100644 --- a/src/mvc/ListAction.js +++ b/src/mvc/ListAction.js @@ -21,6 +21,14 @@ define(function (require) { util.inherits(ListAction, BaseAction); + + /** + * 在搜索、翻页等操作后选择触发跳转还是仅刷新列表 + * + * @type {boolean} + */ + ListAction.prototype.redirectAfterChange = true; + /** * 进行查询 * @@ -31,8 +39,8 @@ define(function (require) { var defaultArgs = this.model.getDefaultArgs(); var extraArgs = this.model.getExtraQuery(); args = u.chain(args) - .purify(defaultArgs) .extend(extraArgs) + .purify(defaultArgs) .value(); var event = this.fire('search', { args: args }); @@ -49,17 +57,17 @@ define(function (require) { ListAction.prototype.redirectForSearch = function (args) { var path = this.model.get('url').getPath(); var url = URL.withQuery(path, args); - this.redirect(url, { force: true }); + this.loadList(url); }; /** * 获取指定页码的跳转URL * * @param {number} pageNo 指定的页码 - * @return {string} + * @return {er/URL} 生成的分页URL对象 */ ListAction.prototype.getURLForPage = function (pageNo) { - var url = this.context.url; + var url = this.model.get('url'); var path = url.getPath(); var query = url.getQuery(); query.pageNo = pageNo; @@ -69,7 +77,7 @@ define(function (require) { query = u.omit(query, 'pageNo'); } - return require('er/URL').withQuery(path, query).toString(); + return require('er/URL').withQuery(path, query); }; /** @@ -93,10 +101,31 @@ define(function (require) { var event = this.fire('pagechange', { page: e.page }); if (!event.isDefaultPrevented()) { var url = this.getURLForPage(e.page); - this.redirect(url); + this.loadList(url); } } + /** + * 根据新的URL参数刷新列表 + * + * @param {er/URL} [url] 新的URL对象,没有时按当前URL刷新 + * @return {er/Promise=} 返回请求的Promise对象 + */ + ListAction.prototype.loadList = function (url) { + if (this.redirectAfterChange) { + this.redirect(url, { force: true }); + } + else { + var me = this; + url = url || me.model.get('url'); + + return me.model.loadData(url).then(function () { + me.redirect(url, { silent: true }); + me.view.refresh(); + }); + } + }; + /** * 初始化交互行为 * @@ -106,16 +135,45 @@ define(function (require) { ListAction.prototype.initBehavior = function () { BaseAction.prototype.initBehavior.apply(this, arguments); this.view.on('search', search, this); - this.view.on('pagesizechange', search, this); this.view.on('pagechange', forwardToPage, this); }; + /** + * 初始化交互行为 + * + * @protected + * @override + */ + ListAction.prototype.reload = function () { + if (this.redirectAfterChange) { + BaseAction.prototype.reload.call(this); + } + else { + this.loadList(); + } + }; + /** * 根据布局变化重新调整自身布局 */ ListAction.prototype.adjustLayout = function () { this.view.adjustLayout(); }; - + + // /** + // * @inheritDoc + // * + // * @protected + // * @override + // */ + // ListAction.prototype.filterRedirect = function (url) { + // if (url.getPath() !== this.model.get('url').getPath() + // || this.redirectAfterChange) { + // return true; + // } + // this.loadList(url); + // return false; + // }; + return ListAction; }); diff --git a/src/mvc/ListModel.js b/src/mvc/ListModel.js index 97a4147..91bbcea 100644 --- a/src/mvc/ListModel.js +++ b/src/mvc/ListModel.js @@ -32,9 +32,9 @@ define(function (require) { /** * 配置默认查询参数 - * + * * 如果某个参数与这里的值相同,则不会加到URL中 - * + * * 创建`Model`时,如果某个参数不存在,则会自动补上这里的值 * * @type {Object} @@ -54,6 +54,20 @@ define(function (require) { return u.defaults(this.defaultArgs || {}, { pageNo: 1 }); }; + /** + * 转换列表数据 + * 将返回值中的`result`字段改为`tableData`来放到Model中 + * + * @param {Object} data 列表请求接口返回的数据 + * @return {Object} 转换完毕的数据 + */ + function adaptData(data) { + var page = data; + page.tableData = page.result; + delete page.result; + return page; + } + /** * @inheritDoc */ @@ -61,12 +75,7 @@ define(function (require) { listPage: { retrieve: function (model) { return model.listRequester(model.getQuery()) - .then(function(data) { - var page = data; - page.tableData = page.result; - delete page.result; - return page; - }); + .then(adaptData); }, dump: true }, @@ -122,7 +131,7 @@ define(function (require) { ListModel.prototype.defaultTimeRange = null; /** - * 默认选择的时间范围 + * 生成默认的后端请求参数 * * @return {Object} * @protected @@ -145,17 +154,16 @@ define(function (require) { } // 合并默认参数、附加参数,最后再统一处理一下输出 - query = u.chain(query) + u.chain(query) .defaults(this.getDefaultArgs()) .defaults(range) - .extend(this.getExtraQuery()) - .value(); + .extend(this.getExtraQuery()); - return this.filterQuery(query); + return this.prepareQuery(query); }; /** - * 获取附加的请求参数 + * 获取除列表本身参数外附加的请求参数 * * @return {Object} * @protected @@ -167,13 +175,44 @@ define(function (require) { /** * 对合并好的请求参数进行统一的后续处理 * + * @deprecated since v0.2.1 名字起得不好,后面使用`prepareQuery`替代 * @param {Object} query 需要处理的参数对象 * @return {Object} * @protected */ - ListModel.prototype.filterQuery = function(query) { + ListModel.prototype.filterQuery = function (query) { return query; }; + /** + * 对合并好的请求参数进行统一的后续处理 + * + * @param {Object} query 需要处理的参数对象 + * @return {Object} + * @protected + */ + ListModel.prototype.prepareQuery = function (query) { + return this.filterQuery(query); + }; + + /** + * 重新读取列表数据 + * + * @param {er/URL} url 根据指定URL读取数据 + * @return {er/Promise} 返回异步请求的Promise对象 + * @protected + */ + ListModel.prototype.loadData = function (url) { + var me = this; + var urlQuery = url.getQuery(); + me.set('url', url); + me.fill(urlQuery); + + return me.listRequester(me.getQuery()) + .then(function(data) { + me.fill(adaptData(data)); + }); + }; + return ListModel; }); diff --git a/src/mvc/ListView.js b/src/mvc/ListView.js index 8dfb29b..b2aa3f2 100644 --- a/src/mvc/ListView.js +++ b/src/mvc/ListView.js @@ -10,7 +10,7 @@ define(function (require) { var BaseView = require('./BaseView'); var u = require('underscore'); var moment = require('moment'); - + /** * 列表`View`基类 * @@ -20,7 +20,7 @@ define(function (require) { function ListView() { BaseView.apply(this, arguments); } - + /** * @inheritDoc */ @@ -45,15 +45,11 @@ define(function (require) { args.order = e.order; } - // 总是带上每页显示数 - args.pageSize = this.get('pager').get('pageSize'); - - this.fire('search', { args: args }); }; /** - * 获取查询参数,默认是取`filter`表单的所有数据,加上表格的排序字段 + * 获取查询参数,默认是取`filter`表单的所有数据,加上表格的排序字段和每页显示条目数 * * @return {Object} */ @@ -61,6 +57,7 @@ define(function (require) { // 获取表单的字段 var form = this.get('filter'); var args = form ? form.getData() : {}; + // 加上原本的排序方向和排序字段名 args.order = this.model.get('order'); args.orderBy = this.model.get('orderBy'); @@ -70,7 +67,7 @@ define(function (require) { // 关键词去空格 args.keyword = u.trim(keyword.getValue()); } - + // 日期是独立的 var range = this.get('range'); if (range) { @@ -78,6 +75,13 @@ define(function (require) { args.startTime = moment(range[0]).format('YYYYMMDDHHmmss'); args.endTime = moment(range[1]).format('YYYYMMDDHHmmss'); } + + // 带上每页显示数 + var pager = this.get('pager'); + if (pager) { + args.pageSize = pager.get('pageSize'); + } + return args; }; @@ -99,6 +103,7 @@ define(function (require) { var items = this.getSelectedItems(); this.getGroup('batch').set('disabled', u.isEmpty(items)); + this.getGroup('batch').set('readOnly', u.isEmpty(items)); }; /** @@ -120,12 +125,36 @@ define(function (require) { function batchModify(e) { var args = { // 批量操作的类型 - type: e.target.getData('type') + action: e.target.getData('type'), + selectedItems: this.get('table').getSelectedItems() }; this.fire('batchmodify', args); } + /** + * 侧边栏模式改变时要调整整体布局 + * + * @param {Object} e 模式切换事件对象 + */ + function sidebarModeChange(e) { + // Sidebar目前只提供了操作DOM的方式更新布局,需要对ESUI做优化后再升级这块 + var neighbor = document.getElementById('neighbor'); + + if (!neighbor) { + return; + } + + if (e.mode === 'fixed') { + neighbor.className = 'ui-sidebar-neighbor'; + } + else { + neighbor.className = 'ui-sidebar-neighbor-hide'; + } + + this.adjustLayout(); + } + /** * @inheritDoc */ @@ -151,11 +180,18 @@ define(function (require) { filter.on('submit', this.submitSearch, this); } + var sidebar = this.get('sidebar'); + if (sidebar) { + sidebar.on('modechange', sidebarModeChange, this); + } + u.each( this.getGroup('batch'), function (button) { - // 批量更新 - button.on('click', batchModify, this); + if (button instanceof require('esui/Button')) { + // 批量更新 + button.on('click', batchModify, this); + } }, this ); @@ -171,6 +207,52 @@ define(function (require) { ListView.prototype.enterDocument = function () { BaseView.prototype.enterDocument.apply(this, arguments); this.updateBatchButtonStatus(); + + this.adjustLayout(); + }; + + /** + * 根据布局变化重新调整自身布局 + */ + ListView.prototype.adjustLayout = function () { + var table = this.get('table'); + if (table) { + table.adjustWidth(); + } + }; + + /** + * 根据Model数据重新渲染页面 + */ + ListView.prototype.refresh = function () { + // 刷新列表 + this.refreshList(); + + // 最后刷新权限显示 + this.refreshAuth(); + }; + + /** + * 根据Model数据重新渲染列表 + */ + ListView.prototype.refreshList = function () { + var model = this.model; + var table = this.get('table'); + if (table) { + table.setDatasource(model.get('tableData')); + } + + var pager = this.get('pager'); + if (pager) { + pager.setProperties( + { + count: model.get('totalCount'), + page: model.get('pageNo'), + pageSize: model.get('pageSize') + }, + { silent: true } + ); + } }; require('er/util').inherits(ListView, BaseView); diff --git a/src/mvc/NotFoundAction.js b/src/mvc/NotFoundAction.js new file mode 100644 index 0000000..50dacd9 --- /dev/null +++ b/src/mvc/NotFoundAction.js @@ -0,0 +1,27 @@ +/** + * @file 404页 Action + * @author Justineo(justice360@gmail.com) + */ + +define(function (require) { + var util = require('er/util'); + var Action = require('er/Action'); + + /** + * 404页 Action + * + * @extends er/Action + * @constructor + */ + function NotFoundAction() { + Action.apply(this, arguments); + } + + util.inherits(NotFoundAction, Action); + + NotFoundAction.prototype.modelType = require('./NotFoundModel'); + NotFoundAction.prototype.viewType = require('./NotFoundView'); + + return NotFoundAction; +}); + diff --git a/src/mvc/NotFoundModel.js b/src/mvc/NotFoundModel.js new file mode 100644 index 0000000..8cce2d1 --- /dev/null +++ b/src/mvc/NotFoundModel.js @@ -0,0 +1,24 @@ +/** + * @file 404页 Model + * @author Justineo(justice360@gmail.com) + */ + +define(function (require) { + var util = require('er/util'); + var Model = require('er/Model'); + + /** + * 404页 Model + * + * @param {Object=} context 初始化时的数据 + * + * @constructor + * @extends er/Model + */ + function NotFoundModel(context) { + Model.call(this, context); + } + util.inherits(NotFoundModel, Model); + + return NotFoundModel; +}); diff --git a/src/mvc/NotFoundView.js b/src/mvc/NotFoundView.js new file mode 100644 index 0000000..45f3fa4 --- /dev/null +++ b/src/mvc/NotFoundView.js @@ -0,0 +1,31 @@ +/** + * @file 404页 View + * @author Justineo(justice360@gmail.com) + */ + +define(function (require) { + // require template + require('../tpl!../tpl/not_found.tpl.html'); + + var util = require('er/util'); + var View = require('er/View'); + + /** + * 404页 View + * + * @extends er.View + * @constructor + */ + function NotFoundView() { + View.apply(this, arguments); + } + + util.inherits(NotFoundView, View); + + /** + * @inheritDoc + */ + NotFoundView.prototype.template = 'TPL_not_found'; + + return NotFoundView; +}); diff --git a/src/system/auth.js b/src/system/auth.js new file mode 100644 index 0000000..3f970fd --- /dev/null +++ b/src/system/auth.js @@ -0,0 +1,205 @@ +/** + * @file 权限验证相关功能 + * @author Justineo(justice360@gmail.com) + */ + +define(function (require) { + + var u = require('underscore'); + + var auth = {}; + + /** + * 权限类型 + * + * @enum {string} + */ + var AuthType = { + NONE: 'none', + EDITABLE: 'editable', + READONLY: 'readonly', + MIXED: '-' + }; + + auth.AuthType = AuthType; + + /** + * 获取某个权限模块在某个权限集合中的权限类型 + * @param {string} authName 权限模块的名字 + * @param {Object} authMap 权限模块集合 + * @return {AuthType} 返回权限类型 + */ + auth.get = function(authName, authMap) { + + var filter; + if (/^\(.+\)$/.test(authName)) { + return auth.get(authName.substr(1, authName.length - 2), authMap); + } + var authNames = []; + if (/^(\w+):/.test(authName)) { + var authPattern = /^(?:(\w+):)(.+)$/; + var arr = authPattern.exec(authName); + if (!arr) { + throw 'Auth error: [' + authName + '] is not a valid auth expression.'; + } + filter = arr[1]; + var paramStr = arr[2].replace(/^[\s,|]+|[\s,|]+$/g, ''); // trim + var cache = []; + var bracketCount = 0; + for (var i = 0; i < paramStr.length; i++) { + var ch = paramStr.charAt(i); + if (ch === '(') { + if (bracketCount === 0 && cache.length) { // 括号前有非分隔字符,非法 + throw 'Auth error: [' + authName + '] is not a valid auth expression.'; + } + if (++bracketCount === 1) { // 最外层第一个括号丢弃 + continue; + } + } + else if (ch === ')') { + // 最外层括号结束,取出整个括号内的内容递归调用 + if (--bracketCount <= 0) { + var brachketContent = cache.join(''); + authNames.push(auth.get(brachketContent, authMap).toUpperCase()); + cache = []; + continue; + } + } + else if (bracketCount <= 0 // 不在括号内的内容,碰到分隔字符,将cache里的字符作为一个完整的authName + && (ch === ',' + || ch === '|' + || /\s/.test(ch) + ) + ) { + if (cache.length) { + authNames.push(cache.join('')); + cache = []; + } + continue; + } + cache.push(ch); + } + if (cache.length) { + authNames.push(cache.join('')); + } + if (bracketCount > 0) { + throw 'Auth error: [' + authName + '] brackets doesn\'t match.'; + } + } + else { + authNames = [u.trim(authName)]; + } + + var types = []; + u.each(authNames, function(name) { + + // 如果是权限类型关键字作为直接量,不需要查询模块 + if (u.contains(u.keys(AuthType), name)) { + types.push(AuthType[name]); + return true; + } + + var nameSpaces = name.split('.'); + + do { + var type = authMap[nameSpaces.join('.')]; + if (type) { + types.push(type); + return true; + } + nameSpaces.pop(); + } while (nameSpaces.length); + + throw 'Auth error: [' + name + '] not found.'; + }); + + if (filter) { + return filters[filter].call( + null, types.length > 1 ? types : types[0] + ); + } + else { + return types[0]; + } + }; + + /** + * 获取某个权限模块在某个权限集合中是否有基本权限 + * @param {string} authName 权限模块的名字 + * @param {Object} authMap 权限模块集合 + * @return {boolean} 是否可见 + */ + auth.permit = function(authName, authMap) { + return auth.get(authName, authMap) !== AuthType.NONE; + }; + + /** + * 已经注册的权限类型转换器 + * @type {Object.} + * @private + */ + var filters = { + rtoe: function(type) { + return type === AuthType.READONLY + ? AuthType.EDITABLE + : type; + }, + + rton: function(type) { + return type === AuthType.READONLY + ? AuthType.NONE + : type; + }, + + ntor: function(type) { + return type === AuthType.NONE + ? AuthType.READONLY + : type; + }, + + up: function(type) { + return type === AuthType.NONE + ? AuthType.READONLY + : AuthType.EDITABLE; + }, + + max: function(types) { + var max = AuthType.NONE; + u.each(types, function(type) { + if (type === AuthType.EDITABLE) { + max = type; + return false; + } + else if (type === AuthType.READONLY) { + max = type; + } + }); + return max; + }, + + min: function(types) { + var min = AuthType.EDITABLE; + u.each(types, function(type) { + if (type === AuthType.NONE) { + min = type; + return false; + } + else if (type === AuthType.READONLY) { + min = type; + } + }); + return min; + } + }; + + /** + * 注册新的权限类型转换器 + * @param {string} name 转换器名字 + * @param {function(AuthType)} filter 转换器处理函数 + */ + auth.registerFilter = function(name, filter) { + filters[name] = filter; + }; + + return auth; +}); diff --git a/src/system/user.js b/src/system/user.js index 4c336ac..404f4b9 100644 --- a/src/system/user.js +++ b/src/system/user.js @@ -7,12 +7,14 @@ define(function (require) { var u = require('underscore'); var permission = require('er/permission'); + var URI = require('urijs'); + var auth = require('./auth'); /** * 用户信息模块 */ var exports = { - init: function(session) { + init: function (session) { if (session.visitor) { this.visitor = session.visitor; } @@ -30,6 +32,46 @@ define(function (require) { return value !== 'none'; })); } + }, + + getVisitor: function () { + return this.visitor || null; + }, + + getVisitorId: function () { + return this.visitor && this.visitor.id; + }, + + getAder: function () { + return this.ader || null; + }, + + getAderId: function () { + return this.ader && this.ader.id + || URI.parseQuery(document.location.search).aderId + || this.visitor && this.visitor.id; + }, + + getAuthMap: function () { + var authMap = this.visitor && this.visitor.auth; + + return authMap || null; + }, + + getAuthType: function (authId) { + return auth.get(authId, this.getAuthMap()); + }, + + getAuth: function (authId) { + var authType = this.getAuthType(authId); + return { + type: authType, + id: authId, + isReadOnly: authType === auth.AuthType.READONLY, + isEditable: authType === auth.AuthType.EDITABLE, + isVisible: authType !== auth.AuthType.NONE, + isNone: authType === auth.AuthType.NONE + }; } }; diff --git a/src/tpl.js b/src/tpl.js index 80585ef..df369b2 100644 --- a/src/tpl.js +++ b/src/tpl.js @@ -45,9 +45,10 @@ define( ActionDialog: 'ef', SelectorTreeStrategy: './ui', TreeRichSelector: './ui', - tableRichSelector: './ui', + TableRichSelector: './ui', RichSelector: './ui', - ToggleButton: './ui' + ToggleButton: './ui', + AuthPanel: './ui' }; var extensionModulePrefix = { diff --git a/src/tpl/forbidden.tpl.html b/src/tpl/forbidden.tpl.html new file mode 100644 index 0000000..9e53cdc --- /dev/null +++ b/src/tpl/forbidden.tpl.html @@ -0,0 +1,5 @@ + +
+

对不起,您没有访问该页面的权限!

+

点击返回首页 »

+
diff --git a/src/tpl/not_found.tpl.html b/src/tpl/not_found.tpl.html new file mode 100644 index 0000000..94a3a7c --- /dev/null +++ b/src/tpl/not_found.tpl.html @@ -0,0 +1,5 @@ + +
+

很抱歉,您要访问的页面不存在。

+

点击返回首页 »

+
diff --git a/src/ui/AuthPanel.js b/src/ui/AuthPanel.js new file mode 100644 index 0000000..7d7c4e6 --- /dev/null +++ b/src/ui/AuthPanel.js @@ -0,0 +1,160 @@ +/** + * JN 2.0 + * Copyright 2014 Baidu Inc. All rights reserved. + * + * @ignore + * @file 锦囊进行权限验证的基础容器 + * @author Justineo(justice360@gmail.com) + */ +define(function (require) { + var u = require('underscore'); + var lib = require('esui/lib'); + var Panel = require('esui/Panel'); + var auth = require('../system/auth'); + + /** + * 锦囊权限编辑器权限节点 + * + * @param {Object=} options 初始化参数 + * @extends esui/Panel + * @constructor + * @public + */ + function AuthPanel(options) { + Panel.apply(this, arguments); + } + + AuthPanel.prototype.type = 'AuthPanel'; + + /** + * 权限类型 + * + * @type {Object} + */ + var AuthType = auth.AuthType; + + /** + * 默认属性 + * + * @type {Object} + * @public + */ + AuthPanel.defaultProperties = { + auth: AuthType.EDITABLE + }; + + /** + * @inheritDoc + */ + AuthPanel.prototype.initOptions = function (options) { + var properties = {}; + u.extend(properties, AuthPanel.defaultProperties, options); + + this.setProperties(properties); + }; + + /** + * 对有auth属性的元素根据指定权限集合进行权限初始化 + * + * @param {HTMLElement=} root 根元素,如果为空,则为控件主元素 + */ + AuthPanel.prototype.initAuth = function (root) { + var me = this; + var root = root || me.main; + + // 可以通过设置disabled属性禁用的控件 + // 见 http://www.w3.org/html/wg/drafts/html/master/disabled-elements.html + var disableableTagNames = [ + 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', + 'OPTGROUP', 'OPTION', 'COMMAND', 'FIELDSET' + ]; + + u.each(root.querySelectorAll('[data-auth]'), function(elem) { + if (elem.getAttribute('data-auth-done')) { + return true; + } + + var authId = elem.getAttribute('data-auth'); + + // 如果元素的auth属性值包含inherit,层层查找其DOM父节点直到找到 + // 有auth属性但不包含inherit的元素,并使用该属性值替换掉inherit + // 如果找不到,一定是发生错误了 + if (/\binherit\b/.test(authId)) { + var parent = elem; + while (parent = parent.parentNode) { + var parentAuth = parent.getAttribute('data-auth'); + if (parentAuth && !/\binherit\b/.test(parentAuth)) { + authId = authId.replace('inherit', '(' + parentAuth + ')'); + elem.setAttribute('data-auth', authId); + break; + } + else if (parent === document.body) { + throw 'Auth error: [inherit] source not found.'; + } + } + } + + var authType = require('../system/user').getAuthType(authId); + var controlId = elem.getAttribute('data-ctrl-id'); + var control = controlId ? me.viewContext.get(controlId) : null; + if (authType === AuthType.NONE) { + if (control) { // UI控件调用自己的hide方法,就不删除了 + control.hide(); + } + else { + elem.parentNode.removeChild(elem); // 直接删除内容 + } + } + else if (authType === AuthType.READONLY) { + if (control) { // UI控件 + if (control.setReadOnly) { + control.setReadOnly(true); + } + else { + control.disable(); + } + } + else { // 普通DOM元素 + var tagName = elem.tagName.toUpperCase(); + + // 当元素是某种文本输入框时,可以直接设置readonly + if (tagName === 'INPUT' + && (elem.type === 'text' || elem.type === 'password') + || tagName === 'TEXTAREA') { + elem.readonly = true; + } + // 否则,对于可以通过disabled属性禁用的元素,设置disabled + else if (u.contains(disableableTagNames, tagName)) { + elem.disabled = true; + } + // 其他类型的元素,劫持onclick + else { + lib.un(elem, 'click'); + elem.onclick = function(e) { + lib.event.stopPropagation(e); + lib.event.preventDefault(e); + return false; + }; + } + + // 不管哪种情况,都给元素增加class + lib.addClass(elem, 'auth-disabled'); + } + } + elem.setAttribute('data-auth-done', 'auth-done'); + }); + }; + + /** + * @inheritDoc + */ + AuthPanel.prototype.repaint = function () { + Panel.prototype.repaint.call(this); + + this.initAuth(); + }; + + lib.inherits(AuthPanel, Panel); + require('esui').register(AuthPanel); + return AuthPanel; +}); diff --git a/src/ui/RichSelector.js b/src/ui/RichSelector.js index f9a88a1..a6da910 100644 --- a/src/ui/RichSelector.js +++ b/src/ui/RichSelector.js @@ -88,7 +88,7 @@ define( } lib.extend(properties, options); - properties.width = Math.max(200, properties.width); + // properties.width = Math.max(200, properties.width); this.setProperties(properties); }; @@ -465,10 +465,10 @@ define( contentHeight -= 30; } - + content.style.height = contentHeight + 'px'; } - + }; RichSelector.prototype.adaptData = function () {}; diff --git a/src/ui/SelectorTreeStrategy.js b/src/ui/SelectorTreeStrategy.js index da3d49d..46d2781 100644 --- a/src/ui/SelectorTreeStrategy.js +++ b/src/ui/SelectorTreeStrategy.js @@ -74,11 +74,6 @@ define( canSelect = false; } } - else { - if (!isLeafNode) { - canSelect = false; - } - } if (canSelect) { this.selectNode(e.node.id); diff --git a/src/ui/TreeRichSelector.js b/src/ui/TreeRichSelector.js index 859615c..bfa3e31 100644 --- a/src/ui/TreeRichSelector.js +++ b/src/ui/TreeRichSelector.js @@ -100,6 +100,11 @@ define( // 其中包含父节点信息,以及节点选择状态 var indexData = {}; if (this.allData && this.allData.children) { + indexData[this.allData.id] = { + parentId: null, + node: this.allData, + isSelected: false + }; walkTree( this.allData, this.allData.children, @@ -121,8 +126,8 @@ define( */ TreeRichSelector.prototype.refreshContent = function () { var treeData = this.isQuery() ? this.queriedData : this.allData; - if (!treeData - || !treeData.children + if (!treeData + || !treeData.children || !treeData.children.length) { this.addState('empty'); } @@ -270,7 +275,7 @@ define( /** * 撤销选择当前项 - * @param {ui.TreeRichSelector} control 类实例 + * @param {ui.TreeRichSelector} control 类实例 * @ignore */ function unselectCurrent(control) { diff --git a/src/ui/css/TreeRichSelector.less b/src/ui/css/TreeRichSelector.less index f8ec7b7..e8a3470 100644 --- a/src/ui/css/TreeRichSelector.less +++ b/src/ui/css/TreeRichSelector.less @@ -4,7 +4,7 @@ .ui-tree-content-wrapper { border-left: medium none; padding: 0 5px; - line-height: 35px; + line-height: 24px; cursor: pointer; .clearfix(); &:hover { diff --git a/src/ui/css/wizard.less b/src/ui/css/wizard.less index 9a9af75..9881fde 100644 --- a/src/ui/css/wizard.less +++ b/src/ui/css/wizard.less @@ -1,95 +1,93 @@ -.skin-default-wizard { - @commonColor: #E7F0FA; - @doneColor: #E7F0FA; - @activeColor: #B6D2F1; - - @arrowHeight: 24px; - - @arrowGap: 4px; - .ui-wizard-node { - color: #999999; - font-size: 12px; - font-weight: normal; - height: @arrowHeight; - line-height: @arrowHeight; - padding: 0 15px 0 0; - *padding: 0; - text-align: center; - position: relative; - background: #fff; - - span { - margin-right: @arrowGap; - color: #999999; - height: @arrowHeight; - float: left; - position: relative; - text-align: center; - width: auto; - padding: 0 10px; - background-color: @commonColor; - - &:after { - border-bottom: (@arrowHeight/2) solid transparent; - border-left: (@arrowHeight/2) solid @commonColor; - border-top: (@arrowHeight/2) solid transparent; - content: " "; - height: 0; - position: absolute; - right: -(@arrowHeight/2); - top: 0; - width: 0; - } - &:before { - border-bottom: (@arrowHeight/2) solid @commonColor; - border-left: (@arrowHeight/2) solid transparent; - border-top: (@arrowHeight/2) solid @commonColor; - content: ""; - height: 0; - left: -(@arrowHeight/2); - position: absolute; - top: 0; - width: 0; - } - } - } - - .ui-wizard-node-done { - span { - background-color: @doneColor; - color: #999999; - - &:after { - border-left: (@arrowHeight/2) solid @doneColor; - } - &:before { - border-bottom: (@arrowHeight/2) solid @doneColor; - border-top: (@arrowHeight/2) solid @doneColor; - } - } - } - - .ui-wizard-node-last { - background: none; - } - - .ui-wizard-node-active-prev { - } - - .ui-wizard-node-active { - span { - background-color: @activeColor; - color: #000; - &:after { - border-left: (@arrowHeight/2) solid @activeColor; - } - &:before { - border-bottom: (@arrowHeight/2) solid @activeColor; - border-top: (@arrowHeight/2) solid @activeColor; - } - } - - } - - +.skin-default-wizard { + @commonColor: #E7F0FA; + @doneColor: #E7F0FA; + @activeColor: #B6D2F1; + + @arrowHeight: 24px; + + @arrowGap: 4px; + .ui-wizard-node { + color: #999999; + font-size: 12px; + font-weight: normal; + height: @arrowHeight; + line-height: @arrowHeight; + padding: 0 15px 0 0; + *padding: 0; + text-align: center; + position: relative; + background: #fff; + + span { + margin-right: @arrowGap; + color: #999999; + height: @arrowHeight; + float: left; + position: relative; + text-align: center; + width: auto; + padding: 0 10px; + background-color: @commonColor; + + &:after { + border-bottom: (@arrowHeight/2) solid transparent; + border-left: (@arrowHeight/2) solid @commonColor; + border-top: (@arrowHeight/2) solid transparent; + content: " "; + height: 0; + position: absolute; + right: -(@arrowHeight/2); + top: 0; + width: 0; + } + &:before { + border-bottom: (@arrowHeight/2) solid @commonColor; + border-left: (@arrowHeight/2) solid transparent; + border-top: (@arrowHeight/2) solid @commonColor; + content: ""; + height: 0; + left: -(@arrowHeight/2); + position: absolute; + top: 0; + width: 0; + } + } + } + + .ui-wizard-node-done { + span { + background-color: @doneColor; + color: #999999; + + &:after { + border-left: (@arrowHeight/2) solid @doneColor; + } + &:before { + border-bottom: (@arrowHeight/2) solid @doneColor; + border-top: (@arrowHeight/2) solid @doneColor; + } + } + } + + .ui-wizard-node-last { + background: none; + } + + .ui-wizard-node-active-prev { + } + + .ui-wizard-node-active { + span { + background-color: @activeColor; + color: #000; + &:after { + border-left: (@arrowHeight/2) solid @activeColor; + } + &:before { + border-bottom: (@arrowHeight/2) solid @activeColor; + border-top: (@arrowHeight/2) solid @activeColor; + } + } + + } } \ No newline at end of file diff --git a/src/ui/extension/TableTip.js b/src/ui/extension/TableTip.js index f330daf..947fdfb 100644 --- a/src/ui/extension/TableTip.js +++ b/src/ui/extension/TableTip.js @@ -77,8 +77,8 @@ define( showMode: 'over', delayTime: 200, positionOpt: { - bottom: 'bottom', - left: 'left' + top: 'top', + right: 'left' } }; tip.attachTo(options); @@ -94,7 +94,8 @@ define( return; } - var elements = document.querySelectorAll('.table-operation'); + // 根据Table主元素来限定范围 + var elements = this.target.main.querySelectorAll('.table-operation'); u.chain(elements) .groupBy(getTipType) @@ -109,7 +110,7 @@ define( TableTip.prototype.activate = function () { Extension.prototype.activate.apply(this, arguments); - this.target.on('afterrender', this.initTips, this); + this.target.on('bodyChange', this.initTips, this); }; /** @@ -118,7 +119,7 @@ define( * @override */ TableTip.prototype.inactivate = function () { - this.target.un('afterrender', this.initTips, this); + this.target.un('bodyChange', this.initTips, this); Extension.prototype.inactivate.apply(this, arguments); }; @@ -128,4 +129,4 @@ define( return TableTip; } -); +); diff --git a/src/ui/loading.js b/src/ui/loading.js index 7c0a6e7..74b426d 100644 --- a/src/ui/loading.js +++ b/src/ui/loading.js @@ -38,7 +38,9 @@ define(function (require) { properties = u.extend(properties, options); globalLoading.setProperties(properties); globalLoading.show(); + globalLoading.main.style.zIndex = parseInt(globalLoading.main.style.zIndex, 10) + 1; loadingCount++; + loadingTimer && clearTimeout(loadingTimer); return globalLoading; } function hideLoading() { diff --git a/src/ui/navigator.js b/src/ui/navigator.js index 60f0ff0..ce22b01 100644 --- a/src/ui/navigator.js +++ b/src/ui/navigator.js @@ -1,6 +1,6 @@ /** * Copyright 2014 Baidu Inc. All rights reserved. - * + * * @file Navigator * @author chestnutchen(chenli11@baidu.com) * @date $DATE$ @@ -49,8 +49,7 @@ define(function (require) { * 这里的逻辑`exclude`优先级比`include`高,但是也不要两个都配同个规则吧。。 * @cfg {Array} [config.tabs] 子导航,结构和一级导航config中每一项保持一致 * @cfg {string} [config.auth] 与er.permission对应的权限控制 - * TODO:二级导航暂未支持权限 - * + * * @sample: * nav: { * navId: 'nav', @@ -112,7 +111,7 @@ define(function (require) { */ function toggleNav(type) { this.main.style.cssText = 'display:' + type + ';'; - u.some(this.subNavs, function (subNav, index) { + u.some(this.subNavs, function (subNav) { if (u.isObject(subNav)) { if (lib.hasClass(subNav.nav, 'nav-sub-item-current')) { subNav.nav.cssText = 'display:' + type + ';'; @@ -172,10 +171,10 @@ define(function (require) { /** * 高亮当前导航元素 * - * @navItems {object} 缓存导航的集合,传入来记录当前高亮的索引 - * @element {object} 要高亮的元素 - * @index {string} 要高亮的导航索引 - * @className {string} 通过添加和移除current className来更换高亮样式 + * @param {object} navItems 缓存导航的集合,传入来记录当前高亮的索引 + * @param {object} element 要高亮的元素 + * @param {string} index 要高亮的导航索引 + * @param {string} className 通过添加和移除current className来更换高亮样式 */ function activateNavElement(navItems, element, index, className) { if (!u.isNumber(navItems.activeIndex)) { @@ -192,9 +191,9 @@ define(function (require) { /** * 展示或隐藏二级导航 * - * @navItems {object} 缓存导航的集合,传入来记录当前高亮的索引 - * @element {object} 要高亮的元素,不传表示隐藏所有 - * @index {string} 要高亮的导航索引 + * @param {object} navItems 缓存导航的集合,传入来记录当前高亮的索引 + * @param {object} element 要展示的子tab,不传表示隐藏所有 + * @param {string} index 要展示的导航索引 */ function toggleSubNav(navItems, element, index) { var className = 'nav-sub-current'; @@ -218,22 +217,62 @@ define(function (require) { /** * 创建nav元素 - * - * @config {object} globalconfig - * @navItems {object} 缓存nav元素的对象 - * @nav {object} nav父元素 - * @isSub {string} '' 或 'sub-',主导航或二级子导航 + * + * @param {object} config globalconfig + * @param {object} navItems 缓存nav元素的对象 + * @param {object} nav nav父元素 + * @param {string} isSub {''|'sub-'},主导航或二级子导航 */ function createNavElements(config, navItems, nav, isSub) { var isSub = isSub || ''; u.each(config, function (item, index) { if (!item.auth || permission.isAllow(item.auth)) { - var url = item.externalUrl || ('#' + item.url); + var internalUrl = fixErUrl(item.url || ''); + var url = item.externalUrl || internalUrl; + var target = (item.externalUrl ? ' target="_blank"' : ''); var element = document.createElement('li'); var separate = ''; + // 处理父tab和子tab权限耦合的部分 + if (item.tabs) { + // 看看默认要跳转的地址所在的子tab有没有权限,没有的话得找备胎 + var hasDefaultTabAuth = u.every(item.tabs, function (subItem) { + if (isActive(item.url.replace(/^#/, ''), subItem)) { + if (!subItem.auth || permission.isAllow(subItem.auth)) { + return true; + } + else { + return false; + } + } + }); + + // 如果没有权限就得找到第一个有权限的子tab,把父tab跳转链接改成这个 + if (!hasDefaultTabAuth) { + // 做最坏的打算,所有不外跳的子tab都没权限,那这个父tab只能直接往外跳了 + var externalUrlBackUp = ''; + var isInternalUrlFound = u.some(item.tabs, function (subItem) { + if (!subItem.auth || permission.isAllow(subItem.auth)) { + if (subItem.url) { + url = fixErUrl(subItem.url); + return true; + } + else if (subItem.externalUrl && !externalUrlBackUp) { + externalUrlBackUp = subItem.externalUrl; + } + } + }); + + if (!isInternalUrlFound) { + // 如果找不到内跳和外跳的备胎 + // 就说明所有子tab都没权限,那就用原来父tab配的url就可以了 + url = externalUrlBackUp || url; + } + } + } + element.className = 'nav-' + isSub + 'item'; - element.innerHTML = '' + element.innerHTML = '' + '' + u.escape(item.text) + '' + ''; @@ -253,11 +292,11 @@ define(function (require) { /** * 创建二级nav元素,如果存在,直接展示 * - * @config {object} config.tabs,某个主导航的二级导航配置 - * @navItems {object} 缓存一级导航的对象,用来计算位置 - * @subNavs {object} 缓存二级导航的对象 - * @parentNav {object} 一级导航容器 - * @index {string} 一级导航navElement的index + * @param {object} config config.tabs,某个主导航的二级导航配置 + * @param {object} navItems 缓存一级导航的对象,用来计算位置 + * @param {object} subNavs 缓存二级导航的对象 + * @param {object} main 一级导航容器 + * @param {string} index 一级导航navElement的index */ function createOrShowSubNav(config, navItems, subNavs, main, index) { var ul = subNavs[index].nav; @@ -294,8 +333,10 @@ define(function (require) { /** * 验证url是否匹配 * - * @url {string} 当前页面的er path - * @patterns {RegExp | string} 验证的表达式或path + * @param {string} url 某个path + * @param {RegExp | string} patterns 验证的表达式或path + * + * @return {boolean} */ function testUrlIn(url, patterns) { return u.some(patterns, function(pattern) { @@ -308,11 +349,26 @@ define(function (require) { }); } + /** + * 将传入的path格式化为er path + * + * @param {string} url 某个path + * @return {string} + */ + function fixErUrl(url) { + if (!url) { + return ''; + } + return (url.indexOf('#') === 0 ? url : ('#' + url)); + } + /** * 验证url是否命中某个导航元素config的规则 * - * @url {string} 当前页面的er path - * @patterns {RegExp | string} 验证的表达式或path + * @param {string} url 某个path,不带# + * @param {RegExp | string} item 一个tab的配置信息 + * + * @return {boolean} */ function isActive(url, item) { return !testUrlIn(url, item.exclude || []) && testUrlIn(url, item.include || []); @@ -325,7 +381,7 @@ define(function (require) { }; } - var commonNavigator = new Navigator(); + var commonNavigator = new Navigator(); return { init: u.bind(commonNavigator.init, commonNavigator), diff --git a/src/util.js b/src/util.js index 117d0d3..1f1d2d3 100644 --- a/src/util.js +++ b/src/util.js @@ -1,6 +1,6 @@ /** * Copyright 2014 Baidu Inc. All rights reserved. - * + * * @ignore * @file BAT工具模块 * @author Justineo @@ -33,7 +33,7 @@ define(function (require) { // 过滤掉不需要生成的URL isRequester = isRequester || function (path) { - // 默认跳过以`/download`和`/upload`结尾的路径 + // 默认跳过以`/download`和`/upload`结尾的路径 return !/\/(?:up|down)load$/.test(path); }; @@ -253,8 +253,8 @@ define(function (require) { 'data-command': command.type }; - if (u.typeOf(command.args) === 'String') { - attrs['data-command-args'] = command.args; + if (typeof command.args !== 'undefined') { + attrs['data-command-args'] = '' + command.args; } if (u.typeOf(command.extra) === 'Object') { @@ -282,19 +282,19 @@ define(function (require) { */ util.genListOperations = function (operations, config) { config = config || {}; - var html = u.map( - operations, - function (operation) { + var html = u.chain(operations) + .map(function (operation) { if (operation.url) { return util.genListLink(operation); } else { return util.genListCommand(operation); } - } - ); + }) + .compact() + .value(); - return html.join(config.separator || '|'); + return html.join(config.separator || ''); }; /**