(path: string) => T) => void) => void;
16 | }
17 |
18 | declare var module: {
19 | exports: any
20 | }
21 |
22 | declare var exports: any
--------------------------------------------------------------------------------
/components/ms-tree/ms-tree.less:
--------------------------------------------------------------------------------
1 | @import '../../styles/index';
2 | @tree-prefix-cls: ~"@{ane-prefix}-tree";
3 |
4 | .@{tree-prefix-cls} {
5 | list-style: none;
6 | padding-left: 30px;
7 |
8 | &-icon {
9 | width: 24px;
10 | height: 24px;
11 | line-height: 24px;
12 | display: inline-block;
13 | vertical-align: middle;
14 | border: 0 none;
15 | cursor: pointer;
16 | outline: none;
17 | text-align: center;
18 | }
19 |
20 | li {
21 | padding: 4px 0;
22 | }
23 | }
--------------------------------------------------------------------------------
/tests/beforeit.js:
--------------------------------------------------------------------------------
1 | require('es5-shim');
2 | require('es6-promise/dist/es6-promise.auto');
3 |
4 | require('jquery');
5 | window.$ = window.jQuery = jQuery;
6 | require('bootstrap');
7 | var bootbox = require('bootbox');
8 | bootbox.setLocale('zh_CN');
9 |
10 | var avalon = require('avalon2');
11 | avalon.config({
12 | debug: false
13 | });
14 | if (avalon.msie === 8) {
15 | Object.defineProperty = function (obj, property, meta) {
16 | obj[property] = meta.value;
17 | }
18 | }
19 | require('es5-shim/es5-sham');
20 | require('../output/ane.js');
--------------------------------------------------------------------------------
/components/ms-radio/ms-radio.html:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
--------------------------------------------------------------------------------
/components/ms-select/test/ms-select.test.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import { createForm } from '../../ms-form/create-form';
3 | import '../../ms-form';
4 | import '../';
5 |
6 | export const name = 'component-demo-select';
7 |
8 | avalon.component(name, {
9 | template: require('./ms-select.test.html'),
10 | defaults: {
11 | json: '',
12 | $form: createForm(),
13 | onInit() {
14 | this.$form.onFieldsChange = (fields, record) => {
15 | this.json = JSON.stringify(record);
16 | }
17 | }
18 | }
19 | });
--------------------------------------------------------------------------------
/components/ms-upload/ms-upload-card.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
![]()
4 |
上传中 {{file.progress}}%
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/components/ms-input/test/ms-input.test.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import { createForm } from '../../ms-form/create-form';
3 | import '../../ms-form';
4 | import '../ms-input';
5 |
6 | export const name = 'component-demo-input';
7 |
8 | avalon.component(name, {
9 | template: require('./ms-input.test.html'),
10 | defaults: {
11 | value: '123',
12 | json: '',
13 | $form: createForm(),
14 | onInit() {
15 | this.$form.onFieldsChange = (fields, record) => {
16 | this.json = JSON.stringify(record);
17 | }
18 | }
19 | }
20 | });
--------------------------------------------------------------------------------
/.deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | npm run ci:doc
4 |
5 | if [ -d output ]; then
6 | # 提交代码至gh-pages分支
7 | echo "➥ Commit files"
8 | git clone --quiet --branch=gh-pages https://${GH_TOKEN}@github.com/${REPO_SLUG}.git ${REPO_SLUG} > /dev/null
9 | rm -rf ${REPO_SLUG}/*
10 | cp -rf output/* ${REPO_SLUG}
11 | cd ${REPO_SLUG}
12 | git status
13 | git config user.email "travis@travis-ci.org"
14 | git config user.name "travis-ci"
15 | git add -A
16 | git commit -m "[ci skip] publish gh-pages"
17 | git push -fq origin gh-pages > /dev/null
18 | else
19 | echo '➥ Fail'
20 | exit 1
21 | fi
--------------------------------------------------------------------------------
/components/ms-checkbox/ms-checkbox.html:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
--------------------------------------------------------------------------------
/components/ms-upload/ms-upload-card.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 |
3 | avalon.component('ms-upload-card', {
4 | template: require('./ms-upload-card.html'),
5 | defaults: {
6 | fileList: [],
7 | getTextClass(file) {
8 | switch (file.status) {
9 | case 'done': return 'text-primary';
10 | case 'uploading': return 'text-muted';
11 | case 'error': return 'text-danger';
12 | }
13 | return '';
14 | },
15 | onRemove: avalon.noop,
16 | del(file) {
17 | this.onRemove(file);
18 | }
19 | }
20 | });
--------------------------------------------------------------------------------
/components/ms-upload/ms-upload-list.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 |
3 | avalon.component('ms-upload-list', {
4 | template: require('./ms-upload-list.html'),
5 | defaults: {
6 | fileList: [],
7 | getTextClass(file) {
8 | switch (file.status) {
9 | case 'done': return 'text-primary';
10 | case 'uploading': return 'text-muted';
11 | case 'error': return 'text-danger';
12 | }
13 | return '';
14 | },
15 | onRemove: avalon.noop,
16 | del(file) {
17 | this.onRemove(file);
18 | }
19 | }
20 | });
--------------------------------------------------------------------------------
/components/ms-form/ms-form-item.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{@reasons.length ? @reasons[0].message : ''}}
6 |
--------------------------------------------------------------------------------
/components/ms-timepicker/ms-timepicker.md:
--------------------------------------------------------------------------------
1 | ## 时间选择器
2 |
3 | ### 基本用法
4 |
5 | ```html
6 |
7 |
10 |
11 | ```
12 |
13 | ### 格式化时间
14 |
15 | ```html
16 |
17 |
21 |
22 | ```
23 |
24 | ### 组件参数
25 |
26 | | 参数 | 说明 | 类型 | 默认值 |
27 | |-----|-----|-----|-----|
28 | | format | 日期格式,参考 momentjs | string | `'HH:mm:ss'` |
29 | | direction | 下拉框弹出方向,目前只有 `up`/`down` 两个选项 | string | `down` |
30 |
31 | > 继承 [ms-control 组件](#!/form-control) 的所有参数
--------------------------------------------------------------------------------
/components/ms-upload/ms-upload-list.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/styles/index.less:
--------------------------------------------------------------------------------
1 | @import '~bootstrap/less/variables';
2 | @ane-prefix: ane;
3 |
4 | // 这里覆盖 bootstrap 默认变量
5 |
6 | //** Disabled state
7 | @disabled-color: lighten(@gray-base, 70%);
8 |
9 | //** The background colors for active and hover states for things like
10 | //** list items or table cells.
11 | @item-active-bg: @brand-primary;
12 | @item-hover-bg: lighten(@brand-primary, 40%);
13 |
14 | //== Calendar
15 | //
16 | //##
17 |
18 | //** Disabled date
19 | @calendar-disabled-date-color: @disabled-color;
20 | @calendar-disabled-date-bg: @gray-lighter;
21 |
22 | //= Menu
23 | //
24 | //##
25 |
26 | //** Selected
27 | @menu-item-selected-border-color: @brand-primary;
28 | @menu-item-selected-bg: lighten(@brand-primary, 50%);
--------------------------------------------------------------------------------
/components/ms-calendar/ms-calendar-year-view.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | |
11 | {{cell.label}}
12 | |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/components/ms-select/test/ms-select.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 海贼王
6 | 名侦探柯南
7 | 进击的巨人
8 | 禁用
9 | 一拳超人
10 |
11 |
12 |
13 |
{{@json}}
14 |
--------------------------------------------------------------------------------
/components/ms-form/ms-control.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import { findParentComponent } from '../../ane-util';
3 |
4 | export default avalon.component('ms-control', {
5 | template: ' ',
6 | defaults: {
7 | $formItem: null,
8 | $rules: null,
9 | value: '',
10 | col: '',
11 | placeholder: '',
12 | width: 'x',
13 | onChange: avalon.noop,
14 | emitValue(e) {
15 | let v = e.target.value;
16 | v = v.toJSON ? v.toJSON() : v;
17 | this.$formItem && this.$formItem.onFormChange({
18 | name: this.col, value: v, denyValidate: e.denyValidate
19 | });
20 | },
21 | handleChange(e) {
22 | this.emitValue(e);
23 | this.onChange(e);
24 | }
25 | }
26 | });
--------------------------------------------------------------------------------
/components/ms-pagination/ms-pagination.md:
--------------------------------------------------------------------------------
1 | ## 分页组件
2 |
3 | ### 基本用法
4 |
5 | ```html
6 |
7 |
8 |
9 | ```
10 |
11 | ```js
12 | import * as avalon from 'avalon2';
13 | import 'ane';
14 |
15 | const vm = avalon.define({
16 | $id: 'doc-pagination-basic',
17 | current: 1,
18 | pageSize: 10,
19 | total: 30,
20 | handlePageChange(currentPage) {
21 | console.log('当前第' + currentPage + '页');
22 | }
23 | });
24 | ```
25 |
26 | ### 组件参数
27 |
28 | | 参数 | 说明 | 类型 | 默认值 |
29 | |-----|-----|-----|-----|
30 | | current | 当前页,从 1 开始 | number | 1 |
31 | | pageSize | 每页条数 | number | 10 |
32 | | total | 数据总数 | total | 0 |
33 | | onChange | 翻页时的回调 | function(currentPage:number) | noop |
--------------------------------------------------------------------------------
/components/ms-datepicker/ms-datepicker.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
21 |
22 |
--------------------------------------------------------------------------------
/components/ms-timepicker/ms-timepicker.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
21 |
22 |
--------------------------------------------------------------------------------
/components/ms-input/test/ms-input.test.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'input value': function (browser) {
3 | browser
4 | .url(browser.launchUrl + '/#!/ms-input')
5 | .waitForElementVisible('input', 1000)
6 | .assert.value('input', '123');
7 | },
8 | 'form and validate': function (browser) {
9 | browser
10 | .assert.containsText('pre', '{"name":"123"}')
11 | .click('input')
12 | .setValue('input', '4')
13 | .assert.containsText('pre', '{"name":"1234"}')
14 | .keys([1,2,3,4].map(function (n) { return browser.Keys.BACK_SPACE }))
15 | .assert.attributeContains('.form-group', 'class', 'has-error')
16 | .assert.visible('small.help-block')
17 | .assert.containsText('small.help-block', '请输入名字')
18 | .end();
19 | }
20 | };
--------------------------------------------------------------------------------
/docs/components/doc-sidebar/doc-sidebar.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 |
3 | import * as navConfig from '../../nav.config.js';
4 | import 'ane';
5 | import { menu as menuStore } from '../../stores';
6 |
7 | export const name = 'doc-sidebar';
8 |
9 | avalon.component(name, {
10 | template: require('./doc-sidebar.html'),
11 | defaults: {
12 | menu: [],
13 | selectedKeys: [],
14 | openKeys: ['components'],
15 | handleMenuClick(item, key, keyPath) {
16 | avalon.history.setHash(item.uri);
17 | },
18 | handleOpenChange(openKeys) {
19 | this.openKeys = openKeys.slice(-1);
20 | },
21 | onInit(event) {
22 | this.menu = navConfig;
23 | menuStore.selectedKeys$.subscribe(v => {
24 | this.selectedKeys = v;
25 | });
26 | }
27 | }
28 | });
--------------------------------------------------------------------------------
/components/ms-form/ms-form.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import { findParentComponent } from '../../ane-util';
3 |
4 | avalon.component('ms-form', {
5 | template: ``,
6 | defaults: {
7 | items: '',
8 | $form: null,
9 | type: '',
10 | horizontal: false,
11 | inline: false,
12 | onFormChange(meta) {
13 | if (this.$form) {
14 | this.$form.setFieldsValue({
15 | [meta.name]: { value: meta.value }
16 | });
17 | }
18 | },
19 | onInit(event) {
20 | event.target._ctype_ = 'ms-form';
21 | event.target._vm_ = this;
22 | },
23 | onReady(event) {
24 | }
25 | },
26 | soleSlot: 'items'
27 | });
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 非常抱歉,此项目已不再被维护。这个项目陪我度过很艰难的一段时间,有些不舍但是要说再见了。
2 |
3 | :cyclone:ane:cyclone:
4 |
5 |
8 |
9 | 中文名:安逸
10 |
11 |
12 | ## 快速开始
13 |
14 | ``` bash
15 | npm install ane --save
16 | ```
17 |
18 | ``` javascript
19 | import * as avalon from 'avalon2';
20 | import 'ane';
21 | // 打印一下会发现组件库已经挂在 avalon 上了
22 | console.log(avalon.components);
23 | ```
24 |
25 | ## 组件文档及示例
26 |
27 | https://xxapp.github.io/ane
28 |
29 | ## 浏览器支持
30 |
31 | 现代浏览器、IE8 及以上
32 |
33 | ## 贡献
34 |
35 | 欢迎提 issue
36 |
37 | 如果希望贡献组件,请阅读:
38 |
39 | https://github.com/xxapp/ane/wiki/如何贡献组件
40 |
41 | ## 鸣谢
42 |
43 | 组件开发过程中从 [ant-design](https://ant.design) 和 [element-ui](http://element.eleme.io/) 这两个优秀的组件库借鉴了很多设计和实现方法,在这里表示衷心的感谢。
44 |
--------------------------------------------------------------------------------
/components/ms-calendar/test/ms-calendar.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | calendar 组件
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
25 |
26 |
--------------------------------------------------------------------------------
/components/ms-loading/test/ms-loading.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
15 |
25 |
26 |
--------------------------------------------------------------------------------
/components/ms-input/ms-input.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import controlComponent from '../ms-form/ms-control';
3 | import { emitToFormItem } from '../ms-form/utils';
4 | import { findParentComponent } from '../../ane-util';
5 |
6 | controlComponent.extend({
7 | displayName: 'ms-input',
8 | template: require('./ms-input.html'),
9 | defaults: {
10 | text: '',
11 | mapValueToText(value) {
12 | this.text = value;
13 | },
14 | onInit: function (event) {
15 | emitToFormItem(this);
16 | this.$watch('value', v => {
17 | this.mapValueToText(v);
18 | this.handleChange({
19 | target: { value: v },
20 | denyValidate: true,
21 | type: 'changed'
22 | });
23 | });
24 | this.mapValueToText(this.value);
25 | }
26 | }
27 | });
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | import './components/ms-menu';
2 | import './components/ms-table/ms-table';
3 | import './components/ms-pagination/ms-pagination';
4 | import './components/ms-dialog';
5 | import './components/ms-form';
6 | export { createForm } from './components/ms-form/create-form';
7 | import './components/ms-input';
8 | import './components/ms-textarea';
9 | import './components/ms-select';
10 | import './components/ms-upload';
11 | import './components/ms-datepicker';
12 | import './components/ms-timepicker';
13 | import './components/ms-tree-select';
14 | import './components/ms-checkbox';
15 | import './components/ms-checkbox/ms-checkbox-group';
16 | import './components/ms-radio';
17 | import './components/ms-radio/ms-radio-group';
18 | import './components/ms-tree'
19 |
20 | export { Loading } from './components/ms-loading';
21 | export { default as notification } from './components/ms-notification';
22 | export { default as message } from './components/ms-message';
--------------------------------------------------------------------------------
/tests/index.js:
--------------------------------------------------------------------------------
1 | require('bootstrap/dist/css/bootstrap.css');
2 | require('font-awesome/css/font-awesome.css');
3 | require('highlight.js/styles/atom-one-light.css');
4 |
5 | require('es5-shim');
6 | require('es6-promise/dist/es6-promise.auto');
7 |
8 | var jQuery = require('jquery');
9 | window.$ = window.jQuery = jQuery;
10 | require('bootstrap');
11 | var bootbox = require('bootbox');
12 | bootbox.setLocale('zh_CN');
13 |
14 | require('./router');
15 |
16 | var avalon = require('avalon2');
17 | avalon.config({
18 | debug: true
19 | });
20 | if (avalon.msie === 8) {
21 | Object.defineProperty = function (obj, property, meta) {
22 | obj[property] = meta.value;
23 | }
24 | }
25 | require('es5-shim/es5-sham');
26 |
27 | avalon.define({
28 | $id: 'root',
29 | currentPage: ''
30 | });
31 | avalon.history.start({
32 | fireAnchor: false
33 | });
34 | if (!/#!/.test(global.location.hash)) {
35 | avalon.router.navigate('/', 2);
36 | }
37 | avalon.scan(document.body);
--------------------------------------------------------------------------------
/components/ms-menu/ms-menu.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 |
3 | avalon.component('ms-menu', {
4 | template: require('./ms-menu.html'),
5 | defaults: {
6 | menu: [],
7 | selectedKeys: [],
8 | openKeys: [],
9 | onClick: avalon.noop,
10 | onOpenChange: avalon.noop,
11 | handleClick(item, key, keyPath) {
12 | if (!item.children || item.children.length === 0) {
13 | // 叶子节点
14 | //this.selectedKeys.ensure(item.key);
15 | this.selectedKeys = [item.key];
16 | this.onClick(item, key, keyPath);
17 | } else {
18 | // 非叶子节点
19 | if (this.openKeys.contains(item.key)) {
20 | this.openKeys.remove(item.key);
21 | } else {
22 | this.openKeys.push(item.key);
23 | }
24 | this.onOpenChange(this.openKeys.toJSON());
25 | }
26 | }
27 | }
28 | });
--------------------------------------------------------------------------------
/components/ms-upload/ms-upload.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/components/ms-calendar/ms-calendar.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | | {{day}} |
14 |
15 |
16 |
17 |
18 | |
19 | {{el.date}}
20 | |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/components/ms-select/ms-select-panel.html:
--------------------------------------------------------------------------------
1 |
2 |
19 |
--------------------------------------------------------------------------------
/components/ms-loading/ms-loading.md:
--------------------------------------------------------------------------------
1 | ## 加载中蒙版
2 |
3 | ### 基本用法
4 |
5 | ```html
6 |
7 |
8 | hello world!
9 |
10 |
11 |
12 | ```
13 |
14 | ```js
15 | import * as avalon from 'avalon2';
16 | import 'ane';
17 |
18 | avalon.define({
19 | $id: 'doc-loading-basic',
20 | loading: true
21 | });
22 | ```
23 |
24 | ### 全局 loading 方法
25 |
26 | ```html
27 |
28 |
29 |
30 | ```
31 |
32 | ```js
33 | import { Loading } from 'ane';
34 |
35 | avalon.define({
36 | $id: 'doc-loading-global',
37 | show() {
38 | Loading.show();
39 |
40 | setTimeout(function () {
41 | Loading.hide();
42 | }, 3000);
43 | }
44 | });
45 | ```
--------------------------------------------------------------------------------
/nightwatch.json:
--------------------------------------------------------------------------------
1 | {
2 | "src_folders": ["tests"],
3 | "output_folder": "output/reports",
4 |
5 | "selenium": {
6 | "start_process": true,
7 | "server_path": "./bin/selenium-server-standalone-3.4.0.jar",
8 | "log_path" : "",
9 | "port": 4444,
10 | "cli_args": {
11 | "webdriver.chrome.driver": "./bin/chromedriver",
12 | "webdriver.ie.driver" : "./bin/IEDriverServer.exe"
13 | }
14 | },
15 |
16 | "test_settings": {
17 | "default": {
18 | "launch_url": "http://127.0.0.1:9000",
19 | "selenium_port" : 4444,
20 | "selenium_host" : "localhost",
21 | "desiredCapabilities": {
22 | "browserName": "chrome"
23 | }
24 | },
25 |
26 | "ie": {
27 | "launch_url": "http://127.0.0.1:9000",
28 | "selenium_port" : 4444,
29 | "selenium_host" : "localhost",
30 | "desiredCapabilities": {
31 | "browserName": "internet explorer"
32 | }
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/components/ms-timepicker/ms-timepicker-panel.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import * as moment from 'moment';
3 |
4 | export default function (cmpVm) {
5 | if (avalon.vmodels[cmpVm.panelVmId] !== undefined) {
6 | return avalon.vmodels[cmpVm.panelVmId];
7 | }
8 |
9 | return avalon.define({
10 | $id: cmpVm.panelVmId,
11 | currentDateArray: '',
12 | $moment: moment(),
13 | reset() {
14 | this.$moment = cmpVm.selected ? moment(cmpVm.selected, cmpVm.format) : moment();
15 | this.currentDateArray = this.$moment.toArray().toString();
16 | },
17 | handleTimepickerChange(e) {
18 | const { hour, minute, second } = e.target;
19 | this.$moment.hour(hour).minute(minute).second(second);
20 | this.currentDateArray = this.$moment.toArray().toString();
21 | cmpVm.selected = this.$moment.format(cmpVm.format);
22 |
23 | cmpVm.handleChange({
24 | target: { value: cmpVm.selected },
25 | type: 'timepicker-changed'
26 | });
27 | }
28 | });
29 | };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 PIEr_xx
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 |
--------------------------------------------------------------------------------
/components/ms-table/ms-table.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | |
6 |
7 | |
8 | {{el.title}} |
9 |
10 |
11 |
12 |
13 | |
14 |
15 | |
16 | |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/components/ms-textarea/ms-textarea.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import controlComponent from '../ms-form/ms-control';
3 | import { emitToFormItem } from '../ms-form/utils';
4 | import { findParentComponent } from '../../ane-util';
5 |
6 | /**
7 | * 多行文本输入组件
8 | * @prop value 组件值(inherit)
9 | * @prop col 字段路径(inherit)
10 | * @prop rows 文本框行数
11 | *
12 | * @example
13 | * ``` html
14 | *
15 | * ```
16 | */
17 | controlComponent.extend({
18 | displayName: 'ms-textarea',
19 | template: require('./ms-textarea.html'),
20 | defaults: {
21 | rows: '',
22 | text: '',
23 | mapValueToText(value) {
24 | this.text = value;
25 | },
26 | onInit(event) {
27 | emitToFormItem(this);
28 | this.$watch('value', v => {
29 | this.mapValueToText(v);
30 | this.handleChange({
31 | target: { value: v },
32 | denyValidate: true,
33 | type: 'changed'
34 | });
35 | });
36 | this.mapValueToText(this.value);
37 | }
38 | }
39 | });
--------------------------------------------------------------------------------
/components/ms-pagination/ms-pagination.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 |
3 | /**
4 | * 分页组件
5 | * @prop {Number} [current=1] 当前页
6 | * @prop {Number} [pageSize=10] 每页的数据量
7 | * @prop {Number} total 数据总量
8 | * @event {Function} onChange 当页码改变时触发,参数current
9 | *
10 | * @example
11 | * ```
12 | *
13 | *
14 | *
15 | * ```
16 | */
17 | avalon.component('ms-pagination', {
18 | template: require('./ms-pagination.html'),
19 | defaults: {
20 | current: 1,
21 | pageSize: 10,
22 | total: 0,
23 | prevPage() {
24 | if (this.current > 1) {
25 | this.onChange(--this.current);
26 | }
27 | },
28 | nextPage() {
29 | if (this.current < Math.ceil(this.total/this.pageSize)) {
30 | this.onChange(++this.current);
31 | }
32 | },
33 | onChange: avalon.noop,
34 | onInit(event) {
35 | },
36 | onReady(event) {
37 | },
38 | onDispose(event) {
39 | }
40 | }
41 | });
--------------------------------------------------------------------------------
/components/ms-timepicker/ms-timepicker-view.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/ms-radio/ms-radio.md:
--------------------------------------------------------------------------------
1 | ## 单选框
2 |
3 | ### 基本用法
4 |
5 | ```html
6 |
7 | radio
8 |
9 | ```
10 |
11 | ### 单选框组
12 |
13 | ```html
14 |
15 |
22 |
23 |
24 | ```
25 |
26 | ```js
27 | import * as avalon from 'avalon2';
28 | import 'ane';
29 |
30 | const vm = avalon.define({
31 | $id: 'doc-radio-group',
32 | handleChange(e) {
33 | console.log(e.target.value);
34 | }
35 | });
36 | ```
37 |
38 | ### 组件参数
39 |
40 | radio
41 |
42 | | 参数 | 说明 | 类型 | 默认值 |
43 | |-----|-----|-----|-----|
44 | | label | 展示值 | string | '' |
45 | | checked | 当前选择的 value 值 | string | '' |
46 | | value | 此选项的 value 值 | string | '' |
47 | | disabled | 是否禁用 | boolean | false |
48 | | onChange | 选择改变时的回调 | function(e) | noop |
49 |
50 | radio-group
51 |
52 | | 参数 | 说明 | 类型 | 默认值 |
53 | |-----|-----|-----|-----|
54 | | disabled | 是否禁用所有选项 | boolean | false |
55 | | options | 选项数组 | Array<{ label: string value: string disabled?: boolean }> | \[\] |
56 |
57 | > radio-group 继承 [ms-control 组件](#!/form-control) 的所有参数
--------------------------------------------------------------------------------
/docs/index.js:
--------------------------------------------------------------------------------
1 | require('bootstrap/dist/css/bootstrap.css');
2 | require('font-awesome/css/font-awesome.css');
3 | require('highlight.js/styles/atom-one-light.css');
4 |
5 | require('es5-shim');
6 | require('es6-promise/dist/es6-promise.auto');
7 |
8 | var jQuery = require('jquery');
9 | window.$ = window.jQuery = jQuery;
10 | require('bootstrap');
11 | var bootbox = require('bootbox');
12 | bootbox.setLocale('zh_CN');
13 |
14 | // 提前禁止avalon对Object.create的实现
15 | if (!Object.create) {
16 | Object.create = function () {
17 | function F() {}
18 |
19 | return function (o) {
20 | F.prototype = o;
21 | return new F();
22 | };
23 | }();
24 | }
25 | var avalon = require('avalon2');
26 | avalon.config({
27 | debug: true
28 | });
29 | if (avalon.msie < 8) {
30 | Object.defineProperty = function (obj, property, meta) {
31 | obj[property] = meta.value;
32 | }
33 | }
34 | require('es5-shim/es5-sham');
35 | require('./router');
36 | require('../components/ms-layout');
37 | require('./components/doc-sidebar/doc-sidebar');
38 |
39 | avalon.define({
40 | $id: 'root',
41 | currentPage: '',
42 | breadcrumb: []
43 | });
44 | avalon.history.start({
45 | fireAnchor: false
46 | });
47 | if (!/#!/.test(global.location.hash)) {
48 | avalon.router.navigate('/', 2);
49 | }
50 | avalon.scan(document.body);
--------------------------------------------------------------------------------
/components/ms-radio/ms-radio.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import { parseSlotToVModel } from '../../ane-util';
3 |
4 | if (avalon.msie <= 8) {
5 | const doc = document;
6 | const head = doc.getElementsByTagName('head')[0];
7 | const style: any = doc.createElement('style');
8 | const cssStr = `
9 | .ane-radio-inner-ie input {
10 | left: 0;
11 | position: static !important;
12 | margin-left: 0 !important;
13 | margin-top: 6px !important;
14 | }
15 | .ane-radio-inner-ie span {
16 | display: none !important;
17 | }
18 | `;
19 | style.setAttribute('type', 'text/css');
20 |
21 | if (style.styleSheet) {
22 | style.styleSheet.cssText = cssStr;
23 | } else {
24 | style.appendChild(doc.createTextNode(cssStr));
25 | }
26 |
27 | head.appendChild(style);
28 | }
29 |
30 | avalon.component('ms-radio', {
31 | soleSlot: 'label',
32 | template: require('./ms-radio.html'),
33 | defaults: {
34 | wrapper: 'radio',
35 | label: '',
36 | checked: '',
37 | value: '',
38 | name: '',
39 | group: false,
40 | disabled: false,
41 | onChange: avalon.noop,
42 | helpId: '',
43 | onInit(event) {
44 | this.helpId = this.$id;
45 | }
46 | }
47 | });
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Ane 组件文档
8 |
9 |
10 |
11 |
12 | ANE 组件文档
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
28 |
29 |
30 |
33 |
34 |
--------------------------------------------------------------------------------
/components/ms-datepicker/ms-datepicker.md:
--------------------------------------------------------------------------------
1 | ## 日期选择器
2 |
3 | ### 基本用法
4 |
5 | ```html
6 |
7 |
10 |
11 | ```
12 |
13 | ### 格式化日期
14 |
15 | ```html
16 |
17 |
21 |
22 | ```
23 |
24 | ### 不可选择的日期
25 |
26 | ```html
27 |
28 |
33 |
34 | ```
35 |
36 | ### 日期时间选择
37 |
38 | ```html
39 |
40 |
44 |
45 | ```
46 |
47 | ### 组件参数
48 |
49 | | 参数 | 说明 | 类型 | 默认值 |
50 | |-----|-----|-----|-----|
51 | | format | 日期格式,参考 momentjs | string | `'YYYY-MM-DD'` |
52 | | startDate | 控制可以选择的日期范围的开始日期 | string | '' |
53 | | endDate | 控制可以选择的日期范围的结束日期 | string | '' |
54 | | disabledDate | 不可选择日期的判断函数,传入 current(当前遍历日期的毫秒值),返回 true 表示此日期不可选 | function(current:number) | `() => false` |
55 | | showTime | 是否需要选择时间,如果此项为 true,则 format 默认为 YYYY-MM-DD HH:mm:ss | boolean | false |
56 | | direction | 下拉框弹出方向,目前只有 `up`/`down` 两个选项 | string | `down` |
57 |
58 | > 继承 [ms-control 组件](#!/form-control) 的所有参数
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | 0.1.11 / 2017-09-25
2 | ------------------
3 |
4 | - dialog 组件,增加 className 属性
5 |
6 | 0.1.10 / 2017-09-03
7 | ------------------
8 |
9 | - dialog 组件,实现完全自定义 footer 功能
10 |
11 | 0.1.9 / 2017-09-02
12 | ------------------
13 |
14 | - tree 组件,增加 expandedKeys 属性
15 |
16 | 0.1.8 / 2017-08-31
17 | ------------------
18 |
19 | - select 组件,修复外部更新 options 数据,不会重新渲染的问题
20 |
21 | 0.1.7 / 2017-08-25
22 | ------------------
23 |
24 | - tree-select 组件,增加 direction (“下拉”方向)属性,同时 select/datepicker/timepicker 也增加了此属性
25 | - tree-select 组件,onChange 回调传入更多信息
26 |
27 | 0.1.6 / 2017-08-23
28 | ------------------
29 |
30 | - dialog 组件,修复通过叉叉关闭后无法再次打开的问题
31 |
32 | 0.1.5 / 2017-08-20
33 | ------------------
34 |
35 | - dialog 组件,增加自定义弹出框按钮文字的功能
36 |
37 | 0.1.4 / 2017-08-16
38 | ------------------
39 |
40 | - tree 组件,修复外部更新 tree 数据,不会重新渲染的问题
41 | - tree 组件,修复 onSelect 回调事件对象属性名错误的问题
42 |
43 | 0.1.3 / 2017-08-14
44 | ------------------
45 |
46 | - table 组件,增加自动序号列
47 |
48 | ``` html
49 |
50 | ```
51 |
52 | 0.1.2 / 2017-08-14
53 | ------------------
54 |
55 | - form 组件,validateField 方法只需传字段名称
56 |
57 | 0.1.1 / 2017-07-28
58 | ------------------
59 |
60 | - 添加组件文档
61 | - 解决菜单没有 active 状态的问题
62 | - 解决表单组件共享一个 record 对象导致的数据错乱问题
63 | - loading 组件,调整蒙版高度获取方式,将整体loading方法通过ane入口模块暴露出去
64 | - 用 less 替代 sass 重写组件样式,并和 bootstrap less 源文件结合
65 | - 增加组件:tree/tree-select
--------------------------------------------------------------------------------
/components/ms-checkbox/ms-checkbox.md:
--------------------------------------------------------------------------------
1 | ## 多选框
2 |
3 | ### 基本用法
4 |
5 | ```html
6 |
7 | checkbox
8 |
9 | ```
10 |
11 | ### 单选框组
12 |
13 | ```html
14 |
15 |
22 |
23 |
24 | ```
25 |
26 | ```js
27 | import * as avalon from 'avalon2';
28 | import 'ane';
29 |
30 | const vm = avalon.define({
31 | $id: 'doc-checkbox-group',
32 | handleChange(e) {
33 | console.log(e.target.value);
34 | }
35 | });
36 | ```
37 |
38 | ### 组件参数
39 |
40 | checkbox
41 |
42 | | 参数 | 说明 | 类型 | 默认值 |
43 | |-----|-----|-----|-----|
44 | | label | 展示值 | string | '' |
45 | | checked | 当前选择的 value 值 | boolean | false |
46 | | value | 此选项的 value 值 | string | '' |
47 | | disabled | 是否禁用 | boolean | false |
48 | | onChange | 选择改变时的回调 | function(e) | noop |
49 | | indeterminate | 设置半选状态,只负责样式控制 | boolean | false |
50 |
51 | radio-checkbox
52 |
53 | | 参数 | 说明 | 类型 | 默认值 |
54 | |-----|-----|-----|-----|
55 | | value | 选中的值数组 | string\[\] | \[\] |
56 | | disabled | 是否禁用所有选项 | boolean | false |
57 | | options | 选项数组 | Array<{ label: string value: string disabled?: boolean }> | \[\] |
58 |
59 | > radio-checkbox 继承 [ms-control 组件](#!/form-control) 的所有参数
--------------------------------------------------------------------------------
/components/ms-view/ms-view.js:
--------------------------------------------------------------------------------
1 | var avalon = require('avalon2');
2 |
3 | var states = {}
4 |
5 | avalon.component('ms-view', {
6 | template: '',
7 | defaults: {
8 | page: ' ',
9 | path: 'no',
10 |
11 | onReady: function(e) {
12 | var path = e.vmodel.path
13 | var state = states[path]
14 | avalon.vmodels[state.vm.$id] = state.vm
15 | setTimeout(function() {//必须等它扫描完这个template,才能替换
16 | e.vmodel.page = state.html
17 | },100)
18 |
19 | },
20 | onDispose: function(e) {
21 | var path = e.vmodel.path
22 | var state = states[path]
23 | var vm = state.vm
24 | var render = vm.render
25 | render && render.dispose()
26 | delete avalon.vmodels[vm.$id]
27 | }
28 | }
29 | });
30 |
31 | exports.add = function addState(path, html, vm) {
32 | states[path] = {
33 | vm: vm,
34 | html: html
35 | }
36 | }
37 |
38 | exports.resolve = function (path) {
39 | var state = states[path];
40 | state.html = typeof state.html == 'function' ? state.html() : state.html;
41 | state.vm = typeof state.vm == 'function' ? state.vm() : state.vm;
42 | return Promise.all([state.html, state.vm]).then(function (result) {
43 | state.html = result[0];
44 | state.vm = result[1];
45 | });
46 | }
--------------------------------------------------------------------------------
/components/ms-radio/ms-radio-group.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import controlComponent from '../ms-form/ms-control';
3 | import { emitToFormItem } from '../ms-form/utils';
4 | import { findParentComponent } from '../../ane-util';
5 | import './ms-radio';
6 |
7 | controlComponent.extend({
8 | displayName: 'ms-radio-group',
9 | template: require('./ms-radio-group.html'),
10 | defaults: {
11 | value: '',
12 | disabled: false,
13 | options: [],
14 | selected: '',
15 | toggleOption(e, option) {
16 | this.selected = option.value;
17 | this.handleChange({
18 | target: { value: this.selected },
19 | type: 'radio-group'
20 | });
21 | },
22 | helpId: '',
23 | mapValueToSelected(value) {
24 | this.selected = value;
25 | },
26 | onInit(event) {
27 | this.helpId = this.$id;
28 | emitToFormItem(this);
29 | this.$watch('value', v => {
30 | this.mapValueToSelected(v);
31 | this.handleChange({
32 | target: { value: v },
33 | denyValidate: true,
34 | type: 'radio-group'
35 | });
36 | });
37 | this.mapValueToSelected(this.value);
38 | },
39 | onReady(event) {
40 | },
41 | onDispose(event) {
42 | }
43 | }
44 | });
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Nodejs
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 | *.pid.lock
12 |
13 | # Directory for instrumented libs generated by jscoverage/JSCover
14 | lib-cov
15 |
16 | # Coverage directory used by tools like istanbul
17 | coverage
18 |
19 | # nyc test coverage
20 | .nyc_output
21 |
22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
23 | .grunt
24 |
25 | # node-waf configuration
26 | .lock-wscript
27 |
28 | # Compiled binary addons (http://nodejs.org/api/addons.html)
29 | build/Release
30 |
31 | # Dependency directories
32 | node_modules
33 | jspm_packages
34 |
35 | # Optional npm cache directory
36 | .npm
37 |
38 | # Optional eslint cache
39 | .eslintcache
40 |
41 | # Optional REPL history
42 | .node_repl_history
43 |
44 | ## MacOS
45 | *.DS_Store
46 | .AppleDouble
47 | .LSOverride
48 |
49 | # Icon must end with two \r
50 | Icon
51 |
52 |
53 | # Thumbnails
54 | ._*
55 |
56 | # Files that might appear in the root of a volume
57 | .DocumentRevisions-V100
58 | .fseventsd
59 | .Spotlight-V100
60 | .TemporaryItems
61 | .Trashes
62 | .VolumeIcon.icns
63 | .com.apple.timemachine.donotpresent
64 |
65 | # Directories potentially created on remote AFP share
66 | .AppleDB
67 | .AppleDesktop
68 | Network Trash Folder
69 | Temporary Items
70 | .apdisk
71 |
72 | ## VSC
73 | .vscode/*
74 | !.vscode/settings.json
75 | !.vscode/tasks.json
76 | !.vscode/launch.json
77 |
78 | dist/*
79 | output/*
80 | bin/*
--------------------------------------------------------------------------------
/components/ms-checkbox/ms-checkbox.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import { parseSlotToVModel } from '../../ane-util';
3 |
4 | if (avalon.msie <= 8) {
5 | const doc = document;
6 | const head = doc.getElementsByTagName('head')[0];
7 | const style: any = doc.createElement('style');
8 | const cssStr = `
9 | .ane-checkbox-inner-ie input {
10 | left: 0;
11 | position: static !important;
12 | margin-left: 0 !important;
13 | margin-top: 6px !important;
14 | }
15 | .ane-checkbox-inner-ie span {
16 | display: none !important;
17 | }
18 | `;
19 | style.setAttribute('type', 'text/css');
20 |
21 | if (style.styleSheet) {
22 | style.styleSheet.cssText = cssStr;
23 | } else {
24 | style.appendChild(doc.createTextNode(cssStr));
25 | }
26 |
27 | head.appendChild(style);
28 | }
29 |
30 | avalon.component('ms-checkbox', {
31 | soleSlot: 'label',
32 | template: require('./ms-checkbox.html'),
33 | defaults: {
34 | wrapper: 'checkbox',
35 | label: '',
36 | checked: false,
37 | indeterminate: false,
38 | group: false,
39 | disabled: false,
40 | onChange: avalon.noop,
41 | flush: avalon.noop,
42 | helpId: '',
43 | onInit(event) {
44 | this.helpId = this.$id;
45 | // // inline在IE8下显示有问题,待解决
46 | // if (this.inline != void 0) {
47 | // this.wrapper = 'checkbox-inline';
48 | // }
49 | }
50 | }
51 | });
--------------------------------------------------------------------------------
/components/ms-layout/ms-layout.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 |
3 | const layoutComponent = avalon.component('ms-layout', {
4 | template: `
`,
5 | soleSlot: 'slot',
6 | defaults: {
7 | style: {},
8 | className: ''
9 | }
10 | });
11 |
12 | layoutComponent.extend({
13 | displayName: 'ms-layout-sider',
14 | template: ``,
15 | soleSlot: 'slot',
16 | defaults: {
17 | fixed: false,
18 | width: '300px'
19 | }
20 | });
21 |
22 | layoutComponent.extend({
23 | displayName: 'ms-layout-header',
24 | template: ``,
25 | soleSlot: 'slot',
26 | defaults: {
27 | fixed: false,
28 | width: '60px'
29 | }
30 | });
31 |
32 | layoutComponent.extend({
33 | displayName: 'ms-layout-content',
34 | template: `
`,
35 | soleSlot: 'slot',
36 | defaults: {
37 | fixed: false
38 | }
39 | });
40 |
41 | layoutComponent.extend({
42 | displayName: 'ms-layout-footer',
43 | template: ``,
44 | soleSlot: 'slot',
45 | defaults: {
46 | fixed: false,
47 | width: '60px'
48 | }
49 | });
--------------------------------------------------------------------------------
/components/ms-message/ms-message.md:
--------------------------------------------------------------------------------
1 | ## 全局提示
2 |
3 | ### 基本用法
4 |
5 | ```html
6 |
7 |
8 |
9 |
10 |
11 |
12 | ```
13 |
14 | ```js
15 | import * as avalon from 'avalon2';
16 | import { message } from 'ane';
17 |
18 | avalon.define({
19 | $id: 'doc-message-basic',
20 | info() {
21 | message.info({
22 | content: '这是一条普通提示'
23 | });
24 | },
25 | success() {
26 | message.success({
27 | content: '这是一条成功提示'
28 | });
29 | },
30 | error() {
31 | message.error({
32 | content: '这是一条失败提示'
33 | });
34 | },
35 | warning() {
36 | message.warn({
37 | content: '这是一条警告提示'
38 | });
39 | }
40 | });
41 | ```
42 |
43 | ### API
44 |
45 | - `message.success({ content, duration })`
46 | - `message.error({ content, duration })`
47 | - `message.info({ content, duration })`
48 | - `message.warning({ content, duration })`
49 | - `message.warn({ content, duration })` 同 message.warning
50 |
51 | | 参数 | 说明 | 类型 | 默认值 | 是否可选 |
52 | | --- | --- | --- | --- | --- |
53 | | content | 提示内容 | string | - | 必选 |
54 | | duration | 自动关闭的延时,单位毫秒 | number | 1500 | 可选 |
55 |
56 | 可以改变默认参数
57 |
58 | - `message.config({ duration })`
59 |
60 | | 参数 | 说明 | 类型 | 默认值 | 是否可选 |
61 | | --- | --- | --- | --- | --- |
62 | | duration | 自动关闭的延时,单位毫秒 | number | 1500 | 可选 |
--------------------------------------------------------------------------------
/components/ms-select/ms-select.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/typings/mmRouter.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | /**
4 | * avalon.history.start 配置项
5 | */
6 | interface mmHistoryOptions {
7 | /**
8 | * 根路径
9 | */
10 | root?: string,
11 | /**
12 | * 是否使用HTML 5 history
13 | */
14 | html5?: boolean,
15 | /**
16 | * hash 前缀
17 | */
18 | hashPrefix?: '!',
19 | /**
20 | * 滚动
21 | */
22 | autoScroll?: boolean,
23 | fireAnchor: boolean
24 | }
25 |
26 | interface AvalonStatic {
27 | history: {
28 | hash: string,
29 | check: () => void,
30 | start: (options: mmHistoryOptions) => void,
31 | stop: () => void,
32 | setHash: (s: string, replace?: boolean) => void,
33 | writeFrame: (s: string) => void,
34 | syncHash: () => this,
35 | getPath: () => string,
36 | onHashChanged: (hash: string, clickMode: number) => void
37 | },
38 | router: {
39 | getLastHash: () => string,
40 | setLastHash: (path: string) => void,
41 | /**
42 | * 当目标页面不匹配我们所有路由规则时, 就会执行此回调.有点像404
43 | * @param cb 回调
44 | */
45 | error: (cb?: () => void) => void,
46 | /**
47 | * 添加 一个路由规则与对象的回调, cb为rule规则中捕捉的参数
48 | * @param rule 路由规则
49 | * @param cb 匹配时的回调
50 | */
51 | add: (rule: string, cb?: (rule: object, args) => any, opts?) => void,
52 | /**
53 | * 手动触发对应的回调
54 | * @param hash 路径
55 | * @param mode 0或undefined, 不改变URL, 不产生历史实体, 执行回调; 1, 改变URL, 不产生历史实体, 执行回调; 2, 改变URL, 产生历史实体, 执行回调
56 | */
57 | navigate: (hash: string, mode: number) => string
58 | }
59 | }
60 |
61 | declare module 'mmRouter' {
62 | export = AvalonStatic
63 | }
--------------------------------------------------------------------------------
/components/ms-message/ms-message.ts:
--------------------------------------------------------------------------------
1 | import * as noty from 'noty';
2 |
3 | type messageArgs = {
4 | content: string,
5 | duration?: number
6 | };
7 |
8 | let defaultOptions = {
9 | duration: 1500
10 | };
11 |
12 | export default {
13 | info({ content, duration }: messageArgs): void {
14 | noty({
15 | text: '' + content,
16 | type: 'information',
17 | layout: 'topCenter',
18 | timeout: duration || defaultOptions.duration
19 | });
20 | },
21 | success({ content, duration}: messageArgs): void {
22 | noty({
23 | text: '' + content,
24 | type: 'success',
25 | layout: 'topCenter',
26 | timeout: duration || defaultOptions.duration
27 | });
28 | },
29 | error({ content, duration}: messageArgs): void {
30 | noty({
31 | text: '' + content,
32 | type: 'error',
33 | layout: 'topCenter',
34 | timeout: duration || defaultOptions.duration
35 | });
36 | },
37 | warning({ content, duration}: messageArgs): void {
38 | noty({
39 | text: '' + content,
40 | type: 'warning',
41 | layout: 'topCenter',
42 | timeout: duration || defaultOptions.duration
43 | });
44 | },
45 | warn({ content, duration}: messageArgs): void {
46 | this.warning({ content, duration });
47 | },
48 | config(options: messageArgs): void {
49 | if (options.duration !== undefined) {
50 | defaultOptions.duration = options.duration;
51 | }
52 | }
53 | };
--------------------------------------------------------------------------------
/components/ms-tree-select/ms-tree-select.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/ms-checkbox/ms-checkbox-group.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import controlComponent from '../ms-form/ms-control';
3 | import { emitToFormItem } from '../ms-form/utils';
4 | import { findParentComponent } from '../../ane-util';
5 | import './ms-checkbox';
6 |
7 | controlComponent.extend({
8 | displayName: 'ms-checkbox-group',
9 | template: require('./ms-checkbox-group.html'),
10 | defaults: {
11 | value: [],
12 | disabled: false,
13 | options: [],
14 | selection: [],
15 | toggleOption(option) {
16 | const optionIndex = this.selection.indexOf(option.value);
17 | if (optionIndex === -1 ) {
18 | this.selection.push(option.value);
19 | } else {
20 | this.selection.remove(option.value);
21 | }
22 | this.handleChange({
23 | target: { value: this.selection.toJSON() },
24 | type: 'checkbox-group'
25 | });
26 | },
27 | mapValueToSelection(value) {
28 | this.selection = this.options.filter(o => value.contains(o.value)).map(o => o.value);
29 | },
30 | onInit(event) {
31 | emitToFormItem(this);
32 | this.$watch('value', v => {
33 | this.mapValueToSelection(v);
34 | this.handleChange({
35 | target: { value: v.toJSON() },
36 | denyValidate: true,
37 | type: 'checkbox-group'
38 | });
39 | });
40 | this.mapValueToSelection(this.value);
41 | },
42 | onReady(event) {
43 | //vm.elHiddenInput = $(el).find('input:hidden');
44 | },
45 | onDispose(event) {
46 | }
47 | }
48 | });
--------------------------------------------------------------------------------
/components/ms-radio/test/ms-radio.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | checkbox 组件
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 |
24 |
{{@json}}
25 |
26 |
44 |
45 |
--------------------------------------------------------------------------------
/tests/router.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import 'mmRouter';
3 |
4 | function getPage(component) {
5 | const html = ``;
6 | return html
7 | }
8 |
9 | function applyRouteConfig(config, parentRoute, accPath = '') {
10 | config.map(function (route) {
11 | let components:any = {};
12 | if (route.component) {
13 | components.currentPage = route.component;
14 | }
15 | if (route.components) {
16 | components = route.components;
17 | }
18 | avalon.router.add(accPath + route.path, function () {
19 | Object.keys(components).map(viewName => {
20 | let component = components[viewName];
21 | if (typeof component === 'function') {
22 | component(function (m) {
23 | avalon.vmodels[parentRoute.name][viewName] = getPage(m.name);
24 | });
25 | } else {
26 | avalon.vmodels[parentRoute.name][viewName] = getPage(component.name);
27 | }
28 | });
29 | });
30 | // TODO 支持嵌套路由
31 | //route.children && applyRouteConfig(route.children, route, accPath + route.path);
32 | });
33 | }
34 |
35 | const routeConfig = [{
36 | path: '/ms-input',
37 | component(resolve) {
38 | require.ensure([], function () {
39 | resolve(require('../components/ms-input/test/ms-input.test.ts'));
40 | });
41 | }
42 | }, {
43 | path: '/ms-select',
44 | component(resolve) {
45 | require.ensure([], function () {
46 | resolve(require('../components/ms-select/ms-select.md'));
47 | });
48 | }
49 | }];
50 |
51 | applyRouteConfig(routeConfig, {
52 | name: 'root'
53 | });
--------------------------------------------------------------------------------
/components/ms-checkbox/test/ms-checkbox.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | checkbox 组件
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 |
24 |
{{@json}}
25 |
26 |
44 |
45 |
--------------------------------------------------------------------------------
/docs/router.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import 'mmRouter';
3 | import { menu as menuStore } from './stores';
4 | import * as navConfig from './nav.config.js';
5 |
6 | function getPage(component) {
7 | const html = ``;
8 | return html
9 | }
10 |
11 | function applyRouteConfig(config, parentRoute, accPath = '') {
12 | config.map(function (route) {
13 | let components:any = {};
14 | if (route.component) {
15 | components.currentPage = route.component;
16 | }
17 | if (route.components) {
18 | components = route.components;
19 | }
20 | avalon.router.add(accPath + route.path, function () {
21 | Object.keys(components).map(viewName => {
22 | let component = components[viewName];
23 | if (typeof component === 'function') {
24 | component(function (m) {
25 | menuStore.selectedKeys$.onNext([m.name]);
26 | avalon.vmodels[parentRoute.name][viewName] = getPage(m.name);
27 | });
28 | } else {
29 | avalon.vmodels[parentRoute.name][viewName] = getPage(component.name);
30 | }
31 | });
32 | });
33 | // TODO 支持嵌套路由
34 | //route.children && applyRouteConfig(route.children, route, accPath + route.path);
35 | });
36 | }
37 |
38 | const routeConfig = [];
39 | const travel = item => {
40 | if (!item.children || item.children.length === 0) {
41 | routeConfig.push({
42 | path: item.uri,
43 | component: item.location
44 | });
45 | } else {
46 | item.children.map(travel);
47 | }
48 | };
49 | navConfig.map(travel);
50 |
51 | applyRouteConfig(routeConfig, {
52 | name: 'root'
53 | });
--------------------------------------------------------------------------------
/components/ms-notification/ms-notification.md:
--------------------------------------------------------------------------------
1 | ## 通知提醒框
2 |
3 | ### 基本用法
4 |
5 | ```html
6 |
7 |
8 |
9 |
10 |
11 |
12 | ```
13 |
14 | ```js
15 | import * as avalon from 'avalon2';
16 | import { notification } from 'ane';
17 |
18 | avalon.define({
19 | $id: 'doc-notification-basic',
20 | info() {
21 | notification.info({
22 | message: '这是一条普通通知',
23 | title: '通知'
24 | });
25 | },
26 | success() {
27 | notification.success({
28 | message: '这是一条成功通知',
29 | title: '通知'
30 | });
31 | },
32 | error() {
33 | notification.error({
34 | message: '这是一条失败通知',
35 | title: '通知'
36 | });
37 | },
38 | warning() {
39 | notification.warn({
40 | message: '这是一条警告通知',
41 | title: '通知'
42 | });
43 | }
44 | });
45 | ```
46 |
47 | ### API
48 |
49 | - `notification.success({ message, title, timeout })`
50 | - `notification.error({ message, title, timeout })`
51 | - `notification.info({ message, title, timeout })`
52 | - `notification.warning({ message, title, timeout })`
53 | - `notification.warn({ message, title, timeout })` 同 notification.warning
54 |
55 | | 参数 | 说明 | 类型 | 默认值 | 是否可选 |
56 | | --- | --- | --- | --- | --- |
57 | | message | 通知内容 | string | - | 必选 |
58 | | title | 通知标题 | string | - | 可选 |
59 | | timeout | 自动关闭的延时,单位毫秒 | number | 3000 | 可选 |
60 |
61 | 可以改变默认参数
62 |
63 | - `notification.config({ timeout })`
64 |
65 | | 参数 | 说明 | 类型 | 默认值 | 是否可选 |
66 | | --- | --- | --- | --- | --- |
67 | | timeout | 自动关闭的延时,单位毫秒 | number | 3000 | 可选 |
--------------------------------------------------------------------------------
/components/ms-select/ms-select-panel.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 |
3 | export default function (cmpVm) {
4 | if (avalon.vmodels[cmpVm.panelVmId] !== undefined) {
5 | return avalon.vmodels[cmpVm.panelVmId];
6 | }
7 |
8 | return avalon.define({
9 | $id: cmpVm.panelVmId,
10 | selection: [],
11 | loading: false,
12 | isMultiple: cmpVm.isMultiple,
13 | options: cmpVm.options.toJSON(),
14 | searchValue: '',
15 | getFilteredOptions() {
16 | return this.options.filter(this.filterFn);
17 | },
18 | filterFn(el) {
19 | if (this.loading) {
20 | return false;
21 | }
22 | if (cmpVm.remote) {
23 | return true;
24 | }
25 | const reg = new RegExp(avalon.escapeRegExp(this.searchValue), 'i');
26 | return reg.test(el.label) || reg.test(el.value);
27 | },
28 | handleOptionClick(e, option) {
29 | if (option.disabled) {
30 | return false;
31 | }
32 | if (cmpVm.isMultiple) {
33 | if (this.selection.some(o => o.value === option.value)) {
34 | this.selection.removeAll(o => o.value === option.value);
35 | } else {
36 | this.selection.push(option);
37 | }
38 | cmpVm.focusSearch();
39 | } else {
40 | this.selection = [option];
41 | cmpVm.panelVisible = false;
42 | }
43 | const selection = this.selection.toJSON();
44 | const value = selection.map(s => s.value);
45 | cmpVm.handleChange({
46 | target: { value: cmpVm.isMultiple ? value : value[0] || '' },
47 | type: 'select'
48 | });
49 | cmpVm.displayValue = option.label;
50 | cmpVm.selection = selection;
51 | }
52 | });
53 | }
--------------------------------------------------------------------------------
/ane-util.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 |
3 | export function findParentComponent(vm, ctype) {
4 | let parent = vm.$element.parentElement;
5 | while (parent) {
6 | if (parent._vm_ && (!ctype || parent._ctype_ === ctype)) {
7 | return parent._vm_;
8 | }
9 | parent = parent.parentElement;
10 | }
11 | return null;
12 | }
13 |
14 | export function parseSlotToVModel(vmodel, vnodes?: any[]): void {
15 | if (vnodes === undefined) {
16 | vnodes = vmodel.$render.root ? vmodel.$render.root.children : [];
17 | }
18 | vnodes.forEach(vnode => {
19 | if (!vnode || !vnode.nodeName || vnode.dom.nodeType !== 1) return true;
20 | let slotName = vnode.dom.getAttribute('slot');
21 | if (slotName) {
22 | delete vnode.props[':skip'];
23 | delete vnode.props['ms-skip'];
24 | vmodel[slotName] = avalon.vdom(vnode, 'toHTML');
25 | } else {
26 | parseSlotToVModel(vmodel, vnode.children);
27 | }
28 | });
29 | }
30 |
31 | export function getChildTemplateDescriptor(vmodel, render = vmodel.$render): any[] {
32 | if (render.directives === undefined) {
33 | return [];
34 | }
35 | return render.directives.reduce((acc, action) => {
36 | if (action.is) {
37 | acc.push({
38 | is: action.is,
39 | props: action.value,
40 | inlineTemplate: action.fragment,
41 | children: getChildTemplateDescriptor(vmodel, action.innerRender || { directives: [] })
42 | });
43 | }
44 | return acc;
45 | }, []);
46 | }
47 |
48 | export function debounce(func, wait: number = 300, immediate: boolean = false) {
49 | let timeout;
50 | return function(...args) {
51 | let context = this;
52 | let later = function() {
53 | timeout = null;
54 | if (!immediate) func.apply(context, args);
55 | };
56 | let callNow = immediate && !timeout;
57 | clearTimeout(timeout);
58 | timeout = setTimeout(later, wait);
59 | if (callNow) func.apply(context, args);
60 | };
61 | }
--------------------------------------------------------------------------------
/components/ms-tree-select/ms-tree-select.md:
--------------------------------------------------------------------------------
1 | ## 树选择组件
2 |
3 | ### 基本用法
4 |
5 | ```html
6 |
7 |
8 |
9 | ```
10 |
11 | ```js
12 | import * as avalon from 'avalon2';
13 | import 'ane';
14 |
15 | avalon.define({
16 | $id: "tree",
17 | data: [
18 | {key: 1, title: "aaa", children: [
19 | {key: 7, title: 1111, children: []},
20 | {key: 8, title: 2222, children: [
21 | {key: 14, title: 777, children: []}
22 | ]},
23 | {key: 9, title: 3333, children: [
24 | {key: 15, title: 8888, children: []},
25 | {key: 16, title: 9999, children: [
26 | {key: 17, title: '司徒正美', children: []}
27 | ]}
28 | ]}
29 | ]},
30 | {key: 2, title: "bbb", children: [
31 | {key: 10, title: 4444, children: []},
32 | {key: 11, title: 5555, children: []},
33 | {key: 12, title: 6666, children: []}
34 | ]},
35 | {key: 3, title: "ccc", children: []},
36 | {key: 4, title: "ddd", children: []},
37 | {key: 5, title: "eee", children: [
38 | {key: 13, title: 1234, children: []}
39 | ]},
40 | {key: 6, title: "fff", children: []}
41 | ]
42 | })
43 | ```
44 |
45 | ### 组件参数
46 |
47 | | 参数 | 说明 | 类型 | 默认值 |
48 | |-----|-----|-----|-----|
49 | | value | 默认值 | string\[\] | \[\] |
50 | | multiple | 是否多选 | boolean | false |
51 | | treeData | 树数据 | TreeNode\[\] | \[\] |
52 | | showSearch | 是否显示搜索框 | boolean | false |
53 | | direction | 下拉框弹出方向,目前只有 `up`/`down` 两个选项 | string | `down` |
54 |
55 | > 继承 [ms-control 组件](#!/form-control) 的所有参数
56 |
57 | TreeNode
58 |
59 | | 参数 | 说明 | 类型 | 默认值 |
60 | |-----|-----|-----|-----|
61 | | title | 标题 | string | - |
62 | | key | 节点标识 | string | - |
63 | | children | 子节点 | TreeNode\[\] | - |
64 |
65 | > 关于 TreeNode 的更多配置,请参考 [z-tree 官方文档](http://www.treejs.cn/v3/main.php)
66 |
--------------------------------------------------------------------------------
/components/ms-calendar/ms-calendar-year-view.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import * as moment from 'moment';
3 |
4 | const monthTable = [];
5 |
6 | avalon.component('ms-calendar-year-view', {
7 | template: require('./ms-calendar-year-view.html'),
8 | defaults: {
9 | table: [],
10 | // 0-月视图,1-年视图,2-十年视图,3-百年视图
11 | view: 1,
12 | currentMonth: '',
13 | currentYear: 0,
14 | isSelected(el) {
15 | return false;
16 | },
17 | onSelect: avalon.noop,
18 | handleCellClick(el) {
19 | this.onSelect(el);
20 | },
21 | onInit() {
22 | const monthList = moment.localeData().monthsShort();
23 | if (monthTable.length === 0) {
24 | [0, 3, 6, 9].forEach(n => {
25 | monthTable.push(monthList.slice(n, n + 3).map(m => ({ label: m, value: m })));
26 | });
27 | }
28 | this.$watch('view', v => {
29 | const startOfDecade = this.currentYear - this.currentYear % 10;
30 | const startOfCentury = this.currentYear - this.currentYear % 100;
31 | switch (v) {
32 | case 1:
33 | this.table = monthTable; break;
34 | case 2:
35 | this.table = [0, 3, 6, 9].map(n => avalon.range(startOfDecade - 1, startOfDecade + 11)
36 | .slice(n, n + 3)
37 | .map(m => ({ label: m, value: m }))); break;
38 | case 3:
39 | this.table = [0, 3, 6, 9].map(n => avalon.range(startOfCentury - 10, startOfCentury + 110, 10)
40 | .slice(n, n + 3)
41 | .map(m => ({ label: `${m}-${m + 9}`, value: m }))); break;
42 | }
43 | });
44 | this.$watch('currentYear', v => {
45 | this.$fire('view', this.view);
46 | });
47 | }
48 | }
49 | });
--------------------------------------------------------------------------------
/components/ms-calendar/ms-calendar.less:
--------------------------------------------------------------------------------
1 | @import '../../styles/index';
2 | @calendar-prefix-cls: ~"@{ane-prefix}-calendar";
3 |
4 | .@{calendar-prefix-cls} {
5 | table {
6 | width: 100%;
7 | max-width: 100%;
8 | }
9 |
10 | &-column-header {
11 | line-height: 18px;
12 | width: 33px;
13 | padding: 6px 0;
14 | text-align: center;
15 | }
16 | &-cell {
17 | padding: 4px 0;
18 | }
19 | &-prev-month-cell &-date, &-next-month-cell &-date {
20 | color: @calendar-disabled-date-color;
21 | }
22 | &-today &-date {
23 | border-color: @brand-primary;
24 | font-weight: 700;
25 | color: @brand-primary;
26 | }
27 | &-selected-day &-date {
28 | background: @brand-primary;
29 | color: #fff;
30 | border: 1px solid transparent;
31 |
32 | &:hover {
33 | background: @brand-primary;
34 | }
35 | }
36 | &-disabled-cell &-date {
37 | cursor: not-allowed;
38 | color: @calendar-disabled-date-color;
39 | background: @calendar-disabled-date-bg;
40 | border-radius: 0;
41 | width: auto;
42 | border: 1px solid transparent;
43 |
44 | &:hover {
45 | background: @calendar-disabled-date-bg;
46 | }
47 | }
48 | &-date {
49 | display: block;
50 | margin: 0 auto;
51 | color: @text-color;
52 | border-radius: 2px;
53 | width: 20px;
54 | height: 20px;
55 | line-height: 18px;
56 | border: 1px solid transparent;
57 | padding: 0;
58 | background: transparent;
59 | text-align: center;
60 | cursor: pointer;
61 |
62 | &:hover {
63 | background: @item-hover-bg;
64 | }
65 | }
66 |
67 | &-year-view {
68 | table-layout: fixed;
69 | text-align: center;
70 | }
71 | &-year-view &-cell {
72 | padding: 20px 0;
73 | }
74 | &-year-view &-date {
75 | width: auto;
76 | display: inline-block;
77 | padding: 0 6px;
78 | white-space: nowrap;
79 | }
80 | }
--------------------------------------------------------------------------------
/components/ms-menu/ms-menu.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ane",
3 | "version": "0.1.11",
4 | "description": "avalonjs components package",
5 | "main": "dist/ane.js",
6 | "files": [
7 | "dist/*"
8 | ],
9 | "scripts": {
10 | "i": "npm install --registry https://registry.npm.taobao.org",
11 | "start": "webpack --config webpack.test.config.js",
12 | "dev": "webpack-dev-server --config webpack.test.config.js",
13 | "doc": "webpack-dev-server --config webpack.doc.config.js",
14 | "ci:doc": "webpack --config webpack.doc.config.js --output-path=output --env.production",
15 | "test": "npm-run-all -p -r server nightwatch",
16 | "server": "http-server dist -s -p 9000",
17 | "nightwatch": "nightwatch ./tests/nightwatch.js",
18 | "pub": "webpack",
19 | "version": "npm run pub"
20 | },
21 | "peerDependencies": {
22 | "avalon2": "^2.2.7",
23 | "jquery": "^1.12.4"
24 | },
25 | "dependencies": {
26 | "async-validator": "^1.6.8",
27 | "bootbox": "^4.4.0",
28 | "dom-align": "^1.5.3",
29 | "moment": "^2.17.1",
30 | "noty": "^2.4.1",
31 | "up-loader": "0.0.8"
32 | },
33 | "devDependencies": {
34 | "ane-markdown-loader": "^2.0.15",
35 | "avalon2": "^2.2.7",
36 | "bootstrap": "^3.2.0",
37 | "css-loader": "^0.28.4",
38 | "es3ify-webpack-plugin": "0.0.1",
39 | "es5-shim": "^4.5.9",
40 | "es6-promise": "^4.1.0",
41 | "extract-text-webpack-plugin": "^2.1.0",
42 | "file-loader": "^0.11.2",
43 | "font-awesome": "^4.7.0",
44 | "highlight.js": "^9.12.0",
45 | "html-webpack-plugin": "^2.28.0",
46 | "http-server": "^0.10.0",
47 | "jquery": "^1.12.4",
48 | "less": "^2.7.2",
49 | "less-loader": "^4.0.4",
50 | "mmRouter": "^0.9.6",
51 | "node-sass": "^4.5.3",
52 | "npm-run-all": "^4.0.2",
53 | "raw-loader": "^0.5.1",
54 | "string-replace-loader": "^1.2.0",
55 | "style-loader": "^0.18.2",
56 | "ts-loader": "^2.1.0",
57 | "typescript": "^2.4.1",
58 | "webpack": "^2.6.1",
59 | "webpack-dev-server": "^2.4.5"
60 | },
61 | "keywords": [
62 | "avalonjs",
63 | "component"
64 | ],
65 | "author": "xxapp",
66 | "license": "MIT"
67 | }
68 |
--------------------------------------------------------------------------------
/components/ms-timepicker/ms-timepicker-view.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import * as moment from 'moment';
3 |
4 | const OPTION_HEIGHT = 24;
5 |
6 | avalon.component('ms-timepicker-view', {
7 | template: require('./ms-timepicker-view.html'),
8 | defaults: {
9 | value: '',
10 | currentHour: 0,
11 | currentMinute: 0,
12 | currentSecond: 0,
13 | hourOptions: avalon.range(24).map(n => ('0' + n).substr(-2)),
14 | minuteOptions: avalon.range(60).map(n => ('0' + n).substr(-2)),
15 | secondOptions: avalon.range(60).map(n => ('0' + n).substr(-2)),
16 | onChange: avalon.noop,
17 | select(el, type) {
18 | this.$element.querySelector('.ane-timepicker-view-select[name=' + type + '-options]').scrollTop = el * 24;
19 | if (type === 'hour') {
20 | this.currentHour = el;
21 | } else if (type === 'minute') {
22 | this.currentMinute = el;
23 | } else {
24 | this.currentSecond = el;
25 | }
26 | this.onChange({
27 | target: {
28 | hour: this.currentHour,
29 | minute: this.currentMinute,
30 | second: this.currentSecond,
31 | },
32 | type: 'timepicker-view-changed'
33 | });
34 | },
35 | onInit() {
36 | this.$watch('value', v => {
37 | const m = moment(v.split(','));
38 | this.currentHour = m.hour();
39 | this.currentMinute = m.minute();
40 | this.currentSecond = m.second();
41 |
42 | this.$element.querySelector('.ane-timepicker-view-select[name=hour-options]').scrollTop = this.currentHour * OPTION_HEIGHT;
43 | this.$element.querySelector('.ane-timepicker-view-select[name=minute-options]').scrollTop = this.currentMinute * OPTION_HEIGHT;
44 | this.$element.querySelector('.ane-timepicker-view-select[name=second-options]').scrollTop = this.currentSecond * OPTION_HEIGHT;
45 | });
46 | this.$fire('value', this.value);
47 | }
48 | }
49 | });
--------------------------------------------------------------------------------
/components/ms-notification/ms-notification.ts:
--------------------------------------------------------------------------------
1 | import * as noty from 'noty';
2 |
3 | type notificationArgs = {
4 | /**
5 | * 通知正文
6 | */
7 | message: string,
8 | /**
9 | * 通知标题
10 | */
11 | title?: string,
12 | /**
13 | * 没有用户操作的情况下通知保持显示的时间(毫秒),默认为 5000ms
14 | */
15 | timeout?: number
16 | };
17 |
18 | let defaultOptions = {
19 | timeout: 3000
20 | };
21 |
22 | export default {
23 | info({ message, title, timeout }: notificationArgs): void {
24 | noty({
25 | text: template(title, message, 'fa fa-info-circle'),
26 | type: 'information',
27 | timeout: timeout || defaultOptions.timeout
28 | });
29 | },
30 | success({ message, title, timeout }: notificationArgs): void {
31 | noty({
32 | text: template(title, message, 'fa fa-check-circle'),
33 | type: 'success',
34 | timeout: timeout || defaultOptions.timeout
35 | });
36 | },
37 | error({ message, title, timeout }: notificationArgs): void {
38 | noty({
39 | text: template(title, message, 'fa fa-times-circle'),
40 | type: 'error',
41 | timeout: timeout || defaultOptions.timeout
42 | });
43 | },
44 | warning({ message, title, timeout }: notificationArgs): void {
45 | noty({
46 | text: template(title, message, 'fa fa-warning'),
47 | type: 'warning',
48 | timeout: timeout || defaultOptions.timeout
49 | });
50 | },
51 | warn({ message, title, timeout }: notificationArgs): void {
52 | this.warning({ message, title, timeout });
53 | },
54 | config(options: notificationArgs): void {
55 | if (options.timeout !== undefined) {
56 | defaultOptions.timeout = options.timeout;
57 | }
58 | }
59 | };
60 |
61 | function template(title: string, message: string, icon: string): string {
62 | title = title ? `${title}
` : '';
63 | return `
64 |
65 | ${title}
66 | ${message}
67 |
`;
68 | }
--------------------------------------------------------------------------------
/components/ms-radio/ms-radio.less:
--------------------------------------------------------------------------------
1 | @import '../../styles/index';
2 | @radio-prefix-cls: ~"@{ane-prefix}-radio";
3 |
4 | .@{radio-prefix-cls} {
5 | display: inline;
6 | padding-left: 0;
7 |
8 | label {
9 | font-weight: 400;
10 | font-size: 13px;
11 | vertical-align: middle;
12 | }
13 |
14 | input[type=radio] {
15 | opacity: 0;
16 | position: absolute;
17 | left: -9999px;
18 | z-index: 12;
19 | width: 18px;
20 | height: 18px;
21 | cursor: pointer;
22 | }
23 |
24 | input[type=radio]:checked, input[type=radio]:focus {
25 | outline: 0!important;
26 | }
27 | input[type=radio]+.text {
28 | position: relative;
29 | z-index: 11;
30 | display: inline-block;
31 | margin: 0;
32 | line-height: 20px;
33 | min-height: 18px;
34 | min-width: 18px;
35 | font-weight: 400;
36 | }
37 | input[type="radio"]:checked + .text::before {
38 | display: inline-block;
39 | content: "\f111";
40 | background-color: rgb(245, 248, 252);
41 | border-color: rgb(51, 51, 51);
42 | }
43 | input[type="radio"] + .text::before {
44 | font-family: fontAwesome;
45 | font-weight: 700;
46 | font-size: 13px;
47 | color: rgb(51, 51, 51);
48 | content: " ";
49 | background-color: rgb(250, 250, 250);
50 | box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px;
51 | display: inline-block;
52 | text-align: center;
53 | vertical-align: middle;
54 | height: 18px;
55 | line-height: 16px;
56 | min-width: 18px;
57 | margin-right: 5px;
58 | margin-bottom: 2px;
59 | border-width: 1px;
60 | border-style: solid;
61 | border-color: rgb(200, 200, 200);
62 | border-image: initial;
63 | border-radius: 0px;
64 | transition: all 0.3s ease;
65 | }
66 | input[type=radio]+.text:before {
67 | border-radius: 100%;
68 | font-size: 10px;
69 | font-family: FontAwesome;
70 | line-height: 17px;
71 | height: 19px;
72 | min-width: 19px;
73 | }
74 |
75 | label.ane-radio-inner {
76 | padding-left: 0;
77 | }
78 | }
--------------------------------------------------------------------------------
/components/ms-checkbox/ms-checkbox.less:
--------------------------------------------------------------------------------
1 | @import '../../styles/index';
2 | @checkbox-prefix-cls: ~"@{ane-prefix}-checkbox";
3 |
4 | .@{checkbox-prefix-cls} {
5 | display: inline;
6 | padding-left: 0;
7 |
8 | label {
9 | font-weight: 400;
10 | font-size: 13px;
11 | vertical-align: middle;
12 | }
13 |
14 | input[type=checkbox] {
15 | opacity: 0;
16 | position: absolute;
17 | left: -9999px;
18 | z-index: 12;
19 | width: 18px;
20 | height: 18px;
21 | cursor: pointer;
22 | }
23 |
24 | input[type=checkbox]:checked, input[type=checkbox]:focus {
25 | outline: 0!important;
26 | }
27 | input[type=checkbox]+.text {
28 | position: relative;
29 | z-index: 11;
30 | display: inline-block;
31 | margin: 0;
32 | line-height: 20px;
33 | min-height: 18px;
34 | min-width: 18px;
35 | font-weight: 400;
36 | }
37 | input[type="checkbox"].@{checkbox-prefix-cls}-indeterminate + .text::before {
38 | display: inline-block;
39 | content: "\f068";
40 | background-color: rgb(245, 248, 252);
41 | border-color: rgb(51, 51, 51);
42 | }
43 | input[type="checkbox"]:checked + .text::before {
44 | display: inline-block;
45 | content: "\f00c";
46 | background-color: rgb(245, 248, 252);
47 | border-color: rgb(51, 51, 51);
48 | }
49 | input[type="checkbox"] + .text::before {
50 | font-family: fontAwesome;
51 | font-weight: 700;
52 | font-size: 13px;
53 | color: rgb(51, 51, 51);
54 | content: " ";
55 | background-color: rgb(250, 250, 250);
56 | box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px;
57 | display: inline-block;
58 | text-align: center;
59 | vertical-align: middle;
60 | height: 18px;
61 | line-height: 16px;
62 | min-width: 18px;
63 | margin-right: 5px;
64 | margin-bottom: 2px;
65 | border-width: 1px;
66 | border-style: solid;
67 | border-color: rgb(200, 200, 200);
68 | border-image: initial;
69 | border-radius: 0px;
70 | transition: all 0.3s ease;
71 | }
72 |
73 | label.ane-checkbox-inner {
74 | padding-left: 0;
75 | }
76 | }
--------------------------------------------------------------------------------
/components/ms-menu/ms-menu.less:
--------------------------------------------------------------------------------
1 | @import '../../styles/index';
2 | @menu-prefix-cls: ~"@{ane-prefix}-menu";
3 |
4 | .@{menu-prefix-cls} {
5 | min-height: 100%;
6 | list-style: none;
7 | padding: 0;
8 | margin: 0;
9 | background: #fff;
10 | border-right: 1px solid #e9e9e9;
11 | color: @text-color;
12 |
13 | a {
14 | color: @text-color;
15 | cursor: pointer;
16 | text-decoration: none;
17 | }
18 |
19 | &-item {
20 | height: 40px;
21 | line-height: 40px;
22 | cursor: pointer;
23 | position: relative;
24 |
25 | & > a > i:first-child {
26 | min-width: 14px;
27 | margin-right: 8px;
28 | }
29 | & > a > i.ane-menu-caret {
30 | display: none;
31 | }
32 | a {
33 | display: block;
34 | &:hover {
35 | color: @brand-primary;
36 | }
37 | }
38 |
39 | &-selected {
40 | background-color: @menu-item-selected-bg;
41 |
42 | a {
43 | color: @brand-primary;
44 | }
45 |
46 | &:after {
47 | content: "";
48 | border-right: 3px solid @menu-item-selected-border-color;
49 | top: 0;
50 | right: 0;
51 | bottom: 0;
52 | position: absolute;
53 | }
54 | }
55 | }
56 |
57 | &-submenu {
58 | position: relative;
59 | height: auto;
60 | line-height: 40px;
61 | cursor: pointer;
62 |
63 | & > a > i:first-child {
64 | min-width: 14px;
65 | margin-right: 8px;
66 | }
67 | & > a > i.ane-menu-caret {
68 | display: none;
69 | }
70 | & > a {
71 | display: block;
72 | &:hover {
73 | color: @brand-primary;
74 | }
75 |
76 | i.ane-menu-caret.fa {
77 | position: absolute;
78 | display: inline-block;
79 | top: 0;
80 | right: 16px;
81 | line-height: inherit;
82 | }
83 | }
84 | }
85 |
86 | & & {
87 | border-right: 0;
88 | display: none;
89 | }
90 |
91 | &-open > & {
92 | display: block;
93 | }
94 | }
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Thu May 18 2017 09:45:27 GMT+0800 (CST)
3 |
4 | module.exports = function(config) {
5 | config.set({
6 |
7 | // base path that will be used to resolve all patterns (eg. files, exclude)
8 | basePath: '',
9 |
10 |
11 | // frameworks to use
12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
13 | frameworks: ['jasmine'],
14 |
15 |
16 | // list of files / patterns to load in the browser
17 | files: [
18 | './node_modules/es5-shim/es5-shim.js',
19 | './node_modules/es6-promise/dist/es6-promise.auto.js',
20 | './node_modules/jquery/dist/jquery.js',
21 | './node_modules/bootstrap/dist/js/bootstrap.js',
22 | './node_modules/bootbox/bootbox.js',
23 | './node_modules/avalon2/dist/avalon.js',
24 | './tests/beforeit.js',
25 | './node_modules/es5-shim/es5-sham.js',
26 | './dist/ane-test.js'
27 | ],
28 |
29 |
30 | // list of files to exclude
31 | exclude: [
32 | ],
33 |
34 |
35 | // preprocess matching files before serving them to the browser
36 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
37 | preprocessors: {
38 | },
39 |
40 |
41 | // test results reporter to use
42 | // possible values: 'dots', 'progress'
43 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
44 | reporters: ['spec'],
45 |
46 |
47 | // web server port
48 | port: 9876,
49 |
50 |
51 | // enable / disable colors in the output (reporters and logs)
52 | colors: true,
53 |
54 |
55 | // level of logging
56 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
57 | logLevel: config.LOG_INFO,
58 |
59 |
60 | // enable / disable watching file and executing tests whenever any file changes
61 | autoWatch: false,
62 |
63 |
64 | // start these browsers
65 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
66 | browsers: ['Chrome'/*, 'PhantomJS', 'Firefox'*/],
67 |
68 |
69 | // Continuous Integration mode
70 | // if true, Karma captures browsers, runs the tests and exits
71 | singleRun: true,
72 |
73 | // Concurrency level
74 | // how many browser should be started simultaneous
75 | concurrency: Infinity
76 | })
77 | }
78 |
--------------------------------------------------------------------------------
/components/ms-tree/ms-tree.md:
--------------------------------------------------------------------------------
1 | ## 树组件
2 |
3 | ### 基本用法
4 |
5 | ```html
6 |
7 |
8 |
9 | ```
10 |
11 | ```js
12 | import * as avalon from 'avalon2';
13 | import 'ane';
14 |
15 | avalon.define({
16 | $id: "tree",
17 | data: [
18 | {key: 1, title: "aaa", children: [
19 | {key: 7, title: 1111, children: []},
20 | {key: 8, title: 2222, children: [
21 | {key: 14, title: 777, children: []}
22 | ]},
23 | {key: 9, title: 3333, children: [
24 | {key: 15, title: 8888, children: []},
25 | {key: 16, title: 9999, children: [
26 | {key: 17, title: '司徒正美', children: []}
27 | ]}
28 | ]}
29 | ]},
30 | {key: 2, title: "bbb", children: [
31 | {key: 10, title: 4444, children: []},
32 | {key: 11, title: 5555, children: []},
33 | {key: 12, title: 6666, children: []}
34 | ]},
35 | {key: 3, title: "ccc", children: []},
36 | {key: 4, title: "ddd", children: []},
37 | {key: 5, title: "eee", children: [
38 | {key: 13, title: 1234, children: []}
39 | ]},
40 | {key: 6, title: "fff", children: []}
41 | ],
42 | expandedKeys: [1, 8],
43 | checkedKeys: [10, 11, 12],
44 | handleCheck(checkedKeys) {
45 | console.log(checkedKeys);
46 | }
47 | })
48 | ```
49 |
50 | ### 组件参数
51 |
52 | | 参数 | 说明 | 类型 | 默认值 |
53 | |-----|-----|-----|-----|
54 | | checkable | 是否现实复选框 | boolean | false |
55 | | tree | 树数据 | TreeNode\[\] | \[\] |
56 | | expandedKeys | 展开的父节点的 key 集合 | string\[\] | \[\] |
57 | | checkedKeys | 勾选的节点的 key 集合 | string\[\] | \[\] |
58 | | onCheck | 勾选节点的回调,只有当 checkable 为 true 时有效 | function(checkedKeys, e:{checked: bool, checkedNodes, node, event}) | noop |
59 | | onSelect | 选择节点的回调 | function(selectedKeys, e:{selected: bool, selectedNodes, node, event}) | noop |
60 |
61 |
62 | TreeNode
63 |
64 | | 参数 | 说明 | 类型 | 默认值 |
65 | |-----|-----|-----|-----|
66 | | title | 标题 | string | - |
67 | | key | 节点标识 | string | - |
68 | | children | 子节点 | TreeNode\[\] | - |
69 |
70 | > 关于 TreeNode 的更多配置,请参考 [z-tree 官方文档](http://www.treejs.cn/v3/main.php)
71 |
--------------------------------------------------------------------------------
/components/ms-datepicker/test/ms-datepicker.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | datepicker 组件
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
24 |
25 |
26 |
34 |
35 |
36 |
41 |
42 |
43 |
{{@json}}
44 |
45 |
63 |
64 |
--------------------------------------------------------------------------------
/components/ms-form/ms-form-item.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import { findParentComponent } from '../../ane-util';
3 |
4 | /**
5 | * 表单项组件
6 | * @prop label 表单项标签
7 | *
8 | * @example
9 | * ``` html
10 | *
11 |
12 |
13 | * ```
14 | */
15 | avalon.component('ms-form-item', {
16 | template: require('./ms-form-item.html'),
17 | defaults: {
18 | $formVm: null,
19 | label: '',
20 | control: '',
21 | inline: false,
22 | dirty: false,
23 | reasons: [],
24 | hasRules: false,
25 | showIcon: true,
26 | className: '',
27 | inlineFormGroupStyle: { verticalAlign: 'top' },
28 | inlineMessageStyle: { marginBottom: 0 },
29 | onFieldChange(descriptor) {
30 | this.$formVm.type !== 'search' && this.$formVm.$form && this.$formVm.$form.setFieldsValue({
31 | [descriptor.name]: { value: descriptor.value, denyValidate: descriptor.denyValidate }
32 | });
33 | if (!descriptor.rules) return ;
34 | if (descriptor.showIcon === false) {
35 | this.showIcon = false;
36 | }
37 | delete descriptor.showIcon;
38 | this.hasRules = true;
39 | this.$formVm.$form && this.$formVm.$form.addFields({
40 | [descriptor.name]: { rules: descriptor.rules }
41 | });
42 | this.$formVm.$form && this.$formVm.$form.on('error' + descriptor.name, (reasons) => {
43 | this.dirty = true;
44 | this.reasons = reasons;
45 | });
46 | this.$formVm.$form && this.$formVm.$form.on('reset', fields => {
47 | if (~Object.keys(fields).indexOf(descriptor.name)) {
48 | this.dirty = false;
49 | this.reasons = [];
50 | }
51 | });
52 | },
53 | onFormChange(meta) {
54 | if (this.$formVm.$form.autoAsyncChange) {
55 | this.dirty = true;
56 | }
57 | this.$formVm.onFormChange(meta);
58 | },
59 | onInit(event) {
60 | event.target._ctype_ = 'ms-form-item';
61 | event.target._vm_ = this;
62 | this.$formVm = findParentComponent(this, 'ms-form');
63 | if (this.$formVm === null) {
64 | throw 'ms-form-item 必须放在 ms-form 内';
65 | }
66 | this.inline = this.$formVm.inline;
67 | },
68 | onReady(event) {
69 | }
70 | },
71 | soleSlot: 'control'
72 | });
--------------------------------------------------------------------------------
/components/ms-timepicker/ms-timepicker.less:
--------------------------------------------------------------------------------
1 | @import '../../styles/index';
2 |
3 | @timepicker-prefix-cls: ~"@{ane-prefix}-timepicker";
4 |
5 | .form-inline .@{timepicker-prefix-cls} {
6 | display: inline-block;
7 | width: auto;
8 | vertical-align: middle;
9 | }
10 | .@{timepicker-prefix-cls} {
11 | position: relative;
12 |
13 | &-icon {
14 | position: absolute;
15 | right: 8px;
16 | top: 50%;
17 | margin-top: -7px;
18 | }
19 | &-clear {
20 | position: absolute;
21 | right: 8px;
22 | top: 50%;
23 | margin-top: -7px;
24 | padding: 1px;
25 | cursor: pointer;
26 | display: none;
27 | background: #fff;
28 | }
29 | &:hover &-clear {
30 | display: inline-block;
31 | }
32 | & &-input {
33 | background-color: #fff;
34 | }
35 | }
36 |
37 | .@{timepicker-prefix-cls}-panel-container {
38 | background-color: #fff;
39 | box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
40 | box-sizing: border-box;
41 | user-select: none;
42 | }
43 | .@{timepicker-prefix-cls}-panel {
44 | width: 252px;
45 | }
46 |
47 | .@{timepicker-prefix-cls}-view {
48 | height: 198px;
49 |
50 | &-combobox {
51 | width: 100%;
52 | }
53 |
54 | &-select {
55 | display: inline-block;
56 | width: 33.33%;
57 | border-left: 1px solid @gray-lighter;
58 | float: left;
59 | overflow: hidden;
60 |
61 | &:first-child {
62 | border-left: none;
63 | }
64 |
65 | &:hover {
66 | overflow: auto;
67 | }
68 |
69 | ul {
70 | list-style: none;
71 | margin: 0;
72 | padding: 0;
73 | width: 100%;
74 | max-height: 200px;
75 |
76 | &:after {
77 | content: "";
78 | display: block;
79 | height: 176px;
80 | }
81 | }
82 | li {
83 | text-align: center;
84 | list-style: none;
85 | box-sizing: content-box;
86 | margin: 0;
87 | width: 100%;
88 | height: 24px;
89 | line-height: 24px;
90 | cursor: pointer;
91 |
92 | &:hover {
93 | background: @item-hover-bg;
94 | }
95 | }
96 |
97 | & &-option-selected {
98 | background: @brand-primary;
99 | font-weight: 700;
100 | color: #fff;
101 |
102 | &:hover {
103 | background: @brand-primary;
104 | }
105 | }
106 | }
107 | }
--------------------------------------------------------------------------------
/components/ms-tree-select/ms-tree-select-panel.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 |
3 | export default function (cmpVm) {
4 | if (avalon.vmodels[cmpVm.panelVmId] !== undefined) {
5 | return avalon.vmodels[cmpVm.panelVmId];
6 | }
7 |
8 | const vm = avalon.define({
9 | $id: cmpVm.panelVmId,
10 | checkedKeys: [],
11 | selection: [],
12 | loading: false,
13 | multiple: cmpVm.multiple,
14 | treeData: cmpVm.treeData.toJSON(),
15 | searchValue: '',
16 | getFilteredOptions() {
17 | return this.options.filter(this.filterFn);
18 | },
19 | filterFn(el) {
20 | if (this.loading) {
21 | return false;
22 | }
23 | if (cmpVm.remote) {
24 | return true;
25 | }
26 | const reg = new RegExp(avalon.escapeRegExp(this.searchValue), 'i');
27 | return reg.test(el.label) || reg.test(el.value);
28 | },
29 | handleSelect(selectedKeys, e) {
30 | if (this.multiple || e.node.disabled) {
31 | return false;
32 | }
33 |
34 | this.selection = [{
35 | key: e.node.key,
36 | title: e.node.title
37 | }];
38 | cmpVm.panelVisible = false;
39 |
40 | const selection = this.selection.toJSON();
41 | const value = selection.map(s => s.key);
42 | cmpVm.handleChange({
43 | target: { value: value[0] || '', selection: e.node },
44 | type: 'tree-select'
45 | });
46 | cmpVm.displayValue = e.node.title;
47 | cmpVm.selection = selection;
48 | },
49 | handleCheck(checkedKeys, e) {
50 | if (!this.multiple || e.node.disabled) {
51 | return false;
52 | }
53 |
54 | this.selection = e.checkedNodes.map(n => ({ key: n.key, title: n.title }));
55 | // if (e.checked) {
56 | // this.selection.push({
57 | // key: e.node.key,
58 | // title: e.node.title
59 | // });
60 | // } else {
61 | // this.selection.removeAll(o => o.key === e.node.key);
62 | // }
63 | cmpVm.focusSearch();
64 |
65 | const selection = this.selection.toJSON();
66 | const value = selection.map(s => s.key);
67 | cmpVm.handleChange({
68 | target: { value: value, selection: e.checkedNodes },
69 | type: 'tree-select'
70 | });
71 | cmpVm.displayValue = e.node.title;
72 | cmpVm.selection = selection;
73 | }
74 | });
75 |
76 | return vm;
77 | }
--------------------------------------------------------------------------------
/components/ms-upload/test/ms-upload.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
24 | 选择文件
25 |
26 |
27 |
28 |
{{@json}}
29 |
30 |
31 |
32 |
33 |
40 | 选择文件
41 |
42 |
43 |
44 |
{{@json}}
45 |
46 |
47 |
66 |
67 |
--------------------------------------------------------------------------------
/components/ms-timepicker/ms-timepicker.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import controlComponent from '../ms-form/ms-control';
3 | import '../ms-trigger';
4 | import './ms-timepicker-view'
5 | import getPanelVm from './ms-timepicker-panel'
6 | import { emitToFormItem } from '../ms-form/utils';
7 |
8 | /**
9 | * 时间选择组件
10 | * @prop value 组件值(inherit)
11 | * @prop col 字段路径(inherit)
12 | * @prop format 日期格式,参考 momentjs,默认为 HH:mm:ss
13 | *
14 | * @example
15 | * ``` html
16 | *
17 | * ```
18 | */
19 | controlComponent.extend({
20 | displayName: 'ms-timepicker',
21 | template: require('./ms-timepicker.html'),
22 | defaults: {
23 | selected: '',
24 | format: 'HH:mm:ss',
25 | clear() {
26 | this.selected = '';
27 | avalon.vmodels[this.panelVmId].reset();
28 | this.handleChange({
29 | target: { value: '' },
30 | type: 'timepicker-changed'
31 | });
32 | },
33 | withInBox(el) {
34 | return this.$element === el || avalon.contains(this.$element, el);
35 | },
36 | getTarget() {
37 | return this.$element;
38 | },
39 | handleClick(e) {
40 | if (!this.panelVisible) {
41 | avalon.vmodels[this.panelVmId].reset();
42 | this.panelVisible = true;
43 | } else {
44 | this.panelVisible = false;
45 | }
46 | },
47 |
48 | direction: 'down',
49 | panelVmId: '',
50 | panelVisible: false,
51 | panelClass: 'ane-timepicker-panel-container',
52 | panelTemplate: `
53 |
54 | `,
55 | handlePanelHide() {
56 | this.panelVisible = false;
57 | },
58 |
59 | mapValueToSelected(value) {
60 | this.selected = value;
61 | },
62 | onInit: function (event) {
63 | const self = this;
64 | emitToFormItem(this, {
65 | showIcon: false
66 | });
67 | this.$watch('value', v => {
68 | this.mapValueToSelected(v);
69 | this.handleChange({
70 | target: { value: v },
71 | denyValidate: true,
72 | type: 'timepicker-changed'
73 | });
74 | });
75 | this.panelVmId = this.$id + '_panel';
76 | const innerVm = getPanelVm(this);
77 | this.mapValueToSelected(this.value);
78 | innerVm.reset();
79 | },
80 | onDispose() {
81 | delete avalon.vmodels[this.panelVmId];
82 | }
83 | }
84 | });
--------------------------------------------------------------------------------
/components/ms-upload/ms-upload.md:
--------------------------------------------------------------------------------
1 | ## 文件上传
2 |
3 | ### 基本用法
4 |
5 | ```html
6 |
7 |
8 | 选择文件
9 |
10 |
11 | ```
12 |
13 | ```js
14 | import * as avalon from 'avalon2';
15 | import 'ane';
16 |
17 | avalon.define({
18 | $id: 'doc-upload-basic',
19 | fileUploadUrl: '/api/file/uploadFile',
20 | handleChange(e) {
21 | console.log(e.target.value);
22 | }
23 | });
24 | ```
25 |
26 | ### 照片墙模式
27 |
28 | ```html
29 |
30 |
31 | 选择图片
32 |
33 |
34 | ```
35 |
36 | ```js
37 | import * as avalon from 'avalon2';
38 | import { message } from 'ane';
39 |
40 | avalon.define({
41 | $id: 'doc-upload-card',
42 | fileUploadUrl: '/api/file/uploadFile',
43 | handleBeforeUpload(file) {
44 | if (file.type !== 'image/jpeg' && file.type !== 'image/png') {
45 | message.error({
46 | content: '只能选择jpg或者png类型的图片!'
47 | });
48 | return false;
49 | }
50 | if (file.size / 1024 / 1024 > 1) {
51 | message.error({
52 | content: '选择的图片必须小于1MB!'
53 | });
54 | return false;
55 | }
56 | return true;
57 | },
58 | handleChange(e) {
59 | console.log(e.target.value);
60 | }
61 | });
62 | ```
63 |
64 | ### 上传头像
65 |
66 | ```html
67 |
68 |
69 | 选择图片
70 |
71 |
72 | ```
73 |
74 | ```js
75 | import * as avalon from 'avalon2';
76 | import 'ane';
77 |
78 | avalon.define({
79 | $id: 'doc-upload-avatar',
80 | avatar: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
81 | fileUploadUrl: '/api/file/uploadFile',
82 | handleChange(e) {
83 | console.log(e.target.value);
84 | }
85 | });
86 | ```
87 |
88 | ### 组件参数
89 |
90 | | 参数 | 说明 | 类型 | 默认值 |
91 | |-----|-----|-----|-----|
92 | | value | 文件 url 数组 | string\[\] | \[\] |
93 | | action | 必选,文件上传地址 | string | - |
94 | | listType | 文件列表展示方式 | 'text-list' \| 'picture-card' | 'text-list' |
95 | | showUploadList | 是否显示文件列表,在照片墙模式下,如果此项为 false,则为单文件模式 | boolean | true |
96 | | btnClass | 上传按钮 class,只在非照片墙模式下有效 | string | 'btn btn-default' |
97 | | beforeUpload | 在文件开始上传前触发的回调,如果返回 false,则不上传文件 | function(file:File) | `() => true` |
98 |
99 | > 继承 [ms-control 组件](#!/form-control) 的所有参数
100 |
101 | > beforeUpload 参数在 IE8 下不能获取文件大小,类型只能根据文件后缀名判断。只能和服务器端配合做到准确判断
--------------------------------------------------------------------------------
/components/ms-trigger/ms-trigger.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import * as domAlign from 'dom-align';
3 |
4 | avalon.component('ms-trigger', {
5 | template: '',
6 | defaults: {
7 | width: 0,
8 | visible: false,
9 | direction: 'down',
10 | innerVmId: '',
11 | innerClass: '',
12 | innerTemplate: '',
13 | initialized: false,
14 | withInBox() { return true; },
15 | getTarget: avalon.noop,
16 | onHide: avalon.noop,
17 | hide(panel) {
18 | panel.style.top = '-9999px';
19 | panel.style.left = '-9999px';
20 | this.onHide();
21 | },
22 | initPanel(panel: HTMLDivElement) {
23 | const DOC = document, body = DOC.body;
24 | const medium = DOC.createElement('div');
25 | medium.setAttribute('id', this.$id);
26 | medium.setAttribute('style', 'position: absolute; top: 0px; left: 0px; width: 100%;');
27 | panel.setAttribute('class', this.innerClass);
28 | panel.setAttribute('style', 'z-index: 1050;left: -9999px;top: -9999px;position: absolute;outline: none;overflow: hidden;');
29 | panel.setAttribute(':important', this.innerVmId);
30 | panel.innerHTML = this.innerTemplate.replace(/\r|\n/g, '');
31 | medium.appendChild(panel);
32 | body.appendChild(medium);
33 |
34 | avalon.scan(panel, avalon.vmodels[this.innerVmId]);
35 |
36 | avalon.bind(DOC, 'click', e => {
37 | if (this.visible && panel !== e.target && !avalon.contains(panel, e.target) && !this.withInBox(e.target)) {
38 | this.hide(panel);
39 | }
40 | });
41 | },
42 | onInit(event) {
43 | const DOC = document;
44 | const panel = DOC.createElement('div');
45 | this.$watch('visible', v => {
46 | if (v) {
47 | if (!this.initialized) {
48 | this.initPanel(panel);
49 | this.initialized = true;
50 | }
51 | panel.style.width = this.width === 0 ? 'auto' : (this.width + 'px');
52 | panel.scrollTop = 0;
53 | const points = ['tl', 'bl'];
54 | domAlign(panel, this.getTarget(), {
55 | points: this.direction === 'up' ? points.reverse() : points,
56 | offset: [0, 1],
57 | //targetOffset: ['0%','100%']
58 | overflow: {
59 | adjustY: true
60 | }
61 | })
62 | } else {
63 | this.hide(panel);
64 | }
65 | });
66 | },
67 | onDispose(event) {
68 | if (!this.initialized) {
69 | return;
70 | }
71 | const DOC = document, body = DOC.body;
72 | const medium = DOC.getElementById(this.$id);
73 | body.removeChild(medium);
74 | }
75 | }
76 | });
--------------------------------------------------------------------------------
/components/ms-dialog/ms-dialog.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import * as bootbox from 'bootbox';
3 | import { parseSlotToVModel } from '../../ane-util';
4 | import * as $ from 'jquery';
5 |
6 | avalon.component('ms-dialog', {
7 | template: '
',
8 | defaults: {
9 | body: 'blank',
10 | footer: '',
11 | $dialog: null,
12 | show: false,
13 | className: '',
14 | size: '',
15 | uploading: false,
16 | $innerVm: '',
17 | okText: '',
18 | cancelText: '',
19 | onOk() {},
20 | onCancel() {},
21 | onInit(event) {
22 | var vm = event.vmodel;
23 | vm.$watch('show', (newV) => {
24 | if (newV) {
25 | vm.$dialog = bootbox.dialog({
26 | message: vm.body,
27 | title: '{{title}}',
28 | className: vm.className,
29 | size: vm.size,
30 | buttons: this.footer.length ? null : {
31 | save: {
32 | label: vm.okText || '保存',
33 | className: 'btn-primary',
34 | callback() {
35 | vm.onOk();
36 | return false;
37 | }
38 | },
39 | cancel: {
40 | label: vm.cancelText || '取消',
41 | className: 'btn-default',
42 | callback() {
43 | }
44 | }
45 | }
46 | }).on('hidden.bs.modal', (e) => {
47 | vm.onCancel();
48 | setTimeout(() => {
49 | if ($('.modal.in').length) {
50 | $('body').addClass('modal-open');
51 | } else {
52 | $('body').removeClass('modal-open');
53 | }
54 | }, 100);
55 | })
56 | .on('shown.bs.modal', () => {
57 |
58 | });
59 | const $content = vm.$dialog.find('.modal-content').attr(':controller', this.$innerVm);
60 | if (this.footer.length) {
61 | $content.append($(this.footer));
62 | }
63 | avalon.scan(vm.$dialog.get(0));
64 | } else {
65 | if (vm.$dialog) {
66 | vm.$dialog.find('.bootbox-close-button').trigger('click');
67 | }
68 | }
69 | });
70 | },
71 | onReady(event) {
72 | parseSlotToVModel(this);
73 | this.show && this.$fire('show', true);
74 | },
75 | onDispose(event) {
76 | }
77 | }
78 | });
--------------------------------------------------------------------------------
/typings/avalon.d.ts:
--------------------------------------------------------------------------------
1 | interface avalonComponentRecycleFn {
2 | (event: {
3 | target: Element,
4 | type: string,
5 | vmodel
6 | });
7 | }
8 |
9 | interface avalonComponent {
10 | (name: string, component: {
11 | template: string,
12 | defaults: {
13 | onInit: avalonComponentRecycleFn,
14 | onReady: avalonComponentRecycleFn,
15 | onViewChange: avalonComponentRecycleFn,
16 | onDispose: avalonComponentRecycleFn
17 | }
18 | }): any
19 | }
20 |
21 | interface avalonInstance {
22 | /**
23 | * 用于获取或修改样式,自动修正厂商前缀及加px,与jQuery的css方法一样智能
24 | */
25 | css(name: string, value: string | number): number | avalonInstance,
26 | /**
27 | * 取得目标的高,不带单位,如果目标为window,则取得窗口的高,为document取得页面的高
28 | */
29 | height(value?: string | number) : number,
30 | /**
31 | * 取得元素的位置, 如 {top:111, left: 222}
32 | */
33 | offset(): { top: number, left: number }
34 | }
35 |
36 | interface AvalonStatic {
37 | /**
38 | * 定义ViewModel,需要指定$id
39 | */
40 | define(definition): any;
41 | /**
42 | * 定义avalon组件
43 | */
44 | component(name: string, component): any;
45 | /**
46 | * 扫描元素,与ViewModel绑定
47 | */
48 | scan(node: Element|string, vm?, beforeReady?: () => void): any;
49 | /**
50 | * 定义指令
51 | */
52 | directive(name: string, options): any;
53 | /**
54 | * 指令集合
55 | */
56 | directives: any[];
57 | /**
58 | * avalon动画
59 | */
60 | effect(name: string, opts?: any): any;
61 | /**
62 | * 判断一个元素是否包含另一个元素
63 | */
64 | contains(root: Element, el: Element): boolean
65 | /**
66 | * 给元素绑定事件
67 | */
68 | bind(elem: Node, type: string, fn: (e) => boolean | void)
69 | /**
70 | * 移除一个元素的事件
71 | */
72 | unbind(elem: Node, type?: string, fn?: (e) => boolean | void)
73 | root: HTMLElement;
74 | /**
75 | * ViewModel 列表
76 | */
77 | vmodels: any;
78 | /**
79 | * 过滤器列表
80 | */
81 | filters: any
82 | /**
83 | * 用于合并多个对象或深克隆,类似于jQuery.extend
84 | */
85 | mix(target: any, object1?: any, ...objectN: any[]): any;
86 | mix(deep: boolean, target: any, object1?: any, ...objectN: any[]): any,
87 | /**
88 | * no operation
89 | */
90 | noop(): void,
91 | /**
92 | * virtual dom
93 | */
94 | vdom(vnode: any, method: string): void
95 | /**
96 | * 生成指定长度数组
97 | */
98 | range(start: number, end?: number, step?: number): number[]
99 | /**
100 | * IE版本
101 | */
102 | msie: number,
103 | /**
104 | * 打印日志
105 | */
106 | log(message?: any, ...optionalParams: any[]): void;
107 | /**
108 | * 打印错误信息
109 | */
110 | error(message?: any, ...optionalParams: any[]): void;
111 | /**
112 | * 注册文档加载完成事件
113 | */
114 | ready(fn): void;
115 | /**
116 | * 将字符串安全格式化为正则表达式的源码
117 | */
118 | escapeRegExp(target): string;
119 | /**
120 | * 构造avalon实例
121 | */
122 | (el: Node): avalonInstance
123 | }
124 |
125 | declare module 'avalon2' {
126 | export = avalon
127 | }
128 |
129 | declare var avalon: AvalonStatic
--------------------------------------------------------------------------------
/components/ms-datepicker/ms-datepicker.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import controlComponent from '../ms-form/ms-control';
3 | import '../ms-trigger';
4 | import '../ms-calendar';
5 | import '../ms-timepicker/ms-timepicker-view'
6 | import getPanelVm from './ms-datepicker-panel';
7 | import { emitToFormItem } from '../ms-form/utils';
8 |
9 | /**
10 | * 日期选择组件
11 | * @prop value 组件值(inherit)
12 | * @prop col 字段路径(inherit)
13 | * @prop format 日期格式,参考 momentjs,默认为 YYYY-MM-DD
14 | * @prop startDate 控制可已选择的时间的开始日期,日期字符串,格式与 format 参数匹配,设置此项自动忽略 disabledDate
15 | * @prop endDate 控制可已选择的时间的结束日期,日期字符串,格式与 format 参数匹配,设置此项自动忽略 disabledDate
16 | * @prop disabledDate 不可选择日期的判断函数,传入 current(当前遍历日期),返回 true 表示此日期不可选
17 | * @prop showTime 是否显示时间选择,如果此项为 true,则 format 默认为 YYYY-MM-DD HH:mm:ss
18 | *
19 | * @example
20 | * ``` html
21 | *
22 | * ```
23 | */
24 | controlComponent.extend({
25 | displayName: 'ms-datepicker',
26 | template: require('./ms-datepicker.html'),
27 | defaults: {
28 | selected: '',
29 | format: 'YYYY-MM-DD',
30 | startDate: '',
31 | endDate: '',
32 | disabledDate() { return false; },
33 | showTime: false,
34 | clear() {
35 | this.selected = '';
36 | avalon.vmodels[this.panelVmId].reset();
37 | this.handleChange({
38 | target: { value: '' },
39 | type: 'datepicker-changed'
40 | });
41 | },
42 | withInBox(el) {
43 | return this.$element === el || avalon.contains(this.$element, el);
44 | },
45 | getTarget() {
46 | return this.$element;
47 | },
48 | handleClick(e) {
49 | if (!this.panelVisible) {
50 | avalon.vmodels[this.panelVmId].reset();
51 | this.panelVisible = true;
52 | } else {
53 | this.panelVisible = false;
54 | }
55 | },
56 |
57 | direction: 'up',
58 | panelVmId: '',
59 | panelVisible: false,
60 | panelClass: 'ane-datepicker-panel-container',
61 | panelTemplate: require('./ms-datepicker-panel.html'),
62 | handlePanelHide() {
63 | this.panelVisible = false;
64 | },
65 |
66 | mapValueToSelected(value) {
67 | this.selected = value;
68 | },
69 | onInit: function (event) {
70 | const self = this;
71 | emitToFormItem(this, {
72 | showIcon: false
73 | });
74 | this.$watch('value', v => {
75 | this.mapValueToSelected(v);
76 | this.handleChange({
77 | target: { value: v },
78 | denyValidate: true,
79 | type: 'datepicker-changed'
80 | });
81 | });
82 | if (this.showTime && this.format === 'YYYY-MM-DD') {
83 | // 允许选择时间的模式下,用户如果没自定义格式,则自动转为日期时间格式
84 | this.format = 'YYYY-MM-DD HH:mm:ss';
85 | }
86 | this.panelVmId = this.$id + '_panel';
87 | const innerVm = getPanelVm(this);
88 | this.mapValueToSelected(this.value);
89 | innerVm.reset();
90 | },
91 | onDispose() {
92 | delete avalon.vmodels[this.panelVmId];
93 | }
94 | }
95 | });
--------------------------------------------------------------------------------
/components/ms-layout/ms-layout.less:
--------------------------------------------------------------------------------
1 | @import '../../styles/index';
2 | @layout-prefix-cls: ~"@{ane-prefix}-layout";
3 |
4 | @layout-sider-bg: #fff;
5 | @layout-sider-width: 224px;
6 | @layout-header-bg: aquamarine;
7 | @layout-header-height: 60px;
8 | @layout-footer-bg: aquamarine;
9 | @layout-footer-height: 30px;
10 | @layout-content-bg: #ececec;
11 |
12 | xmp {
13 | display: none;
14 | }
15 | body {
16 | background-color: @layout-sider-bg;
17 |
18 | &:before {
19 | content: "";
20 | display: block;
21 | position: fixed;
22 | top: 0;
23 | bottom: 0;
24 | left: @layout-sider-width;
25 | right: 0;
26 | z-index: -1;
27 | background-color: @layout-content-bg;
28 | }
29 | }
30 |
31 | .@{layout-prefix-cls} {
32 | height: 100%;
33 | background: @layout-content-bg;
34 | position: relative;
35 |
36 | &-sider {
37 | float: left;
38 | width: @layout-sider-width;
39 | height: 100%;
40 | top: 0;
41 | bottom: 0;
42 | background: @layout-sider-bg;
43 |
44 | &:after {
45 | content: "";
46 | display: block;
47 | height: 60px;
48 | }
49 | }
50 | &-fixed-sider {
51 | height: auto;
52 | position: fixed;
53 | border-bottom: solid @layout-footer-height @layout-sider-bg;
54 | }
55 | &-fixed-sider &-sider-inner {
56 | height: 100%;
57 | overflow: auto;
58 | border-bottom: solid 0 @layout-sider-bg;
59 | margin-top: @layout-header-height;
60 | }
61 |
62 | &-header {
63 | height: @layout-header-height;
64 | background: @layout-header-bg;
65 | }
66 | &-fixed-header {
67 | position: fixed;
68 | width: 100%;
69 | z-index: 200;
70 | }
71 | &-fixed-header ~ & > &-fixed-sider,
72 | &-fixed-header ~ &-fixed-sider {
73 | border-bottom-width: 0;
74 | }
75 | &-fixed-header ~ & > &-sider,
76 | &-fixed-header ~ &-sider {
77 | margin-top: @layout-header-height;
78 | }
79 | &-fixed-header ~ & > &-sider > &-sider-inner,
80 | &-fixed-header ~ &-sider > &-sider-inner {
81 | margin-top: 0;
82 | }
83 | &-fixed-header ~ & > &-content,
84 | &-fixed-header ~ &-content {
85 | padding-top: @layout-header-height;
86 | }
87 |
88 | &-content {
89 | margin-left: @layout-sider-width;
90 | background-color: inherit;
91 |
92 | &:before {
93 | content: "";
94 | display: block;
95 | position: fixed;
96 | top: 0;
97 | bottom: 0;
98 | left: @layout-sider-width;
99 | right: 0;
100 | z-index: -1;
101 | }
102 |
103 | &-wrapper {
104 | margin: 0 16px;
105 | background: #fff;
106 | margin-bottom: 16px;
107 | padding: 24px;
108 | }
109 | }
110 |
111 | &-footer {
112 | width: 100%;
113 | height: @layout-footer-height;
114 | background: @layout-footer-bg;
115 | bottom: 0;
116 | }
117 | &-fixed-footer {
118 | position: fixed;
119 | z-index: 200;
120 | }
121 | &-fixed-footer ~ & > &-sider > &-sider-inner,
122 | &-fixed-footer ~ &-sider > &-sider-inner {
123 | border-bottom-width: @layout-footer-height;
124 | }
125 | &-fixed-footer ~ & > &-content,
126 | &-fixed-footer ~ &-content {
127 | padding-bottom: @layout-footer-height;
128 | }
129 | }
--------------------------------------------------------------------------------
/components/ms-tree/ms-tree.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import '../ms-checkbox';
3 | import './metroStyle.css';
4 | import * as $ from 'jquery';
5 | import './jquery.ztree.core';
6 | import './jquery.ztree.excheck';
7 |
8 | avalon.component('ms-tree', {
9 | template: require('./ms-tree.html'),
10 | defaults: {
11 | checkable: false,
12 | tree: [],
13 | expandedKeys: [],
14 | checkedKeys: [],
15 | selectedKeys: [],
16 | onCheck: avalon.noop,
17 | onSelect: avalon.noop,
18 | handleCheck(e, treeId, node) {
19 | const treeObj = $.fn.zTree.getZTreeObj(treeId);
20 | const checkedNodes = treeObj.getNodesByFilter(n => {
21 | const parentNode = n.getParentNode();
22 | const checkStatus = n.getCheckStatus();
23 | const parentCheckStatus = parentNode ? parentNode.getCheckStatus() : { checked: false, half: false };
24 | return (checkStatus.checked && !checkStatus.half) && (!parentCheckStatus.checked || parentCheckStatus.half);
25 | });
26 | const checkedKeys = checkedNodes.map(n => n.key);
27 |
28 | //this.checkedKeys = checkedKeys
29 | this.onCheck(checkedKeys, {
30 | checked: node.checked,
31 | checkedNodes: checkedNodes,
32 | node: node,
33 | event: e
34 | });
35 | },
36 | handleSelect(e, treeId, node, clickFlag) {
37 | this.selectedKeys = [node.key];
38 | this.onSelect(this.selectedKeys.toJSON(), {
39 | selected: clickFlag,
40 | selectedNodes: [{
41 | key: node.key, title: node.title
42 | }],
43 | node: node,
44 | event: e
45 | });
46 | },
47 | onInit(event) {
48 | var initTree = (el, tree) => {
49 | return $.fn.zTree.init($(el), {
50 | check: { enable: this.checkable },
51 | data: {
52 | key: {
53 | name: 'title'
54 | }
55 | },
56 | callback: {
57 | onCheck: (e, treeId, node) => {
58 | this.handleCheck(e, treeId, node);
59 | },
60 | onClick: (e, treeId, node, clickFlag) => {
61 | this.handleSelect(e, treeId, node, clickFlag);
62 | }
63 | }
64 | }, tree);
65 | };
66 | var treeObj = initTree(event.target, this.tree.toJSON());
67 |
68 | this.$watch('checkedKeys', v => {
69 | if (this.checkable) {
70 | treeObj.checkAllNodes(false);
71 | treeObj.getNodesByFilter(n => v.contains(n.key)).forEach(n => {
72 | treeObj.checkNode(n, true, true);
73 | });
74 | } else {
75 | treeObj.getNodesByFilter(n => v.contains(n.key)).forEach(n => {
76 | treeObj.selectNode(n);
77 | });
78 | }
79 | });
80 |
81 | this.$watch('expandedKeys', v => {
82 | treeObj.expandAll(false);
83 | treeObj.getNodesByFilter(n => v.contains(n.key)).forEach(n => {
84 | treeObj.expandNode(n, true);
85 | });
86 | });
87 |
88 | this.$watch('tree', v => {
89 | treeObj = initTree(event.target, v.toJSON());
90 | });
91 |
92 | this.$fire('checkedKeys', this.checkedKeys);
93 | this.$fire('expandedKeys', this.expandedKeys);
94 | }
95 | }
96 | });
--------------------------------------------------------------------------------
/components/ms-form/ms-form.md:
--------------------------------------------------------------------------------
1 | ## 表单组件
2 |
3 | ### 带验证功能的表单
4 |
5 | ```html
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
{{@json}}
17 |
18 | ```
19 |
20 | ```js
21 | import * as avalon from 'avalon2';
22 | import { createForm, message } from 'ane';
23 |
24 | const vm = avalon.define({
25 | $id: 'doc-form-validate',
26 | json: '',
27 | $form: createForm({
28 | onFieldsChange(fields, record) {
29 | vm.json = JSON.stringify(record);
30 | }
31 | }),
32 | save() {
33 | vm.$form.validateFields().then(isAllValid => {
34 | if (isAllValid) {
35 | message.success({
36 | content: '保存成功'
37 | });
38 | }
39 | })
40 | /*
41 | // 验证某个字段
42 | vm.$form.validateField('title').then(result => {
43 | if (!result.isOk) {
44 | message.success({
45 | content: result.message
46 | });
47 | }
48 | })
49 | */
50 | }
51 | });
52 | ```
53 |
54 | ### 用于搜索的表单
55 |
56 | ```html
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
{{@json}}
68 |
69 | ```
70 |
71 | ```js
72 | import * as avalon from 'avalon2';
73 | import { createForm, message } from 'ane';
74 |
75 | const vm1 = avalon.define({
76 | $id: 'doc-form-search',
77 | json: '',
78 | $form: createForm({
79 | onFieldsChange(fields, record) {
80 | vm1.json = JSON.stringify(record);
81 | }
82 | }),
83 | save() {
84 | vm1.$form.validateFields().then(isAllValid => {
85 | if (isAllValid) {
86 | message.success({
87 | content: JSON.stringify(vm1.$form.record)
88 | });
89 | }
90 | })
91 | }
92 | });
93 | ```
94 |
95 | ### 组件参数
96 |
97 | | 参数 | 说明 | 类型 | 默认值 |
98 | |-----|-----|-----|-----|
99 | | $form | 表单数据集散中心,详见下文 | createForm() | null |
100 | | type | 如果为 search,则只在表单项的值被用户手动修改时,才会加入到最后要提交的数据对象上,用于搜索表单 | string | '' |
101 | | horizontal | 是否添加 form-horizontal 到 class | boolean | false |
102 | | inline | 是否添加 form-inline 到 class | boolean | false |
103 |
104 | #### createFrom(options)
105 |
106 | 由于 avalon2 自带的表单验证只能配合 ms-duplex 使用,表单中每个组件都写 onChnage 配置又很繁琐,并且为了方便的收集和分发表单数据,所以有了这个 `“表单数据集散中心”`。
107 |
108 | options 配置:
109 |
110 | | 参数 | 说明 | 类型 | 默认值 |
111 | |-----|-----|-----|-----|
112 | | record | 表单数据 | any | `{}` |
113 | | autoAsyncChange | 是否在表单项改变时同步数据到 record | boolean | true |
114 | | onFieldsChange | 表单项改变的回调 | function(fields, record) | noop |
115 |
116 | $form 对象可访问的属性如下:
117 |
118 | | 参数 | 说明 | 类型 |
119 | |-----|-----|-----|
120 | | fields | 所有的字段集合 | { \[string\]: meta } |
121 | | setFieldsValue | 设置字段值的方法 | (fields) => void |
122 | | addFields | 添加字段 | (fields) => void |
123 | | validateField | 验证某个字段 | (fieldName) => Promise<{isOk: boolean, name: string, message: string}> |
124 | | validateFields | 验证多个或者所有字段 | (field?) => Promise<boolean> |
125 | | resetFields | 重置多个或者所有字段 | (field?) => void |
--------------------------------------------------------------------------------
/components/ms-select/ms-select.md:
--------------------------------------------------------------------------------
1 | ## 选择组件
2 |
3 | ### 基本用法
4 |
5 | ``` html
6 |
7 |
8 |
9 |
10 | 海贼王
11 | 名侦探柯南
12 | 进击的巨人
13 | 禁用
14 | 一拳超人
15 |
16 |
17 |
18 |
{{@json}}
19 |
20 | ```
21 |
22 | ``` js
23 | import * as avalon from 'avalon2';
24 | import { createForm } from 'ane';
25 |
26 | const vm = avalon.define({
27 | $id: 'doc-select-basic',
28 | json: '',
29 | $form: createForm({
30 | onFieldsChange(fields, record) {
31 | vm.json = JSON.stringify(record);
32 | }
33 | })
34 | });
35 | ```
36 |
37 | ### 多选
38 |
39 | ``` html
40 |
41 |
42 | 海贼王
43 | 名侦探柯南
44 | 进击的巨人
45 | 禁用
46 | 一拳超人
47 |
48 |
49 | ```
50 |
51 | ``` js
52 | import * as avalon from 'avalon2';
53 | import { createForm } from 'ane';
54 |
55 | avalon.define({
56 | $id: 'doc-select-multiple'
57 | });
58 | ```
59 |
60 | ### 带搜索框
61 |
62 | ``` html
63 |
64 |
65 | 海贼王
66 | 名侦探柯南
67 | 进击的巨人
68 | 禁用
69 | 一拳超人
70 |
71 |
72 | ```
73 |
74 | ``` js
75 | import * as avalon from 'avalon2';
76 | import { createForm } from 'ane';
77 |
78 | avalon.define({
79 | $id: 'doc-select-multiple'
80 | });
81 | ```
82 |
83 | ### 远程加载数据
84 |
85 | ``` html
86 |
87 |
88 |
89 | ```
90 |
91 | ``` js
92 | import * as avalon from 'avalon2';
93 | import { createForm } from 'ane';
94 | import * as $ from 'jquery';
95 |
96 | avalon.define({
97 | $id: 'doc-select-remote',
98 | fetchOptions(query) {
99 | return $.getJSON('https://randomuser.me/api/?results=5').then(json => {
100 | return json.results.map(user => ({
101 | label: user.name.first + user.name.last,
102 | value: user.login.username
103 | }));
104 | });
105 | }
106 | });
107 | ```
108 |
109 | ### 组件参数
110 |
111 | | 参数 | 说明 | 类型 | 默认值 |
112 | |-----|-----|-----|-----|
113 | | value | 默认值 | string\[\] | \[\] |
114 | | mode | 模式 | 'combobox' \| 'multiple' \| 'tags' | '' |
115 | | options | 下拉选项,可以替代ms-select-option | {label:string,value:string,disabled:boolean}\[\] | \[\] |
116 | | showSearch | 是否显示搜索框 | boolean | false |
117 | | remote | 是否为远程搜索 | boolean | false |
118 | | remoteMethod | remoteMethod 当remote为true时调用,包含远程搜索要执行的请求,要求返回一个Promise<options> | function(query) | noop |
119 | | direction | 下拉框弹出方向,目前只有 `up`/`down` 两个选项 | string | `down` |
120 | | onChange | 组件值改变回调 | function(e:{target:{value:string\[\]},type:string}) | noop |
121 |
122 | > 继承 [ms-control 组件](#!/form-control) 的所有参数
--------------------------------------------------------------------------------
/webpack.test.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
4 | var HtmlWebpackPlugin = require('html-webpack-plugin');
5 |
6 | var extractLess = new ExtractTextPlugin({
7 | filename: "bundle[chunkHash].css",
8 | disable: false,
9 | allChunks: true
10 | });
11 | var extractCss = new ExtractTextPlugin({
12 | filename: "vendor[chunkHash].css",
13 | disable: false
14 | });
15 |
16 | module.exports = {
17 | entry: {
18 | app: './tests/index.js'
19 | },
20 | output: {
21 | path: path.resolve(__dirname, 'dist'),
22 | filename: '[name][chunkHash].js',
23 | library: 'index',
24 | libraryTarget: 'umd'
25 | },
26 | module: {
27 | rules: [{
28 | test: /\.ts$/,
29 | include: [
30 | path.resolve(__dirname, 'index.ts'),
31 | path.resolve(__dirname, 'ane-util.ts'),
32 | path.resolve(__dirname, 'components'),
33 | path.resolve(__dirname, 'tests')
34 | ],
35 | loader: 'ts-loader',
36 | options: { appendTsSuffixTo: [/\.md$/] }
37 | }, {
38 | test: /\.less$/,
39 | include: [
40 | path.resolve(__dirname, 'styles'),
41 | path.resolve(__dirname, 'components')
42 | ],
43 | use: extractLess.extract({
44 | use: [{
45 | loader: 'css-loader'
46 | }, {
47 | loader: 'less-loader'
48 | }]
49 | })
50 | }, {
51 | test: /\.css$/,
52 | include: [
53 | path.resolve(__dirname, 'components'),
54 | path.resolve(__dirname, 'node_modules')
55 | ],
56 | use: extractCss.extract({
57 | use: [{
58 | loader: 'css-loader'
59 | }]
60 | })
61 | }, {
62 | test: /\.html$/,
63 | include: [
64 | path.resolve(__dirname, 'components')
65 | ],
66 | use: [
67 | {
68 | loader: 'raw-loader'
69 | },
70 | {
71 | loader: 'string-replace-loader',
72 | query: {
73 | multiple: [
74 | { search: '\r', replace: '', flags: 'g' }
75 | ]
76 | }
77 | }
78 | ]
79 | }, {
80 | test: /\.(eot|otf|ttf|woff|woff2|svg|png|gif)\w*/,
81 | include: [
82 | path.resolve(__dirname, 'components'),
83 | path.resolve(__dirname, 'node_modules')
84 | ],
85 | loader: 'file-loader',
86 | query: {
87 | limit: 1,
88 | name: '[name].[ext]'
89 | }
90 | }, {
91 | test: /\.md$/,
92 | include: [
93 | path.resolve(__dirname, 'components'),
94 | ],
95 | use: [
96 | { loader: 'ane-markdown-loader', options: { highlight: false } }
97 | ]
98 | }]
99 | },
100 | resolve: {
101 | mainFields: ['browser', 'main'],
102 | extensions: ['.js', '.ts', '.less', '.md'],
103 | alias: {
104 | ane: path.resolve(__dirname, "index.ts")
105 | }
106 | },
107 | plugins: [
108 | extractLess,
109 | extractCss,
110 | new HtmlWebpackPlugin({
111 | template: 'tests/index.html'
112 | }),
113 | new webpack.optimize.CommonsChunkPlugin({
114 | name: 'vendor',
115 | minChunks: function (module) {
116 | return module.context && module.context.indexOf('node_modules') !== -1;
117 | }
118 | }),
119 | new webpack.optimize.CommonsChunkPlugin({
120 | name: 'manifest'
121 | })
122 | ],
123 | devServer: {
124 | contentBase: path.join(__dirname, "dist"),
125 | compress: true,
126 | port: 9000,
127 | watchOptions: {
128 | ignored: /node_modules/
129 | }
130 | },
131 | devtool: 'inline-source-map'
132 | };
--------------------------------------------------------------------------------
/components/ms-upload/ms-upload.less:
--------------------------------------------------------------------------------
1 | @import '../../styles/index';
2 | @select-prefix-cls: ~"@{ane-prefix}-upload";
3 |
4 | .@{select-prefix-cls} {
5 | &-container input {
6 | position: absolute;
7 | clip: rect(0 0 0 0);
8 | outline: none;
9 | }
10 |
11 | &-list {
12 | list-style: none;
13 | padding: 0;
14 | margin: 0;
15 |
16 | li {
17 | margin: 8px 0;
18 | line-height: 24px;
19 | position: relative;
20 | width: 500px;
21 |
22 | .@{select-prefix-cls}-btn-close {
23 | cursor: pointer;
24 | display: none;
25 | }
26 | .@{select-prefix-cls}-list-progress {
27 | position: absolute;
28 | right: 0;
29 | top: 0;
30 | text-align: right;
31 | }
32 | &.text-danger .@{select-prefix-cls}-btn-close {
33 | display: inline-block;
34 | }
35 | &:hover {
36 | .@{select-prefix-cls}-list-progress {
37 | right: 10px;
38 | }
39 | i.@{select-prefix-cls}-btn-close {
40 | display: inline-block;
41 | }
42 | i.text-success {
43 | display: none;
44 | }
45 | }
46 | i {
47 | position: absolute;
48 | right: 0;
49 | top: 0;
50 | line-height: 24px;
51 | }
52 | span {
53 | display: inline-block;
54 | }
55 | }
56 |
57 | &-info {
58 | display: inline-block;
59 | i {
60 | position: absolute;
61 | left: 0;
62 | top: 0;
63 | line-height: 24px;
64 | }
65 | span {
66 | position: absolute;
67 | left:18px;
68 | top: 0;
69 | white-space: nowrap;
70 | text-overflow: ellipsis;
71 | overflow: hidden;
72 | width: 350px;
73 | }
74 | }
75 | }
76 |
77 | &-card {
78 | &-wall {
79 | display: inline-block;
80 | }
81 | &-item {
82 | height: 96px;
83 | width: 96px;
84 | border: 1px solid #d9d9d9;
85 | border-radius: 4px;
86 | background-color: #f5f5f5;
87 | display: inline-block;
88 | text-align: center;
89 | word-wrap: break-word;
90 | margin-right: 8px;
91 | margin-bottom: 8px;
92 | vertical-align: middle;
93 | padding: 8px;
94 | position: relative;
95 |
96 | .@{select-prefix-cls}-card-progress {
97 | position: absolute;
98 | left: 50%;
99 | top: 50%;
100 | margin-left: -40px;
101 | margin-top: -7px;
102 | }
103 | img {
104 | height: 100%;
105 | width: 100%;
106 | }
107 | .@{select-prefix-cls}-card-tool {
108 | display: none;
109 | .fa {
110 | margin: 0 4px;
111 | color: #ffffff;
112 | cursor: pointer;
113 | }
114 | }
115 | &:hover .@{select-prefix-cls}-card-tool {
116 | display: block;
117 | position: absolute;
118 | width: 78px;
119 | height: 78px;
120 | left: 8px;
121 | top: 8px;
122 | padding: 30px 0;
123 | /* 支持IE8的透明背景 */
124 | background: transparent url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACAQMAAACjTyRkAAAAA1BMVEUAAACnej3aAAAAAXRSTlO7HFWyYgAAAAxJREFUeNpjYGBgAAAABAAByOrr+QAAAABJRU5ErkJggg==") repeat scroll 0 0;
125 | }
126 | }
127 | }
128 |
129 | &-select-card {
130 | padding: 20px 0;
131 | border-style: dashed;
132 | cursor: pointer;
133 |
134 | &:hover {
135 | border-color: @brand-primary;
136 | }
137 | .fa {
138 | display: block;
139 | font-size: 2em;
140 | }
141 | }
142 | }
--------------------------------------------------------------------------------
/components/ms-datepicker/ms-datepicker-panel.html:
--------------------------------------------------------------------------------
1 |
2 |
20 |
31 |
42 |
51 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
72 |
79 |
--------------------------------------------------------------------------------
/components/ms-table/ms-table.md:
--------------------------------------------------------------------------------
1 | ## 数据表格
2 |
3 | ### 本地分页
4 |
5 | ```html
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{record.name}}
13 |
14 |
15 |
16 |
17 |
18 | ```
19 |
20 | ```js
21 | import * as avalon from 'avalon2';
22 | import * as $ from 'jquery';
23 | import { message } from 'ane';
24 |
25 | const vm = avalon.define({
26 | $id: 'doc-table-local',
27 | list: avalon.range(29).map(n => ({
28 | id: n, name: '老狼' + n, address: '深山', province: '老林'
29 | })),
30 | actions(type, text, record, index) {
31 | if (type == 'delete') {
32 | vm.list.removeAll(el => el.id == record.id );
33 | message.success({
34 | content: '删除成功'
35 | });
36 | }
37 | },
38 | handleSelect(record, selected, selectedRows) {
39 | console.log(record, selected, selectedRows);
40 | },
41 | handleSelectAll(selected, selectedRows) {
42 | console.log(selected, selectedRows);
43 | },
44 | handleSelectionChange(selectedRowKeys, selectedRows) {
45 | console.log('selectedRowKeys: ' + selectedRowKeys, 'selectedRows: ', selectedRows);
46 | }
47 | });
48 | ```
49 |
50 | ### 远程分页
51 |
52 | ```html
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | ```
61 |
62 | ```js
63 | import * as avalon from 'avalon2';
64 | import * as $ from 'jquery';
65 | import { message } from 'ane';
66 |
67 | const vm1 = avalon.define({
68 | $id: 'doc-table-remote',
69 | remoteList: [],
70 | loading: false,
71 | pagination: {
72 | pageSize: 6, total: 0
73 | },
74 | fetch(params = {}) {
75 | vm1.loading = true;
76 | $.getJSON('http://easy-mock.com/mock/58ff1b7c5e43ae5dbea5eff3/api/demo', params).then(data => {
77 | vm1.pagination.total = data.total;
78 | data.rows[0].region_parent_id = Date.now();
79 | vm1.remoteList = data.rows;
80 | vm1.loading = false;
81 | });
82 | },
83 | handleTableChange(pagination) {
84 | if (this.pagination.hasOwnProperty('current')) {
85 | vm1.pagination.current = pagination.current;
86 | }
87 | this.fetch({
88 | start: pagination.pageSize * (pagination.current - 1),
89 | limit: pagination.pageSize
90 | });
91 | }
92 | });
93 | vm1.fetch();
94 | ```
95 |
96 | ### 组件参数
97 |
98 | | 参数 | 说明 | 类型 | 默认值 |
99 | |-----|-----|-----|-----|
100 | | columns | 表格列定义 | {title:string,dataIndex:string,template:string}\[\] | \[\] |
101 | | data | 表格数据 | any\[\] | \[\] |
102 | | key | 数据行的唯一标识字段 | string | 'id' |
103 | | loading | 数据是否正在加载中 | boolean | false |
104 | | needSelection | 是否需要选择数据行 | boolean | false |
105 | | actions | handle方法被调用后,这个方法就会被调用 | function(type:string,text:string,record,index:number,...extra) | noop |
106 | | pagination | 分页对象 | object | {current: 1, pageSize: 10, total: NaN, onChange: avalon.noop} |
107 | | onSelect | 用户选择/取消选择行的回调,传入行数据、是否选中和所有选择的行数据 | function(record,selected:boolean,selectedRows) | noop |
108 | | onSelectAll | 用户全选/取消全选的回调,传入是否选中和所有选择的行数据 | function(selected:boolean,selectedRows) | noop |
109 | | selectionChange | 选择项变化时候的回调,传入所有选择的行数据key的集合和所有选择的行数据 | function(selectedRowKeys:string\[\],selectedRows) | noop |
110 | | onChange | 分页、过滤或排序变化时的回调 | function(pagination) | noop |
111 |
112 | > 当 pagination 中的 total 属性为 `NaN` 时,表示本地分页,否则表示远程分页
113 |
114 | > handle 是一个内置的方法,用于相应自定义的表格数据操作,并交给 actions 参数统一处理
--------------------------------------------------------------------------------
/components/ms-menu/ms-menu.md:
--------------------------------------------------------------------------------
1 | ## 菜单
2 |
3 | ### 基本用法
4 |
5 | ```html
6 |
7 |
14 |
15 | ```
16 |
17 | ```js
18 | import * as avalon from 'avalon2';
19 | import 'ane';
20 |
21 | avalon.define({
22 | $id: 'doc-menu-basic',
23 | menu: [{
24 | key: 'nav1',
25 | title: '导航一',
26 | icon: 'fa fa-home',
27 | children: [{
28 | key: 'option1',
29 | title: '选项一'
30 | }, {
31 | key: 'option2',
32 | title: '选项二'
33 | }, {
34 | key: 'option3',
35 | title: '选项三'
36 | }]
37 | }, {
38 | key: 'nav2',
39 | title: '导航二',
40 | icon: 'fa fa-book',
41 | children: [{
42 | key: 'option4',
43 | title: '选项四'
44 | }, {
45 | key: 'option5',
46 | title: '选项五'
47 | }, {
48 | key: 'submenu',
49 | title: '子菜单',
50 | children: [{
51 | key: 'option6',
52 | title: '选项六'
53 | }, {
54 | key: 'option7',
55 | title: '选项七'
56 | }]
57 | }]
58 | }],
59 | selectedKeys: ['option1'],
60 | openKeys: ['nav1'],
61 | handleMenuClick(item, key, keyPath) {
62 | console.log(item, key, keyPath);
63 | },
64 | onInit(event) {
65 | }
66 | });
67 | ```
68 |
69 | ### 只展开当前父级菜单
70 |
71 | ```html
72 |
73 |
80 |
81 | ```
82 |
83 | ```js
84 | import * as avalon from 'avalon2';
85 | import 'ane';
86 |
87 | avalon.define({
88 | $id: 'doc-menu-single',
89 | menu: [{
90 | key: 'nav1',
91 | title: '导航一',
92 | icon: 'fa fa-home',
93 | children: [{
94 | key: 'option1',
95 | title: '选项一'
96 | }, {
97 | key: 'option2',
98 | title: '选项二'
99 | }, {
100 | key: 'option3',
101 | title: '选项三'
102 | }]
103 | }, {
104 | key: 'nav2',
105 | title: '导航二',
106 | icon: 'fa fa-book',
107 | children: [{
108 | key: 'option4',
109 | title: '选项四'
110 | }, {
111 | key: 'option5',
112 | title: '选项五'
113 | }, {
114 | key: 'submenu',
115 | title: '子菜单',
116 | children: [{
117 | key: 'option6',
118 | title: '选项六'
119 | }, {
120 | key: 'option7',
121 | title: '选项七'
122 | }]
123 | }]
124 | }],
125 | selectedKeys: ['option1'],
126 | openKeys: ['nav1'],
127 | handleMenuClick(item, key, keyPath) {
128 | console.log(item, key, keyPath);
129 | this.selectedKeys = [key];
130 | },
131 | handleOpenChange(openKeys) {
132 | const state = this;
133 | const latestOpenKey = openKeys.filter(key => !(state.openKeys.indexOf(key) > -1))[0] || undefined;
134 | const latestCloseKey = state.openKeys.filter(key => !(openKeys.indexOf(key) > -1))[0] || undefined;
135 |
136 | let nextOpenKeys = [];
137 | if (latestOpenKey) {
138 | nextOpenKeys = this.getAncestorKeys(latestOpenKey).concat(latestOpenKey);
139 | }
140 | if (latestCloseKey) {
141 | nextOpenKeys = this.getAncestorKeys(latestCloseKey);
142 | }
143 | state.openKeys = nextOpenKeys;
144 | },
145 | getAncestorKeys(key) {
146 | const map = {
147 | submenu: ['nav2'],
148 | };
149 | return map[key] || [];
150 | },
151 | onInit(event) {
152 | }
153 | });
154 | ```
155 |
156 | ### 组件参数
157 |
158 | | 参数 | 说明 | 类型 | 默认值 |
159 | |-----|-----|-----|-----|
160 | | menu | 菜单数据 | {key:string,title:string,icon:string,children:\[\]}\[\] | \[\] |
161 | | selectedKeys | 选择的菜单的key的集合 | string\[\] | \[\] |
162 | | openKeys | 展开的菜单的key的集合 | string\[\] | \[\] |
163 | | onClick | 点击菜单项的回调 | function(item, key, keyPath) | noop |
164 | | onOpenChange | 菜单展开/收起的回调 | function(openKeys:string\[\]) | noop |
--------------------------------------------------------------------------------
/components/ms-datepicker/ms-datepicker.less:
--------------------------------------------------------------------------------
1 | @import '../../styles/index';
2 | @datepicker-prefix-cls: ~"@{ane-prefix}-datepicker";
3 |
4 | @media (min-width: 768px) {
5 | .form-inline .@{datepicker-prefix-cls} {
6 | display: inline-block;
7 | width: auto;
8 | vertical-align: middle;
9 | }
10 | }
11 | .@{datepicker-prefix-cls} {
12 | position: relative;
13 |
14 | &-icon {
15 | position: absolute;
16 | right: 8px;
17 | top: 50%;
18 | margin-top: -7px;
19 | }
20 | &-clear {
21 | position: absolute;
22 | right: 8px;
23 | top: 50%;
24 | margin-top: -7px;
25 | padding: 1px;
26 | cursor: pointer;
27 | display: none;
28 | background: #fff;
29 | }
30 | &:hover &-clear {
31 | display: inline-block;
32 | }
33 | & &-input {
34 | background-color: #fff;
35 | }
36 | }
37 |
38 | .@{datepicker-prefix-cls}-panel-container {
39 | background-color: #fff;
40 | box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
41 | box-sizing: border-box;
42 | user-select: none;
43 | }
44 | .@{datepicker-prefix-cls}-panel {
45 | width: 252px;
46 |
47 | a {
48 | color: @link-color;
49 | cursor: pointer;
50 | text-decoration: none;
51 | &:hover {
52 | color: @link-hover-color;
53 | }
54 | }
55 |
56 | &-header {
57 | text-align: center;
58 | height: 34px;
59 | line-height: 34px;
60 | border-bottom: 1px solid @gray-lighter;
61 | }
62 | .@{datepicker-prefix-cls}-prev-year-btn {
63 | float: left;
64 | margin-left: 7px;
65 | padding: 0 5px;
66 | font-size: 16px;
67 | }
68 | .@{datepicker-prefix-cls}-prev-month-btn {
69 | float: left;
70 | margin-left: 7px;
71 | padding: 0 5px;
72 | font-size: 16px;
73 | }
74 | .@{datepicker-prefix-cls}-next-year-btn {
75 | float: right;
76 | margin-right: 7px;
77 | padding: 0 5px;
78 | font-size: 16px;
79 | }
80 | .@{datepicker-prefix-cls}-next-month-btn {
81 | float: right;
82 | margin-right: 7px;
83 | padding: 0 5px;
84 | font-size: 16px;
85 | }
86 | .@{datepicker-prefix-cls}-month-select {
87 | padding: 0 2px;
88 | font-weight: 700;
89 | display: inline-block;
90 | color: rgba(0,0,0,.65);
91 | line-height: 34px;
92 | }
93 | .@{datepicker-prefix-cls}-year-select {
94 | padding: 0 2px;
95 | font-weight: 700;
96 | display: inline-block;
97 | color: rgba(0,0,0,.65);
98 | line-height: 34px;
99 | }
100 | &-today-btn {
101 | display: inline-block;
102 | text-align: center;
103 | margin: 0 0 0 8px;
104 | }
105 | &-now-btn {
106 | display: inline-block;
107 | text-align: center;
108 | float: left;
109 | margin: 0;
110 | padding-left: 12px;
111 | }
112 | &-timepicker-btn {
113 | display: inline-block;
114 | text-align: center;
115 | margin-right: 20px;
116 | float: right;
117 | }
118 | & &-ok-btn {
119 | display: inline-block;
120 | margin-bottom: 0;
121 | font-weight: normal;
122 | text-align: center;
123 | white-space: nowrap;
124 | vertical-align: middle;
125 | -ms-touch-action: manipulation;
126 | touch-action: manipulation;
127 | cursor: pointer;
128 | -webkit-user-select: none;
129 | -moz-user-select: none;
130 | -ms-user-select: none;
131 | user-select: none;
132 | background-image: none;
133 | border: 1px solid transparent;
134 | padding: 1px 5px;
135 | font-size: 12px;
136 | line-height: 1.5;
137 | border-radius: 3px;
138 | color: @btn-primary-color;
139 | background-color: @btn-primary-bg;
140 | border-color: @btn-primary-border;
141 | float: right;
142 | margin-right: 9px;
143 |
144 | &:hover {
145 | color: @btn-primary-color;
146 | background-color: darken(@btn-primary-bg, 17%);
147 | border-color: darken(@btn-primary-border, 25%);
148 | }
149 | }
150 |
151 | &-body {
152 | padding: 4px 8px;
153 | }
154 |
155 | &-footer {
156 | &-btn {
157 | border-top: 1px solid @gray-lighter;
158 | text-align: center;
159 | display: block;
160 | line-height: 1.5;
161 | padding: 10px 0;
162 |
163 | &:after {
164 | clear: both;
165 | content: ' ';
166 | display: inline-block;
167 | }
168 | }
169 | }
170 |
171 | }
--------------------------------------------------------------------------------
/components/ms-loading/ms-loading-directive.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 |
3 | /**
4 | * loading 指令
5 | *
6 | * @example
7 | * ``` html
8 | *
9 | * ```
10 | */
11 | avalon.directive('loading', {
12 | init() {
13 | this.instance = null;
14 | this.oldPositionStyle = '';
15 | },
16 | update(vdom, value) {
17 | if (value) {
18 | if (!this.instance) {
19 | const t = setInterval(() => {
20 | const dom = vdom.dom;
21 | const computedStyle = global.getComputedStyle ? global.getComputedStyle(dom) : dom.currentStyle;
22 | const width = dom.offsetWidth, height = dom.scrollHeight, className = dom.className;
23 | const {
24 | borderLeftWidth,
25 | borderTopWidth,
26 | display
27 | } = computedStyle;
28 | this.oldPositionStyle = dom.style.position;
29 |
30 | // 如果元素是隐藏的,什么都不做
31 | if (display === 'none') {
32 | clearInterval(t);
33 | }
34 |
35 | // 如果宽度和高度都不为0,则添加loading遮罩
36 | if (width !== 0 && height !== 0) {
37 | clearInterval(t);
38 | } else {
39 | return ;
40 | }
41 |
42 | const maskElement = global.document.createElement('div');
43 | maskElement.className = 'ane-loading-mask';
44 | maskElement.innerText = '加载中...';
45 | maskElement.style.left = 0 - (borderLeftWidth === 'medium' ? 0 : parseFloat(borderLeftWidth)) + 'px';
46 | maskElement.style.top = 0 - (borderTopWidth === 'medium' ? 0 : parseFloat(borderTopWidth)) + 'px';
47 | maskElement.style.width = width + 'px';
48 | maskElement.style.height = height + 'px';
49 | maskElement.style.lineHeight = height + 'px';
50 |
51 | dom.style.position = 'relative';
52 | if (!~` ${className} `.indexOf(' masked ')) {
53 | dom.className += ' masked';
54 | }
55 | dom.appendChild(maskElement);
56 | this.instance = maskElement;
57 | }, 100);
58 | } else {
59 | const dom = vdom.dom;
60 | const maskElement = this.instance;
61 | const className = dom.className;
62 | this.oldPositionStyle = dom.style.position;
63 | maskElement.style.display = 'block';
64 | dom.style.position = 'relative';
65 | if (!~` ${className} `.indexOf(' masked ')) {
66 | dom.className = className + ' masked';
67 | }
68 | }
69 | } else {
70 | setTimeout(() => {
71 | if (this.instance) {
72 | const dom = vdom.dom;
73 | const maskElement = this.instance;
74 | const className = dom.className;
75 | maskElement.style.display = 'none';
76 | if (this.oldPositionStyle) {
77 | dom.style.position = this.oldPositionStyle;
78 | }
79 | dom.className = ` ${className} `.replace(/\s*masked\s*/, ' ');
80 | }
81 | }, 100);
82 | }
83 | },
84 | beforeDispose() {
85 | const dom = this.node.dom;
86 | this.instance && dom.removeChild(this.instance);
87 | }
88 | });
89 |
90 | /**
91 | * 全局 loading 方法
92 | *
93 | * @example
94 | * ``` js
95 | * import { Loading } from './components/ms-loading';
96 | * Loading.show();
97 | * setTimeout(() => {
98 | * Loading.hide();
99 | * }, 5000)
100 | * ```
101 | */
102 | const loadingDirective = avalon.directives['loading'];
103 | const globalLoadingContext: {
104 | node: { dom: HTMLElement },
105 | instance?: HTMLDivElement
106 | } = {
107 | node: { dom: document.body }
108 | };
109 |
110 | export const Loading = {
111 | show() {
112 | if (globalLoadingContext.instance === undefined) {
113 | loadingDirective.init.call(globalLoadingContext);
114 | avalon.ready(() => {
115 | loadingDirective.update.call(globalLoadingContext, {
116 | dom: globalLoadingContext.node.dom
117 | }, true);
118 | });
119 | } else {
120 | loadingDirective.update.call(globalLoadingContext, {
121 | dom: globalLoadingContext.node.dom
122 | }, true);
123 | }
124 | },
125 | hide() {
126 | if (globalLoadingContext.instance !== undefined) {
127 | loadingDirective.update.call(globalLoadingContext, {
128 | dom: globalLoadingContext.node.dom
129 | }, false);
130 | }
131 | }
132 | };
--------------------------------------------------------------------------------
/components/ms-select/ms-select.less:
--------------------------------------------------------------------------------
1 | @import '../../styles/index';
2 | @select-prefix-cls: ~"@{ane-prefix}-select";
3 |
4 | .item-selected() {
5 | background-color: #eee;
6 | font-weight: 700;
7 | color: rgba(0,0,0,.65);
8 | }
9 |
10 | .@{select-prefix-cls} {
11 | position: relative;
12 | cursor: pointer;
13 |
14 | &.@{select-prefix-cls}-multiple {
15 | cursor: text;
16 | height: initial;
17 | padding: 0 0 0 12px;
18 | min-height: 34px;
19 |
20 | &:before {
21 | content: " ";
22 | display: table;
23 | }
24 |
25 | &:after {
26 | content: " ";
27 | display: table;
28 | clear: both;
29 | visibility: hidden;
30 | font-size: 0;
31 | height: 0;
32 | }
33 |
34 | .@{select-prefix-cls}-selection {
35 | .@{select-prefix-cls}-search {
36 | height: 25px;
37 | line-height: 25px;
38 | margin-top: 4px;
39 | }
40 | }
41 | }
42 |
43 | &-selection {
44 | list-style: none;
45 | padding: 0;
46 |
47 | &.@{select-prefix-cls}-tags li.@{select-prefix-cls}-choice {
48 | position: relative;
49 | display: block;
50 | float: left;
51 | margin: 4px 4px 0 0;
52 | background-color: #d0d0d0;
53 | height: 25px;
54 | max-width: 99%;
55 | line-height: 25px;
56 | padding: 0 8px;
57 | user-select: none;
58 |
59 | span {
60 | padding-right: 16px;
61 | display: inline-block;
62 | max-width: 100%;
63 | overflow: hidden;
64 | white-space: nowrap;
65 | text-overflow: ellipsis;
66 | }
67 |
68 | i {
69 | display: inline;
70 | cursor: pointer;
71 | position: absolute;
72 | right: 4px;
73 | line-height: inherit;
74 | padding-left: 8px;
75 | padding-right: 4px;
76 |
77 | &:hover {
78 | color: #404040;
79 | }
80 | }
81 | }
82 |
83 | .@{select-prefix-cls}-selected {
84 | position: absolute;
85 | left: 12px;
86 | padding: 1px;
87 | overflow: hidden;
88 | text-overflow: ellipsis;
89 | white-space: nowrap;
90 | max-width: 100%;
91 | padding-right: 14px;
92 | }
93 |
94 | .@{select-prefix-cls}-search {
95 | display: block;
96 | float: left;
97 | }
98 |
99 | input.@{select-prefix-cls}-search-field {
100 | height: 100%;
101 | border-width: 0;
102 | box-shadow: none;
103 | background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABNJREFUeNpi+P//PwMTAxAABBgAF/wDAMEBHdcAAAAASUVORK5CYII=') repeat scroll 0 0;
104 | outline: none;
105 | line-height: inherit;
106 | }
107 |
108 | li.@{select-prefix-cls}-choice {
109 | display: none;
110 | }
111 | }
112 |
113 | .@{select-prefix-cls}-arrow {
114 | position: absolute;
115 | top: 7px;
116 | right: 8px;
117 | line-height: 20px;
118 | }
119 | }
120 |
121 | .@{select-prefix-cls}-dropdown {
122 | background-color: #fff;
123 | box-shadow: 0 1px 6px rgba(0,0,0,.2);
124 | box-sizing: border-box;
125 |
126 | &-menu {
127 | list-style: none;
128 | padding-left: 0;
129 | margin-bottom: 0;
130 | max-height: 250px;
131 |
132 | &-item {
133 | position: relative;
134 | display: block;
135 | padding: 7px 13px;
136 | cursor: pointer;
137 | white-space: nowrap;
138 | overflow: hidden;
139 | user-select: none;
140 |
141 | &:focus,
142 | &:hover {
143 | background: @item-hover-bg;
144 |
145 | i {
146 | color: #dddddd;
147 | display: inline-block;
148 | }
149 | }
150 |
151 | i {
152 | display: none;
153 | position: absolute;
154 | right: 13px;
155 | line-height: inherit;
156 | }
157 | }
158 |
159 | &-item-selected {
160 | .item-selected;
161 |
162 | &:hover {
163 | .item-selected;
164 | }
165 |
166 | i {
167 | color: @brand-primary;
168 | display: inline-block;
169 | }
170 | }
171 |
172 | &-item-disabled,
173 | &-item-disabled:hover {
174 | background-color: inherit;
175 | color: @disabled-color;
176 | cursor: not-allowed;
177 | }
178 | }
179 | }
--------------------------------------------------------------------------------
/webpack.doc.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
4 | var HtmlWebpackPlugin = require('html-webpack-plugin');
5 | var es3ifyPlugin = require('es3ify-webpack-plugin');
6 |
7 | var extractLess = new ExtractTextPlugin({
8 | filename: "bundle[chunkHash].css",
9 | disable: false,
10 | allChunks: true
11 | });
12 | var extractCss = new ExtractTextPlugin({
13 | filename: "vendor[chunkHash].css",
14 | disable: false
15 | });
16 |
17 | var debug = process.env.NODE_ENV !== 'production';
18 |
19 | var config = {
20 | entry: {
21 | app: './docs/index.js'
22 | },
23 | output: {
24 | path: path.resolve(__dirname, 'dist'),
25 | filename: '[name][chunkHash].js',
26 | library: 'index',
27 | libraryTarget: 'umd'
28 | },
29 | module: {
30 | rules: [{
31 | test: /\.ts$/,
32 | include: [
33 | path.resolve(__dirname, 'index.ts'),
34 | path.resolve(__dirname, 'ane-util.ts'),
35 | path.resolve(__dirname, 'components'),
36 | path.resolve(__dirname, 'docs')
37 | ],
38 | loader: 'ts-loader',
39 | options: { appendTsSuffixTo: [/\.md$/] }
40 | }, {
41 | test: /\.less$/,
42 | include: [
43 | path.resolve(__dirname, 'styles'),
44 | path.resolve(__dirname, 'components')
45 | ],
46 | use: extractLess.extract({
47 | use: [{
48 | loader: 'css-loader'
49 | }, {
50 | loader: 'less-loader'
51 | }]
52 | })
53 | }, {
54 | test: /\.css$/,
55 | include: [
56 | path.resolve(__dirname, 'components'),
57 | path.resolve(__dirname, 'node_modules')
58 | ],
59 | use: extractCss.extract({
60 | use: [{
61 | loader: 'css-loader'
62 | }]
63 | })
64 | }, {
65 | test: /\.html$/,
66 | include: [
67 | path.resolve(__dirname, 'components'),
68 | path.resolve(__dirname, 'docs/components')
69 | ],
70 | use: [
71 | {
72 | loader: 'raw-loader'
73 | },
74 | {
75 | loader: 'string-replace-loader',
76 | query: {
77 | multiple: [
78 | { search: '\r', replace: '', flags: 'g' }
79 | ]
80 | }
81 | }
82 | ]
83 | }, {
84 | test: /\.(eot|otf|ttf|woff|woff2|svg|png|gif)\w*/,
85 | include: [
86 | path.resolve(__dirname, 'components'),
87 | path.resolve(__dirname, 'node_modules')
88 | ],
89 | loader: 'file-loader',
90 | query: {
91 | limit: 1,
92 | name: '[name].[ext]'
93 | }
94 | }, {
95 | test: /\.md$/,
96 | include: [
97 | path.resolve(__dirname, 'README.md'),
98 | path.resolve(__dirname, 'CHANGELOG.md'),
99 | path.resolve(__dirname, 'components'),
100 | ],
101 | use: [
102 | { loader: 'ane-markdown-loader', options: { highlight: false } }
103 | ]
104 | }]
105 | },
106 | resolve: {
107 | mainFields: ['browser', 'main'],
108 | extensions: ['.js', '.ts', '.less', '.md'],
109 | alias: {
110 | ane: path.resolve(__dirname, "index.ts")
111 | }
112 | },
113 | watch: true,
114 | plugins: [
115 | extractLess,
116 | extractCss,
117 | new HtmlWebpackPlugin({
118 | template: 'docs/index.html'
119 | }),
120 | new webpack.optimize.CommonsChunkPlugin({
121 | name: 'vendor',
122 | minChunks: function (module) {
123 | return module.context && module.context.indexOf('node_modules') !== -1;
124 | }
125 | }),
126 | new webpack.optimize.CommonsChunkPlugin({
127 | name: 'manifest'
128 | })
129 | ],
130 | devServer: {
131 | contentBase: path.join(__dirname, "dist"),
132 | compress: true,
133 | port: 9000,
134 | watchOptions: {
135 | ignored: /node_modules/
136 | },
137 | proxy: {
138 | '/api': {
139 | target: 'https://www.easy-mock.com/mock/58ff1b7c5e43ae5dbea5eff3',
140 | secure: false
141 | }
142 | }
143 | },
144 | devtool: 'inline-source-map'
145 | };
146 |
147 | module.exports = function (env) {
148 | if (env && env.production) {
149 | config.plugins.unshift(new es3ifyPlugin());
150 | config.watch = false;
151 | delete config.devtool;
152 | }
153 | return config;
154 | };
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var ExtractTextPlugin = require("extract-text-webpack-plugin");
4 |
5 | var extractLess = new ExtractTextPlugin({
6 | filename: "ane.css",
7 | disable: false
8 | });
9 | var extractLayoutLess = new ExtractTextPlugin({
10 | filename: "layout.css",
11 | disable: false
12 | });
13 |
14 | module.exports = {
15 | entry: {
16 | ane: './index.ts',
17 | layout: './components/ms-layout/index.ts'
18 | },
19 | output: {
20 | path: path.resolve(__dirname, 'dist'),
21 | filename: '[name].js',
22 | library: '[name]',
23 | libraryTarget: 'umd'
24 | },
25 | externals: {
26 | avalon2: {
27 | root: 'avalon',
28 | commonjs: 'avalon2',
29 | commonjs2: 'avalon2',
30 | amd: 'avalon2'
31 | },
32 | jquery: {
33 | root: 'jQuery',
34 | commonjs: 'jquery',
35 | commonjs2: 'jquery',
36 | amd: 'jquery'
37 | },
38 | 'async-validator': {
39 | root: 'Schema',
40 | commonjs: 'async-validator',
41 | commonjs2: 'async-validator',
42 | amd: 'async-validator'
43 | },
44 | bootbox: {
45 | root: 'bootbox',
46 | commonjs: 'bootbox',
47 | commonjs2: 'bootbox',
48 | amd: 'bootbox'
49 | },
50 | 'dom-align': {
51 | root: 'domAlign',
52 | commonjs: 'dom-align',
53 | commonjs2: 'dom-align',
54 | amd: 'dom-align'
55 | },
56 | moment: true,
57 | noty: {
58 | root: 'noty',
59 | commonjs: 'noty',
60 | commonjs2: 'noty',
61 | amd: 'noty'
62 | },
63 | 'up-loader': {
64 | root: 'Uploader',
65 | commonjs: 'up-loader',
66 | commonjs2: 'up-loader',
67 | amd: 'up-loader'
68 | }
69 | },
70 | module: {
71 | rules: [{
72 | test: /\.ts$/,
73 | include: [
74 | path.resolve(__dirname, 'index.ts'),
75 | path.resolve(__dirname, 'ane-util.ts'),
76 | path.resolve(__dirname, 'components')
77 | ],
78 | loader: 'ts-loader'
79 | }, {
80 | test: /\.less$/,
81 | include: [
82 | path.resolve(__dirname, 'styles'),
83 | path.resolve(__dirname, 'components')
84 | ],
85 | exclude: [
86 | path.resolve(__dirname, 'components/ms-layout')
87 | ],
88 | use: extractLess.extract({
89 | use: [{
90 | loader: 'css-loader'
91 | }, {
92 | loader: 'less-loader'
93 | }]
94 | })
95 | }, {
96 | test: /\.less$/,
97 | include: [
98 | path.resolve(__dirname, 'styles'),
99 | path.resolve(__dirname, 'components/ms-layout')
100 | ],
101 | use: extractLayoutLess.extract({
102 | use: [{
103 | loader: 'css-loader'
104 | }, {
105 | loader: 'less-loader'
106 | }]
107 | })
108 | }, {
109 | test: /\.css$/,
110 | include: [
111 | path.resolve(__dirname, 'components'),
112 | path.resolve(__dirname, 'node_modules')
113 | ],
114 | use: extractLess.extract({
115 | use: [{
116 | loader: 'css-loader'
117 | }]
118 | })
119 | }, {
120 | test: /\.html$/,
121 | include: [
122 | path.resolve(__dirname, 'components')
123 | ],
124 | use: [
125 | {
126 | loader: 'raw-loader'
127 | },
128 | {
129 | loader: 'string-replace-loader',
130 | query: {
131 | multiple: [
132 | { search: '\r', replace: '', flags: 'g' }
133 | ]
134 | }
135 | }
136 | ]
137 | }, {
138 | test: /\.(eot|otf|ttf|woff|woff2|svg|png|gif)\w*/,
139 | include: [
140 | path.resolve(__dirname, 'components'),
141 | path.resolve(__dirname, 'node_modules')
142 | ],
143 | loader: 'file-loader',
144 | query: {
145 | limit: 1,
146 | name: '[name].[ext]'
147 | }
148 | }]
149 | },
150 | resolve: {
151 | mainFields: ['browser', 'main'],
152 | extensions: ['.js', '.ts', '.less']
153 | },
154 | plugins: [
155 | extractLess,
156 | extractLayoutLess,
157 | new webpack.ProvidePlugin({
158 | $: 'jquery',
159 | jQuery: 'jquery'
160 | })
161 | ]
162 | };
--------------------------------------------------------------------------------
/components/ms-tree-select/ms-tree-select.ts:
--------------------------------------------------------------------------------
1 | import * as avalon from 'avalon2';
2 | import controlComponent from "../ms-form/ms-control";
3 | import '../ms-trigger';
4 | import '../ms-tree';
5 | import getPanelVm from './ms-tree-select-panel';
6 |
7 | import { getChildTemplateDescriptor, debounce } from '../../ane-util';
8 | import { emitToFormItem } from '../ms-form/utils';
9 |
10 | controlComponent.extend({
11 | displayName: 'ms-tree-select',
12 | template: require('./ms-tree-select.html'),
13 | defaults: {
14 | value: [],
15 | multiple: false,
16 | treeData: [],
17 | selection: [],
18 |
19 | // 下拉框展示和操作部分
20 | displayValue: '',
21 | showSearch: false,
22 | searchValue: '',
23 | focusSearch() {
24 | this.$element.getElementsByTagName('input').search.focus();
25 | },
26 | withInBox(el) {
27 | return this.$element === el || avalon.contains(this.$element, el);
28 | },
29 | getTarget() {
30 | return this.$element;
31 | },
32 | handleClick(e) {
33 | if (!this.panelVisible) {
34 | this.searchValue = '';
35 | this.panelWidth = this.$element.offsetWidth;
36 | this.panelVisible = true;
37 | this.focusSearch();
38 | } else if (!this.multiple) {
39 | this.panelVisible = false;
40 | }
41 | },
42 | handleDelete(e) {
43 | if ((e.which === 8 || e.which === 46) && this.searchValue === '') {
44 | this.selection.removeAt(this.selection.length - 1);
45 | const selection = this.selection.toJSON();
46 | const value = selection.map(s => s.key);
47 | const nodes = [];
48 | getTreeNodesByKeys({children:this.treeData}, value, nodes);
49 | avalon.vmodels[this.panelVmId].checkedKeys = value;
50 | this.handleChange({
51 | target: { value: this.multiple ? value.toJSON() : value.toJSON()[0] || '', selection: nodes },
52 | type: 'tree-select'
53 | });
54 | }
55 | },
56 | removeSelection(e, option) {
57 | this.selection.removeAll(o => o.key === option.key);
58 | const selection = this.selection.toJSON();
59 | const value = selection.map(s => s.key);
60 | const nodes = [];
61 | getTreeNodesByKeys({children:this.treeData}, value, nodes);
62 | avalon.vmodels[this.panelVmId].checkedKeys = value;
63 | this.focusSearch();
64 | this.handleChange({
65 | target: { value: this.multiple ? value.toJSON() : value.toJSON()[0] || '', selection: nodes },
66 | type: 'tree-select'
67 | });
68 | },
69 |
70 | // 下拉框下拉列表部分
71 | direction: 'down',
72 | panelWidth: 0,
73 | panelVmId: '',
74 | panelVisible: false,
75 | panelClass: 'ane-tree-select-dropdown',
76 | panelTemplate: require('./ms-tree-select-panel.html'),
77 | handlePanelHide() {
78 | this.panelVisible = false;
79 | },
80 |
81 | // 生命周期
82 | mapValueToSelection(value) {
83 | const nodes = [];
84 | getTreeNodesByKeys({children:this.treeData}, value, nodes);
85 | if (nodes.length) {
86 | this.displayValue = nodes[0].title;
87 | }
88 | avalon.vmodels[this.panelVmId].checkedKeys = value;
89 | this.selection = nodes.map(n => ({ key: n.key, title: n.title }));
90 | return nodes;
91 | },
92 | onInit(event) {
93 | const self = this;
94 |
95 | emitToFormItem(this);
96 | this.$watch('value', v => {
97 | const value = v.toJSON();
98 | const nodes = this.mapValueToSelection(value);
99 | this.handleChange({
100 | target: { value: this.multiple ? value : value[0] || '', selection: nodes },
101 | denyValidate: true,
102 | type: 'tree-select'
103 | });
104 | });
105 |
106 | this.panelVmId = this.$id + '_panel';
107 | const innerVm = getPanelVm(this);
108 | this.$watch('searchValue', debounce(v => {
109 | innerVm.searchValue = v;
110 | }));
111 | this.$watch('multiple', v => {
112 | innerVm.multiple = v;
113 | });
114 | const value = this.value.toJSON();
115 | this.mapValueToSelection(value);
116 | },
117 | onDispose() {
118 | delete avalon.vmodels[this.panelVmId];
119 | }
120 | }
121 | });
122 |
123 | function getTreeNodesByKeys(root, keys, results) {
124 | if (keys.indexOf(root.key) > -1) {
125 | results.push(root);
126 | } else {
127 | for (var i = 0; i < root.children.length; i++) {
128 | getTreeNodesByKeys(root.children[i], keys, results);
129 | }
130 | }
131 | }
--------------------------------------------------------------------------------