├── .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 |
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 |
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 |
31 | - ng-model
32 | - ng-value
33 | - ng-disabled
34 |
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 |
58 |
59 | 按钮
60 |
61 |
62 |
说明:
63 |
64 | - 按钮样式不带 margin, 放在具体使用环境中调整
65 | - 样式有按颜色, 大小分的基础款 + 拼装好的常用款
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 |
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 = '';
26 | this.style = {'z-index': 1000};
27 | });
28 |
--------------------------------------------------------------------------------
/demos/components/tree/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 树
4 |
5 |
6 |
14 |
15 | 搜索、菜单、复选、最大可新增层级为3
16 |
35 |
36 | 无搜索、无菜单、带单选
37 |
48 |
49 |
50 | 无搜索、无菜单、无选择
51 |
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: '',
23 | isFestival: '',
24 | customFestivals: '',
25 | disableYear: '',
26 | disableMonth: '',
27 | displayFormat: '@?',
28 | minYear: '',
29 | maxYear: ''
30 | },
31 | template,
32 |
33 |
34 | link($scope, $element, $attrs, ctrls) {
35 | this.$scope = $scope;
36 |
37 | this._registerToDatePicker(ctrls[0]);
38 | this._registerToRangePicker(ctrls[0], ctrls[1]);
39 | },
40 |
41 |
42 | /**
43 | * 将日期选择器和文本框关联起来
44 | * @param datePickerCtrl
45 | * @private
46 | */
47 | _registerToDatePicker(datePickerCtrl) {
48 | datePickerCtrl.calendar = this.$scope.ctrl;
49 | this.$scope.ctrl.datePicker = datePickerCtrl;
50 |
51 | this.$scope.ctrl.dateOnly = datePickerCtrl.dateOnly;
52 | this.$scope.ctrl.disabled = datePickerCtrl.disabled;
53 | },
54 |
55 |
56 | /**
57 | * 注册 range 组件
58 | * @param datePickerCtrl
59 | * @param rangeCtrl
60 | * @private
61 | */
62 | _registerToRangePicker(datePickerCtrl, rangeCtrl) {
63 | this.$scope.ctrl.range = rangeCtrl;
64 |
65 | if (datePickerCtrl.rangeStart) {
66 | rangeCtrl.startCalendar = this.$scope.ctrl;
67 | }
68 |
69 | if (datePickerCtrl.rangeEnd) {
70 | rangeCtrl.endCalendar = this.$scope.ctrl;
71 | }
72 | }
73 | };
74 |
--------------------------------------------------------------------------------
/src/components/calendar/_calendar.scss:
--------------------------------------------------------------------------------
1 | @import '../styles/button';
2 |
3 | .calendar {
4 | background: #fff;
5 | border: 1px solid #d9d9d9;
6 | font-size: 12px;
7 | display: none;
8 | margin-top: -1px;
9 | position: absolute;
10 | left: 0;
11 | top: 100%;
12 | z-index: $calendar-z-index;
13 | text-align: center;
14 |
15 | li {
16 | float: left;
17 | list-style: none;
18 | width: percentage(1 / 7);
19 | }
20 | }
21 |
22 | .calendar-header {
23 | background: #f4f4f4;
24 | border-bottom: 1px solid #d9d9d9;
25 | overflow: auto;
26 |
27 | > 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 |
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: '',
19 | ngDisabled: '',
20 | ngModel: '',
21 | ngTrueValue: '',
22 | ngFalseValue: '',
23 | indeterminate: ''
24 | },
25 | require: {
26 | ngModelController: '?ngModel'
27 | }
28 | };
29 |
30 | export default angular
31 | .module('ccms.components.checkbox', [])
32 | .component('ccCheckbox', ccmsCheckboxSetting)
33 | .name;
34 |
--------------------------------------------------------------------------------
/src/components/date-picker/DatePicker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by AshZhang on 2016-3-4.
3 | */
4 |
5 |
6 | import './_date-picker.scss';
7 | import template from './date-picker.tpl.html';
8 | import { destructDate, setTextWidth } from './dateUtils';
9 | import DatePickerCtrl from './DatePickerCtrl';
10 |
11 |
12 | export default {
13 |
14 | bindToController: true,
15 | controller: DatePickerCtrl,
16 | controllerAs: 'ctrl',
17 | replace: true,
18 | require: 'ngModel',
19 | restrict: 'E',
20 | scope: {
21 | dateOnly: '=',
22 | disabled: '=',
23 | disabledInput: '=',
24 | minDate: '=',
25 | maxDate: '=',
26 | containerElement: '=?',
27 | rangeStart: '=',
28 | rangeEnd: '=',
29 | start: '=',
30 | end: '=',
31 | onCalendarOpen: '&?',
32 | onCalendarClose: '&?',
33 | defaultCalendarValue: '',
34 | isFestival: '',
35 | customFestivals: '',
36 | disableScrollIntoView: '',
37 | disableYear: '',
38 | disableMonth: '',
39 | displayFormat: '@?',
40 | minYear: '',
41 | maxYear: ''
42 | },
43 | template,
44 |
45 | link($scope, $element, $attrs, ngModelCtrl) {
46 | $scope.inputs = [].slice.call($element[0].querySelectorAll('input'));
47 | $scope.ctrl.ngModelCtrl = ngModelCtrl;
48 |
49 | this.setAllInputsWidth($scope);
50 |
51 | /**
52 | * 根据日期渲染模板
53 | */
54 | ngModelCtrl.$render = () => {
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: '',
23 | customFestivals: ''
24 | },
25 | template
26 | };
27 |
--------------------------------------------------------------------------------
/src/components/date-range/DateRangeCtrl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by AshZhang on 公元2016-3-14.
3 | */
4 |
5 |
6 | import { Inject } from 'angular-es-utils';
7 |
8 |
9 | @Inject('$scope', '$element')
10 | export default class DateRangeCtrl {
11 |
12 | constructor($scope, $element) {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/date-range/_date-range.scss:
--------------------------------------------------------------------------------
1 | .date-range {
2 | display: inline-block;
3 | }
4 |
5 | .date-range-sep {
6 | color: #999;
7 | //display: inline-block;
8 | line-height: 30px;
9 | vertical-align: top;
10 | }
--------------------------------------------------------------------------------
/src/components/date-range/date-range.tpl.html:
--------------------------------------------------------------------------------
1 |
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 |
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 |
7 |
24 |
25 |
49 |
50 |
51 |
52 |
53 |
54 |
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: '',
18 | containerElement: '=?',
19 | hasSelectAll: '',
20 | autoClose: '',
21 | mapping: '',
22 | disabled: '',
23 | datalist: '<',
24 | searchable: '',
25 | confirmButton: '',
26 | placeholder: '@?',
27 | searchFields: '=?',
28 | onSelectChange: '&?',
29 | onDropdownOpen: '&?',
30 | onDropdownClose: '&?',
31 | onBeforeSelectChange: '&?'
32 | },
33 | bindToController: true
34 | };
35 |
36 | export default angular
37 | .module('ccms.components.dropdownMultiselect', [bindHtml])
38 | .directive('ccDropdownMultiselect', () => dropdownMultiselectDDO)
39 | .name;
40 |
41 |
--------------------------------------------------------------------------------
/src/components/dropdown/dropdown-select/dropdown-select.tpl.html:
--------------------------------------------------------------------------------
1 |
7 |
23 |
24 |
25 | -
29 |
30 |
31 |
32 | -
34 | 没有匹配
35 |
36 |
37 |
38 |
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: '',
20 | autoClose: '',
21 | mapping: '',
22 | datalist: '<',
23 | searchable: '',
24 | supportInputValue: '',
25 | disabled: '',
26 | placeholder: '@?',
27 | onSelectChange: '&?',
28 | onDropdownOpen: '&?',
29 | onDropdownClose: '&?',
30 | onBeforeSelectChange: '&?'
31 | },
32 | bindToController: true
33 | };
34 |
35 | export default angular
36 | .module('ccms.components.dropdownSelect', [bindHtml, icon])
37 | .directive('ccDropdownSelect', () => 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: '',
17 | onDropdownOpen: '&?',
18 | onDropdownClose: '&?'
19 | },
20 | bindToController: true
21 | };
22 |
23 | const dropdownToggleDDO = {
24 | restrict: 'EA',
25 | require: {
26 | parent: '^^ccDropdown'
27 | },
28 | controller: DropdownToggleCtrl,
29 | controllerAs: '$ctrl',
30 | scope: {},
31 | bindToController: true
32 | };
33 |
34 | const dropdownPanelDDO = {
35 | restrict: 'EA',
36 | require: {
37 | parent: '^^ccDropdown'
38 | },
39 | template: '',
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 |
5 |
6 |
7 |
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 |
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: '',
20 | onSearch: '&?',
21 | onSelect: '&?'
22 | },
23 | bindToController: true
24 | };
25 |
26 | export default angular
27 | .module('ccms.components.instantSearch', [ngResource])
28 | .directive('instantSearch', () => DDO)
29 | .name;
30 |
31 |
--------------------------------------------------------------------------------
/src/components/instant-search/instant-search.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
8 | -
11 |
12 |
13 | -
14 | 没有匹配
15 |
16 |
17 |
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 |
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: '', // 是否关闭其他菜单, 当激活某个子菜单时 默认值是 true
29 | expandMenus: '', // 初始化时, 是否展开所有菜单 (不包含, 含有 active 的子菜单) 默认值是 false
30 | menuSource: '<',
31 | shopSource: '',
32 | menuPlaceholder: '', // 菜单占位符,可用于做一下自由化的插入功能
33 | menuPlaceholderClick: '&?',
34 | searchPlaceholder: '',
35 | shopItemTpl: '', // 店铺模板
36 | shopLogoStyle: '', // logo样式
37 | shopLogoSubConfig: '' // 角标配置
38 | }
39 | },
40 | menusNodeDDO = {
41 | template: menuNodeTemplate,
42 | controller: MenusNodeCtrl,
43 | controllerAs: 'childNode',
44 | bindings: {
45 | list: '=',
46 | toggle: '=',
47 | level: '='
48 | }
49 | };
50 |
51 | export default angular
52 | .module('ccms.components.menus', [uiRouter, utils, shopSelect])
53 | .component('ccMenuBar', menusBarDDO)
54 | .component('ccMenuNode', menusNodeDDO)
55 | .value('$ccMenus', $menus)
56 | .name;
57 |
--------------------------------------------------------------------------------
/src/components/menus/menus-node.tpl.html:
--------------------------------------------------------------------------------
1 |
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: '',
20 | shopInfo: '=',
21 | retract: '',
22 | onRetract: '&?',
23 | isInit: '',
24 | placeholder: '',
25 | shopItemTpl: ''
26 | }
27 | };
28 |
29 | export default angular
30 | .module('ccms.components.menus.shops', [])
31 | .component('ccShopSelect', shopSelectDDO)
32 | .name;
33 |
--------------------------------------------------------------------------------
/src/components/menus/shop-select/shop-selects.tpl.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
11 |
12 |
15 |
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 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
23 |
24 |
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: '', // 总页数
22 | pageNum: '', // 当前页码
23 | pageSize: '', // 每页大小
24 | pageSizeList: '',
25 | pageSizeListDisabled: '', // disabled翻页
26 | pageSizeListHidden: '', // 隐藏翻页
27 | onChange: '&?' // 刷新回调
28 | }
29 |
30 | };
31 |
32 | export default angular
33 | .module('ccms.components.pagination', [ngEnter, ngDomValue])
34 | .component('ccPagination', paginationDDO)
35 | .name;
36 |
37 |
--------------------------------------------------------------------------------
/src/components/pagination/pagination.tpl.html:
--------------------------------------------------------------------------------
1 |
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: '',
19 | ngModel: '',
20 | ngValue: '',
21 | value: '@?'
22 | },
23 | require: {
24 | ngModelController: '?ngModel'
25 | }
26 | };
27 |
28 | export default angular
29 | .module('ccms.components.radioButton', [])
30 | .component('ccRadio', ccmsRadioButtonSetting)
31 | .name;
32 |
--------------------------------------------------------------------------------
/src/components/radio/radio.tpl.html:
--------------------------------------------------------------------------------
1 |
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 |
6 |
--------------------------------------------------------------------------------
/src/components/tabset/tpls/tabset.tpl.html:
--------------------------------------------------------------------------------
1 |
7 |
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 |
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: '',
16 | ngFalseValue: '',
17 | valueOn: '',
18 | valueOff: '',
19 | disabled: '
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: '',
26 | treeMap: '<',
27 | addToPosition: '',
28 | activeNode: '<'
29 | }
30 | };
31 |
32 | const treeDDO = {
33 | controller: treeController,
34 | template: treeTemplate,
35 | bindings: {
36 | data: '<',
37 | searchPlaceholder: '',
38 | searchMaxLen: '',
39 | nodeMaxLen: '',
40 | maxLevel: '',
41 | supportMenu: '',
42 | supportSearch: '',
43 | supportCheckbox: '',
44 | isRadioModel: '',
45 | hideRadioModel: '',
46 | addToPosition: '',
47 | onSelectedAction: '',
48 | onRemoveAction: '',
49 | onAddAction: '',
50 | onRenameAction: '',
51 | onDoubleClickAction: ''
52 | }
53 | };
54 |
55 |
56 | const treeListDDO = {
57 | controller: treeListController,
58 | template: treeListTemplate,
59 | bindings: {
60 | treeMap: '<',
61 | nodes: '<',
62 | searchText: '<',
63 | nodeMaxLen: '<',
64 | supportCheckbox: '',
65 | isRadioModel: '',
66 | hideRadioModel: '',
67 | onOpenMenu: '',
68 | onDoubleClickAction: ''
69 | }
70 | };
71 |
72 |
73 | const treeNodeDDO = {
74 | controller: treeNodeController,
75 | template: treeNodeTemplate,
76 | bindings: {
77 | treeMap: '<',
78 | node: '<',
79 | searchText: '<',
80 | nodeMaxLen: '<',
81 | supportCheckbox: '',
82 | isRadioModel: '',
83 | hideRadioModel: '',
84 | onOpenMenu: '',
85 | onDoubleClickAction: ''
86 | }
87 | };
88 |
89 | const rightClickDirective = $parse => {
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 |
16 |
--------------------------------------------------------------------------------
/src/components/tree/menu/menu.tpl.html:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/components/tree/node/node.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
11 |
12 |
17 |
18 |
19 |
20 |
23 |
24 |
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: '',
11 | searchMaxLen: '',
12 | onDone: '&'
13 | }
14 | };
15 |
16 |
17 | export default angular
18 | .module('ccms.components.treeSearch', [])
19 | .component('ccTreeSearch', ddo)
20 | .name;
21 |
22 |
--------------------------------------------------------------------------------
/src/components/tree/search/search.tpl.html:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------