├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmrc ├── .travis.yml ├── LICENSE ├── README.md ├── build-utils ├── webpack.dev.js ├── webpack.prod.js └── webpack.site.js ├── demos ├── common │ ├── layout.html │ └── nav.html ├── components │ ├── checkbox │ │ ├── index.html │ │ └── index.js │ ├── date-picker │ │ └── index.html │ ├── form │ │ ├── index.html │ │ └── index.js │ ├── grid │ │ ├── customer-footer.tpl.html │ │ ├── customer-row.html │ │ ├── external-data-grid.html │ │ ├── external-data-grid.js │ │ ├── index.html │ │ ├── index.js │ │ └── test.html │ ├── instant-search │ │ └── index.html │ ├── loading │ │ └── index.html │ ├── menus │ │ ├── app.js │ │ ├── menus-flex.html │ │ ├── menus.html │ │ ├── style.css │ │ └── tpls │ │ │ ├── shop-item.css │ │ │ └── shop-item.html │ ├── modal │ │ ├── index.html │ │ ├── index.js │ │ ├── modal-body-child-child.tpl.html │ │ ├── modal-body-child.tpl.html │ │ ├── modal-body-parent.tpl.html │ │ ├── modal-body.tpl.html │ │ ├── modal-footer.tpl.html │ │ └── modal-header.tpl.html │ ├── pagination │ │ └── index.html │ ├── radio-button │ │ ├── index.html │ │ └── index.js │ ├── select │ │ └── index.html │ ├── styles │ │ └── index.html │ ├── tabset │ │ ├── index.html │ │ └── tpl │ │ │ ├── tabset-content.tpl.html │ │ │ └── url.tpl.html │ ├── tips │ │ ├── index.html │ │ ├── index.js │ │ └── modal-body.tpl.html │ ├── title │ │ └── index.html │ ├── toggle │ │ └── index.html │ ├── tooltip │ │ ├── index.html │ │ └── index.js │ └── tree │ │ ├── index.html │ │ └── index.js ├── favicon.ico └── index.html ├── index.js ├── mock └── mock.js ├── package.json ├── postcss.config.js ├── src ├── assets │ └── images │ │ └── logo-default.png ├── common │ ├── bases │ │ ├── Popup.js │ │ └── constant.js │ └── utils │ │ ├── adaptor │ │ └── index.js │ │ ├── browser │ │ └── index.js │ │ ├── dom-event │ │ └── index.js │ │ ├── highlight │ │ └── index.js │ │ ├── index.js │ │ ├── object │ │ └── filter.js │ │ ├── performance │ │ ├── index.js │ │ └── throttle.js │ │ ├── style-helper │ │ └── index.js │ │ └── tpl-req-helper │ │ └── index.js ├── components │ ├── alt-src │ │ └── index.js │ ├── bind-html │ │ ├── BindHtmlCtrl.js │ │ └── index.js │ ├── calendar │ │ ├── Calendar.js │ │ ├── CalendarCtrl.js │ │ ├── _calendar.scss │ │ ├── calendar.tpl.html │ │ └── index.js │ ├── capture-event │ │ └── index.js │ ├── checkbox │ │ ├── CheckboxCtrl.js │ │ ├── __tests__ │ │ │ └── test-CheckboxCtrl.js │ │ ├── _checkbox.scss │ │ ├── checkbox.tpl.html │ │ └── index.js │ ├── date-picker │ │ ├── DatePicker.js │ │ ├── DatePickerCtrl.js │ │ ├── _date-picker.scss │ │ ├── date-picker.tpl.html │ │ ├── dateUtils.js │ │ └── index.js │ ├── date-range │ │ ├── DateRange.js │ │ ├── DateRangeCtrl.js │ │ ├── _date-range.scss │ │ ├── date-range.tpl.html │ │ └── index.js │ ├── draggable │ │ └── index.js │ ├── dropdown │ │ ├── DropdownCtrl.js │ │ ├── DropdownPanelCtrl.js │ │ ├── DropdownService.js │ │ ├── DropdownToggleCtrl.js │ │ ├── __tests__ │ │ │ └── test-cc-dropdown.js │ │ ├── _dropdown.scss │ │ ├── dropdown-multiselect │ │ │ ├── DropdownMultiselectCtrl.js │ │ │ ├── __tests__ │ │ │ │ └── test-cc-dropdown-multiselect.js │ │ │ ├── _dropdown-multiselect.scss │ │ │ ├── dropdown-multiselect.tpl.html │ │ │ └── index.js │ │ ├── dropdown-select │ │ │ ├── DropdownSelectCtrl.js │ │ │ ├── __tests__ │ │ │ │ └── test-cc-dropdown-select.js │ │ │ ├── _dropdown-select.scss │ │ │ ├── dropdown-select.tpl.html │ │ │ └── index.js │ │ └── index.js │ ├── dynamic-attr │ │ ├── DynamicAttrDirective.js │ │ └── index.js │ ├── form │ │ ├── Constant.js │ │ ├── ValidatorCtrl.js │ │ ├── ValidatorService.js │ │ ├── ValidatorsCtrl.js │ │ ├── _form.scss │ │ └── index.js │ ├── grid │ │ ├── Constant.js │ │ ├── GridCellLink.js │ │ ├── GridCtrl.js │ │ ├── GridHelper.js │ │ ├── __tests__ │ │ │ ├── test-GridCtrl.js │ │ │ └── test-GridHelper.js │ │ ├── _grid.scss │ │ ├── grid.tpl.html │ │ ├── index.js │ │ └── tpls │ │ │ ├── checkbox-header.tpl.html │ │ │ ├── checkbox-row-cell.tpl.html │ │ │ ├── default-footer.tpl.html │ │ │ ├── default-header.tpl.html │ │ │ ├── default-row-cell.tpl.html │ │ │ ├── empty-grid-tip.tpl.html │ │ │ ├── error.tpl.html │ │ │ └── row.tpl.html │ ├── icon │ │ ├── index.js │ │ ├── index.scss │ │ └── index.tpl.html │ ├── index.js │ ├── instant-search │ │ ├── InstantSearchCtrl.js │ │ ├── _instant-search.scss │ │ ├── index.js │ │ └── instant-search.tpl.html │ ├── loading │ │ ├── _loading.scss │ │ ├── index.js │ │ └── loading.tpl.html │ ├── menus │ │ ├── MenuService.js │ │ ├── MenusCtrl.js │ │ ├── MenusNodeCtrl.js │ │ ├── _menu.scss │ │ ├── index.js │ │ ├── menus-node.tpl.html │ │ ├── menus.tpl.html │ │ └── shop-select │ │ │ ├── ShopSelectsCtrl.js │ │ │ ├── ShopSelectsHelper.js │ │ │ ├── _shop-selector.scss │ │ │ ├── index.js │ │ │ ├── shop-selects.tpl.html │ │ │ └── tpls │ │ │ └── shop-item.tpl.html │ ├── modal │ │ ├── Constants.js │ │ ├── Modal.js │ │ ├── ModalService.js │ │ ├── __tests__ │ │ │ ├── modal-body.url.html │ │ │ ├── modal-footer.url.html │ │ │ └── test-ModalService.js │ │ ├── _modal.scss │ │ ├── confirm.tpl.html │ │ ├── index.js │ │ └── modal.tpl.html │ ├── ng-dom-value │ │ └── index.js │ ├── ng-enter │ │ └── index.js │ ├── nice-scroll │ │ └── index.js │ ├── pagination │ │ ├── PaginationCtrl.js │ │ ├── _pagination.scss │ │ ├── index.js │ │ └── pagination.tpl.html │ ├── radio │ │ ├── RadioCtrl.js │ │ ├── __tests__ │ │ │ └── test-RadioCtrl.js │ │ ├── _radio.scss │ │ ├── index.js │ │ └── radio.tpl.html │ ├── styles │ │ ├── _animation.scss │ │ ├── _base.scss │ │ ├── _button.scss │ │ ├── _input.scss │ │ ├── _layout-flex.scss │ │ ├── _layout.scss │ │ ├── _mixins.scss │ │ ├── _placeholders.scss │ │ └── _variables.scss │ ├── tab │ │ └── _tab.scss │ ├── tabset │ │ ├── TabsetCtrl.js │ │ ├── _tabset.scss │ │ ├── index.js │ │ ├── tabContentTranscludeLink.js │ │ ├── tabTitleTranscludeLink.js │ │ └── tpls │ │ │ ├── tab.tpl.html │ │ │ └── tabset.tpl.html │ ├── tips │ │ ├── Tips.js │ │ ├── TipsCtrl.js │ │ ├── TipsService.js │ │ ├── _tips.scss │ │ ├── float-tips.tpl.html │ │ ├── index.js │ │ └── normal-tips.tpl.html │ ├── toggle │ │ ├── ToggleCtrl.js │ │ ├── _toggle.scss │ │ ├── index.js │ │ └── toggle.tpl.html │ ├── tooltip │ │ ├── Contants.js │ │ ├── Tooltip.js │ │ ├── TooltipCtrl.js │ │ ├── _tooltip.scss │ │ ├── index.js │ │ └── tooltip.tpl.html │ └── tree │ │ ├── Handler.js │ │ ├── Store.js │ │ ├── TreeController.js │ │ ├── doc.md │ │ ├── index.js │ │ ├── index.scss │ │ ├── list │ │ ├── TreeListController.js │ │ └── list.tpl.html │ │ ├── menu │ │ ├── menu.tpl.html │ │ └── menuController.js │ │ ├── node │ │ ├── TreeNodeController.js │ │ └── node.tpl.html │ │ ├── search │ │ ├── TreeSearchController.js │ │ ├── index.js │ │ └── search.tpl.html │ │ └── tree.tpl.html └── index.js ├── test ├── karma.base.conf.js ├── karma.cover.conf.js ├── karma.unit.conf.js └── test.index.js ├── webpack-common-loaders.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "browsers": [ 8 | "last 2 versions", "safari 7" 9 | ] 10 | }, 11 | "useBuiltIns": "entry" 12 | } 13 | ] 14 | ], 15 | "plugins": [ 16 | [ 17 | "@babel/plugin-proposal-decorators", 18 | { 19 | "legacy": true 20 | } 21 | ], 22 | "@babel/plugin-proposal-object-rest-spread", 23 | "@babel/plugin-proposal-class-properties", 24 | "@babel/plugin-proposal-function-bind" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [**] 5 | # Unix-style newlines with a newline ending every file 6 | end_of_line = lf 7 | insert_final_newline = true 8 | 9 | # 去除行尾空白字符 10 | trim_trailing_whitespace = true 11 | 12 | # 编码格式,编码格式,支持latin1、utf-8、utf-8-bom、utf-16be和utf-16le,不建议使用uft-8-bom。 13 | charset = utf-8 14 | 15 | # 项目中采用tab作为代码缩进样式 16 | indent_style = tab 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | 21 | [{package.json,.babelrc,.eslintrc}] 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.yml] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/dev-styles/** 2 | **/styles/** 3 | **/assets/** 4 | **/build-utils/** 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache/ 2 | dist/ 3 | docs/_gh_pages/ 4 | node_modules/ 5 | test/coverage/ 6 | coverage/ 7 | npm-debug.log 8 | .idea/ 9 | .DS_Store 10 | 11 | cloud-angular-site/ 12 | 13 | package-lock.json 14 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ 2 | phantomjs_cdnurl=https://npm.taobao.org/mirrors/phantomjs/ 3 | electron_mirror=https://npm.taobao.org/mirrors/electron/ 4 | registry=https://registry.npm.taobao.org 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | addons: 4 | apt: 5 | update: true 6 | sources: 7 | - google-chrome 8 | packages: 9 | - google-chrome-stable 10 | language: node_js 11 | node_js: 12 | - 7.9.0 13 | cache: 14 | directories: 15 | - node_modules 16 | before_install: 17 | - export DISPLAY=:99.0 18 | - sh -e /etc/init.d/xvfb start 19 | - sleep 3 20 | script: 21 | - npm test 22 | - cat ./test/coverage/lcov.info | ./node_modules/.bin/codecov 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Shuyun FF2E 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build-utils/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const bodyParser = require('body-parser'); 5 | 6 | const { gridData, menuData, shopsData } = require('../mock/mock'); 7 | const { version } = require('../package.json'); 8 | const publicPath = '/'; 9 | 10 | 11 | module.exports = () => ({ 12 | mode: 'development', 13 | devtool: 'source-map', 14 | entry: { 15 | components: './src/index.js' 16 | }, 17 | output: { 18 | path: path.join(__dirname, 'build'), 19 | filename: '[name].js', 20 | publicPath 21 | }, 22 | devServer: { 23 | disableHostCheck: true, 24 | proxy: { 25 | '/api': { 26 | target: 'http://localhost:8080', 27 | pathRewrite: { '^/api': '' }, 28 | logLevel: 'debug' 29 | } 30 | }, 31 | before(app) { 32 | app.use(bodyParser.json()); 33 | app.use(bodyParser.urlencoded({ extended: false })); 34 | 35 | app.get('/pages/1', (req, res) => { 36 | res.json(gridData); 37 | }); 38 | 39 | app.get('/menus', (req, res) => { 40 | res.json(menuData); 41 | }); 42 | 43 | app.get('/shops', (req, res) => { 44 | res.json(shopsData); 45 | }); 46 | } 47 | }, 48 | plugins: [ 49 | new webpack.DefinePlugin({ 50 | 'process.env': { 51 | VERSION: JSON.stringify(version) 52 | } 53 | }), 54 | new webpack.HotModuleReplacementPlugin(), 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'demos/index.html', 58 | favicon: 'demos/favicon.ico' 59 | }) 60 | ], 61 | module: { 62 | rules: [ 63 | { 64 | test: /\.(sc|c)ss$/, 65 | use: [ 66 | { loader: 'style-loader' }, 67 | { loader: 'css-loader', options: { sourceMap: true } }, 68 | { loader: 'postcss-loader' }, 69 | { loader: 'resolve-url-loader' }, 70 | { loader: 'sass-loader', options: { sourceMap: true } } 71 | ], 72 | exclude: /node_modules/ 73 | } 74 | ] 75 | } 76 | }); 77 | -------------------------------------------------------------------------------- /build-utils/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const CleanPlugin = require('clean-webpack-plugin'); 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 5 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 6 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 7 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 8 | 9 | const { name, version } = require('../package.json'); 10 | const buildOutputDir = path.join(__dirname, '../dist'); 11 | 12 | module.exports = () => ({ 13 | mode: 'production', 14 | devtool: 'cheap-source-map', 15 | entry: { 16 | 'ccms-components': './src/index.js' 17 | }, 18 | output: { 19 | path: buildOutputDir, 20 | filename: '[name].js' 21 | }, 22 | externals: { 23 | 'angular': 'angular', 24 | 'angular-resource': '\'ngResource\'', 25 | 'angular-ui-router': '\'ui.router\'' 26 | }, 27 | optimization: { 28 | minimizer: [ 29 | new UglifyJsPlugin({ 30 | cache: true, 31 | parallel: true, 32 | sourceMap: true 33 | }), 34 | new OptimizeCssAssetsPlugin() 35 | ] 36 | }, 37 | plugins: [ 38 | new webpack.DefinePlugin({ 39 | 'process.env': { 40 | NODE_ENV: JSON.stringify('production'), 41 | VERSION: JSON.stringify(version) 42 | } 43 | }), 44 | new CleanPlugin([buildOutputDir], { 45 | root: process.cwd() 46 | }), 47 | new webpack.BannerPlugin(` 48 | ${name} v${version} 49 | Copyright 2016-present, Shuyun, Inc. 50 | All rights reserved. 51 | `), 52 | new MiniCssExtractPlugin({ 53 | filename: '[name].css', 54 | chunkFilename: '[id].css' 55 | }), 56 | new CopyWebpackPlugin([ 57 | { from: path.join(__dirname, '../index.js'), to: buildOutputDir, toType: 'dir' }, 58 | { from: path.join(__dirname, '../package.json'), to: buildOutputDir, toType: 'dir' }, 59 | { from: path.join(__dirname, '../README.md'), to: buildOutputDir, toType: 'dir' }, 60 | { from: path.join(__dirname, '../src/components/styles/'), to: `${buildOutputDir}/scss`, toType: 'dir' } 61 | ]) 62 | ], 63 | module: { 64 | rules: [ 65 | { 66 | test: /\.(sc|c)ss$/, 67 | use: [ 68 | { loader: MiniCssExtractPlugin.loader }, 69 | { loader: 'css-loader', options: { sourceMap: true } }, 70 | { loader: 'postcss-loader' }, 71 | { loader: 'resolve-url-loader' }, 72 | { loader: 'sass-loader', options: { sourceMap: true } } 73 | ], 74 | exclude: /node_modules/ 75 | } 76 | ] 77 | } 78 | }); 79 | -------------------------------------------------------------------------------- /build-utils/webpack.site.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CleanPlugin = require('clean-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 5 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 6 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 7 | 8 | const buildOutputDir = path.join(__dirname, '../cloud-angular-site'); 9 | 10 | module.exports = () => ({ 11 | mode: 'production', 12 | devtool: 'cheap-source-map', 13 | entry: { 14 | 'components': './src/index.js' 15 | }, 16 | output: { 17 | path: buildOutputDir, 18 | filename: '[name].js' 19 | }, 20 | optimization: { 21 | minimizer: [ 22 | new UglifyJsPlugin({ 23 | cache: true, 24 | parallel: true, 25 | sourceMap: true 26 | }), 27 | new OptimizeCssAssetsPlugin() 28 | ] 29 | }, 30 | plugins: [ 31 | new CleanPlugin([buildOutputDir], { 32 | root: process.cwd() 33 | }), 34 | new MiniCssExtractPlugin({ 35 | filename: '[name].css', 36 | chunkFilename: '[id].css' 37 | }), 38 | new CopyWebpackPlugin([ 39 | { from: path.join(__dirname, '../demos/**/*'), to: buildOutputDir, toType: 'dir' }, 40 | { from: path.join(__dirname, '../demos/favicon.ico'), to: buildOutputDir, toType: 'dir' }, 41 | ]) 42 | ], 43 | module: { 44 | rules: [ 45 | { 46 | test: /\.(sc|c)ss$/, 47 | use: [ 48 | { loader: MiniCssExtractPlugin.loader }, 49 | { loader: 'css-loader', options: { sourceMap: true } }, 50 | { loader: 'postcss-loader' }, 51 | { loader: 'resolve-url-loader' }, 52 | { loader: 'sass-loader', options: { sourceMap: true } } 53 | ], 54 | exclude: /node_modules/ 55 | } 56 | ] 57 | } 58 | }); 59 | -------------------------------------------------------------------------------- /demos/common/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | demos 布局 4 | 5 | 6 | 21 | 22 | -------------------------------------------------------------------------------- /demos/components/checkbox/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author jianzhe.ding 3 | * @homepage https://github.com/discipled/ 4 | * @since 2016-07-07 17:36 5 | */ 6 | 7 | ;(function (angular, undefined) { 8 | 9 | 'use strict'; 10 | 11 | angular 12 | .module('app', ['ccms.components']) 13 | .controller('ctrl', function ($scope) { 14 | 15 | $scope.demo2 = false; 16 | $scope.demo3 = { 17 | trueValue: 'up', 18 | falseValue: 'down', 19 | state: 'up' 20 | }; 21 | $scope.demo4 = { 22 | value: false, 23 | indeterminate: true 24 | }; 25 | $scope.demo5 = { 26 | value: false, 27 | disabled: true 28 | }; 29 | 30 | $scope.change = function () { 31 | console.log('something changed.'); 32 | } 33 | }); 34 | 35 | })(window.angular); 36 | 37 | -------------------------------------------------------------------------------- /demos/components/form/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-08 5 | */ 6 | ;(function(angular, undefined) { 7 | 8 | 'use strict'; 9 | 10 | angular 11 | .module('app', ['ccms.components']) 12 | .controller('appCtrl', function($ccValidator) { 13 | 14 | this.validators = { 15 | 16 | required: '要填东西哦亲!', 17 | 18 | number: /(^\s*$)|(^\d+$)/, 19 | 20 | handsome: { 21 | msg: '不够帅', 22 | fn: (modelValue, viewValue) => { 23 | const value = modelValue || viewValue; 24 | return value ? value.startsWith('kuitos') : !value; 25 | } 26 | }, 27 | 28 | letter: { 29 | msg: '必须为字母', 30 | regex: /(^\s*$)|(^\w+$)/ 31 | } 32 | 33 | }; 34 | 35 | this.reset = function(formCtrl) { 36 | $ccValidator.setPristine(formCtrl); 37 | }; 38 | 39 | this.validateGay = function() { 40 | $ccValidator.validate(this.gay).then(() => { 41 | console.log('校验成功!'); 42 | }, () => { 43 | console.log('校验失败!'); 44 | }); 45 | }; 46 | 47 | this.validate = function() { 48 | 49 | $ccValidator.validate(this.guy).then(() => { 50 | console.log('校验成功!'); 51 | }, () => { 52 | console.log('校验失败!'); 53 | }); 54 | 55 | }; 56 | }); 57 | 58 | })(window.angular); 59 | 60 | -------------------------------------------------------------------------------- /demos/components/grid/customer-footer.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /demos/components/grid/customer-row.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /demos/components/grid/external-data-grid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | External data grid 4 | 5 | 6 | 22 | 23 |
24 | 25 |
26 | 27 | 表格宽高由用户设定,默认继承自父容器 | 自定义返回列表数据字段名 28 | 姓名: 29 | 31 | 32 |

{{app.selectedItems}}

33 |
34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /demos/components/grid/test.html: -------------------------------------------------------------------------------- 1 | 这几乎是全天下最好用的grid -------------------------------------------------------------------------------- /demos/components/instant-search/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | instant search demo 4 | 5 | 6 | 7 |
8 |
9 |

local data

10 |
11 | 16 |
17 |
18 |

remote data

19 |
20 | 25 |
26 |
27 | 28 | 29 | 75 | 76 | -------------------------------------------------------------------------------- /demos/components/loading/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | loading 4 | 5 | 6 | 7 | 24 | 25 |
26 | 27 | 请耐心等待 28 |
29 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | -------------------------------------------------------------------------------- /demos/components/menus/menus-flex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 菜单栏 4 | 5 |
6 | 7 |
8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /demos/components/menus/menus.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 菜单栏 4 | 5 | 6 | 7 | 8 |
9 |
10 |
11 | 24 | 25 |
26 | 27 |
28 | 29 |
30 |
31 | right 32 |
33 |
34 |
35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /demos/components/menus/style.css: -------------------------------------------------------------------------------- 1 | .menu-goto-old{ 2 | height: 30px; 3 | line-height: 30px; 4 | background-color: #829abe; 5 | cursor: pointer; 6 | } 7 | .menu-goto-old:hover{ 8 | opacity: 0.9; 9 | } 10 | .menu-goto-old span{ 11 | color: #fff; 12 | font-size: 14px; 13 | } 14 | .menu-goto-old span:hover{ 15 | color: #fff; 16 | } 17 | -------------------------------------------------------------------------------- /demos/components/menus/tpls/shop-item.css: -------------------------------------------------------------------------------- 1 | .wechat-box { 2 | font-size: 14px; 3 | color: #08bb0b; 4 | display: inline-block; 5 | position: relative; 6 | } 7 | 8 | .tooltip-box { 9 | display: none; 10 | position: absolute; 11 | top: 20px; 12 | background: #ffffcc; 13 | width: 500px; 14 | height: 100px; 15 | border: solid 1px red; 16 | z-index: 45; 17 | } 18 | 19 | .wechat-box:hover .tooltip-box { 20 | display: block; 21 | } -------------------------------------------------------------------------------- /demos/components/menus/tpls/shop-item.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /demos/components/modal/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | modal 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /demos/components/modal/modal-body-child-child.tpl.html: -------------------------------------------------------------------------------- 1 | child-child 2 | -------------------------------------------------------------------------------- /demos/components/modal/modal-body-child.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /demos/components/modal/modal-body-parent.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /demos/components/modal/modal-body.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 6 | 7 |
8 | 9 | 10 | 13 | 14 | 15 |
16 | 17 |
18 | 19 |
20 | 21 |
22 |

短信编辑器结果:

23 |
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
66 | -------------------------------------------------------------------------------- /demos/components/modal/modal-footer.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | -------------------------------------------------------------------------------- /demos/components/modal/modal-header.tpl.html: -------------------------------------------------------------------------------- 1 | 5 |

6 | 7 | 8 |

9 |
10 | 11 | 12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /demos/components/pagination/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 分页(cc-pagination) 4 | 5 | 6 | 7 | 16 | 17 |
18 |
19 |

type: normal

20 | 27 |
28 |
29 |

type: simple

30 | 38 |
39 |
40 |

type: normal & disable pagesize list

41 | 49 |
50 |
51 |

type: normal & hide pagesize list

52 | 60 |
61 |
62 | 63 | 64 | 91 | 92 | -------------------------------------------------------------------------------- /demos/components/radio-button/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | radio-button 4 | 5 | 6 | 7 | 26 | 27 |
28 |

Radio button Demo

29 |

Support Attribute

30 | 35 | 36 |
37 |

Demo

38 |

ng-model & ng-value should be setting on every radio button component.

39 | value: {{demo.value}}
40 | disabled: {{demo.disabled}}
41 | {{item}} 42 | d 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /demos/components/radio-button/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author jianzhe.ding 3 | * @homepage https://github.com/discipled/ 4 | * @since 2016-07-07 17:36 5 | */ 6 | 7 | ;(function (angular, undefined) { 8 | 9 | 'use strict'; 10 | 11 | angular 12 | .module('app', ['ccms.components']) 13 | .controller('ctrl', function ($scope) { 14 | 15 | $scope.demo = { 16 | value: 'a', 17 | setting: ['a', 'b', 'c'], 18 | disabled: false 19 | }; 20 | 21 | $scope.generatorValues = () =>{ 22 | $scope.demo.setting = $scope.demo.setting.map(() => Math.floor(Math.random() * 10)); 23 | }; 24 | 25 | $scope.change = function () { 26 | console.log('something changed.'); 27 | } 28 | }); 29 | 30 | })(window.angular); 31 | 32 | -------------------------------------------------------------------------------- /demos/components/styles/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 样式 4 | 5 | 6 | 7 | 31 | 32 |
33 |

文本框

34 | 35 |
36 |
37 | 38 | 39 | 40 |
41 |
42 | 43 | 44 | 45 | 46 |
47 |
48 | 49 | 50 |
51 |
52 | 55 | 56 |
57 |
58 | 59 |

按钮

60 | 61 |
62 |

说明:

63 |
    64 |
  1. 按钮样式不带 margin, 放在具体使用环境中调整
  2. 65 |
  3. 样式有按颜色, 大小分的基础款 + 拼装好的常用款
  4. 66 |
67 |
68 | 69 |
70 |

基本按钮

71 |
.btn-ok: .btn-primary + .btn-large
72 |
.btn-cancel: .btn-normal + .btn-large
73 | 74 |
75 | 76 | 77 | 78 | 79 | 80 |
81 |
82 | 83 |
84 |

功能性按钮

85 |

注: icon 和文字的间距, 用空格即可

86 |
.btn-function-normal: .btn-primary + .btn-large + .btn-auto-width
87 |
.btn-function-highlight: .btn-normal + .btn-large + .btn-auto-width
88 | 89 |
90 | 91 | 92 | 93 | 94 |
95 |
96 | 97 |
98 |

可拼装 placeholder

99 |

按颜色:

100 |
%btn-normal, %btn-primary, %btn-text, %btn-text-primary
101 | 102 |

按大小

103 |
%btn-base, %btn-large, %btn-small, %btn-auto-width
104 |
105 |
106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /demos/components/tabset/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | tabset demo 4 | 5 | 6 | 7 |
8 | 9 |
10 | 11 | 12 | 13 | 14 |
15 |
查询的结果共 0 条
16 |
根据您的搜索提示为您推荐 百度一下>>
17 |
18 |
19 | 20 | 21 |
22 |
404
23 |
not a found
24 |
25 |
26 | 27 | 28 |
29 | 30 |
31 |
32 | 33 |
34 | 35 |
36 |
37 |
38 |
39 | 40 | 41 | 42 | 43 |
44 |
查询的结果共 0 条
45 |
根据您的搜索提示为您推荐 百度一下>>
46 |
47 |
48 | 49 | 50 |
51 |
404
52 |
not a found
53 |
54 |
55 | 56 |
57 |
58 | 59 | 60 | 77 | 78 | -------------------------------------------------------------------------------- /demos/components/tabset/tpl/tabset-content.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /demos/components/tabset/tpl/url.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

{{form.input.$error}}

5 |
6 |
7 | -------------------------------------------------------------------------------- /demos/components/tips/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | cc-tips 4 | 5 | 6 | 7 |
8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /demos/components/tips/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-08 5 | */ 6 | ;(function (angular, undefined) { 7 | 8 | 'use strict'; 9 | 10 | angular 11 | .module('app', ['ccms.components', 'ui.router']) 12 | .config(function ($stateProvider, $urlRouterProvider) { 13 | 14 | $urlRouterProvider.otherwise('/app'); 15 | 16 | $stateProvider 17 | .state('app', { 18 | url: '/app', 19 | template: '
' + 20 | '

第一层容器

' + 21 | '' + 25 | '' + 26 | '
' 27 | }) 28 | .state('app.container1', { 29 | url: '/module1', 30 | template: '第二层容器:container1' 31 | }) 32 | .state('app.container2', { 33 | url: '/module2', 34 | template: '第二层容器:container2' 35 | }); 36 | 37 | }) 38 | .controller('ctrl', function($scope, $ccTips, $ccModal) { 39 | 40 | let tips = null; 41 | 42 | $scope.showSuccess = function() { 43 | $ccTips.success('成功提示'); 44 | }; 45 | 46 | $scope.showError = function() { 47 | if (!tips || !tips.element) { 48 | tips = $ccTips.error('出错提示这个提示需要手动关闭' + Math.random()); 49 | } 50 | }; 51 | 52 | $scope.showAutoCloseError = function() { 53 | if (!tips || !tips.element) { 54 | tips = $ccTips.error('出错提示这个提示3秒后将自动关闭' + Math.random(), true); 55 | } 56 | }; 57 | 58 | $scope.showModal = function() { 59 | $ccModal.modal({ 60 | 61 | title: 'tips in modal', 62 | body: '/demos/components/tips/modal-body.tpl.html', 63 | controller: ['$element', function($element) { 64 | 65 | this.showSuccess = function() { 66 | $ccTips.success('hhhhh', $element[0].querySelector('.modal-body')); 67 | }; 68 | 69 | this.showError = function() { 70 | $ccTips.error('sssssss', $element[0].querySelector('.modal-body')); 71 | }; 72 | 73 | }] 74 | 75 | }).open(); 76 | }; 77 | }); 78 | 79 | })(window.angular); 80 | 81 | -------------------------------------------------------------------------------- /demos/components/tips/modal-body.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |

9 | 10 |
11 | 12 | 13 | {{a}} 14 |
15 |
16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 |
30 | -------------------------------------------------------------------------------- /demos/components/title/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Title组件 4 | 5 | 6 | 7 |
8 | 9 |
10 | 不配置name属性的使用请移步 Menus 11 |
12 |
13 | 14 | 15 | 24 | 25 | -------------------------------------------------------------------------------- /demos/components/toggle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 开关 cc-toggle 4 | 5 | 6 | 7 |
8 |

Demo1

9 |

value: {{demo1}}

10 | 11 | 12 |

value: {{demo1}}

13 | 14 | 15 |

value: {{demo1}}

16 | 17 | 18 |

Demo2

19 |

value: {{demo2}} | disabled: {{demo2Disabled}}

20 | 21 | 22 | 23 |

Demo3: 自定义值

24 |

value: {{demo3}}

25 | 26 | 27 |

Demo4: disabled

28 | 29 |
30 | 31 | 32 | 43 | 44 | -------------------------------------------------------------------------------- /demos/components/tooltip/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-29 5 | */ 6 | 7 | angular 8 | .module('app', ['ccms.components']) 9 | .controller('appCtrl', function () { 10 | 11 | this.entity = ['A', 'B', 'C']; 12 | 13 | this.join = function (entity) { 14 | return entity.join(','); 15 | }; 16 | 17 | this.opened = true; 18 | 19 | this.click = function (i) { 20 | console.log(i); 21 | }; 22 | 23 | this.template = '
' + 24 | '
' + 25 | '
'; 26 | this.style = {'z-index': 1000}; 27 | }); 28 | -------------------------------------------------------------------------------- /demos/components/tree/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 |
15 |

搜索、菜单、复选、最大可新增层级为3

16 |
17 | 33 | 34 |
35 | 36 |

无搜索、无菜单、带单选

37 |
38 | 46 | 47 |
48 | 49 | 50 |

无搜索、无菜单、无选择

51 |
52 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /demos/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShuyunFF2E/ccms-components/b55a1e6af3bd6db426be449c56e2f7d9a50a7eb4/demos/favicon.ico -------------------------------------------------------------------------------- /demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 数云 angular 组件库 demos 4 | 5 | 6 | 13 | 14 |
15 |

导航菜单文件: /demos/common/nav.html

16 |

组件 demos 路径: /demos/components

17 |

创建新的组件 demo 请使用以下模板:

18 |
19 | <!DOCTYPE html>
20 | <meta charset="UTF-8">
21 | <title>组件名称</title>
22 | <link rel="import" href="/demos/common/layout.html">
23 | 
24 | <style>
25 |   /* demo css */
26 | </style>
27 | 
28 | <main ng-app="..." ng-controller="...">
29 |   <!-- demo html -->
30 | </main>
31 | 
32 | <script src="/components.js"></script>
33 | <script>
34 |   // demo js
35 | </script>
36 | 	
37 |
38 | 39 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./ccms-components.css'); 2 | require('./ccms-components.js'); 3 | module.exports = 'ccms.components'; 4 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer')({browsers: ['Chrome > 35', 'Firefox > 30', 'Safari > 7']}) 4 | ] 5 | }; 6 | -------------------------------------------------------------------------------- /src/assets/images/logo-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShuyunFF2E/ccms-components/b55a1e6af3bd6db426be449c56e2f7d9a50a7eb4/src/assets/images/logo-default.png -------------------------------------------------------------------------------- /src/common/bases/Popup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-10 5 | */ 6 | 7 | import { addOnceEventListener } from '../../common/utils/dom-event'; 8 | 9 | export default class Popup { 10 | 11 | constructor(element, container, openedClass, closedClass) { 12 | this.element = element; 13 | this.container = container; 14 | this.openedClass = openedClass; 15 | this.closedClass = closedClass; 16 | this.hashChangeListener = null; 17 | } 18 | 19 | /** 20 | * @abstract 21 | */ 22 | open() { 23 | 24 | } 25 | 26 | _onHashChange(onChange) { 27 | // 路由发生变更时自动关闭弹出框 28 | // FIXME 因为会存在路由切换过程中弹出modal的场景,所以必须确保路由切换完成之后才对modal做新的hashchange绑定 29 | setTimeout(() => { 30 | this.hashChangeListener = addOnceEventListener(window, 'hashchange', (onChange || this.close).bind(this)); 31 | }, 0); 32 | } 33 | 34 | _offHashChange() { 35 | window.removeEventListener('hashchange', this.hashChangeListener); 36 | } 37 | 38 | /** 39 | * @abstract 40 | */ 41 | close() { 42 | 43 | } 44 | 45 | /** 46 | * @protected 47 | * @param append2Container 48 | * @param referNode 49 | */ 50 | init(append2Container = true, referNode) { 51 | this.container[append2Container ? 'appendChild' : 'insertBefore'](this.element, referNode); 52 | // 强制浏览器渲染dom(触发相应transition/animation) 53 | this.element.offsetHeight; 54 | } 55 | 56 | /** 57 | * @protected 58 | */ 59 | destroy() { 60 | this.container.removeChild(this.element); 61 | this.element = this.container = this.openedClass = this.closedClass = null; 62 | } 63 | 64 | /** 65 | * @protected 66 | */ 67 | show() { 68 | if (this.openedClass) { 69 | this.element.classList.add(this.openedClass); 70 | } 71 | } 72 | 73 | /** 74 | * @protected 75 | */ 76 | hide() { 77 | this.element.classList.remove(this.openedClass); 78 | if (this.closedClass) { 79 | this.element.classList.add(this.closedClass); 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/common/bases/constant.js: -------------------------------------------------------------------------------- 1 | const DISPLAY_FORMAT = { 2 | 'YYYYMMDD': 0, 3 | 'MMDD': 1, 4 | 'DD': 2, 5 | 'YYYYMMDDhhmm': 3 6 | }; 7 | 8 | export { 9 | DISPLAY_FORMAT 10 | }; 11 | -------------------------------------------------------------------------------- /src/common/utils/browser/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-08-15 5 | */ 6 | 7 | const browser = (function() { 8 | const ua = navigator.userAgent; 9 | 10 | let OSName = 'Unknown OS'; 11 | if (navigator.appVersion.indexOf('Win') !== -1) OSName = 'Windows'; 12 | if (navigator.appVersion.indexOf('Mac') !== -1) OSName = 'MacOS'; 13 | if (navigator.appVersion.indexOf('X11') !== -1) OSName = 'UNIX'; 14 | if (navigator.appVersion.indexOf('Linux') !== -1) OSName = 'Linux'; 15 | 16 | let tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; 17 | if (/trident/i.test(M[1])) { 18 | tem = /\brv[ :]+(\d+)/g.exec(ua) || []; 19 | return { 20 | name: 'IE', 21 | version: (tem[1] || ''), 22 | os: OSName 23 | }; 24 | } 25 | if (M[1] === 'Chrome') { 26 | tem = ua.match(/\b(OPR|Edge)\/(\d+)/); 27 | if (tem != null) return tem.slice(1).join(' ').replace('OPR', 'Opera'); 28 | } 29 | M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?']; 30 | if ((tem = ua.match(/version\/(\d+)/i)) != null) M.splice(1, 1, tem[1]); 31 | return { 32 | name: M[0], 33 | version: M[1], 34 | os: OSName 35 | }; 36 | }()); 37 | 38 | export default browser; 39 | -------------------------------------------------------------------------------- /src/common/utils/dom-event/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-08-15 5 | */ 6 | 7 | 8 | function addOnceEventListener(element, event, listener) { 9 | 10 | const onceListener = () => { 11 | listener(); 12 | element.removeEventListener(event, onceListener); 13 | }; 14 | 15 | element.addEventListener(event, onceListener); 16 | 17 | return onceListener; 18 | } 19 | 20 | export { 21 | addOnceEventListener 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /src/common/utils/highlight/index.js: -------------------------------------------------------------------------------- 1 | export default function highlight() { 2 | return function(content, hiText) { 3 | return content.replace(new RegExp(hiText, 'gi'), '$&'); 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /src/common/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-01-11 5 | */ 6 | 7 | import angular from 'angular'; 8 | 9 | export default angular 10 | .module('ccms.components.utils', []) 11 | .name; 12 | -------------------------------------------------------------------------------- /src/common/utils/object/filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-09-07 5 | */ 6 | 7 | export default (obj, iterator) => { 8 | 9 | const result = {}; 10 | 11 | for (let prop in obj) { 12 | if (obj.hasOwnProperty(prop)) { 13 | if (iterator(obj[prop], prop)) { 14 | result[prop] = obj[prop]; 15 | } 16 | } 17 | } 18 | 19 | return result; 20 | }; 21 | -------------------------------------------------------------------------------- /src/common/utils/performance/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-01-11 5 | */ 6 | 7 | import {Inject} from 'angular-es-utils'; 8 | import throttle from './throttle'; 9 | 10 | @Inject('$timeout') 11 | export default class Performance { 12 | 13 | constructor($timeout) { 14 | this.$timeout = $timeout; 15 | } 16 | 17 | /** 18 | * 函数只会在确定时间延时后才会被触发(只会执行间隔时间内的最后一次调用) 19 | */ 20 | debounce(func, delay, scope, invokeApply) { 21 | 22 | const $timeout = this.$timeout; 23 | let timer; 24 | 25 | return (...args) => { 26 | const context = scope; 27 | 28 | $timeout.cancel(timer); 29 | timer = $timeout(() => { 30 | 31 | timer = undefined; 32 | func.apply(context, args); 33 | 34 | }, delay || 300, invokeApply); 35 | }; 36 | } 37 | 38 | throttle(...args) { 39 | return throttle(...args); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/common/utils/performance/throttle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-02 5 | */ 6 | 7 | export default function throttle(func, delay, context = null) { 8 | let recent; 9 | return (...args) => { 10 | const now = Date.now(); 11 | 12 | if (!recent || (now - recent > (delay || 10))) { 13 | func.apply(context, args); 14 | recent = now; 15 | } 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/common/utils/tpl-req-helper/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-02-25 5 | * 模板获取帮助类 6 | */ 7 | 8 | import injector from 'angular-es-utils/injector'; 9 | 10 | // 模板正则 11 | const TEMPLATE_REGEXP = /<.+>/; 12 | 13 | export default { 14 | 15 | get(tpl) { 16 | 17 | return injector.get('$q')(resolve => { 18 | 19 | // 如果是一个模板url 20 | if (!TEMPLATE_REGEXP.test(tpl)) { 21 | injector.get('$templateRequest')(tpl).then(template => resolve(template)); 22 | } else { 23 | resolve(tpl); 24 | } 25 | 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/alt-src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created with index.js.js 3 | * @Author: maxsmu 4 | * @Homepage: https://github.com/maxsmu 5 | * @Date: 2016-12-22 PM3:28 6 | */ 7 | import angular from 'angular'; 8 | 9 | const DDO = { 10 | restrict: 'A', 11 | link: (scope, element) => { 12 | const imgElement = element[0]; 13 | imgElement.onerror = event => { 14 | event.target.src = scope.ccAltSrc; 15 | }; 16 | }, 17 | scope: { 18 | ccAltSrc: '<' 19 | } 20 | }; 21 | export default angular 22 | .module('components.imgAltSrc', []) 23 | .directive('ccAltSrc', () => DDO) 24 | .name; 25 | -------------------------------------------------------------------------------- /src/components/bind-html/BindHtmlCtrl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-08-17 5 | */ 6 | 7 | import { Inject } from 'angular-es-utils/decorators'; 8 | 9 | function isPromiseLik(object) { 10 | return object && typeof object.then === 'function'; 11 | } 12 | 13 | @Inject('$element', '$compile', '$scope') 14 | export default class BindHtmlCtrl { 15 | 16 | $onChanges(changesObj) { 17 | 18 | const contentObj = changesObj.content; 19 | 20 | if (contentObj.currentValue !== contentObj.previousValue || contentObj.isFirstChange()) { 21 | this.compileContent(contentObj.currentValue); 22 | } 23 | 24 | } 25 | 26 | compileContent(content) { 27 | 28 | const compile = tpl => { 29 | this._$element[0].innerHTML = tpl; 30 | this._$compile(this._$element[0].childNodes)(this._$scope); 31 | }; 32 | 33 | if (isPromiseLik(content)) { 34 | content.then(tpl => compile(tpl)); 35 | } else { 36 | compile(content); 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/bind-html/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-08 5 | */ 6 | import angular from 'angular'; 7 | 8 | import controller from './BindHtmlCtrl'; 9 | 10 | const bindHTMLDDO = { 11 | name: 'ccBindHtml', 12 | restrict: 'A', 13 | controller, 14 | controllerAs: '$$bindHtmlCtrl', 15 | bindToController: { 16 | content: ' bindHTMLDDO) 22 | .name; 23 | -------------------------------------------------------------------------------- /src/components/calendar/Calendar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by AshZhang on 2016-3-5. 3 | */ 4 | 5 | 6 | import './_calendar.scss'; 7 | import template from './calendar.tpl.html'; 8 | import CalendarCtrl from './CalendarCtrl'; 9 | 10 | 11 | export default { 12 | 13 | bindToController: true, 14 | controller: CalendarCtrl, 15 | controllerAs: 'ctrl', 16 | replace: true, 17 | require: ['^ccDatePicker', '^^?ccDateRange'], 18 | restrict: 'E', 19 | scope: { 20 | onCalendarOpen: '&?', 21 | onCalendarClose: '&?', 22 | defaultCalendarValue: ' select { 28 | font-size: 12px; 29 | height: 20px; 30 | margin: 6px 2px 0; 31 | width: 80px; 32 | } 33 | } 34 | 35 | .calendar-prev, 36 | .calendar-next { 37 | color: #b7b7b7; 38 | cursor: pointer; 39 | font-size: 12px; 40 | line-height: 30px; 41 | width: 30px; 42 | -webkit-user-select: none; 43 | } 44 | 45 | .calendar-prev { 46 | float: left; 47 | transform: rotate(180deg); 48 | } 49 | 50 | .calendar-next { 51 | float: right; 52 | } 53 | 54 | .calendar-week-title { 55 | color: #2d2d2d; 56 | font-size: 12px; 57 | line-height: 32px; 58 | margin: 0 2px; 59 | overflow: auto; 60 | padding: 0; 61 | } 62 | 63 | .calendar-days { 64 | color: #3d3d3d; 65 | font-size: 12px; 66 | line-height: 24px; 67 | margin: 0 3px 3px; 68 | overflow: auto; 69 | padding: 0; 70 | width: 224px; 71 | 72 | > li { 73 | border: 1px solid #fff; 74 | cursor: pointer; 75 | margin: 1px; 76 | width: 28px; 77 | 78 | &:hover { 79 | border-color: #aacbe1; 80 | } 81 | 82 | &.disabled { 83 | background: #fff; 84 | border-color: #fff; 85 | color: #c0c0c0; 86 | cursor: not-allowed; 87 | } 88 | 89 | &.in-range { 90 | background: #ecf8fc; 91 | } 92 | 93 | &.active { 94 | background: #ecf8fc; 95 | border-color: #aacbe1; 96 | color: #0083ba; 97 | } 98 | 99 | &.festival { 100 | color: #ff7800; 101 | } 102 | } 103 | } 104 | 105 | .calendar-day-other-month { 106 | color: #c0c0c0; 107 | } 108 | 109 | .calendar-time { 110 | color: #3d3d3d; 111 | padding: 10px 12px; 112 | text-align: left; 113 | } 114 | 115 | .calendar .calendar-time-input { 116 | border: 1px solid #d9d9d9; 117 | font-size: 12px; 118 | height: 24px; 119 | line-height: 24px; 120 | margin: 0 1px; 121 | outline: 0; 122 | padding: 0; 123 | text-align: center; 124 | width: 44px !important; 125 | } 126 | 127 | .calendar-buttons { 128 | overflow: auto; 129 | padding: 8px 12px; 130 | text-align: left; 131 | } 132 | 133 | .calendar-button-cancel { 134 | @extend %button; 135 | } 136 | 137 | .calendar-button-ok { 138 | @extend %button; 139 | @extend %btn-primary; 140 | float: right; 141 | } 142 | -------------------------------------------------------------------------------- /src/components/calendar/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by AshZhang on 2016-3-5. 3 | */ 4 | 5 | 6 | import angular from 'angular'; 7 | 8 | import ddo from './Calendar'; 9 | 10 | 11 | export default angular 12 | .module('ccms.components.calendar', []) 13 | .directive('ccCalendar', () => ddo) 14 | .name; 15 | -------------------------------------------------------------------------------- /src/components/capture-event/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * copy from https://github.com/angular/angular.js/blob/master/src/ng/directive/ngEventDirs.js 3 | * 支持捕获事件 4 | * */ 5 | 6 | import angular from 'angular'; 7 | 8 | let ngEventDirectives = {}; 9 | 10 | let directiveNormalize = directiveName => { 11 | return directiveName.replace(/-([a-z])/g, g => g[1].toUpperCase()); 12 | }; 13 | 14 | let forceAsyncEvents = { 15 | 'blur': true, 16 | 'focus': true 17 | }; 18 | 19 | angular.forEach( 20 | 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 21 | function(eventName) { 22 | let directiveName = directiveNormalize('cc-capture-' + eventName); 23 | ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { 24 | return { 25 | restrict: 'A', 26 | compile: function($element, attr) { 27 | var fn = $parse(attr[directiveName]); 28 | return function ngEventHandler(scope, element) { 29 | element[0].addEventListener(eventName, function(event) { 30 | var callback = function() { 31 | fn(scope, {$event: event}); 32 | }; 33 | if (forceAsyncEvents[eventName] && $rootScope.$$phase) { 34 | scope.$evalAsync(callback); 35 | } else { 36 | scope.$apply(callback); 37 | } 38 | }, true); 39 | }; 40 | } 41 | }; 42 | }]; 43 | } 44 | ); 45 | 46 | export default angular.module('ccms.components.captureEvent', []) 47 | .directive(ngEventDirectives) 48 | .name; 49 | -------------------------------------------------------------------------------- /src/components/checkbox/CheckboxCtrl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author jianzhe.ding 3 | * @homepage https://github.com/discipled/ 4 | * @since 2016-07-07 17:36 5 | */ 6 | 7 | export default class CheckboxController { 8 | /** 9 | * @name $onInit 10 | * controller init method, init true, false values 11 | */ 12 | $onInit() { 13 | this.ngTrueValue = this.ngModelController ? (typeof this.ngTrueValue === 'undefined' ? true : this.ngTrueValue) : true; 14 | this.ngFalseValue = this.ngModelController ? (typeof this.ngFalseValue === 'undefined' ? false : this.ngFalseValue) : false; 15 | this.ngChecked = ('ngModel' in this) ? this.ngModel === this.ngTrueValue : this.ngChecked; 16 | } 17 | 18 | /** 19 | * @name $onChanges 20 | * @param {object} opt 21 | * watch the ng-model change situation, when ngModel changed, the checked value changes 22 | */ 23 | $onChanges(opt) { 24 | opt.ngModel && (this.ngChecked = opt.ngModel.currentValue === this.ngTrueValue); 25 | } 26 | 27 | /** 28 | * @name toggleClick 29 | * toggle the checked value when user click the checkbox 30 | */ 31 | toggleClick($event) { 32 | if (this.ngDisabled) { 33 | // 当 checkbox 不可用时, 阻止其它注册的 click listener 34 | $event.stopImmediatePropagation(); 35 | return false; 36 | } 37 | this.indeterminate = false; 38 | this.ngChecked = !this.ngChecked; 39 | this.ngModelController && this.updateNgModel(this.ngChecked); 40 | } 41 | 42 | /** 43 | * @name updateNgModel 44 | * @param {boolean} checked 45 | * update ng-model value 46 | */ 47 | updateNgModel(checked) { 48 | let value = checked ? this.ngTrueValue : this.ngFalseValue; 49 | this.ngModelController.$setViewValue(value); // change model value & $setViewValue method will trigger method binding on model 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/components/checkbox/__tests__/test-CheckboxCtrl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author jianzhe.ding 3 | * @homepage https://github.com/discipled/ 4 | * @since 2016-08-03 10:44 5 | */ 6 | 7 | import { assert } from 'chai'; 8 | 9 | import CheckboxCtrl from '../CheckboxCtrl.js'; 10 | 11 | describe('CheckboxCtrl', () => { 12 | let checkboxCtrl, bindings, $event, marked; 13 | 14 | before(() => { 15 | checkboxCtrl = new CheckboxCtrl(); 16 | 17 | // mock ngModelController 18 | checkboxCtrl.ngModelController = { 19 | $setViewValue: function(value) { 20 | checkboxCtrl.ngModel = value; 21 | } 22 | }; 23 | 24 | marked = 0; 25 | 26 | $event = { 27 | stopImmediatePropagation() { 28 | marked++; 29 | } 30 | }; 31 | }); 32 | 33 | it('$onInit', () => { 34 | bindings = { 35 | ngModel: false 36 | }; 37 | Object.assign(checkboxCtrl, bindings); 38 | checkboxCtrl.$onInit(); 39 | assert.deepEqual(checkboxCtrl.ngChecked, false); 40 | 41 | delete checkboxCtrl.ngModel; 42 | bindings = { 43 | ngChecked: true 44 | }; 45 | Object.assign(checkboxCtrl, bindings); 46 | checkboxCtrl.$onInit(); 47 | assert.deepEqual(checkboxCtrl.ngChecked, true); 48 | }); 49 | 50 | it('$onChanges', () => { 51 | bindings = { 52 | ngModel: 'f', 53 | ngTrueValue: 't', 54 | ngFalseValue: 'f' 55 | }; 56 | Object.assign(checkboxCtrl, bindings); 57 | checkboxCtrl.$onInit(); 58 | assert.deepEqual(checkboxCtrl.ngChecked, false); 59 | checkboxCtrl.$onChanges({ngModel: {currentValue: 't'}}); 60 | assert.deepEqual(checkboxCtrl.ngChecked, true); 61 | }); 62 | 63 | it('toggleClick', () => { 64 | assert.deepEqual(checkboxCtrl.ngChecked, true); 65 | checkboxCtrl.toggleClick($event); 66 | assert.equal(marked, 0); 67 | assert.deepEqual(checkboxCtrl.ngChecked, false); 68 | assert.deepEqual(checkboxCtrl.ngModel, 'f'); 69 | 70 | bindings = { 71 | ngDisabled: true 72 | }; 73 | Object.assign(checkboxCtrl, bindings); 74 | checkboxCtrl.toggleClick($event); 75 | assert.equal(marked, 1); 76 | assert.deepEqual(checkboxCtrl.ngChecked, false); 77 | assert.deepEqual(checkboxCtrl.ngModel, 'f'); 78 | 79 | bindings = { 80 | ngDisabled: false, 81 | indeterminate: true 82 | }; 83 | Object.assign(checkboxCtrl, bindings); 84 | assert.deepEqual(checkboxCtrl.indeterminate, true); 85 | checkboxCtrl.toggleClick($event); 86 | assert.equal(marked, 1); 87 | assert.deepEqual(checkboxCtrl.indeterminate, false); 88 | assert.deepEqual(checkboxCtrl.ngChecked, true); 89 | assert.deepEqual(checkboxCtrl.ngModel, 't'); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /src/components/checkbox/_checkbox.scss: -------------------------------------------------------------------------------- 1 | $default-font-size: 12px !default; 2 | $font-size-number: 12; 3 | $text-color: #3d3d3d !default; 4 | $default-border-width: 2px; 5 | $normal-border-color: #929292; 6 | $normal-hover-border-color: #828282; 7 | $checked-border-color: #0083BA; 8 | $checked-hover-border-color: #00AAF1; 9 | $disabled-color: #D1D1D1; 10 | 11 | cc-checkbox{ 12 | display: inline-block; 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | } 18 | 19 | .cc-checkbox{ 20 | font-size: $default-font-size; 21 | display: flex; 22 | align-items: center; 23 | 24 | &:hover{ 25 | cursor: pointer; 26 | 27 | .cc-checkbox-input{ 28 | border-color: $normal-hover-border-color; 29 | } 30 | } 31 | 32 | .cc-checkbox-input { 33 | border: solid 1px $normal-border-color; 34 | border-radius: $default-border-width; 35 | position: relative; 36 | width: 16px; 37 | height: 16px; 38 | margin-right: 12px; 39 | 40 | &:before{ 41 | position: absolute; 42 | content: ''; 43 | display: block; 44 | } 45 | } 46 | 47 | .cc-checkbox-label{ 48 | white-space: nowrap; 49 | color: $text-color; 50 | } 51 | } 52 | 53 | .cc-checkbox.checked{ 54 | &:hover{ 55 | .cc-checkbox-input{ 56 | border-color: $checked-hover-border-color; 57 | 58 | &:before{ 59 | border-color: $checked-hover-border-color; 60 | } 61 | } 62 | } 63 | 64 | .cc-checkbox-input{ 65 | border-color: $checked-border-color; 66 | 67 | &:before{ 68 | width: 4px; 69 | height: 8px; 70 | transform: rotate(45deg); 71 | border-width: $default-border-width; 72 | border-style: solid; 73 | border-color: $checked-border-color; 74 | border-left: 0; 75 | border-top: 0; 76 | left: 4px; 77 | top: 1px; 78 | } 79 | } 80 | } 81 | 82 | .cc-checkbox.indeterminate{ 83 | &:hover{ 84 | .cc-checkbox-input{ 85 | border-color: $checked-hover-border-color; 86 | 87 | &:before{ 88 | border-color: $normal-hover-border-color; 89 | } 90 | } 91 | } 92 | 93 | .cc-checkbox-input{ 94 | border-color: $checked-border-color; 95 | 96 | &:before{ 97 | width: 0; 98 | height: 0; 99 | transform: rotate(0deg); 100 | border: (4 / $font-size-number) + em solid $normal-border-color; 101 | left: (3 / $font-size-number) + em; 102 | top: (3 / $font-size-number) + em; 103 | } 104 | } 105 | } 106 | 107 | .cc-checkbox.disabled{ 108 | &:hover{ 109 | cursor: default; 110 | 111 | .cc-checkbox-input{ 112 | border-color: $disabled-color; 113 | 114 | &:before{ 115 | border-color: $disabled-color; 116 | } 117 | } 118 | } 119 | 120 | .cc-checkbox-input{ 121 | border-color: $disabled-color; 122 | 123 | &:before{ 124 | border-color: $disabled-color; 125 | } 126 | } 127 | 128 | .cc-checkbox-label{ 129 | color: $disabled-color; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/components/checkbox/checkbox.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | -------------------------------------------------------------------------------- /src/components/checkbox/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author jianzhe.ding 3 | * @homepage https://github.com/discipled/ 4 | * @since 2016-07-07 17:36 5 | */ 6 | 7 | import angular from 'angular'; 8 | 9 | import './_checkbox.scss'; 10 | import controller from './CheckboxCtrl.js'; 11 | import template from './checkbox.tpl.html'; 12 | 13 | const ccmsCheckboxSetting = { 14 | controller, 15 | transclude: true, 16 | template, 17 | bindings: { 18 | ngChecked: ' { 55 | $scope.ctrl.parts = destructDate(ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null, $scope.ctrl.dateOnly); 56 | $scope.ctrl._displayValue = $scope.ctrl.toDateValue($scope.ctrl.parts); 57 | $scope.ctrl.checkValidity($scope.ctrl._displayValue); 58 | this.setAllInputsWidth($scope); 59 | }; 60 | }, 61 | 62 | 63 | setAllInputsWidth($scope) { 64 | for (let i = 0; i < $scope.inputs.length; i += 1) { 65 | setTextWidth($scope.inputs[i]); 66 | } 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /src/components/date-picker/_date-picker.scss: -------------------------------------------------------------------------------- 1 | @import "../styles/variables"; 2 | 3 | $active-color: #aacbe1; 4 | $normal-color: #c9c9c9; 5 | 6 | .date-picker-holder { 7 | display: inline-block; 8 | position: relative; 9 | width: 210px; 10 | 11 | &.ng-empty .date-picker-clear, 12 | &.disabled .date-picker-clear { 13 | display: none; 14 | } 15 | } 16 | 17 | .date-picker { 18 | background: #fff; 19 | border: 1px solid #d9d9d9; 20 | color: $normal-color; 21 | font-size: 12px; 22 | line-height: 28px; 23 | overflow: hidden; 24 | position: relative; 25 | 26 | .date-picker-input { 27 | border: 0 !important; 28 | box-shadow: none; 29 | box-sizing: content-box; 30 | color: #3d3d3d; 31 | font-family: $font-family; 32 | font-size: 12px; 33 | height: 28px; 34 | line-height: 28px; 35 | outline: 0; 36 | padding: 0 1px; 37 | width: 21px; 38 | 39 | &.ng-not-empty + .delimiter { 40 | color: #3d3d3d; 41 | } 42 | } 43 | 44 | &.disabled .date-picker-input { 45 | background: #eee; 46 | color: #999; 47 | } 48 | 49 | &.disabledInput .date-picker-input { 50 | background: #fff; 51 | color: #3d3d3d; 52 | pointer-events: none; 53 | } 54 | 55 | .date-picker-input-year { 56 | padding-left: 8px; 57 | width: 32px; 58 | } 59 | 60 | .date-picker-input-month { 61 | width: 22px; 62 | } 63 | 64 | .date-picker-input-date { 65 | width: 18px; 66 | } 67 | 68 | .date-picker-input-hour { 69 | width: 14px; 70 | } 71 | 72 | .date-picker-input-minute { 73 | width: 21px; 74 | } 75 | 76 | .date-picker-input-second { 77 | width: 14px; 78 | } 79 | 80 | .date-picker-icon { 81 | position: absolute; 82 | right: 0.5em; 83 | top: 0; 84 | } 85 | 86 | .date-picker-clear { 87 | position: absolute; 88 | right: 2em; 89 | top: 1px; 90 | } 91 | 92 | &.active { 93 | border-color: $active-color; 94 | 95 | .date-picker-icon { 96 | color: $active-color; 97 | } 98 | } 99 | 100 | &.disabled { 101 | background: #eee; 102 | } 103 | 104 | &.ng-invalid.ng-dirty { 105 | border-color: $color-error; 106 | 107 | .date-picker-icon { 108 | color: $normal-color; 109 | } 110 | } 111 | 112 | &.disabled .date-picker-clear { 113 | display: none; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/components/date-picker/dateUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by AshZhang on 2016-3-12. 3 | */ 4 | 5 | /** 6 | * 补零至两位数 7 | * @param val 8 | * @returns {string} 9 | * @private 10 | */ 11 | function addZero(val) { 12 | val += ''; 13 | 14 | return val.length === 1 ? '0' + val : val; 15 | } 16 | 17 | /** 18 | * 将字符串解析为数字 19 | * @param val 20 | * @returns {Number} 21 | * @private 22 | */ 23 | function parseNumber(val) { 24 | if (+val === 0) return 0; 25 | 26 | return parseInt(('' + val).replace(/^0+/, ''), 10); 27 | } 28 | 29 | /** 30 | * 将日期对象分解成时分秒 31 | * @param dateValue 32 | * @param dateOnly 33 | * @returns {*} 34 | */ 35 | function destructDate(dateValue, dateOnly) { 36 | let result; 37 | 38 | if (dateValue && !isNaN(dateValue.valueOf())) { 39 | result = { 40 | year: dateValue.getFullYear() + '', 41 | month: addZero(dateValue.getMonth() + 1), 42 | date: addZero(dateValue.getDate()) 43 | }; 44 | 45 | if (dateOnly) { 46 | result.hour = '00'; 47 | result.minute = '00'; 48 | result.second = '00'; 49 | } else { 50 | result.hour = addZero(dateValue.getHours()); 51 | result.minute = addZero(dateValue.getMinutes()); 52 | result.second = addZero(dateValue.getSeconds()); 53 | } 54 | } else { 55 | result = {}; 56 | } 57 | 58 | return result; 59 | } 60 | 61 | /** 62 | * 根据文本长度, 设置 input 的宽度 63 | * @param input 64 | * @private 65 | */ 66 | function setTextWidth(input) { 67 | 68 | setTimeout(() => { 69 | if (!input.value) { 70 | input.style.width = ''; 71 | return; 72 | } 73 | 74 | const span = document.createElement('span'), 75 | inputStyle = window.getComputedStyle(input); 76 | 77 | span.innerHTML = input.value; 78 | span.style.position = 'absolute'; 79 | span.style.left = '-9999px'; 80 | span.style.fontSize = inputStyle.fontSize; 81 | span.style.fontFamily = inputStyle.fontFamily; 82 | 83 | document.body.appendChild(span); 84 | 85 | input.style.width = Math.round(parseFloat(window.getComputedStyle(span).width)) + 'px'; 86 | 87 | document.body.removeChild(span); 88 | }, 0); 89 | } 90 | 91 | export { 92 | addZero, 93 | parseNumber, 94 | destructDate, 95 | setTextWidth 96 | }; 97 | -------------------------------------------------------------------------------- /src/components/date-picker/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by AshZhang on 2016-3-4. 3 | */ 4 | 5 | 6 | import angular from 'angular'; 7 | 8 | import ddo from './DatePicker'; 9 | 10 | 11 | export default angular 12 | .module('ccms.components.datePicker', ['ccms.components.calendar']) 13 | .directive('ccDatePicker', () => ddo) 14 | .name; 15 | -------------------------------------------------------------------------------- /src/components/date-range/DateRange.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by AshZhang on 2016-3-4. 3 | */ 4 | 5 | 6 | import './_date-range.scss'; 7 | import template from './date-range.tpl.html'; 8 | import DateRangeCtrl from './DateRangeCtrl'; 9 | 10 | 11 | export default { 12 | 13 | bindToController: true, 14 | controller: DateRangeCtrl, 15 | controllerAs: 'ctrl', 16 | replace: true, 17 | restrict: 'E', 18 | scope: { 19 | opts: '=', 20 | onCalendarOpen: '&?', 21 | onCalendarClose: '&?', 22 | isFestival: ' 2 | 19 | - 20 | 37 | 38 | -------------------------------------------------------------------------------- /src/components/date-range/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by AshZhang on 2016-3-4. 3 | */ 4 | 5 | 6 | import angular from 'angular'; 7 | 8 | import ddo from './DateRange'; 9 | 10 | 11 | export default angular 12 | .module('ccms.components.dateRange', ['ccms.components.datePicker']) 13 | .directive('ccDateRange', () => ddo) 14 | .name; 15 | -------------------------------------------------------------------------------- /src/components/draggable/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | function ddo($parse) { 4 | return { 5 | 6 | restrict: 'A', 7 | link(scope, element, attrs) { 8 | 9 | const dragHandler = element[0].querySelector('[cc-drag-handler]') || element[0]; 10 | 11 | element[0].addEventListener('mousedown', function(evt) { 12 | 13 | if (!dragHandler.contains(evt.target) && !evt.target.hasAttribute('cc-drag-handler')) { 14 | return; 15 | } 16 | 17 | let L = 0; 18 | let T = 0; 19 | 20 | element[0].style.position = 'absolute'; 21 | 22 | if (element[0].setCapture) { 23 | element[0].setCapture(); 24 | } 25 | 26 | const e = evt || window.event; 27 | const disX = e.clientX - this.offsetLeft; 28 | const disY = e.clientY - this.offsetTop; 29 | 30 | const mousemoveListener = function(evt) { 31 | 32 | const e = evt || window.event; 33 | L = e.clientX - disX; 34 | T = e.clientY - disY; 35 | 36 | if (T < 0) { 37 | T = 0; 38 | } else if (T > document.documentElement.clientHeight - element[0].offsetHeight) { 39 | T = document.documentElement.clientHeight - element[0].offsetHeight; 40 | } 41 | 42 | if (L < 0) { 43 | L = 0; 44 | } else if (L > document.documentElement.clientWidth - element[0].offsetWidth) { 45 | L = document.documentElement.clientWidth - element[0].offsetWidth; 46 | } 47 | 48 | if (T < 0) T = 0; 49 | 50 | element[0].style.left = `${L}px`; 51 | element[0].style.top = `${T}px`; 52 | }; 53 | 54 | const mouseupListener = function() { 55 | 56 | document.removeEventListener('mousemove', mousemoveListener, false); 57 | document.removeEventListener('mouseup', mouseupListener, false); 58 | 59 | if (element[0].releaseCapture) { 60 | element[0].releaseCapture(); 61 | } 62 | 63 | // 建议修改为 EventBus 64 | scope.$broadcast('ccDragEnd', {left: L, top: T}); 65 | 66 | }; 67 | 68 | document.addEventListener('mousemove', mousemoveListener, false); 69 | document.addEventListener('mouseup', mouseupListener, false); 70 | 71 | evt.preventDefault(); 72 | 73 | }, false); 74 | } 75 | }; 76 | } 77 | 78 | ddo.$inject = ['$parse']; 79 | 80 | export default angular.module('ccms.components.ccDraggable', []) 81 | .directive('ccDraggable', ddo) 82 | .name; 83 | -------------------------------------------------------------------------------- /src/components/dropdown/DropdownCtrl.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import { Inject } from 'angular-es-utils'; 3 | 4 | import dropdownService from './DropdownService'; 5 | 6 | @Inject('$scope', '$element') 7 | export default class DropdownCtrl { 8 | constructor() { 9 | this._isOpen = false; 10 | this._phase = ''; 11 | } 12 | 13 | $onInit() { 14 | if (this.autoClose !== false) { 15 | this.autoClose = true; 16 | } 17 | 18 | // 设置初始样式 19 | this._initStyles(); 20 | 21 | // 设置初始状态 22 | this.isOpen ? this.open() : this.close(); 23 | } 24 | 25 | _initStyles() { 26 | const element = angular.element(this.getElement()); 27 | element.toggleClass('dropdown', true); 28 | } 29 | 30 | getElement() { 31 | return this._$element[0]; 32 | } 33 | 34 | getScope() { 35 | return this._$scope; 36 | } 37 | 38 | _setOpenState(openState) { 39 | const element = angular.element(this.getElement()); 40 | 41 | // 设置 open / close 标记类 42 | element.toggleClass('dropdown-opened', openState); 43 | element.toggleClass('dropdown-closed', !openState); 44 | 45 | // 记录打开状态 46 | this._isOpen = openState; 47 | 48 | // 设置 _phase 避免进入 set isOpen 循环 49 | this._phase = openState ? 'opening' : 'closing'; 50 | this.isOpen = openState; 51 | this._phase = ''; 52 | } 53 | 54 | get isOpen() { 55 | return this._isOpen; 56 | } 57 | 58 | set isOpen(openState) { 59 | // 排除 constructor 运行之前就会执行的调用,bug ? 60 | // 调用栈 angular-es-utils/decorators/Inject.js 61 | if (typeof this._phase !== 'string') { 62 | return; 63 | } 64 | 65 | // 设置 isOpen 变量的时候会调用此 setter,然后按需执行 open/close 操作 66 | // 为向组件外层反馈 isOpen 状态,实现双向绑定, 67 | // 调用 open/close 时也会设置 isOpen 变量 68 | // 为了避免如此循环,使用 _phase 记录状态,如果正在进行操作,就跳出循环 69 | if (this._phase.length) { 70 | return; 71 | } 72 | 73 | // 改变 isOpen 状态时,按需执行 open/close 操作 74 | if (this.isOpen !== openState) { 75 | openState ? this.open() : this.close(); 76 | } 77 | } 78 | 79 | open() { 80 | const scope = this.getScope(); 81 | dropdownService.open(this); 82 | this._setOpenState(true); 83 | this.onDropdownOpen && this.onDropdownOpen(); 84 | scope.$root.$$phase || scope.$apply(); 85 | } 86 | 87 | close() { 88 | const scope = this.getScope(); 89 | dropdownService.close(this); 90 | this._setOpenState(false); 91 | this.onDropdownClose && this.onDropdownClose(); 92 | scope.$root.$$phase || scope.$apply(); 93 | } 94 | } 95 | 96 | -------------------------------------------------------------------------------- /src/components/dropdown/DropdownPanelCtrl.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import { Inject } from 'angular-es-utils'; 3 | 4 | @Inject('$element') 5 | export default class DropdownPanelCtrl { 6 | $onInit() { 7 | // 设置初始样式 8 | this._initStyles(); 9 | } 10 | 11 | _initStyles() { 12 | const element = angular.element(this.getElement()); 13 | element.toggleClass('dropdown-panel', true); 14 | } 15 | 16 | getElement() { 17 | return this._$element[0]; 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/components/dropdown/DropdownService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 全局记录打开的 dropdown 实例,处理自动关闭行为 3 | * 以单例模式使用 4 | */ 5 | class DropdownService { 6 | constructor() { 7 | this.lastDropdownCtrl = null; 8 | 9 | // 用于自动关闭 dropdown 的回调 10 | this.autoCloseFn = null; 11 | } 12 | 13 | open(dropdownCtrl) { 14 | // 为需要自动关闭的下拉注册处理事件 15 | if (dropdownCtrl.autoClose) { 16 | this.lastDropdownCtrl = dropdownCtrl; 17 | 18 | this.autoCloseFn = event => { 19 | if (dropdownCtrl.getElement().contains(event.target)) { 20 | // 点击元素自身不自动关闭 21 | return; 22 | } 23 | this.lastDropdownCtrl.close(); 24 | }; 25 | 26 | document.addEventListener('click', this.autoCloseFn, true); 27 | } 28 | } 29 | 30 | close(dropdownCtrl) { 31 | if (dropdownCtrl.autoClose) { 32 | document.removeEventListener('click', this.autoCloseFn, true); 33 | this.lastDropdownCtrl = null; 34 | this.autoCloseFn = null; 35 | } 36 | } 37 | } 38 | 39 | export default new DropdownService(); 40 | 41 | -------------------------------------------------------------------------------- /src/components/dropdown/DropdownToggleCtrl.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import { Inject, Bind } from 'angular-es-utils'; 3 | 4 | @Inject('$element') 5 | export default class DropdownToggleCtrl { 6 | $onInit() { 7 | // 设置初始样式 8 | this._initStyles(); 9 | } 10 | 11 | $postLink() { 12 | this.getElement().addEventListener('click', this.toggle); 13 | } 14 | 15 | _initStyles() { 16 | const element = angular.element(this.getElement()); 17 | element.toggleClass('dropdown-toggle', true); 18 | } 19 | 20 | getElement() { 21 | return this._$element[0]; 22 | } 23 | 24 | @Bind 25 | toggle(event) { 26 | let dropdownCtrl = this.parent; 27 | if (dropdownCtrl.isOpen) { 28 | dropdownCtrl.close(); 29 | } else { 30 | dropdownCtrl.open(); 31 | } 32 | event.stopPropagation(); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/components/dropdown/__tests__/test-cc-dropdown.js: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | 3 | import angular from 'angular'; 4 | import '../../../index'; 5 | 6 | const {module, inject} = window; 7 | 8 | describe('cc-dropdown', () => { 9 | let dropdownEl, ctrl; 10 | let $scope; 11 | 12 | beforeEach(module('ccms.components')); 13 | beforeEach(inject((_$compile_, _$rootScope_) => { 14 | const html = ` 15 | 16 |
17 | 18 |
19 |
1号下拉内容
20 |
21 |
22 |
23 | `; 24 | 25 | $scope = _$rootScope_.$new(); 26 | 27 | dropdownEl = _$compile_(html)($scope); 28 | ctrl = dropdownEl.controller('ccDropdown'); 29 | $scope.$digest(); 30 | })); 31 | 32 | describe('DropdownCtrl', () => { 33 | it('.isOpen', () => { 34 | assert.isBoolean(ctrl.isOpen); 35 | 36 | ctrl.isOpen = true; 37 | assert.strictEqual(ctrl.isOpen, true); 38 | 39 | ctrl.isOpen = false; 40 | assert.strictEqual(ctrl.isOpen, false); 41 | }); 42 | 43 | it('.open()', () => { 44 | ctrl.open(); 45 | assert.strictEqual(ctrl.isOpen, true); 46 | }); 47 | 48 | it('.close()', () => { 49 | ctrl.close(); 50 | assert.strictEqual(ctrl.isOpen, false); 51 | }); 52 | 53 | it('.getElement()', () => { 54 | assert.strictEqual(ctrl.getElement(), dropdownEl[0]); 55 | }); 56 | }); 57 | 58 | describe('DropdownToggleCtrl', () => { 59 | let toggleEl; 60 | 61 | beforeEach(() => { 62 | toggleEl = dropdownEl[0].querySelector('[cc-dropdown-toggle]'); 63 | }); 64 | 65 | it('.parent', () => { 66 | const toggleCtrl = angular.element(toggleEl).controller('ccDropdownToggle'); 67 | assert.strictEqual(toggleCtrl.parent, ctrl); 68 | assert.instanceOf(toggleCtrl.parent, ctrl.constructor); 69 | }); 70 | 71 | it('.toggle()', () => { 72 | const openState = ctrl.isOpen; 73 | ctrl.$$hash = 'xxxkuitos'; 74 | // TODO: need another click 75 | toggleEl.click(); 76 | assert.strictEqual(ctrl.isOpen, !openState); 77 | }); 78 | }); 79 | 80 | describe('DropdownPanelCtrl', () => { 81 | let panelEl; 82 | 83 | beforeEach(() => { 84 | panelEl = dropdownEl[0].querySelector('[cc-dropdown-panel]'); 85 | }); 86 | 87 | it('.parent', () => { 88 | const panelCtrl = angular.element(panelEl).controller('ccDropdownPanel'); 89 | assert.instanceOf(panelCtrl.parent, ctrl.constructor); 90 | }); 91 | }); 92 | }); 93 | 94 | -------------------------------------------------------------------------------- /src/components/dropdown/_dropdown.scss: -------------------------------------------------------------------------------- 1 | cc-dropdown, [cc-dropdown] { 2 | &.dropdown-opened { 3 | > .dropdown-panel { 4 | display: block; 5 | } 6 | } 7 | &.dropdown-closed { 8 | > .dropdown-panel { 9 | display: none; 10 | } 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/components/dropdown/dropdown-multiselect/__tests__/test-cc-dropdown-multiselect.js: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai'; 2 | 3 | import '../../../index'; 4 | 5 | const { module, inject } = window; 6 | 7 | describe('cc-dropdown-multiselect', () => { 8 | let selectEl, ctrl, scope; 9 | 10 | beforeEach(module('ccms.components')); 11 | beforeEach(inject((_$compile_, _$rootScope_) => { 12 | const html = ` 13 | 18 | 19 | `; 20 | 21 | scope = _$rootScope_.$new(); 22 | scope.value1 = []; 23 | scope.datalist1 = [ 24 | {title: '北京', value: 'bj'}, 25 | {title: '上海', value: 'sh'}, 26 | {title: '深圳', value: 'sz'}, 27 | {title: '西安', value: 'xa'}, 28 | {title: '南京', value: 'nj'}, 29 | {title: '武汉', value: 'wh'}, 30 | {title: '重庆', value: 'cq'}, 31 | {title: '广州', value: 'gz'} 32 | ]; 33 | scope.fieldsMap = { 34 | displayField: 'title', 35 | valueField: 'value' 36 | }; 37 | 38 | selectEl = _$compile_(html)(scope); 39 | ctrl = selectEl.controller('ccDropdownMultiselect'); 40 | scope.$digest(); 41 | })); 42 | 43 | describe('DropdownMultiselectCtrl', () => { 44 | it('.setValue()', () => { 45 | ctrl.setValue(['sh', 'xa']); 46 | assert.includeMembers(ctrl.model, ['sh', 'xa']); 47 | assert.strictEqual(ctrl.model.length, 2); 48 | }); 49 | 50 | it('.focusUp()', () => { 51 | ctrl.focusAt(1); 52 | ctrl.focusUp(); 53 | assert.strictEqual(ctrl.focusIndex, 0); 54 | }); 55 | 56 | it('.focusDown()', () => { 57 | ctrl.focusAt(1); 58 | ctrl.focusDown(); 59 | assert.strictEqual(ctrl.focusIndex, 2); 60 | }); 61 | 62 | it('.clear()', () => { 63 | ctrl.clear(); 64 | assert.strictEqual(ctrl.title, ''); 65 | assert.isArray(ctrl.selection); 66 | assert.strictEqual(ctrl.selection.length, 0); 67 | }); 68 | 69 | it('.setActiveState()', () => { 70 | ctrl.setActiveState(true); 71 | assert.strictEqual(ctrl.isActive, true); 72 | ctrl.setActiveState(false); 73 | assert.strictEqual(ctrl.isActive, false); 74 | }); 75 | 76 | it('.open()', () => { 77 | ctrl.open(); 78 | assert.strictEqual(ctrl.isOpen, true); 79 | }); 80 | 81 | it('.close()', () => { 82 | ctrl.close(); 83 | assert.strictEqual(ctrl.isOpen, false); 84 | }); 85 | 86 | // TODO: issue 比较紧急, 稍后修复该 测试用例 87 | // it('.toggle()', done => { 88 | // const openState = ctrl.isOpen; 89 | // ctrl.toggle(); 90 | // setTimeout(() => { 91 | // done(); 92 | // assert.strictEqual(ctrl.isOpen, !openState); 93 | // }, 0); 94 | // }); 95 | 96 | it('.getElement()', () => { 97 | assert.strictEqual(ctrl.getElement(), selectEl[0]); 98 | }); 99 | }); 100 | }); 101 | 102 | -------------------------------------------------------------------------------- /src/components/dropdown/dropdown-multiselect/dropdown-multiselect.tpl.html: -------------------------------------------------------------------------------- 1 | 55 | -------------------------------------------------------------------------------- /src/components/dropdown/dropdown-multiselect/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import bindHtml from '../../bind-html'; 4 | 5 | import './_dropdown-multiselect.scss'; 6 | import template from './dropdown-multiselect.tpl.html'; 7 | import DropdownMultiselectCtrl from './DropdownMultiselectCtrl'; 8 | 9 | const dropdownMultiselectDDO = { 10 | restrict: 'E', 11 | require: 'ccDropdown', 12 | template, 13 | controller: DropdownMultiselectCtrl, 14 | controllerAs: '$ctrl', 15 | scope: { 16 | model: '=?', 17 | isOpen: ' dropdownMultiselectDDO) 39 | .name; 40 | 41 | -------------------------------------------------------------------------------- /src/components/dropdown/dropdown-select/dropdown-select.tpl.html: -------------------------------------------------------------------------------- 1 | 39 | -------------------------------------------------------------------------------- /src/components/dropdown/dropdown-select/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import bindHtml from '../../bind-html'; 4 | 5 | import './_dropdown-select.scss'; 6 | import template from './dropdown-select.tpl.html'; 7 | import DropdownSelectCtrl from './DropdownSelectCtrl'; 8 | import icon from '../../icon'; 9 | 10 | const dropdownSelectDDO = { 11 | restrict: 'E', 12 | require: 'ccDropdown', 13 | template, 14 | controller: DropdownSelectCtrl, 15 | controllerAs: '$ctrl', 16 | scope: { 17 | model: '=?', 18 | containerElement: '=?', 19 | isOpen: ' dropdownSelectDDO) 38 | .name; 39 | 40 | -------------------------------------------------------------------------------- /src/components/dropdown/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import './_dropdown.scss'; 4 | 5 | import DropdownCtrl from './DropdownCtrl'; 6 | import DropdownToggleCtrl from './DropdownToggleCtrl'; 7 | import DropdownPanelCtrl from './DropdownPanelCtrl'; 8 | 9 | const dropdownDDO = { 10 | name: 'ccDropdown', 11 | restrict: 'EA', 12 | controller: DropdownCtrl, 13 | controllerAs: '$ctrl', 14 | scope: { 15 | isOpen: '=?', 16 | autoClose: '', 40 | transclude: true, 41 | controller: DropdownPanelCtrl, 42 | controllerAs: '$ctrl', 43 | scope: {}, 44 | bindToController: true 45 | }; 46 | 47 | export default angular 48 | .module('ccms.components.dropdown', []) 49 | .directive('ccDropdown', () => dropdownDDO) 50 | .directive('ccDropdownToggle', () => dropdownToggleDDO) 51 | .directive('ccDropdownPanel', () => dropdownPanelDDO) 52 | .name; 53 | 54 | -------------------------------------------------------------------------------- /src/components/dynamic-attr/DynamicAttrDirective.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @since 15/5/14. 4 | * @version 1.0.4 5 | * 动态 添加/删除 元素属性,如果是angular支持的事件(如ngClick)则同时对元素做 绑定/解绑 事件切换 6 | */ 7 | 8 | import angular from 'angular'; 9 | 10 | import injector from 'angular-es-utils/injector'; 11 | 12 | // 支持的angular事件集合 13 | const SUPPORTED_EVENTS = 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '); 14 | 15 | export default { 16 | 17 | restrict: 'A', 18 | priority: 1, 19 | 20 | compile(element) { 21 | 22 | // 从元素上收集到的事件属性 23 | let collectedNgEventMapper = {}; 24 | 25 | // 收集当前元素的原始事件(ng-click等) 26 | SUPPORTED_EVENTS.forEach(eventName => { 27 | 28 | const ngEventAttr = `ng-${eventName}`; 29 | let ngEventAttrValue = element.attr(ngEventAttr); 30 | 31 | // 如果绑定存在 32 | if (ngEventAttrValue && (ngEventAttrValue = ngEventAttrValue.trim())) { 33 | 34 | collectedNgEventMapper[ngEventAttr] = { 35 | eventName, 36 | expression: ngEventAttrValue, 37 | fn: injector.get('$parse')(ngEventAttrValue, null, true) 38 | }; 39 | } 40 | 41 | }); 42 | 43 | return (scope, element, attr) => { 44 | 45 | // 因为监听的是一个对象类型,所以这里watch的时候必须是true(调用angular.equals()对比而不是简单的===,简单的===可能会引发TTL负载异常) 46 | scope.$watch(attr.ccDynamicAttr, attributes => { 47 | 48 | if (attributes !== undefined) { 49 | 50 | angular.forEach(attributes, (attrAvailable, attribute) => { 51 | 52 | const originalAttrInfo = collectedNgEventMapper[attribute] || element.attr(attribute) || true; 53 | 54 | // 如果属性为已收集到的angular事件类型 55 | if (originalAttrInfo && originalAttrInfo.eventName) { 56 | 57 | if (attrAvailable) { 58 | 59 | // 如果当前元素上不存在该事件属性但是其原始事件属性存在(表明元素之前做过disable切换),则重新绑定事件回调 60 | if (!element.attr(attribute) && originalAttrInfo) { 61 | 62 | element.removeClass(`${attribute}-disabled`).attr(attribute, originalAttrInfo.expression); 63 | 64 | /** 65 | * rebind event callback 66 | * @see ngClick 67 | */ 68 | element.bind(originalAttrInfo.eventName, event => { 69 | scope.$apply(() => { 70 | originalAttrInfo.fn(scope, {$event: event}); 71 | }); 72 | }); 73 | } 74 | 75 | } else { 76 | 77 | // 状态为false时加入样式并移除对应事件回调 78 | element.addClass(`${attribute}-disabled`).removeAttr(attribute).unbind(originalAttrInfo.eventName); 79 | } 80 | 81 | } else { 82 | 83 | // TODO 当属性不可用时应该移除绑定在元素上相关的逻辑,而可用时则应加上相关逻辑,如何实现这种动态编译某一指令?? 84 | element[attrAvailable ? 'attr' : 'removeAttr'](attribute, originalAttrInfo); 85 | } 86 | 87 | }); 88 | } 89 | }, true); 90 | 91 | // unbind events for performance 92 | scope.$on('$destroy', () => { 93 | angular.forEach(collectedNgEventMapper, eventInfo => { 94 | element.unbind(eventInfo.eventName); 95 | }); 96 | 97 | }); 98 | }; 99 | } 100 | 101 | }; 102 | -------------------------------------------------------------------------------- /src/components/dynamic-attr/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-01-21 5 | */ 6 | 7 | import angular from 'angular'; 8 | import ddo from './DynamicAttrDirective'; 9 | 10 | export default angular 11 | .module('ccms.components.dynamicAttr', []) 12 | .directive('ccDynamicAttr', () => ddo) 13 | .name; 14 | -------------------------------------------------------------------------------- /src/components/form/Constant.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-17 5 | */ 6 | 7 | import {isString, isRegExp, isObject} from 'angular-es-utils/type-auth'; 8 | 9 | /** 10 | * 配置ng内置的校验提示信息 11 | * @ignore 12 | */ 13 | export const DEFAULT_VALIDATORS = { 14 | 15 | required: '必填', 16 | email: '邮件格式不合法', 17 | max: '输入值过大', 18 | maxlength: '输入值太长', 19 | min: '输入值过小', 20 | minlength: '输入值太短', 21 | number: '不合法的数字', 22 | pattern: '不合法的格式', 23 | url: '不合法的url', 24 | date: '不合法的日期', 25 | datetimelocal: '不合法的本地日期', 26 | time: '不合法的时间', 27 | week: '不合法的星期值', 28 | month: '不合法的月份值' 29 | 30 | }; 31 | 32 | /** 33 | * 将配置的VALIDATORS格式化成可供angular直接使用的格式 34 | * @ignore 35 | * @param originalValidators 格式: 36 | * 37 | * { 38 | * validatorId: string|Regex|Object 39 | * } 40 | * 41 | * Object:{ 42 | * msg: string, 43 | * regex: Regex, 44 | * fn: Function 45 | * } 46 | * 47 | * @returns {{errorMsg: {}, validators: {}}} 48 | */ 49 | export const formatValidator = originalValidators => { 50 | 51 | let errorMsg = {}; 52 | let validators = {}; 53 | 54 | Object.keys(originalValidators).forEach(id => { 55 | 56 | const validator = originalValidators[id]; 57 | 58 | if (isString(validator)) { 59 | errorMsg[id] = validator; 60 | } else if (isRegExp(validator)) { 61 | 62 | errorMsg[id] = DEFAULT_VALIDATORS[id]; 63 | validators[id] = (modelVal, viewVal) => { 64 | return validator.test(modelVal || viewVal); 65 | }; 66 | 67 | } else if (isObject(validator)) { 68 | errorMsg[id] = validator.msg; 69 | validators[id] = (validator.fn && validator.fn.bind(validator)) || 70 | ((modelVal, viewVal) => { 71 | return validator.regex.test(modelVal || viewVal); 72 | }); 73 | } 74 | 75 | }); 76 | 77 | return { 78 | errorMsg, 79 | validators 80 | }; 81 | 82 | }; 83 | -------------------------------------------------------------------------------- /src/components/form/ValidatorService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-18 5 | */ 6 | 7 | import injector from 'angular-es-utils/injector'; 8 | 9 | /** 10 | *
11 |  *     表单校验服务,用于手动触发校验逻辑
12 |  *     module: ccms.components.form
13 |  *     service: $Validator
14 |  * 
15 | */ 16 | export default { 17 | 18 | /** 19 | * 验证指定表单 20 | * @param {controller} formCtrl 要校验的表单控制器.通过在表单区块声明name的方式将其自动存入当前scope中 21 | * @return {Promise} 校验结果promise,校验成功会触发resolve,失败reject 22 | */ 23 | validate(formCtrl) { 24 | 25 | const $q = injector.get('$q'); 26 | 27 | let deferred = $q.defer(); 28 | formCtrl.$$validatedDefer = deferred; 29 | 30 | // 获取当前未校验通过的ngModel数量 31 | formCtrl.$$invalidNgModelCtrls = formCtrl.$$ngModelCtrls.filter(ctrl => ctrl.$invalid); 32 | formCtrl.$$invalidCount = formCtrl.$$invalidNgModelCtrls.length; 33 | 34 | if (formCtrl.$$invalidCount) { 35 | formCtrl.$$invalidNgModelCtrls.forEach(ctrl => { 36 | ctrl.$validate(); 37 | }); 38 | 39 | } else { 40 | deferred.resolve(); 41 | } 42 | 43 | return deferred.promise; 44 | }, 45 | 46 | setPristine(formCtrl) { 47 | 48 | if (formCtrl.$setPristine) { 49 | formCtrl.$setPristine(); 50 | } else { 51 | formCtrl.$$ngModelCtrls.forEach(ctrl => ctrl.$setPristine()); 52 | } 53 | 54 | } 55 | 56 | }; 57 | -------------------------------------------------------------------------------- /src/components/form/ValidatorsCtrl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-17 5 | */ 6 | import {Inject} from 'angular-es-utils'; 7 | 8 | import {DEFAULT_VALIDATORS, formatValidator} from './Constant'; 9 | 10 | @Inject('$element', '$parse', '$scope') 11 | export default class ValidatorsCtrl { 12 | 13 | $onInit() { 14 | 15 | // 当未使用form/ngForm声明表单时,同样支持name方式给表单命名 16 | if (!this.formCtrl && this.name) { 17 | const setter = this._$parse(this.name).assign; 18 | setter(this._$scope, this); 19 | } 20 | 21 | const formattedValidator = formatValidator(Object.assign({}, DEFAULT_VALIDATORS, this.validators)); 22 | 23 | this.element = this._$element[0]; 24 | this.validators = formattedValidator.validators; 25 | this.errorMsg = formattedValidator.errorMsg; 26 | } 27 | 28 | $postLink() { 29 | 30 | // 关闭浏览器自带的校验交互 31 | if (this.element.tagName === 'form'.toUpperCase()) { 32 | this.element.setAttribute('novalidate', 'true'); 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/components/form/_form.scss: -------------------------------------------------------------------------------- 1 | @import "../styles/input"; 2 | 3 | .form-wrapper { 4 | 5 | fieldset { 6 | 7 | border: 0; 8 | font-family: $font; 9 | font-size: 12px; 10 | position: relative; 11 | 12 | label { 13 | display: inline-block; 14 | color: #666666; 15 | padding-right: 40px; 16 | text-align: right; 17 | width: 120px; 18 | } 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/form/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-17 5 | */ 6 | 7 | import angular from 'angular'; 8 | 9 | import './_form.scss'; 10 | import ValidatorService from './ValidatorService'; 11 | import ValidatorsCtrl from './ValidatorsCtrl'; 12 | import ValidatorCtrl from './ValidatorCtrl'; 13 | 14 | const validatorsDDO = { 15 | name: 'ccValidators', 16 | restrict: 'A', 17 | require: { 18 | formCtrl: '?form' 19 | }, 20 | controller: ValidatorsCtrl, 21 | controllerAs: '$$validatorsCtrl', 22 | bindToController: { 23 | validators: ' validatorsDDO) 52 | .directive('ccValidator', () => validatorDDO) 53 | // 对built-in的表单添加额外逻辑 54 | .directive('form', () => forbidFormNativeValidator) 55 | .value('$ccValidator', ValidatorService) 56 | .name; 57 | -------------------------------------------------------------------------------- /src/components/grid/Constant.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-05-06 5 | */ 6 | 7 | import defaultHeaderTpl from './tpls/default-header.tpl.html'; 8 | import defaultRowCellTpl from './tpls/default-row-cell.tpl.html'; 9 | import checkboxHeaderTpl from './tpls/checkbox-header.tpl.html'; 10 | import checkboxRowCellTpl from './tpls/checkbox-row-cell.tpl.html'; 11 | import emptyGridTipTpl from './tpls/empty-grid-tip.tpl.html'; 12 | import defaultFooterTpl from './tpls/default-footer.tpl.html'; 13 | 14 | /** 15 | * 表格模板,可配置 16 | * @warning 业务系统如果要配置自定义模板,请以系统名为前缀避免冲突,如 NEWBI_SORTABLE_TEMPLATE 17 | */ 18 | export default { 19 | DEFAULT: [defaultHeaderTpl, defaultRowCellTpl, emptyGridTipTpl, defaultFooterTpl], 20 | SELECTABLE: [checkboxHeaderTpl, checkboxRowCellTpl, emptyGridTipTpl, defaultFooterTpl] 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/grid/GridCellLink.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-05-06 5 | */ 6 | 7 | import injector from 'angular-es-utils/injector'; 8 | 9 | const TEMPLATE_REGEXP = /<.+>/; 10 | 11 | export default (scope, element) => { 12 | 13 | const baseScope = scope.baseScope; 14 | Object.setPrototypeOf(scope, baseScope); 15 | 16 | if (TEMPLATE_REGEXP.test(scope.column.cellTemplate)) { 17 | element.html(scope.column.cellTemplate); 18 | } else { 19 | element.html(``); 20 | } 21 | 22 | injector.get('$compile')(element.contents())(scope); 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /src/components/grid/grid.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | 6 |
7 |
8 | 9 | 10 |
11 |
12 |
15 |
18 |
19 | 20 |
21 |
22 | 23 | 25 |
26 | -------------------------------------------------------------------------------- /src/components/grid/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-01-04 5 | */ 6 | import angular from 'angular'; 7 | 8 | import loading from '../loading'; 9 | import dynamicAttr from '../dynamic-attr'; 10 | import checkbox from '../checkbox'; 11 | 12 | import './_grid.scss'; 13 | import template from './grid.tpl.html'; 14 | import controller from './GridCtrl'; 15 | import link from './GridCellLink'; 16 | 17 | import $grid from './GridHelper'; 18 | 19 | const 20 | gridDDO = { 21 | template, 22 | controller, 23 | bindings: { 24 | opts: '=', 25 | selectedItems: '=?', 26 | type: '@?', 27 | onBeforeRefresh: '&?', 28 | onRefresh: '&?' 29 | } 30 | }, 31 | 32 | gridCellDDO = { 33 | restrict: 'E', 34 | link, 35 | scope: { 36 | entity: '<', 37 | column: '<', 38 | baseScope: '<' 39 | } 40 | }; 41 | 42 | export default angular 43 | .module('ccms.components.grid', [loading, dynamicAttr, checkbox]) 44 | .directive('ccGridCell', () => gridCellDDO) 45 | .component('ccGrid', gridDDO) 46 | .value('$ccGrid', $grid) 47 | .name; 48 | -------------------------------------------------------------------------------- /src/components/grid/tpls/checkbox-header.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ${require('./default-header.tpl.html')} 8 | -------------------------------------------------------------------------------- /src/components/grid/tpls/checkbox-row-cell.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ${require('./default-row-cell.tpl.html')} 8 | -------------------------------------------------------------------------------- /src/components/grid/tpls/default-footer.tpl.html: -------------------------------------------------------------------------------- 1 | 已选 2 | 3 | 4 | 刷新 5 | 6 | 7 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/grid/tpls/default-header.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 20 | 21 | 22 | 23 |
24 |
    25 |
  • 26 | {{ column.displayName }} 30 |
  • 31 |
32 |
33 | 34 | -------------------------------------------------------------------------------- /src/components/grid/tpls/default-row-cell.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/grid/tpls/empty-grid-tip.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 您还没有任何活动 5 | 快去创建吧 6 | 7 |
8 | -------------------------------------------------------------------------------- /src/components/grid/tpls/error.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 加载数据出错,请稍后重试! 4 |
5 | -------------------------------------------------------------------------------- /src/components/grid/tpls/row.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /src/components/icon/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2017-08-03 5 | */ 6 | 7 | import angular from 'angular'; 8 | import template from './index.tpl.html'; 9 | import 'ccms-icon'; 10 | import './index.scss'; 11 | 12 | const ddo = { 13 | template, 14 | bindings: { 15 | symbol: '@', 16 | className: '@' 17 | } 18 | }; 19 | 20 | export default angular 21 | .module('ccms.components.icon', []) 22 | .component('ccIcon', ddo) 23 | .name; 24 | -------------------------------------------------------------------------------- /src/components/icon/index.scss: -------------------------------------------------------------------------------- 1 | .icon-svg { 2 | font-size: 16px; 3 | line-height: 1; 4 | width: 1em; 5 | height: 1em; 6 | overflow: hidden; 7 | vertical-align: middle; 8 | pointer-events: none; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/icon/index.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-01-04 5 | */ 6 | 7 | import angular from 'angular'; 8 | 9 | import altSrc from './alt-src'; 10 | import calendar from './calendar'; 11 | import captureEvent from './capture-event'; 12 | import checkbox from './checkbox'; 13 | import datePicker from './date-picker'; 14 | import dateRange from './date-range'; 15 | import dropdown from './dropdown'; 16 | import dropdownMultiselect from './dropdown/dropdown-multiselect'; 17 | import dropdownSelect from './dropdown/dropdown-select'; 18 | import form from './form'; 19 | import grid from './grid'; 20 | import instantSearch from './instant-search'; 21 | import loading from './loading'; 22 | import menuBar from './menus'; 23 | import modal from './modal'; 24 | import ngEnter from './ng-enter'; 25 | import niceScroll from './nice-scroll'; 26 | import pagination from './pagination'; 27 | import radio from './radio'; 28 | import tabset from './tabset'; 29 | import tips from './tips'; 30 | import toggle from './toggle'; 31 | import tooltip from './tooltip'; 32 | import tree from './tree'; 33 | 34 | export default angular 35 | .module('ccms.components.ui', [ 36 | altSrc, 37 | calendar, 38 | captureEvent, 39 | checkbox, 40 | datePicker, 41 | dateRange, 42 | dropdown, 43 | dropdownMultiselect, 44 | dropdownSelect, 45 | form, 46 | grid, 47 | instantSearch, 48 | loading, 49 | menuBar, 50 | modal, 51 | ngEnter, 52 | niceScroll, 53 | pagination, 54 | radio, 55 | tabset, 56 | tips, 57 | toggle, 58 | tooltip, 59 | tree 60 | ]) 61 | .name; 62 | 63 | -------------------------------------------------------------------------------- /src/components/instant-search/_instant-search.scss: -------------------------------------------------------------------------------- 1 | @import "../../../node_modules/ccms-icon/iconfont"; 2 | @import "../styles/mixins"; 3 | @import "../styles/input"; 4 | 5 | .instant-search { 6 | display: inline-block; 7 | position: relative; 8 | 9 | .instant-search-input { 10 | line-height: $control-height; 11 | vertical-align: top; 12 | padding-right: 28px; 13 | } 14 | 15 | .instant-search-icon { 16 | @extend .iconfont; 17 | @extend .icon-search; 18 | position: absolute; 19 | top: 0; 20 | right: .5em; 21 | line-height: $control-height; 22 | color: #dbdbdb; 23 | } 24 | .search-hint { 25 | position: absolute; 26 | top: calc(100% + 1px); 27 | left: 0; 28 | z-index: 1; 29 | box-sizing: border-box; 30 | min-width: 100%; 31 | max-width: 150%; 32 | padding: 5px 0; 33 | margin: 0; 34 | list-style: none; 35 | border: 1px solid #d9d9d9; 36 | background-color: #fff; 37 | li { 38 | @include cut-long-text; 39 | padding: 5px 8px; 40 | font-size: 12px; 41 | cursor: pointer; 42 | .up-level { 43 | color: #777777; 44 | } 45 | &.focus, &:hover { 46 | background-color: #f6f6f6; 47 | } 48 | &.empty-list-item { 49 | cursor: default; 50 | &:hover { 51 | background-color: #fff; 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/components/instant-search/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author arzyu 3 | */ 4 | 5 | import angular from 'angular'; 6 | import ngResource from 'angular-resource'; 7 | 8 | import './_instant-search.scss'; 9 | import template from './instant-search.tpl.html'; 10 | import InstantSearchCtrl from './InstantSearchCtrl'; 11 | 12 | const DDO = { 13 | restrict: 'E', 14 | template, 15 | controller: InstantSearchCtrl, 16 | controllerAs: '$ctrl', 17 | scope: { 18 | options: '<', 19 | datalist: ' DDO) 29 | .name; 30 | 31 | -------------------------------------------------------------------------------- /src/components/instant-search/instant-search.tpl.html: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /src/components/loading/_loading.scss: -------------------------------------------------------------------------------- 1 | @import "../../../node_modules/ccms-icon/iconfont"; 2 | @import '../styles/variables'; 3 | 4 | @mixin loader($width,$stroke-width) { 5 | position: relative; 6 | width: $width; 7 | &:before { 8 | content: ''; 9 | display: block; 10 | padding-top: 100%; 11 | } 12 | .circular { 13 | animation: rotate 2.9s linear infinite; 14 | height: 100%; 15 | transform-origin: center center; 16 | width: 100%; 17 | position: absolute; 18 | top: 0; 19 | bottom: 0; 20 | left: 0; 21 | right: 0; 22 | margin: auto; 23 | .path { 24 | stroke-width: $stroke-width; 25 | stroke-miterlimit: 10; 26 | stroke: #00AAF1; 27 | stroke-dasharray: 1, 200; 28 | stroke-dashoffset: 0; 29 | animation: dash 1.2s linear infinite; 30 | stroke-linecap: round; 31 | } 32 | } 33 | } 34 | 35 | cc-loading { 36 | display: inline-block; 37 | 38 | section { 39 | 40 | .loader { 41 | @include loader(40px, 3); 42 | margin-top: 5px; 43 | } 44 | 45 | p { 46 | display: none; 47 | } 48 | 49 | &.layer { 50 | width: 70px; 51 | height: 70px; 52 | background: rgba(224, 224, 224, 0.8); 53 | border-radius: 5px; 54 | display: flex; 55 | justify-content: space-around; 56 | flex-direction: column; 57 | align-items: center; 58 | p { 59 | display: block; 60 | font-family: $font; 61 | font-size: 12px; 62 | color: #666666; 63 | margin-bottom: 5px; 64 | } 65 | 66 | } 67 | 68 | } 69 | 70 | } 71 | 72 | -------------------------------------------------------------------------------- /src/components/loading/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-01-08 5 | */ 6 | 7 | import angular from 'angular'; 8 | 9 | import './_loading.scss'; 10 | import template from './loading.tpl.html'; 11 | 12 | const ddo = { 13 | template, 14 | transclude: true, 15 | bindings: { 16 | type: '@?' // loading 类型: default | layer(带背景层) 17 | } 18 | }; 19 | 20 | export default angular 21 | .module('ccms.components.loading', []) 22 | .component('ccLoading', ddo) 23 | .name; 24 | -------------------------------------------------------------------------------- /src/components/loading/loading.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 |
7 |

加载中

8 |
9 | -------------------------------------------------------------------------------- /src/components/menus/MenuService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created with MenuService.js 3 | * @Description: 4 | * @Author: maxsmu 5 | * @Date: 2016-03-10 8:11 PM 6 | */ 7 | 8 | import EventBus from 'angular-es-utils/event-bus'; 9 | import Deferred from 'angular-es-utils/deferred'; 10 | 11 | let deferred = new Deferred(); 12 | let isHadBeenInitialized = false; 13 | let currentShop = {}; 14 | 15 | export function reset() { 16 | currentShop = {}; 17 | deferred = new Deferred(); 18 | isHadBeenInitialized = false; 19 | } 20 | 21 | export function setCurrentPlatShop(plat, shop) { 22 | 23 | const selectedShop = {plat, shop}; 24 | 25 | currentShop = selectedShop; 26 | 27 | if (isHadBeenInitialized) { 28 | deferred = new Deferred(); 29 | dispatchShopChange(selectedShop); 30 | } 31 | deferred.resolve(currentShop); 32 | 33 | isHadBeenInitialized = true; 34 | } 35 | 36 | export function dispatchMenuChange(...args) { 37 | return EventBus.dispatch('cc:menuChange', ...args); 38 | } 39 | 40 | export function dispatchShopChange(...args) { 41 | return EventBus.dispatch('cc:shopChange', ...args); 42 | } 43 | 44 | export function dispatchShopChangeStart(...args) { 45 | return EventBus.dispatch('cc:shopChangeStart', ...args); 46 | } 47 | 48 | export function isHaveBindShopChangeStart() { 49 | return EventBus.getListeners('cc:shopChangeStart').length > 0; 50 | } 51 | 52 | export default { 53 | 54 | getMenus(menusResource) { 55 | const isResource = menusResource && typeof menusResource.query === 'function', 56 | // -如果是Resource则返回Resource,否则返回原数据 57 | resource = !isResource ? menusResource 58 | : menusResource.query(); 59 | return { 60 | isResource, 61 | resource 62 | }; 63 | }, 64 | 65 | getShops(shopsResource) { 66 | 67 | const isResource = shopsResource && typeof shopsResource.query === 'function', 68 | 69 | // -如果是Resource则返回Resource,否则返回原数据 70 | resource = !isResource ? shopsResource 71 | : shopsResource.query(); 72 | return { 73 | isResource, 74 | resource 75 | }; 76 | }, 77 | 78 | getCurrentPlatShop() { 79 | return deferred.promise; 80 | }, 81 | 82 | onMenuChange(listener, scope) { 83 | 84 | const offListener = EventBus.once('cc:menuChange', listener); 85 | 86 | if (scope) { 87 | scope.$on('$destroy', offListener); 88 | } 89 | 90 | return offListener; 91 | }, 92 | 93 | onShopChange(listener, scope) { 94 | const offListener = EventBus.on('cc:shopChange', listener); 95 | 96 | if (scope) { 97 | scope.$on('$destroy', offListener); 98 | } 99 | 100 | return offListener; 101 | }, 102 | 103 | onShopChangeStart(listener, scope) { 104 | const offListener = EventBus.on('cc:shopChangeStart', listener); 105 | 106 | if (scope) { 107 | scope.$on('$destroy', offListener); 108 | } 109 | 110 | return offListener; 111 | }, 112 | 113 | shopChangeEnable() { 114 | EventBus.dispatch('cc:shopStatusChange', true); 115 | }, 116 | 117 | shopChangeDisable() { 118 | EventBus.dispatch('cc:shopStatusChange', false); 119 | } 120 | 121 | }; 122 | -------------------------------------------------------------------------------- /src/components/menus/MenusNodeCtrl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created with MenusNodeCtrl.js 3 | * @Description: 4 | * @Author: maxsmu 5 | * @Date: 2016-03-16 9:04 AM 6 | */ 7 | import {Inject} from 'angular-es-utils'; 8 | import {dispatchMenuChange} from './MenuService'; 9 | @Inject('$state', '$timeout', '$scope') 10 | export default class MenusNodeCtrl { 11 | 12 | $onInit() { 13 | this._$timeout(() => { 14 | 15 | // -获取当前选择的菜单项(初始化时) 16 | const menu = this.getMenu(this.list); 17 | if (menu) { 18 | dispatchMenuChange(menu); 19 | } 20 | }, 0); 21 | } 22 | 23 | /** 24 | * 点击菜单 25 | * @param node 26 | */ 27 | clickParents(node) { 28 | node.toggleNode = !node.toggleNode; 29 | }; 30 | 31 | /** 32 | * 单击菜单时 33 | * @param menu 34 | */ 35 | clickMenus(menu) { 36 | // - 路由未发生变化时,阻断事件广播 37 | if (menu.state !== this._$state.current.name) { 38 | dispatchMenuChange(menu); 39 | } 40 | }; 41 | 42 | /** 43 | * 初始化时默认打开的菜单项 44 | * @param menus 45 | * @returns {*} 46 | */ 47 | getMenu(menus = []) { 48 | if (Array.isArray(menus)) { 49 | return menus.find(item => { 50 | return item.state === this._$state.current.name; 51 | }); 52 | } else { 53 | return {}; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/components/menus/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created with index.js 3 | * @Description: 4 | * @Author: maxsmu 5 | * @Date: 2016-02-29 5:24 PM 6 | */ 7 | import angular from 'angular'; 8 | import uiRouter from 'angular-ui-router'; 9 | import utils from '../../common/utils'; 10 | import $menus from './MenuService'; 11 | 12 | import './_menu.scss'; 13 | import menuBarTemplate from './menus.tpl.html'; 14 | import MenusCtrl from './MenusCtrl'; 15 | 16 | import menuNodeTemplate from './menus-node.tpl.html'; 17 | import MenusNodeCtrl from './MenusNodeCtrl'; 18 | 19 | import shopSelect from './shop-select'; 20 | 21 | const menusBarDDO = { 22 | template: menuBarTemplate, 23 | controller: MenusCtrl, 24 | controllerAs: 'menus', 25 | bindings: { 26 | unfold: '=', 27 | onUnfold: '&?', 28 | collapse: ' 2 |
  • 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 45 | 46 |
  • 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/components/menus/menus.tpl.html: -------------------------------------------------------------------------------- 1 | 81 |
    82 | -------------------------------------------------------------------------------- /src/components/menus/shop-select/ShopSelectsHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created ShopSelectsHelper.js into ccms-components 3 | * Author: Micheal 4 | * Date : 2017/04/11 12:12 5 | * GitHub: https://github.com/maxsmu 6 | * Description: 店铺选择器辅助集合 7 | */ 8 | export default {}; 9 | -------------------------------------------------------------------------------- /src/components/menus/shop-select/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created index.js.js into ccms-components 3 | * Author: Micheal 4 | * Date : 2017/04/11 11:39 5 | * GitHub: https://github.com/maxsmu 6 | * Description: 店铺选择器 7 | */ 8 | import angular from 'angular'; 9 | 10 | import './_shop-selector.scss'; 11 | import template from './shop-selects.tpl.html'; 12 | import ShopSelectsCtrl from './ShopSelectsCtrl'; 13 | 14 | const shopSelectDDO = { 15 | template, 16 | controller: ShopSelectsCtrl, 17 | controllerAs: '$ctrl', 18 | bindings: { 19 | shopSource: ' 3 | 16 | 17 |
    18 | 19 |
    20 |

    21 |
      22 | 23 | 24 |
    • 27 |
      28 | 29 |
    • 30 | 31 |
    32 |
    33 | 34 |
    35 | 36 |
    37 |

    没有搜索结果

    38 |

    请重新输入关键字或者查看全部 40 |

    41 |
    42 | 43 | -------------------------------------------------------------------------------- /src/components/menus/shop-select/tpls/shop-item.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/components/modal/Constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-02 5 | */ 6 | 7 | export default { 8 | CONFIRM_CONTAINER: '.container.confirm', 9 | MODAL_CONTAINER: '.container.modal', 10 | HEADER_SELECTOR: '.modal.container header', 11 | BODY_SELECTOR: '.modal-body', 12 | FOOTER_SELECTOR: '.modal.container footer', 13 | MODAL_OPENED_CLASS: 'modal-opened', 14 | SCALE: 0.85, 15 | THROTTLE_DELAY: 100 16 | }; 17 | -------------------------------------------------------------------------------- /src/components/modal/__tests__/modal-body.url.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 |

    9 | 10 |
    11 | 12 | 13 | {{a}} 14 |
    15 |
    16 | 17 |
    18 | 19 | 20 | 21 |
    22 | 23 |
    24 | 25 | 26 | 27 | 28 | 29 |
    30 | -------------------------------------------------------------------------------- /src/components/modal/__tests__/modal-footer.url.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 |
    6 | -------------------------------------------------------------------------------- /src/components/modal/confirm.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 | 5 |
    6 |
    7 | 8 |
    9 | 10 |

    11 |
    12 | 13 |
    14 | 15 | 16 |
    17 | 18 |
    19 | 20 |
    21 |
    22 | -------------------------------------------------------------------------------- /src/components/modal/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-02-25 5 | */ 6 | 7 | import angular from 'angular'; 8 | import draggable from '../draggable'; 9 | import bindHtml from '../bind-html'; 10 | 11 | import ModalService from './ModalService'; 12 | 13 | export default angular 14 | .module('ccms.components.modal', [bindHtml, draggable]) 15 | .value('$ccModal', ModalService) 16 | .name; 17 | -------------------------------------------------------------------------------- /src/components/modal/modal.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 25 | 26 |
    27 |
    28 | -------------------------------------------------------------------------------- /src/components/ng-dom-value/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-05-24 5 | */ 6 | 7 | import angular from 'angular'; 8 | 9 | const ddo = { 10 | 11 | restrict: 'A', 12 | link(scope, element, attr) { 13 | 14 | scope.$watch(attr.ngDomValue, val => { 15 | element.val(val); 16 | }); 17 | } 18 | 19 | }; 20 | 21 | export default angular.module('ccms.components.ngDomValue', []) 22 | .directive('ngDomValue', () => ddo) 23 | .name; 24 | -------------------------------------------------------------------------------- /src/components/ng-enter/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-01-11 5 | */ 6 | 7 | import angular from 'angular'; 8 | import injector from 'angular-es-utils/injector'; 9 | 10 | const ddo = { 11 | 12 | require: '?ngModel', 13 | compile(element, attr) { 14 | 15 | const $parse = injector.get('$parse'); 16 | const fn = $parse(attr['ngEnter'], null, true); 17 | const ENTER_KEY_CODE = 13; 18 | 19 | // link function 20 | return (scope, element, attr, $ngModelCtrl) => { 21 | 22 | const callback = $event => { 23 | 24 | if ($event.keyCode === ENTER_KEY_CODE) { 25 | scope.$apply(() => { 26 | fn(scope, {$event, $ngModelCtrl}); 27 | }); 28 | } 29 | }; 30 | 31 | element.on('keyup', callback); 32 | 33 | // unbind event listener 34 | scope.$on('$destroy', () => { 35 | element.off('keyup', callback); 36 | }); 37 | 38 | }; 39 | } 40 | 41 | }; 42 | 43 | export default angular 44 | .module('ccms.components.ngEnter', []) 45 | .directive('ngEnter', () => ddo) 46 | .name; 47 | -------------------------------------------------------------------------------- /src/components/nice-scroll/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-09-07 5 | */ 6 | import angular from 'angular'; 7 | import 'jquery.nicescroll'; 8 | import { Bind, Inject, Debounce } from 'angular-es-utils/decorators'; 9 | import browser from '../../common/utils/browser'; 10 | 11 | const $ = window.NiceScroll.getjQuery(); 12 | 13 | @Inject('$element', '$scope') 14 | class Controller { 15 | 16 | $postLink() { 17 | if (browser.os !== 'MacOS') { 18 | this.niceScroll = $(this._$element).niceScroll(this.options || 19 | { 20 | cursorwidth: '7px', 21 | cursorcolor: '#808080', 22 | mousescrollstep: 200 23 | }); 24 | 25 | // 内容发生变更时重算滚动条 26 | this._$element[0].addEventListener('DOMSubtreeModified', this.resize, false); 27 | this._$scope.$on('ccDragEnd', this.resize); 28 | } 29 | } 30 | 31 | $onDestroy() { 32 | if (this.niceScroll) { 33 | this.niceScroll.remove(); 34 | this._$element[0].removeEventListener('DOMSubtreeModified', this.resize, false); 35 | } 36 | } 37 | 38 | @Bind 39 | @Debounce(100) 40 | resize() { 41 | // windows firefox 环境下,可能因为渲染较慢(也可能 dom 监听机制不一样)导致 resize 事件在 removeEventListener 之前触发 42 | // listener 回调却在 removeEventListener 之后执行 43 | if (this.niceScroll.resize) { 44 | this.niceScroll.resize(); 45 | } 46 | } 47 | 48 | } 49 | 50 | const ddo = { 51 | restrict: 'A', 52 | controller: Controller, 53 | controllerAs: '$$niceScroll', 54 | bindToController: { 55 | options: ' ddo) 62 | .name; 63 | 64 | -------------------------------------------------------------------------------- /src/components/pagination/PaginationCtrl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-01-05 5 | * 分页组件控制器 6 | */ 7 | 8 | import {Inject} from 'angular-es-utils'; 9 | 10 | @Inject('$scope', '$element') 11 | export default class PaginationCtrl { 12 | 13 | constructor() { 14 | // 分页风格 15 | // enum: ['normal', 'simple'] 16 | this.type = 'normal'; 17 | 18 | this.pageSizeListDisabled = undefined; 19 | } 20 | 21 | $onInit() { 22 | let element = this.getElement(); 23 | if (element.hasAttribute('page-size-list-hidden') && this.pageSizeListHidden !== false) { 24 | this.pageSizeListHidden = true; 25 | } 26 | if (element.hasAttribute('page-size-list-disabled') && this.pageSizeListDisabled !== false) { 27 | this.pageSizeListDisabled = true; 28 | } 29 | 30 | this._prepareWatches(); 31 | } 32 | 33 | _prepareWatches() { 34 | const scope = this.getScope(); 35 | 36 | scope.$watch(() => this.pageNum, pageNum => { 37 | this.isFirstPage = pageNum === 1; 38 | this.isLastPage = pageNum === this.totalPages; 39 | this.inputPage = pageNum; 40 | }); 41 | 42 | scope.$watch(() => this.totalPages, totalPages => { 43 | this.isLastPage = this.pageNum === this.totalPages; 44 | }); 45 | } 46 | 47 | get totalPages() { 48 | return this._totalPages || 1; 49 | } 50 | 51 | set totalPages(value) { 52 | this._totalPages = value; 53 | } 54 | 55 | get pageSize() { 56 | return this._pageSize || 20; 57 | } 58 | 59 | set pageSize(value) { 60 | this._pageSize = value; 61 | } 62 | 63 | get pageSizeList() { 64 | return this._pageSizeList || [10, 15, 20, 30, 50]; 65 | } 66 | 67 | set pageSizeList(value) { 68 | this._pageSizeList = value; 69 | } 70 | 71 | getElement() { 72 | return this._$element[0]; 73 | } 74 | 75 | getScope() { 76 | return this._$scope; 77 | } 78 | 79 | first() { 80 | this.goto(1); 81 | } 82 | 83 | last() { 84 | this.goto(this.totalPages); 85 | } 86 | 87 | previous() { 88 | this.goto(this.pageNum - 1); 89 | } 90 | 91 | next() { 92 | this.goto(this.pageNum + 1); 93 | } 94 | 95 | goto(pageNum) { 96 | pageNum = Number(pageNum); 97 | if (!Number.isInteger(pageNum) || pageNum < 1 || pageNum > this.totalPages) { 98 | return false; 99 | } 100 | this.pageNum = pageNum; 101 | this.onPageChange(); 102 | return true; 103 | } 104 | 105 | onPageChange() { 106 | const { pageNum, pageSize } = this; 107 | this.onChange({ pageNum, pageSize }); 108 | } 109 | 110 | setPageSize(pageSize) { 111 | this.pageSize = pageSize; 112 | this.pageNum = 1; 113 | this.onPageChange(); 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /src/components/pagination/_pagination.scss: -------------------------------------------------------------------------------- 1 | @import "../styles/input"; 2 | @import "../../../node_modules/ccms-icon/iconfont"; 3 | 4 | cc-pagination { 5 | display: inline-block; 6 | } 7 | 8 | .pagination { 9 | > * { 10 | display: inline-block; 11 | vertical-align: middle; 12 | margin: 0 5px; 13 | &:first-child { 14 | margin-left: 0; 15 | } 16 | &:last-child { 17 | margin-right: 0; 18 | } 19 | } 20 | a { 21 | @extend .iconfont; 22 | padding: 3px; 23 | text-decoration: none; 24 | color: #929292; 25 | cursor: pointer; 26 | overflow: hidden; 27 | 28 | &%disabled { 29 | color: #d1d1d1; 30 | cursor: not-allowed; 31 | } 32 | 33 | @at-root .first-page#{&} { 34 | &.first, &.previous { 35 | @extend %disabled; 36 | } 37 | } 38 | @at-root .last-page#{&} { 39 | &.next, &.last { 40 | @extend %disabled; 41 | } 42 | } 43 | 44 | &:hover { 45 | color: #828282; 46 | } 47 | 48 | &.first { 49 | @extend .icon-lastpage; 50 | transform: rotate(180deg); 51 | } 52 | &.previous { 53 | @extend .icon-nextpage; 54 | transform: rotate(180deg); 55 | } 56 | &.next { 57 | @extend .icon-nextpage; 58 | } 59 | &.last { 60 | @extend .icon-lastpage; 61 | } 62 | 63 | > span { 64 | display: inline-block; 65 | direction: ltr; 66 | text-align: left; 67 | text-indent: -99999px; 68 | } 69 | } 70 | .jump input { 71 | height: 26px; 72 | width: auto; 73 | min-width: 36px; 74 | margin: 0 3px; 75 | padding: 0 4px; 76 | text-align: center; 77 | } 78 | select { 79 | width: auto; 80 | height: 26px; 81 | line-height: 18px; 82 | padding: 0; 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /src/components/pagination/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-01-04 5 | */ 6 | 7 | import angular from 'angular'; 8 | import ngEnter from '../ng-enter'; 9 | import ngDomValue from '../ng-dom-value'; 10 | 11 | import './_pagination.scss'; 12 | import controller from './PaginationCtrl'; 13 | import template from './pagination.tpl.html'; 14 | 15 | const paginationDDO = { 16 | 17 | template, 18 | controller, 19 | bindings: { 20 | type: '@?', 21 | totalPages: ' 4 | 5 | first 6 | 7 | 8 | 13 | / 共页 14 | 15 | 16 | last 17 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/radio/RadioCtrl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author jianzhe.ding 3 | * @homepage https://github.com/discipled/ 4 | * @since 2016-07-07 17:36 5 | */ 6 | 7 | export default class CheckboxController { 8 | /** 9 | * @name $onInit 10 | * controller init method, one of ngModel or value isn't set, method will console an error message 11 | */ 12 | $onInit() { 13 | (typeof this.ngModel === 'undefined' || typeof this._value === 'undefined') && (console.warn('Radio button have to used with ng-model & ng-value') || (this.isError = true)); 14 | } 15 | 16 | /** 17 | * get value from ngValue | value 18 | * @returns {boolean | undefined} 19 | * @private 20 | */ 21 | get _value() { 22 | return typeof this.ngValue !== 'undefined' ? this.ngValue : (typeof this.value !== 'undefined' ? this.value : undefined); 23 | } 24 | 25 | /** 26 | * @name updateNgModel 27 | * @param {any} value 28 | * update ng-model value 29 | */ 30 | updateNgModel(value) { 31 | this.ngModelController && this.ngModelController.$setViewValue(value); // change model value & $setViewValue method will trigger method binding on model 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/radio/__tests__/test-RadioCtrl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author jianzhe.ding 3 | * @homepage https://github.com/discipled/ 4 | * @since 2016-08-11 14:36 5 | */ 6 | import { assert } from 'chai'; 7 | 8 | import RadioCtrl from '../RadioCtrl.js'; 9 | 10 | describe('RadioCtrl', () => { 11 | let radioCtrl, bindings; 12 | 13 | before(() => { 14 | radioCtrl = new RadioCtrl(); 15 | 16 | // mock ngModelController 17 | radioCtrl.ngModelController = { 18 | $setViewValue: function(value) { 19 | radioCtrl.ngModel = value; 20 | } 21 | }; 22 | }); 23 | 24 | it('$onInit', () => { 25 | bindings = { 26 | ngModel: false 27 | }; 28 | Object.assign(radioCtrl, bindings); 29 | radioCtrl.$onInit(); 30 | assert.deepEqual(radioCtrl.isError, true); 31 | 32 | delete radioCtrl.isError; 33 | bindings = { 34 | ngModel: false, 35 | ngValue: false 36 | }; 37 | Object.assign(radioCtrl, bindings); 38 | radioCtrl.$onInit(); 39 | assert.deepEqual(radioCtrl.isError, undefined); 40 | }); 41 | 42 | it('updateNgModel', () => { 43 | assert.deepEqual(radioCtrl.ngModel, false); 44 | radioCtrl.updateNgModel(true); 45 | assert.deepEqual(radioCtrl.ngModel, true); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/components/radio/_radio.scss: -------------------------------------------------------------------------------- 1 | @import "../styles/variables"; 2 | 3 | $font-size-number: 12; 4 | $text-color: #3d3d3d !default; 5 | $normal-border-color: #929292; 6 | $normal-hover-border-color: #828282; 7 | $checked-border-color: #0083BA; 8 | $checked-hover-border-color: #00AAF1; 9 | $disabled-color: #D1D1D1; 10 | 11 | cc-radio{ 12 | display: inline-block; 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | } 18 | 19 | .cc-radio{ 20 | font-size: $font-size-base; 21 | display: flex; 22 | align-items: center; 23 | 24 | &:hover{ 25 | cursor: pointer; 26 | 27 | .radio-button-icon{ 28 | border-color: $normal-hover-border-color; 29 | } 30 | } 31 | 32 | .radio-button-icon{ 33 | border: solid 1px $normal-border-color; 34 | border-radius: 50%; 35 | position: relative; 36 | width: (16 / $font-size-number) + em; 37 | height: (16 / $font-size-number) + em; 38 | margin-right: 1em; 39 | 40 | &:before{ 41 | position: absolute; 42 | content: ''; 43 | display: block; 44 | } 45 | } 46 | 47 | .radio-button-label{ 48 | color: $text-color; 49 | } 50 | } 51 | 52 | .cc-radio.checked{ 53 | &:hover{ 54 | .radio-button-icon{ 55 | border-color: $checked-hover-border-color; 56 | 57 | &:before{ 58 | background-color: $checked-hover-border-color; 59 | } 60 | } 61 | } 62 | 63 | .radio-button-icon{ 64 | border-color: $checked-border-color; 65 | 66 | &:before{ 67 | width: (8 / $font-size-number) + em; 68 | height: (8 / $font-size-number) + em; 69 | border-radius: 50%; 70 | background-color: $checked-border-color; 71 | left: (3 / $font-size-number) + em; 72 | top: (3 / $font-size-number) + em; 73 | } 74 | } 75 | } 76 | 77 | .cc-radio.disabled{ 78 | &:hover{ 79 | cursor: default; 80 | 81 | .radio-button-icon{ 82 | border-color: $disabled-color; 83 | 84 | &:before{ 85 | background-color: $disabled-color; 86 | } 87 | } 88 | } 89 | 90 | .radio-button-icon{ 91 | border-color: $disabled-color; 92 | 93 | &:before{ 94 | background-color: $disabled-color; 95 | } 96 | } 97 | 98 | .radio-button-label{ 99 | color: $disabled-color; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/components/radio/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author jianzhe.ding 3 | * @homepage https://github.com/discipled/ 4 | * @since 2016-07-07 17:36 5 | */ 6 | 7 | import angular from 'angular'; 8 | 9 | import './_radio.scss'; 10 | import controller from './RadioCtrl.js'; 11 | import template from './radio.tpl.html'; 12 | 13 | const ccmsRadioButtonSetting = { 14 | controller, 15 | transclude: true, 16 | template, 17 | bindings: { 18 | ngDisabled: ' 2 |
    3 | 4 | -------------------------------------------------------------------------------- /src/components/styles/_animation.scss: -------------------------------------------------------------------------------- 1 | @keyframes slide-down { 2 | from { 3 | transform: translateY(-100%); 4 | } 5 | to { 6 | transform: translateY(0); 7 | } 8 | } 9 | 10 | @keyframes slide-up { 11 | from { 12 | transform: translateY(0); 13 | } 14 | to { 15 | transform: translateY(-100%); 16 | } 17 | } 18 | 19 | @keyframes fade-in { 20 | from { 21 | opacity: 0; 22 | } 23 | to { 24 | opacity: 1; 25 | } 26 | } 27 | 28 | @keyframes fade-out { 29 | from { 30 | opacity: 1; 31 | } 32 | to { 33 | opacity: 0; 34 | } 35 | } 36 | 37 | @keyframes rotate { 38 | 100% { 39 | transform: rotate(360deg); 40 | } 41 | } 42 | 43 | @keyframes dash { 44 | 0% { 45 | stroke-dasharray: 1, 200; 46 | stroke-dashoffset: 0; 47 | } 48 | 50% { 49 | stroke-dasharray: 89, 200; 50 | stroke-dashoffset: -35px; 51 | } 52 | 100% { 53 | stroke-dasharray: 89, 200; 54 | stroke-dashoffset: -124px; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/components/styles/_base.scss: -------------------------------------------------------------------------------- 1 | @import "../../../node_modules/normalize.css"; 2 | @import "button"; 3 | @import "variables"; 4 | @import "input"; 5 | @import "layout"; 6 | @import "layout-flex"; 7 | 8 | /* 页面默认样式 */ 9 | body { 10 | font-family: $font-family; 11 | font-size: $font-size-base; 12 | } 13 | 14 | ul, li, p { 15 | margin: 0; 16 | padding: 0; 17 | } 18 | 19 | li { 20 | list-style: none; 21 | } 22 | 23 | a { 24 | text-decoration: none; 25 | color: #0083ba; 26 | 27 | &:link { 28 | color: #0083ba; 29 | } 30 | 31 | &:hover { 32 | color: #00aaf1; 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/components/styles/_input.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | 4 | $input-border: #d9d9d9; 5 | $input-border-color: #d9d9d9; 6 | $input-disabled-bg: #eee; 7 | $input-disabled-color: #999; 8 | $input-active-border: #aacbe1; 9 | $input-error-border: #ff867c; 10 | $input-placeholder: #c9c9c9; 11 | 12 | %input-error-border { 13 | border: 1px solid $input-error-border; 14 | } 15 | 16 | input:not([type]), 17 | input[type=text], 18 | input[type=email], 19 | input[type=date], 20 | input[type=url], 21 | input[type=tel], 22 | input[type=number], 23 | select { 24 | background-color: #fff; 25 | border: 1px solid $input-border; 26 | box-sizing: border-box; 27 | color: $color-text; 28 | height: $control-height; 29 | max-width: 100%; 30 | outline: none; 31 | padding: 0 8px; 32 | width: $control-default-width; 33 | 34 | &:focus { 35 | border: 1px solid $input-active-border; 36 | } 37 | 38 | &:disabled { 39 | background-color: $input-disabled-bg; 40 | color: $input-disabled-color; 41 | } 42 | 43 | &.ng-invalid.ng-dirty { 44 | @extend %input-error-border; 45 | } 46 | } 47 | input[type="search"]::-webkit-search-decoration, 48 | input[type="search"]::-webkit-search-cancel-button, 49 | input[type="search"]::-webkit-search-results-button, 50 | input[type="search"]::-webkit-search-results-decoration { display: none; } 51 | 52 | ::-webkit-input-placeholder { 53 | color: $input-placeholder; 54 | } 55 | -------------------------------------------------------------------------------- /src/components/styles/_layout-flex.scss: -------------------------------------------------------------------------------- 1 | $side-container-width: 180px; 2 | 3 | .cc-main-container { 4 | 5 | position: relative; 6 | display: flex; 7 | flex-direction: row; 8 | height: 100%; 9 | 10 | & .cc-side-container { 11 | order: 1; 12 | overflow: auto; 13 | width: $side-container-width; 14 | } 15 | 16 | & .cc-content-container { 17 | flex: 1; 18 | padding: 20px; 19 | order: 2; 20 | overflow: auto; 21 | } 22 | 23 | & .cc-aside-container { 24 | flex: 0 0 16em; 25 | order: 3; 26 | overflow: auto; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/styles/_layout.scss: -------------------------------------------------------------------------------- 1 | $left-width: 180px; 2 | $center-width: 200px; 3 | $right-width: 200px; 4 | $min-width: 30px; 5 | 6 | .ccmsc-main-container { 7 | display: block; 8 | position: absolute; 9 | left: 0; 10 | right: 0; 11 | top: 0; 12 | bottom: 0; 13 | & .left-container { 14 | //@include test-border; 15 | position: absolute; 16 | left: 0; 17 | top: 0; 18 | bottom: 0; 19 | z-index: 200; 20 | width: $left-width; 21 | background: #E9EBEE; 22 | } 23 | & .center-container { 24 | //@include test-border; 25 | //border-color: green; 26 | position: absolute; 27 | left: $left-width; 28 | top: 0; 29 | bottom: 0; 30 | right: 0; 31 | padding: 20px; 32 | } 33 | 34 | & .right-container { 35 | //@include test-border; 36 | //border-color: blue; 37 | position: absolute; 38 | top: 0; 39 | bottom: 0; 40 | padding: 20px; 41 | } 42 | 43 | &.contract-left { 44 | & .left-container { 45 | width: $min-width; 46 | } 47 | & .center-container { 48 | left: $min-width; 49 | } 50 | & .right-container { 51 | padding: 0; 52 | display: none; 53 | } 54 | } 55 | 56 | &.type-two { 57 | & .center-container { 58 | //@include test-border; 59 | right: $right-width + 40; 60 | } 61 | 62 | & .right-container { 63 | //@include test-border; 64 | right: 0; 65 | width: $right-width; 66 | padding: 20px; 67 | display: block; 68 | } 69 | } 70 | 71 | &.type-three { 72 | & .center-container { 73 | width: $center-width; 74 | right: inherit; 75 | } 76 | 77 | & .right-container { 78 | left: $center-width + $left-width + 40; 79 | right: 0; 80 | padding: 20px; 81 | display: block; 82 | } 83 | &.contract-left { 84 | & .right-container { 85 | left: $center-width + $min-width + 40; 86 | right: 0; 87 | } 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/components/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | // links 2 | @mixin all-link($link-color) { 3 | a, a:link, a:hover, a:visited, a:active { 4 | color: $link-color; 5 | text-decoration: none; 6 | } 7 | } 8 | 9 | @mixin self-link($link-color) { 10 | &, &:link, &:hover, &:visited, &:active { 11 | color: $link-color; 12 | text-decoration: none; 13 | } 14 | } 15 | 16 | // test 17 | @mixin test-border { 18 | border: 1px solid red; 19 | } 20 | 21 | @mixin fonts { 22 | font-family: $font-string; 23 | font-size: 12px; 24 | } 25 | 26 | @mixin horiz-center { 27 | position: absolute; 28 | top: 50%; 29 | left: 50%; 30 | transform: translate(-50%, -50%); 31 | } 32 | 33 | //icon border 34 | @mixin order { 35 | font-size: 10px; 36 | height: 38px; 37 | line-height: 38px; 38 | } 39 | 40 | //tab 41 | // opacity ie: $number=50 42 | @mixin opacity($number) { 43 | filter: alpha(opacity=$number); 44 | -moz-opacity: $number / 100; 45 | opacity: $number / 100; 46 | } 47 | 48 | // linear gradient 49 | @mixin linear-gradient($angel, $startColor, $endColor, $background-color: $endColor) { 50 | background: $background-color linear-gradient($angel, $startColor, $endColor); 51 | } 52 | 53 | //grid 54 | @mixin cut-long-text { 55 | overflow: hidden; 56 | white-space: nowrap; 57 | text-overflow: ellipsis; 58 | } 59 | 60 | // area-select 61 | @mixin normalfont { 62 | font-size: 12px; 63 | font-family: $font; 64 | color: #666666; 65 | text-align: left; 66 | } 67 | 68 | @mixin clear-fix { 69 | zoom: 1; 70 | &:before, &:after { 71 | content: ""; 72 | display: block; 73 | clear: both; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/components/styles/_placeholders.scss: -------------------------------------------------------------------------------- 1 | %clear-fix { 2 | zoom: 1; 3 | 4 | &:before, &:after { 5 | content: ""; 6 | display: table; 7 | } 8 | } 9 | 10 | %absolute-center { 11 | position: absolute; 12 | top: 50%; 13 | left: 50%; 14 | transform: translate(-50%, -50%); 15 | } 16 | 17 | %absolute-center-flex { 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | } 22 | -------------------------------------------------------------------------------- /src/components/tab/_tab.scss: -------------------------------------------------------------------------------- 1 | @import "../styles/variables"; 2 | @import "../../../node_modules/ccms-icon/iconfont"; 3 | 4 | // clear fix 5 | .clear-fix { 6 | display: block; 7 | zoom: 1; 8 | 9 | &:after { 10 | content: " "; 11 | display: block; 12 | font-size: 0; 13 | height: 0; 14 | clear: both; 15 | visibility: hidden; 16 | } 17 | } 18 | 19 | 20 | // tab1 21 | .tab-title { 22 | border-bottom: 1px solid $tab-border-hover-color; 23 | font-size: $font-size-tab; 24 | padding: 0; 25 | position: relative; 26 | 27 | li { 28 | background-color: $tab-unactive-background-color; 29 | border-color: $tab-border-color $tab-border-color $tab-border-hover-color; 30 | border-image: none; 31 | border-style: solid; 32 | border-width: 1px; 33 | bottom: -2px; 34 | color: #145681; 35 | cursor: pointer; 36 | float: left; 37 | margin-right: 5px; 38 | overflow: hidden; 39 | padding: 7px 20px; 40 | position: relative; 41 | text-overflow: ellipsis; 42 | white-space: nowrap; 43 | max-width: 150px; 44 | &:hover{ 45 | background-color: $tab-hover-background-color; 46 | color: #3a3a3a; 47 | } 48 | 49 | .tab-close { 50 | @extend .iconfont; 51 | @extend .icon-clear; 52 | position: absolute; 53 | right: 2px; 54 | top: 1px; 55 | display: block; 56 | font-size: 12px; 57 | color: $tab-border-color; 58 | &:hover { 59 | //color: #a3a3a3; 60 | //color: red; 61 | } 62 | } 63 | } 64 | 65 | .active { 66 | background-color: $tab-hover-background-color; 67 | border-color: $tab-border-hover-color $tab-border-hover-color transparent; 68 | border-image: none; 69 | border-style: solid; 70 | border-width: 1px; 71 | color: #3a3a3a; 72 | z-index: 1; 73 | } 74 | .btns{ 75 | position: absolute; 76 | right: 0; 77 | .btn-back, .btn-forward{ 78 | @extend .iconfont; 79 | color: #C7C7C7; 80 | font-size: 20px; 81 | height: 30px; 82 | line-height: 30px; 83 | width: 30px; 84 | text-align: center; 85 | display: inline-block; 86 | cursor: pointer; 87 | &:hover{ 88 | background-color: #F4F4F4; 89 | } 90 | &.disabled{ 91 | color: #EDEDED; 92 | } 93 | &:active{ 94 | background-color: #6AD0CE; 95 | } 96 | &.btn-back{ 97 | @extend .icon-rightarrow; 98 | transform: rotate(180deg); 99 | margin-right:-2px; 100 | } 101 | &.btn-forward{ 102 | @extend .icon-rightarrow; 103 | } 104 | } 105 | 106 | } 107 | } 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/components/tabset/TabsetCtrl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author fengqiu.wu 3 | */ 4 | 5 | import angular from 'angular'; 6 | import {Inject} from 'angular-es-utils'; 7 | 8 | 9 | @Inject('$scope') 10 | export default class TabsetCtrl { 11 | 12 | $onInit() { 13 | 14 | this.tabs = []; 15 | this.active = this.active || 0; 16 | 17 | let oldIndex = null; 18 | let scope = this._$scope; 19 | 20 | scope.$watch('$tabset.active', index => { 21 | if (angular.isDefined(index) && index !== oldIndex) { 22 | this.selectTab(this.findIndex(index)); 23 | } 24 | }); 25 | } 26 | 27 | findIndex(index) { 28 | let findIndex = -1; 29 | this.tabs.forEach((tab, i) => { 30 | if (tab.index === index) { 31 | findIndex = i; 32 | } 33 | }); 34 | return findIndex; 35 | } 36 | 37 | selectTab(index, tab) { 38 | if (index === undefined && this.active === undefined) return; 39 | 40 | this.active = index; 41 | tab && tab.onSelect && tab.onSelect({tab: { 42 | index: tab.index, 43 | title: tab.title, 44 | contents: tab.contents 45 | }}); 46 | } 47 | 48 | addTab(tab) { 49 | tab.index = this.tabs.length; 50 | this.tabs.push({ 51 | tab, 52 | index: tab.index 53 | }); 54 | 55 | if (tab.index === this.active || !angular.isDefined(this.active) && this.tabs.length === 1) { 56 | const activeIndex = this.findIndex(tab.index); 57 | this.selectTab(activeIndex, this.tabs[activeIndex]); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/tabset/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author fengqiu.wu 3 | */ 4 | 5 | import './_tabset.scss'; 6 | 7 | import angular from 'angular'; 8 | 9 | import TabsCtrl from './TabsetCtrl'; 10 | 11 | import tabsetTemplate from './tpls/tabset.tpl.html'; 12 | import tabTemplate from './tpls/tab.tpl.html'; 13 | 14 | import tabTitleTranscludeLink from './tabTitleTranscludeLink'; 15 | import tabContentTranscludeLink from './tabContentTranscludeLink'; 16 | 17 | const tabsetDDO = { 18 | name: 'ccTabset', 19 | transclude: true, 20 | template: tabsetTemplate, 21 | scope: { 22 | active: '=?', 23 | type: '@' 24 | }, 25 | controller: TabsCtrl, 26 | controllerAs: '$tabset', 27 | bindToController: true 28 | }; 29 | 30 | const tabDDO = { 31 | name: 'ccTab', 32 | require: { 33 | tabset: '^ccTabset' 34 | }, 35 | transclude: true, 36 | scope: { 37 | title: '@', 38 | onSelect: '&' 39 | }, 40 | controller: TabsCtrl, 41 | controllerAs: '$tab', 42 | bindToController: true, 43 | template: tabTemplate 44 | }; 45 | 46 | const tabTitleDDO = { 47 | restrict: 'A', 48 | require: '^ccTab', 49 | link: tabTitleTranscludeLink 50 | }; 51 | 52 | const tabContentDDO = { 53 | restrict: 'A', 54 | require: '^ccTabset', 55 | scope: {}, 56 | link: tabContentTranscludeLink 57 | }; 58 | 59 | export default angular 60 | .module('ccms.components.tabsets', []) 61 | .directive('ccTabset', () => tabsetDDO) 62 | .directive('ccTab', () => tabDDO) 63 | .directive('tabTitleTransclude', () => tabTitleDDO) 64 | .directive('tabContentTransclude', () => tabContentDDO) 65 | .name; 66 | -------------------------------------------------------------------------------- /src/components/tabset/tabContentTranscludeLink.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author fengqiu.wu 3 | */ 4 | import angular from 'angular'; 5 | import injector from 'angular-es-utils/injector'; 6 | 7 | export default function tabContentTranscludeLink(scope, element, attrs, tabset) { 8 | 9 | const $compile = injector.get('$compile'); 10 | 11 | scope.active = 0; 12 | 13 | // 点击tab 设置相关内容 14 | scope.$watch('$parent.$tabset.active', () => { 15 | 16 | scope.active = tabset.active; 17 | 18 | tabset.tabs.forEach(tab => { 19 | if (Number(tab.index) === tabset.active) { 20 | if (!tab.pane) { 21 | let contentWrap = angular.element('
    '); 22 | tab.pane = contentWrap; 23 | 24 | contentWrap.append(tab.tab.contents); 25 | element.append(contentWrap); 26 | 27 | $compile(contentWrap)(angular.extend(scope, {index: tab.index})); 28 | } else { 29 | tab.pane.removeClass('ng-hide'); 30 | } 31 | } else if (tab.pane) { 32 | tab.pane.addClass('ng-hide'); 33 | } 34 | }); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /src/components/tabset/tabTitleTranscludeLink.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author fengqiu.wu 3 | */ 4 | 5 | export default function tabTitleTranscludeLink($scope, $element, $attrs, $tab, $transcludeFn) { 6 | 7 | // 过滤没用节点 8 | let contents = [].slice.apply($transcludeFn()).filter(node => { 9 | return node.nodeType !== 3 || node.nodeType === 3 && !/^[\s\n]*$/.test(node.nodeValue); 10 | }); 11 | 12 | if (contents.length) { 13 | $tab.contents = contents; 14 | } 15 | 16 | $tab.tabset.addTab($tab); 17 | 18 | // 设置 tab title 19 | window.requestAnimationFrame(function() { 20 | $tab.title && $element.html($tab.title); 21 | }); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/components/tabset/tpls/tab.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 |
    6 | -------------------------------------------------------------------------------- /src/components/tabset/tpls/tabset.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 |
    6 |
    7 |
    8 |
    9 |
    10 | -------------------------------------------------------------------------------- /src/components/tips/Tips.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-08 5 | */ 6 | 7 | import Animation from 'angular-es-utils/animation'; 8 | 9 | import Popup from '../../common/bases/Popup'; 10 | 11 | const OPENING_CLASS = 'tips-opening'; 12 | 13 | export default class Tips extends Popup { 14 | 15 | constructor(element, container, openHook, closeHook) { 16 | super(element, container); 17 | this.openHook = openHook; 18 | this.closeHook = closeHook; 19 | this._destroy = this.destroy.bind(this); 20 | } 21 | 22 | open() { 23 | 24 | this.init(); 25 | Animation.addClass(this.element, OPENING_CLASS, undefined, true); 26 | 27 | this._onHashChange(this._destroy); 28 | 29 | this.openHook(); 30 | } 31 | 32 | close() { 33 | 34 | this._offHashChange(); 35 | 36 | // FIXME 如何监听多个动画的onend事件(多个动画之间存在delay) 37 | Animation.removeClass(this.element, this.openedClass); 38 | Animation.addClass(this.element, 'tips-fade-out', () => { 39 | Animation.addClass(this.element, 'tips-compress', this.destroy.bind(this)); 40 | }); 41 | 42 | this.closeHook(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/components/tips/TipsCtrl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-28 5 | */ 6 | import {Inject} from 'angular-es-utils'; 7 | 8 | import {isContentOverflow} from '../../common/utils/style-helper'; 9 | 10 | @Inject('$element') 11 | export default class TipsCtrl { 12 | 13 | isContentOverflow(content) { 14 | return isContentOverflow(this._$element[0].querySelector('p'), content); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/tips/float-tips.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |

    4 | 5 |
    6 | -------------------------------------------------------------------------------- /src/components/tips/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-08 5 | */ 6 | 7 | import angular from 'angular'; 8 | 9 | import './_tips.scss'; 10 | 11 | import bindHtml from '../bind-html'; 12 | import template from './normal-tips.tpl.html'; 13 | import controller from './TipsCtrl'; 14 | import TipsService from './TipsService'; 15 | 16 | const ddo = { 17 | template, 18 | controller, 19 | bindings: { 20 | msg: '@', 21 | type: '@?' 22 | } 23 | }; 24 | 25 | export default angular 26 | .module('ccms.components.tips', [bindHtml]) 27 | .component('ccTips', ddo) 28 | .service('$ccTips', TipsService) 29 | .name; 30 | -------------------------------------------------------------------------------- /src/components/tips/normal-tips.tpl.html: -------------------------------------------------------------------------------- 1 |

    2 | 3 | 4 |

    5 | -------------------------------------------------------------------------------- /src/components/toggle/ToggleCtrl.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import { Inject } from 'angular-es-utils'; 3 | 4 | @Inject('$element') 5 | export default class ToggleController { 6 | static valueOn = true; 7 | static valueOff = false; 8 | static textOn = '已开启'; 9 | static textOff = '已关闭'; 10 | 11 | $onInit() { 12 | this._prepareOptions(); 13 | this.updateNgModel(this.state); 14 | } 15 | 16 | $onChanges(changedObjects) { 17 | // 使用 .no-animation 来禁用初始动画 18 | // 在手动触发 ngModel 变化时移除 .no-animation 19 | const ngModelObject = changedObjects.ngModel; 20 | if (ngModelObject && 21 | !ngModelObject.isFirstChange() && 22 | angular.isDefined(ngModelObject.previousValue)) { 23 | 24 | angular 25 | .element(this.getElement().querySelector('.cc-toggle')) 26 | .toggleClass('no-animation', false); 27 | } 28 | } 29 | 30 | _prepareOptions() { 31 | if (angular.isUndefined(this.valueOn)) { 32 | if (angular.isDefined(this.ngTrueValue)) { 33 | this.valueOn = this.ngTrueValue; 34 | console.warn('cc-toggle/cc-switch: ng-true-value/ng-false-value 参数已废弃,请使用 value-on/value-off 代替。'); 35 | } else { 36 | this.valueOn = ToggleController.valueOn; 37 | } 38 | } 39 | 40 | if (angular.isUndefined(this.valueOff)) { 41 | if (angular.isDefined(this.ngFalseValue)) { 42 | this.valueOff = this.ngFalseValue; 43 | console.warn('cc-toggle/cc-switch: ng-true-value/ng-false-value 参数已废弃,请使用 value-on/value-off 代替。'); 44 | } else { 45 | this.valueOff = ToggleController.valueOff; 46 | } 47 | } 48 | 49 | if (angular.isUndefined(this.textOn)) { 50 | if (angular.isDefined(this.openText)) { 51 | this.textOn = this.openText; 52 | console.warn('cc-toggle/cc-switch: open-text/close-text 参数已废弃,请使用 text-on/text-off 代替。'); 53 | } else { 54 | this.textOn = ToggleController.textOn; 55 | } 56 | } 57 | 58 | if (angular.isUndefined(this.textOff)) { 59 | if (angular.isDefined(this.closeText)) { 60 | this.textOff = this.closeText; 61 | console.warn('cc-toggle/cc-switch: open-text/close-text 参数已废弃,请使用 text-on/text-off 代替。'); 62 | } else { 63 | this.textOff = ToggleController.textOff; 64 | } 65 | } 66 | } 67 | 68 | get state() { 69 | return this.ngModel === this.valueOn; 70 | } 71 | 72 | updateNgModel(state) { 73 | const viewValue = state ? this.valueOn : this.valueOff; 74 | this.ngModelController.$setViewValue(viewValue); 75 | } 76 | 77 | getElement() { 78 | return this._$element[0]; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/components/toggle/_toggle.scss: -------------------------------------------------------------------------------- 1 | $size: 1.66667em; 2 | $gap: 0.08333em; 3 | 4 | cc-toggle, cc-switch { 5 | display: inline-block; 6 | vertical-align: middle; 7 | } 8 | 9 | .cc-toggle { 10 | position: relative; 11 | overflow: hidden; 12 | height: $size; 13 | border-radius: $size / 2; 14 | font-size: 12px; 15 | cursor: pointer; 16 | 17 | &.on { 18 | padding-left: $size / 2; 19 | padding-right: $size * 1.2; 20 | background: #47cf5f; 21 | } 22 | 23 | &.off { 24 | padding-right: $size / 2; 25 | padding-left: $size * 1.2; 26 | background: #c2c2c2; 27 | } 28 | 29 | &.disabled { 30 | cursor: not-allowed; 31 | } 32 | 33 | @at-root %text { 34 | display: block; 35 | height: 100%; 36 | line-height: $size; 37 | vertical-align: middle; 38 | text-align: center; 39 | color: #fff; 40 | } 41 | 42 | .checkbox { 43 | // firefox checkbox "appearance: none" 表现与 chrome 不同 44 | // see: https://bugzilla.mozilla.org/show_bug.cgi?id=605985 45 | position: absolute; 46 | top: $gap; 47 | z-index: 1; 48 | width: calc(#{$size} - #{$gap * 2}); 49 | height: calc(100% - #{$gap * 2}); 50 | vertical-align: middle; 51 | border-radius: calc(#{$size / 2} - #{$gap}); 52 | background: #f1f1f1; 53 | 54 | @keyframes slide-on { 55 | from { left: $gap; } to { left: calc(100% - #{$size - $gap}); } 56 | } 57 | 58 | @keyframes slide-off { 59 | from { left: calc(100% - #{$size - $gap}); } to { left: $gap; } 60 | } 61 | 62 | @at-root .on#{&} { 63 | left: calc(100% - #{$size - $gap}); 64 | } 65 | 66 | @at-root :not(.no-animation).on#{&} { 67 | animation: slide-on 0.5s ease-in-out forwards; 68 | } 69 | 70 | @at-root .off#{&} { 71 | left: $gap; 72 | } 73 | 74 | @at-root :not(.no-animation).off#{&} { 75 | animation: slide-off 0.5s ease-in-out forwards; 76 | } 77 | } 78 | 79 | @keyframes slide-text-on { 80 | from { 81 | transform: translateX(-150%); 82 | } to { 83 | transform: translateX(0); 84 | } 85 | } 86 | 87 | @keyframes slide-text-off { 88 | // 保留 translateY 否则会丢失原有的 translateY 89 | from { 90 | transform: translateX(150%) translateY(-100%); 91 | } to { 92 | transform: translateX(0) translateY(-100%); 93 | } 94 | } 95 | 96 | &::before { 97 | @extend %text; 98 | content: attr(data-text-on); 99 | 100 | @at-root :not(.no-animation).on#{&} { 101 | animation: slide-text-on 0.5s ease-in-out forwards; 102 | } 103 | 104 | @at-root .off#{&} { 105 | visibility: hidden; 106 | } 107 | } 108 | 109 | &::after { 110 | @extend %text; 111 | 112 | content: attr(data-text-off); 113 | transform: translateY(-100%); 114 | 115 | @at-root .on#{&} { 116 | visibility: hidden; 117 | } 118 | 119 | @at-root :not(.no-animation).off#{&} { 120 | animation: slide-text-off 0.5s ease-in-out forwards; 121 | } 122 | } 123 | } 124 | 125 | -------------------------------------------------------------------------------- /src/components/toggle/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author arzyu 3 | */ 4 | 5 | import angular from 'angular'; 6 | 7 | import './_toggle.scss'; 8 | import template from './toggle.tpl.html'; 9 | import controller from './ToggleCtrl'; 10 | 11 | const DDO = { 12 | template, 13 | bindings: { 14 | ngModel: '<', 15 | ngTrueValue: ' 6 |
    7 | 8 | 9 | -------------------------------------------------------------------------------- /src/components/tooltip/Contants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-03-17 5 | */ 6 | 7 | export const TOOLTIP_TYPE = { 8 | 9 | ERROR_MAJOR: 'error-major', 10 | ERROR_MINOR: 'error-minor', 11 | NORMAL: 'normal' 12 | 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/tooltip/index.js: -------------------------------------------------------------------------------- 1 | import './_tooltip.scss'; 2 | 3 | import angular from 'angular'; 4 | 5 | import Tooltip from './Tooltip'; 6 | import TooltipCtrl from './TooltipCtrl'; 7 | 8 | const tooltipDDO = { 9 | restrict: 'A', 10 | controller: TooltipCtrl, 11 | controllerAs: '$$tooltipCtrl', 12 | bindToController: { 13 | content: ' tooltipDDO) 28 | .value('$ccTooltip', Tooltip) 29 | .name; 30 | -------------------------------------------------------------------------------- /src/components/tooltip/tooltip.tpl.html: -------------------------------------------------------------------------------- 1 |

    2 | -------------------------------------------------------------------------------- /src/components/tree/Handler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 事件回调处理器 3 | */ 4 | class Handler { 5 | constructor(treeObject) { 6 | this.init(treeObject); 7 | } 8 | /** 9 | * 配置事件回调 10 | * @param name 11 | * @param callback 12 | */ 13 | init(treeObject) { 14 | // 选中事件 15 | this.onSelectedAction = treeObject.onSelectedAction; 16 | 17 | // 右键事件: 新增节点事件 18 | this.onAddAction = treeObject.onAddAction; 19 | 20 | // 右键事件: 节点删除事件 21 | this.onRemoveAction = treeObject.onRemoveAction; 22 | 23 | // 右键事件: 节点重命名事件 24 | this.onRenameAction = treeObject.onRenameAction; 25 | } 26 | } 27 | 28 | export default Handler; 29 | -------------------------------------------------------------------------------- /src/components/tree/TreeController.js: -------------------------------------------------------------------------------- 1 | import Store from './Store'; 2 | import Handler from './Handler'; 3 | 4 | export default class TreeCtrl { 5 | // 搜索词 6 | searchText = ''; 7 | 8 | // 存储树所需要的数据和事件 9 | treeMap = {}; 10 | 11 | // 菜单hash值,用于在menu中监听菜单项的事件 12 | menuHash = 0; 13 | 14 | /** 15 | * 事件:打开右键菜单 16 | * @param node 17 | */ 18 | onOpenMenu = node => { 19 | this.activeNode = node; 20 | this.menuHash++; 21 | }; 22 | 23 | /** 24 | * 更新搜索词 25 | * @param searchText 26 | */ 27 | updateSearchText(searchText) { 28 | this.treeMap.searchText = searchText; 29 | } 30 | 31 | /** 32 | * 初始化数据 33 | * @param treeData 34 | */ 35 | initData(treeData, maxLevel) { 36 | this.treeMap.handler = new Handler(this); 37 | this.treeMap.store = new Store(treeData, maxLevel); 38 | this.treeData = this.treeMap.store.treeData; 39 | } 40 | 41 | $onChanges(changeObj) { 42 | const {data, maxLevel = {currentValue: undefined}} = changeObj; 43 | if (data && data.currentValue) { 44 | this.initData(data.currentValue, maxLevel.currentValue); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/tree/doc.md: -------------------------------------------------------------------------------- 1 | # cc-tree 2 | ## 配置项 3 | ### 配置项列表 4 | - data: 渲染所需数据 `Array` 5 | ``` 6 | const data = [ 7 | { 8 | id: 1, 9 | pId: 0, 10 | name: 'name', 11 | children: [], 12 | checked: false, // 是否处于被选中状态 `非必填项` 13 | isClosed: false, // 是否为闭合状态 `非必填项` 14 | disableAdd: false, // 该节点禁止新增功能 `非必填项` 15 | disableRemove: false, // 该节点禁止删除功能 `非必填项` 16 | disableRename: false, // 该节点禁止重命名功能 `非必填项` 17 | } 18 | ]; 19 | ``` 20 | - searchPlaceholder: 搜索区域为空提示 `String` 21 | - searchMaxLen: 搜索区域最大字符数 `Number` 22 | - maxLevel: 最大可新增层级,只有小于该数值的节点才可以新增节点 `Number` 23 | - supportMenu: 是否支持右键菜单功能 `Boolean` 24 | - supportSearch: 是否支持搜索功能 `Boolean` 25 | - supportCheckbox: 是否支持选择功能 `Boolean` 26 | - isRadioModel: 是否为单选模试, 仅在supportCheckbox为true时生效 `Boolean` 27 | - hideRadioModel: 单选时是否隐藏单选icon `Boolean` 28 | - addToPosition: 新节点添加后所在的位置 `String` 1.`after`, 2.`before` 默认值`after` 29 | - nodeMaxLen: 节点名称最大长度 `String` 默认值`20` 30 | 31 | ### 事件列表 32 | 33 | - onSelectedAction: 节点选中事件 `Function` 34 | ``` 35 | /** 36 | * 节点点击事件 37 | * @param node: 当前选择的节点对象 38 | * @param selectedList: 当前选中的节点列表 39 | */ 40 | this.onSelectedAction = function(node, selectedList) { 41 | console.log(node, selectedList); 42 | }; 43 | ``` 44 | 45 | - onDoubleClickAction: 节点双击事件 `Function` 46 | ``` 47 | /** 48 | * 节点双击事件: 配置该事件后,文本区域的单击选中效果将无效。 49 | * @param node: 当前选择的节点对象 50 | */ 51 | this.onDoubleClickAction = function(node) { 52 | console.log(node); 53 | }; 54 | ``` 55 | 56 | - onAddAction: 新增节点事件, 需要返回promise `Function` `右键菜单事件` 57 | ``` 58 | /** 59 | * 节点新增事件 60 | * @param pId: 父节点 61 | * @param name: 新增的节点名称 62 | * @param node: 将要删除的节点对象 63 | * @returns promise: resolve中需要返回包含id字段的对象 64 | */ 65 | this.onAddAction = function(pId, name) { 66 | return new Promise((resolve, reject) => { 67 | resolve({id: Math.random()}); 68 | }); 69 | }; 70 | ``` 71 | 72 | - onRemoveAction: 节点删除事件, 需要返回promise `Function` `右键菜单事件` 73 | ``` 74 | /** 75 | * 节点删除事件 76 | * @param node: 将要删除的节点对象 77 | * @returns promise 78 | */ 79 | this.onRemoveAction = function(node) { 80 | return new Promise((resolve, reject) => { 81 | resolve(); 82 | // reject('该节点不允许删除'); 83 | }); 84 | }; 85 | ``` 86 | 87 | - onRenameAction: 节点重命名事件, 需要返回promise `Function` `右键菜单事件` 88 | ``` 89 | /** 90 | * 节点重命名事件 91 | * @param node: 将要重命名的节点对象 92 | * @param newName: 变更的名称 93 | * @returns promise 94 | */ 95 | this.onRenameAction = function(node, newName) { 96 | return new Promise((resolve, reject) => { 97 | resolve(); 98 | // reject('重命名失败了'); 99 | }); 100 | }; 101 | ``` 102 | 103 | 104 | 105 | ### 关于右键菜单 106 | 使用右键菜单需要配置参数`supportMenu`,右键菜单相对应的事件名称: 107 | - 新增: `onAddAction` 108 | - 删除: `onRemoveAction` 109 | - 重命名: `onRemoveAction` 110 | 111 | 需要注意的是: 右健菜单事件,未配置的项将不在菜单中展示。且所有项都未配置,则右键菜单将不显示。 112 | 113 | -------------------------------------------------------------------------------- /src/components/tree/index.js: -------------------------------------------------------------------------------- 1 | import './index.scss'; 2 | import angular from 'angular'; 3 | import treeController from './TreeController'; 4 | import treeTemplate from './tree.tpl.html'; 5 | 6 | import treeListTemplate from './list/list.tpl.html'; 7 | import treeListController from './list/TreeListController.js'; 8 | 9 | import treeNodeController from './node/TreeNodeController'; 10 | import treeNodeTemplate from './node/node.tpl.html'; 11 | 12 | import menuController from './menu/menuController'; 13 | import menuTemplate from './menu/menu.tpl.html'; 14 | 15 | import ccCheckbox from '../checkbox'; 16 | import ccRadio from '../radio'; 17 | 18 | import treeSearch from './search'; 19 | 20 | const menuDDO = { 21 | controller: menuController, 22 | template: menuTemplate, 23 | bindings: { 24 | menuHash: '<', 25 | maxLevel: ' { 90 | return { 91 | link: (scope, element, attrs) => { 92 | const fn = $parse(attrs.ccTreeRightClick); 93 | element.bind('contextmenu', event => { 94 | scope.$apply(() => { 95 | event.preventDefault(); 96 | fn(scope, { $event: event }); 97 | }); 98 | }); 99 | } 100 | }; 101 | }; 102 | 103 | export default angular.module('ccms.components.tree', [ccCheckbox, ccRadio, treeSearch]) 104 | .component('ccTree', treeDDO) 105 | .component('ccTreeList', treeListDDO) 106 | .component('ccTreeNode', treeNodeDDO) 107 | .component('ccTreeMenu', menuDDO) 108 | .directive('ccTreeRightClick', ['$parse', rightClickDirective]) 109 | .name; 110 | -------------------------------------------------------------------------------- /src/components/tree/list/TreeListController.js: -------------------------------------------------------------------------------- 1 | import Inject from 'angular-es-utils/decorators/Inject'; 2 | 3 | @Inject('$filter') 4 | export default class TreeListCtrl { 5 | /** 6 | * 过滤器 7 | * @param node 8 | * @returns {*} 9 | */ 10 | filterHandler = node => { 11 | const filterText = this.treeMap.searchText || ''; 12 | // 搜索条件为空 13 | if (!filterText) { 14 | return node; 15 | } 16 | 17 | // 当前节点为编辑状态 18 | if (node.isEditing) { 19 | return node; 20 | } 21 | const _filter = node => { 22 | if (node.name.includes(filterText)) { 23 | // 如果存在父级,则展开父级 24 | if (node.pId) { 25 | this.treeMap.store.findNodeById(node.pId).isClosed = false; 26 | } 27 | return true; 28 | } 29 | if (node.children && node.children.length) { 30 | node.isClosed = false; 31 | return node.children.some(child => _filter(child)); 32 | } 33 | return false; 34 | }; 35 | return _filter(node); 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/components/tree/list/list.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 14 |
    15 |
    16 | -------------------------------------------------------------------------------- /src/components/tree/menu/menu.tpl.html: -------------------------------------------------------------------------------- 1 |
      5 |
    • 6 | 7 | 8 |
    • 9 |
    10 | -------------------------------------------------------------------------------- /src/components/tree/node/node.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 |
    7 | 11 | 12 | 17 | 18 | 19 | 20 | 23 | 24 |
    28 |
    29 |
    30 | 31 | 32 |
    33 |
    34 | 35 |
    38 | 39 | 40 |
    41 |
    42 |
    45 | 46 | 47 | 48 | 49 | 50 |
    51 |
    54 | 64 | 65 |
    66 |
    67 | 68 | -------------------------------------------------------------------------------- /src/components/tree/search/TreeSearchController.js: -------------------------------------------------------------------------------- 1 | export default class TreeSearchController { 2 | searchText = ''; 3 | constructor() { 4 | this.searchMaxLen = this.searchMaxLen || 100; 5 | } 6 | 7 | /** 8 | * 搜索事件 9 | */ 10 | onSearch() { 11 | this.onDone({searchText: this.searchText}); 12 | } 13 | 14 | /** 15 | * 输入框输入事件 16 | * @param event 17 | */ 18 | onInputChangeHandler(event) { 19 | // 回车事件 20 | if (event.keyCode === 13) { 21 | this.onSearch(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/tree/search/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import template from './search.tpl.html'; 3 | import TreeSearchController from './TreeSearchController'; 4 | 5 | const ddo = { 6 | controller: TreeSearchController, 7 | template, 8 | bindings: { 9 | treeMap: '<', 10 | searchPlaceholder: ' 2 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/tree/tree.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 9 |
    10 | 21 |
    22 | 30 |
    31 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-01-19 5 | */ 6 | import '@babel/polyfill'; // 参见 .babelrc useBuiltIns 参数 7 | import 'core-js/modules/es6.regexp.constructor.js'; 8 | 9 | import angular from 'angular'; 10 | import ngSanitize from 'angular-sanitize'; 11 | 12 | // 组件兼容服务 13 | import adaptor from './common/utils/adaptor'; 14 | 15 | import LogicComponents from './common/utils'; 16 | import UIComponents from './components'; 17 | 18 | const ccmsComponents = angular.module('ccms.components', [ 19 | adaptor, 20 | UIComponents, 21 | LogicComponents, 22 | ngSanitize 23 | ]); 24 | 25 | ccmsComponents.version = process.env.VERSION; 26 | 27 | export default ccmsComponents.name; 28 | 29 | -------------------------------------------------------------------------------- /test/karma.cover.conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-07-25 5 | */ 6 | 7 | var base = require('./karma.base.conf'); 8 | 9 | module.exports = function(config) { 10 | 11 | var opts = Object.assign(base, { 12 | reporters: ['progress', 'coverage-istanbul'], 13 | coverageReporter: { 14 | reporters: [ 15 | {type: 'lcov', dir: './coverage', subdir: '.'}, 16 | {type: 'text-summary', dir: './coverage', subdir: '.'} 17 | ] 18 | } 19 | }); 20 | 21 | opts.singleRun = true; 22 | opts.files.unshift('../node_modules/babel-polyfill/browser.js'); 23 | 24 | opts.webpack.module.rules.push({ 25 | test: /\.js$/, 26 | enforce: 'post', 27 | use: { 28 | loader: 'istanbul-instrumenter-loader', 29 | options: { esModules: true } 30 | }, 31 | exclude: /node_modules|__tests__|test/ 32 | }); 33 | 34 | config.set(opts); 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /test/karma.unit.conf.js: -------------------------------------------------------------------------------- 1 | var base = require('./karma.base.conf'); 2 | 3 | module.exports = function(config) { 4 | 5 | config.set(Object.assign(base, { 6 | reporters: ['mocha'] 7 | })); 8 | }; 9 | -------------------------------------------------------------------------------- /test/test.index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Kuitos 3 | * @homepage https://github.com/kuitos/ 4 | * @since 2016-04-21 5 | */ 6 | 7 | import angular from 'angular'; 8 | import ngResource from 'angular-resource'; 9 | import 'angular-mocks'; 10 | import components from '../src'; 11 | // init injector 12 | document.body.innerHTML = ''; 13 | angular.module('app', [components, ngResource]); 14 | angular.bootstrap(document.body); 15 | 16 | const context = require.context('../src', true, /\/__tests__\/test-.*\.js$/); 17 | context.keys().forEach(context); 18 | 19 | // require all `src/**/index.js` 20 | const appContext = require.context('../src', true, /\/[A-Z]+.*\.js$/); 21 | appContext.keys().forEach(appContext); 22 | -------------------------------------------------------------------------------- /webpack-common-loaders.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: fang.yang 3 | * @Date: 2019-12-18 09:47:13 4 | * @Description: [只用于 karma 测试使用] 5 | */ 6 | 7 | var path = require('path'); 8 | module.exports = { 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.js$/, 13 | loaders: ['babel-loader'], 14 | exclude: /(node_modules|bower_components)/ 15 | }, 16 | { 17 | test: /\.js$/, 18 | enforce: 'pre', 19 | use: [{ 20 | loader: 'eslint-loader', 21 | options: { 22 | emitWarning: true, 23 | emitError: true, 24 | formatter: require('eslint-friendly-formatter') 25 | } 26 | }], 27 | exclude: /node_modules/, 28 | include: [path.join(__dirname, 'src')] 29 | }, 30 | { 31 | test: /\.tpl\.html$/, 32 | use: { 33 | loader: 'html-loader', 34 | options: { 35 | interpolate: true 36 | } 37 | }, 38 | exclude: /(node_modules|bower_components)/ 39 | }, 40 | { 41 | test: /\.(jpe?g|png|gif)$/i, 42 | loaders: [ 43 | 'url-loader?limit=1000' 44 | ] 45 | }, 46 | { 47 | test: /\.(woff|woff2)(\?t=\d+)?$/, 48 | loader: 'url-loader?limit=10000&mimetype=application/font-woff&prefix=fonts' 49 | }, 50 | { 51 | test: /\.ttf(\?t=\d+)?$/, 52 | loader: 'url-loader?limit=10000&mimetype=application/octet-stream&prefix=fonts' 53 | }, 54 | { 55 | test: /\.eot(\?t=\d+)?$/, 56 | loader: 'url-loader?limit=10000&mimetype=application/vnd.ms-fontobject&prefix=fonts' 57 | }, 58 | { 59 | test: /\.svg(\?t=\d+)?$/, 60 | loader: 'url-loader?limit=10000&mimetype=image/svg+xml&prefix=fonts' 61 | } 62 | ] 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | const argv = require('yargs').argv; 3 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); 4 | var merge = require('webpack-merge'); 5 | 6 | const modeConfig = env => require(`./build-utils/webpack.${env}`)(env); 7 | const { mode } = argv.env; 8 | 9 | module.exports = () => { 10 | return merge( 11 | { 12 | resolve: { 13 | extensions: ['.js'] 14 | }, 15 | resolveLoader: { 16 | moduleExtensions: ['-loader'] 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.js$/, 22 | loaders: ['babel-loader'], 23 | exclude: /(node_modules|bower_components)/ 24 | }, 25 | { 26 | test: /\.js$/, 27 | enforce: 'pre', 28 | use: [{ 29 | loader: 'eslint-loader', 30 | options: { 31 | emitWarning: true, 32 | emitError: true, 33 | formatter: require('eslint-friendly-formatter') 34 | } 35 | }], 36 | exclude: /node_modules/, 37 | include: [path.join(__dirname, 'src')] 38 | }, 39 | { 40 | test: /\.tpl\.html$/, 41 | use: { 42 | loader: 'html-loader', 43 | options: { 44 | interpolate: true 45 | } 46 | }, 47 | exclude: /(node_modules|bower_components)/ 48 | }, 49 | { 50 | test: /\.(jpe?g|png|gif)$/i, 51 | loaders: [ 52 | 'url-loader?limit=1000' 53 | ] 54 | }, 55 | { 56 | test: /\.(woff|woff2)(\?t=\d+)?$/, 57 | loader: 'url-loader?limit=10000&mimetype=application/font-woff&prefix=fonts' 58 | }, 59 | { 60 | test: /\.ttf(\?t=\d+)?$/, 61 | loader: 'url-loader?limit=10000&mimetype=application/octet-stream&prefix=fonts' 62 | }, 63 | { 64 | test: /\.eot(\?t=\d+)?$/, 65 | loader: 'url-loader?limit=10000&mimetype=application/vnd.ms-fontobject&prefix=fonts' 66 | }, 67 | { 68 | test: /\.svg(\?t=\d+)?$/, 69 | loader: 'url-loader?limit=10000&mimetype=image/svg+xml&prefix=fonts' 70 | } 71 | ] 72 | }, 73 | plugins: [ 74 | // new BundleAnalyzerPlugin() 75 | ] 76 | }, 77 | modeConfig(mode) 78 | ); 79 | }; 80 | --------------------------------------------------------------------------------