├── components ├── ms-input │ ├── index.ts │ ├── ms-input.html │ ├── test │ │ ├── ms-input.test.html │ │ ├── ms-input.test.ts │ │ └── ms-input.test.js │ ├── ms-input.md │ └── ms-input.ts ├── ms-tree │ ├── index.ts │ ├── ms-tree.html │ ├── img │ │ ├── loading.gif │ │ ├── metro.gif │ │ ├── metro.png │ │ └── line_conn.png │ ├── ms-tree.less │ ├── ms-tree.md │ └── ms-tree.ts ├── ms-dialog │ ├── index.ts │ └── ms-dialog.ts ├── ms-trigger │ ├── index.ts │ └── ms-trigger.ts ├── ms-textarea │ ├── index.ts │ ├── ms-textarea.html │ ├── ms-textarea.md │ └── ms-textarea.ts ├── ms-form │ ├── index.ts │ ├── ms-control.md │ ├── utils.ts │ ├── ms-form-item.html │ ├── ms-control.ts │ ├── ms-form.ts │ ├── ms-form-item.ts │ └── ms-form.md ├── ms-menu │ ├── index.ts │ ├── ms-menu.ts │ ├── ms-menu.html │ ├── ms-menu.less │ └── ms-menu.md ├── ms-layout │ ├── index.ts │ ├── ms-layout.ts │ └── ms-layout.less ├── ms-upload │ ├── index.ts │ ├── ms-upload-card.html │ ├── ms-upload-card.ts │ ├── ms-upload-list.ts │ ├── ms-upload-list.html │ ├── ms-upload.html │ ├── test │ │ └── ms-upload.test.html │ ├── ms-upload.md │ └── ms-upload.less ├── ms-calendar │ ├── index.ts │ ├── ms-calendar-year-view.html │ ├── test │ │ └── ms-calendar.test.html │ ├── ms-calendar.html │ ├── ms-calendar-year-view.ts │ └── ms-calendar.less ├── ms-datepicker │ ├── index.ts │ ├── ms-datepicker.html │ ├── ms-datepicker.md │ ├── test │ │ └── ms-datepicker.test.html │ ├── ms-datepicker.ts │ ├── ms-datepicker-panel.html │ └── ms-datepicker.less ├── ms-message │ ├── index.ts │ ├── ms-message.md │ └── ms-message.ts ├── ms-timepicker │ ├── index.ts │ ├── ms-timepicker.md │ ├── ms-timepicker.html │ ├── ms-timepicker-panel.ts │ ├── ms-timepicker-view.html │ ├── ms-timepicker-view.ts │ ├── ms-timepicker.less │ └── ms-timepicker.ts ├── ms-loading │ ├── index.ts │ ├── ms-loading.less │ ├── test │ │ └── ms-loading.test.html │ ├── ms-loading.md │ └── ms-loading-directive.ts ├── ms-notification │ ├── index.ts │ ├── ms-notification.md │ └── ms-notification.ts ├── ms-radio │ ├── index.ts │ ├── ms-radio-group.html │ ├── ms-radio.html │ ├── ms-radio.md │ ├── ms-radio.ts │ ├── ms-radio-group.ts │ ├── test │ │ └── ms-radio.test.html │ └── ms-radio.less ├── ms-select │ ├── index.ts │ ├── ms-select-option.ts │ ├── test │ │ ├── ms-select.test.ts │ │ └── ms-select.test.html │ ├── ms-select-panel.html │ ├── ms-select.html │ ├── ms-select-panel.ts │ ├── ms-select.md │ └── ms-select.less ├── ms-checkbox │ ├── index.ts │ ├── ms-checkbox-group.html │ ├── ms-checkbox.html │ ├── ms-checkbox.md │ ├── ms-checkbox.ts │ ├── ms-checkbox-group.ts │ ├── test │ │ └── ms-checkbox.test.html │ └── ms-checkbox.less ├── ms-tree-select │ ├── index.ts │ ├── ms-tree-select-panel.html │ ├── ms-tree-select.html │ ├── ms-tree-select.md │ ├── ms-tree-select-panel.ts │ └── ms-tree-select.ts ├── ms-table │ ├── ms-table-header.ts │ ├── ms-table.html │ └── ms-table.md ├── ms-pagination │ ├── ms-pagination.html │ ├── ms-pagination.md │ └── ms-pagination.ts └── ms-view │ └── ms-view.js ├── tests ├── nightwatch.js ├── index.html ├── beforeit.js ├── index.js └── router.ts ├── docs ├── components │ └── doc-sidebar │ │ ├── doc-sidebar.html │ │ └── doc-sidebar.ts ├── stores │ └── index.ts ├── index.js ├── index.html └── router.ts ├── nightwatch.conf.js ├── tsconfig.json ├── .travis.yml ├── typings ├── index.d.ts ├── mmRouter.d.ts └── avalon.d.ts ├── .deploy.sh ├── styles └── index.less ├── README.md ├── index.ts ├── nightwatch.json ├── LICENSE ├── CHANGELOG.md ├── .gitignore ├── ane-util.ts ├── package.json ├── karma.conf.js ├── webpack.test.config.js ├── webpack.doc.config.js └── webpack.config.js /components/ms-input/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-input'; -------------------------------------------------------------------------------- /components/ms-tree/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-tree'; -------------------------------------------------------------------------------- /components/ms-dialog/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-dialog'; -------------------------------------------------------------------------------- /components/ms-trigger/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-trigger'; -------------------------------------------------------------------------------- /components/ms-textarea/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-textarea'; -------------------------------------------------------------------------------- /components/ms-tree/ms-tree.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/ms-form/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-form'; 2 | import './ms-form-item'; -------------------------------------------------------------------------------- /components/ms-menu/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-menu.less'; 2 | import './ms-menu'; -------------------------------------------------------------------------------- /components/ms-layout/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-layout.less'; 2 | import './ms-layout'; -------------------------------------------------------------------------------- /components/ms-upload/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-upload'; 2 | import './ms-upload.less'; -------------------------------------------------------------------------------- /components/ms-calendar/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-calendar'; 2 | import './ms-calendar.less'; -------------------------------------------------------------------------------- /components/ms-datepicker/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-datepicker'; 2 | import './ms-datepicker.less'; -------------------------------------------------------------------------------- /components/ms-message/index.ts: -------------------------------------------------------------------------------- 1 | import message from './ms-message'; 2 | export default message; -------------------------------------------------------------------------------- /components/ms-timepicker/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-timepicker'; 2 | import './ms-timepicker.less'; -------------------------------------------------------------------------------- /tests/nightwatch.js: -------------------------------------------------------------------------------- 1 | exports.input = require('../components/ms-input/test/ms-input.test.js'); -------------------------------------------------------------------------------- /components/ms-loading/index.ts: -------------------------------------------------------------------------------- 1 | export { Loading } from './ms-loading-directive'; 2 | import './ms-loading.less'; -------------------------------------------------------------------------------- /components/ms-notification/index.ts: -------------------------------------------------------------------------------- 1 | import notification from './ms-notification'; 2 | export default notification; -------------------------------------------------------------------------------- /components/ms-radio/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-radio'; 2 | import './ms-radio-group'; 3 | import './ms-radio.less'; -------------------------------------------------------------------------------- /components/ms-select/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-select'; 2 | import './ms-select-option' 3 | import './ms-select.less'; -------------------------------------------------------------------------------- /components/ms-tree/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxapp/ane/HEAD/components/ms-tree/img/loading.gif -------------------------------------------------------------------------------- /components/ms-tree/img/metro.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxapp/ane/HEAD/components/ms-tree/img/metro.gif -------------------------------------------------------------------------------- /components/ms-tree/img/metro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxapp/ane/HEAD/components/ms-tree/img/metro.png -------------------------------------------------------------------------------- /components/ms-tree/img/line_conn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxapp/ane/HEAD/components/ms-tree/img/line_conn.png -------------------------------------------------------------------------------- /components/ms-checkbox/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-checkbox'; 2 | import './ms-checkbox-group'; 3 | import './ms-checkbox.less'; -------------------------------------------------------------------------------- /components/ms-tree-select/index.ts: -------------------------------------------------------------------------------- 1 | import './ms-tree-select'; 2 | import './ms-tree-select-panel' 3 | import './ms-tree-select.less'; -------------------------------------------------------------------------------- /docs/components/doc-sidebar/doc-sidebar.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/ms-textarea/ms-textarea.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/ms-input/ms-input.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/ms-tree-select/ms-tree-select-panel.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = (function (settings) { 2 | if (process.platform === 'win32') { 3 | settings.selenium.cli_args['webdriver.chrome.driver'] += '.exe'; 4 | } 5 | return settings; 6 | })(require('./nightwatch.json')); -------------------------------------------------------------------------------- /components/ms-table/ms-table-header.ts: -------------------------------------------------------------------------------- 1 | import * as avalon from 'avalon2'; 2 | 3 | avalon.component('ms-table-header', { 4 | template: '', 5 | soleSlot: 'content', 6 | defaults: { 7 | content: '', 8 | col: '' 9 | } 10 | }); -------------------------------------------------------------------------------- /components/ms-select/ms-select-option.ts: -------------------------------------------------------------------------------- 1 | import * as avalon from 'avalon2'; 2 | 3 | avalon.component('ms-select-option', { 4 | template: ' ', 5 | soleSlot: 'label', 6 | defaults: { 7 | label: '', 8 | value: '', 9 | disabled: false 10 | } 11 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es3", 4 | "module": "commonjs", 5 | "lib": ["es2015", "dom"], 6 | "sourceMap": true, 7 | "allowJs": true 8 | }, 9 | "typeRoots": [ 10 | "typings", 11 | "node_modules/@types" 12 | ] 13 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | env: 5 | global: 6 | - GH_PAGES_DEPLOY: YES 7 | - PATH: node_modules/.bin/:$PATH 8 | - REPO_SLUG: xxapp/ane 9 | install: 10 | - npm i 11 | script: sh .deploy.sh 12 | cache: 13 | directories: 14 | - node_modules 15 | branches: 16 | only: 17 | - master -------------------------------------------------------------------------------- /components/ms-input/test/ms-input.test.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | <ms-form-item :widget="{label:'名字'}"> 4 | <ms-input :widget="{id:'test-value',col:'name',value:@value,$rules:{required:true,message:'请输入名字'}}"></ms-input> 5 | </ms-form-item> 6 | 7 |
{{@json}}
8 |
-------------------------------------------------------------------------------- /components/ms-loading/ms-loading.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/index'; 2 | @loading-prefix-cls: ~"@{ane-prefix}-loading"; 3 | 4 | .@{loading-prefix-cls}-mask { 5 | position: absolute; 6 | background: #ffffff; 7 | filter: alpha(opacity=90); 8 | opacity: .9; 9 | color: #000000; 10 | text-align: center; 11 | z-index: 300; 12 | } -------------------------------------------------------------------------------- /components/ms-form/ms-control.md: -------------------------------------------------------------------------------- 1 | ## 表单控件 2 | 3 | 此组件不应该直接被实例化,只能被其它组件继承。 4 | 5 | ### 组件参数 6 | 7 | | 参数 | 说明 | 类型 | 默认值 | 8 | |-----|-----|-----|-----| 9 | | value | 默认值 | string | '' | 10 | | col | 字段路径 | string | '' | 11 | | placeholder | 占位提示 | string | '' | 12 | | width | 显示宽度 | string | 'x' | 13 | | onChange | 组件值改变回调 | function(e:{target:{value:string},type:string}) | noop | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /components/ms-pagination/ms-pagination.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 上一页 4 | 5 | {{ @current }}/{{ Math.ceil(@total/@pageSize) }} 6 | 7 | 下一页 8 | 9 |
-------------------------------------------------------------------------------- /components/ms-textarea/ms-textarea.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-textarea-basic', 17 | value: '这个家伙很懒,什么也没留下' 18 | }); 19 | ``` 20 | 21 | > 继承 [ms-control 组件](#!/form-control) 的所有参数 -------------------------------------------------------------------------------- /components/ms-checkbox/ms-checkbox-group.html: -------------------------------------------------------------------------------- 1 |
2 | {{option.label}} 12 |
-------------------------------------------------------------------------------- /components/ms-input/ms-input.md: -------------------------------------------------------------------------------- 1 | ## 输入组件 2 | 3 | ### 代码演示 4 | 5 | #### 基本使用 6 | 7 | ``` html 8 |
9 | 10 |
11 | ``` 12 | 13 | ``` js 14 | import * as avalon from 'avalon2'; 15 | import 'ane'; 16 | 17 | const vm = avalon.define({ 18 | $id: 'doc-input-basic', 19 | value: '123' 20 | }); 21 | ``` 22 | 23 | > 继承 [ms-control 组件](#!/form-control) 的所有参数 -------------------------------------------------------------------------------- /components/ms-radio/ms-radio-group.html: -------------------------------------------------------------------------------- 1 |
2 | {{option.label}} 14 |
-------------------------------------------------------------------------------- /docs/stores/index.ts: -------------------------------------------------------------------------------- 1 | export const menu = { 2 | selectedKeys$: Observable(), 3 | openKeys$: Observable() 4 | }; 5 | 6 | function Observable() { 7 | return { 8 | onNextCbList: [], 9 | subscribe(onNext) { 10 | this.onNextCbList.push(onNext); 11 | }, 12 | onNext(value) { 13 | this.onNextCbList.forEach(cb => { 14 | if (typeof cb === 'function') { 15 | cb(value); 16 | } 17 | }); 18 | } 19 | }; 20 | } -------------------------------------------------------------------------------- /components/ms-form/utils.ts: -------------------------------------------------------------------------------- 1 | import { findParentComponent } from '../../ane-util'; 2 | 3 | export function emitToFormItem(vmodel, options = {}): void { 4 | vmodel.$formItem = findParentComponent(vmodel, 'ms-form-item'); 5 | if (vmodel.$formItem === null) { 6 | return; 7 | } 8 | vmodel.$formItem.onFieldChange({ 9 | name: vmodel.col, 10 | rules: vmodel.$rules, 11 | value: vmodel.value.toJSON ? vmodel.value.toJSON() : vmodel.value, 12 | denyValidate: true, 13 | ...options 14 | }); 15 | } -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | // runtime global 2 | 3 | interface MyWindow extends Window { 4 | Promise: Promise, 5 | $, 6 | jQuery, 7 | __REDUX_DEVTOOLS_EXTENSION__ 8 | } 9 | 10 | declare var global: MyWindow 11 | 12 | declare var require: { 13 | (path: string): T; 14 | (paths: string[], callback: (...modules: any[]) => void): void; 15 | ensure: (paths: string[], callback: (require: (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 |
    2 |
  • 4 |
    5 | 6 | {{file.name}} 7 |
    8 | 9 | 上传中 {{file.progress}}% 10 | 11 |
  • 12 |
-------------------------------------------------------------------------------- /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 | 13 | 14 | 15 |
11 |
{{cell.label}}
12 |
16 |
-------------------------------------------------------------------------------- /components/ms-select/test/ms-select.test.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | <ms-form-item :widget="{label:'想看的动漫'}"> 4 | <ms-select :widget="{col:'comic',showSearch:true,mode:'multiple'}"> 5 | <ms-select-option :widget="{value:'onepiece'}">海贼王</ms-select-option> 6 | <ms-select-option :widget="{value:'conna'}">名侦探柯南</ms-select-option> 7 | <ms-select-option :widget="{value:'titan'}">进击的巨人</ms-select-option> 8 | <ms-select-option :widget="{value:'disabled', disabled:true}">禁用</ms-select-option> 9 | <ms-select-option :widget="{value:'onepunchman'}">一拳超人</ms-select-option> 10 | </ms-select> 11 | </ms-form-item> 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 |
6 |  基于 Avalon2 的 组件库 beta 7 |
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 |
13 |
14 |
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 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 |
{{day}}
19 |
{{el.date}}
20 |
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 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 |
6 | 7 | {{el.title}}
14 | 15 |
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 |
2 |
3 |
4 |
    5 |
  • {{hour}}
  • 8 |
9 |
10 |
11 |
    12 |
  • {{minute}}
  • 15 |
16 |
17 |
18 |
    19 |
  • {{second}}
  • 22 |
23 |
24 |
25 |
-------------------------------------------------------------------------------- /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 | <ms-layout-header :widget="{fixed:true}"> 12 | <h3>ANE 组件文档</h3> 13 | </ms-layout-header> 14 | <ms-layout-footer :widget="{fixed:true}"> 15 | <div class="text-center" style="line-height: 30px;"> 16 | <a href="https://github.com/xxapp/ane" target="_blank">Github 仓库地址</a> 17 | </div> 18 | </ms-layout-footer> 19 | <ms-layout :widget="{style:{backgroundColor:'#ececec'}}"> 20 | <ms-layout-sider :widget="{fixed:true}"> 21 | <wbr is="doc-sidebar"/> 22 | </ms-layout-sider> 23 | <ms-layout-content> 24 | <div class="ane-layout-content-wrapper"> 25 | <div ms-html="@currentPage"></div> 26 | </div> 27 | </ms-layout-content> 28 | </ms-layout> 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 |
9 |
    10 |
  • {{@displayValue}}
  • 11 |
  • 12 | {{choice.label}} 13 | 14 |
  • 15 | 24 |
25 | 28 | 38 | 39 |
-------------------------------------------------------------------------------- /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 |
9 |
    10 |
  • {{@displayValue}}
  • 11 |
  • 12 | {{choice.title}} 13 | 14 |
  • 15 | 24 |
25 | 28 | 38 | 39 |
-------------------------------------------------------------------------------- /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 | <ms-form-item :widget="{label:'兴趣'}"> 16 | <ms-radio-group 17 | :widget="{value:@value,col:'gender',options:[ 18 | { label: '男', value: 'M' }, 19 | { label: '女', value: 'F' } 20 | ],$rules:{required:true,message:'请选择性别'}}"> 21 | </ms-radio-group> 22 | </ms-form-item> 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 | <ms-form-item :widget="{label:'兴趣'}"> 16 | <ms-checkbox-group 17 | :widget="{value:@value,col:'hobby',options:[ 18 | { label: '编程', value: 'code' }, 19 | { label: '其他', value: 'other' } 20 | ],$rules:{required:true,type:'array',message:'请选择兴趣'}}"> 21 | </ms-checkbox-group> 22 | </ms-form-item> 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 | <ms-form-item :widget="{label:'入学时间'}"> 16 | <ms-datepicker :widget="{ 17 | placeholder:'请选择入学时间', 18 | col:'date', 19 | format:'YYYY/MM/DD', 20 | startDate:'2017/5/26', 21 | endDate:'2018/7/26', 22 | $rules:{required:true,message:'请选择日期'} 23 | }"></ms-datepicker> 24 | </ms-form-item> 25 | <ms-form-item :widget="{label:'抢购开始时间'}"> 26 | <ms-datepicker :widget="{ 27 | placeholder:'请选择抢购开始时间', 28 | col:'datetime', 29 | startDate:'2017-5-26', 30 | endDate:'2018-7-26', 31 | showTime: true, 32 | $rules:{required:true,message:'请选择日期和时间'} 33 | }"></ms-datepicker> 34 | </ms-form-item> 35 | <ms-form-item :widget="{label:'打卡时间'}"> 36 | <ms-timepicker :widget="{ 37 | placeholder:'请选择打卡时间', 38 | col:'time', 39 | $rules:{required:true,message:'请选择时间'} 40 | }"></ms-timepicker> 41 | </ms-form-item> 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 | <ms-form-item :widget="{label:'附件'}"> 17 | <ms-upload :widget="{ 18 | value:@value, 19 | col:'attachment', 20 | action: @fileUploadUrl, 21 | listType:'text-list', 22 | $rules:{required:true,type:'array',message:'请选择附件'} 23 | }"> 24 | <i class="fa fa-upload"></i>选择文件 25 | </ms-upload> 26 | </ms-form-item> 27 | 28 |
{{@json}}
29 |
30 |
31 | 32 | <ms-form-item :widget="{label:'附件'}"> 33 | <ms-upload :widget="{ 34 | value:@value, 35 | col:'picture', 36 | action: @fileUploadUrl, 37 | listType:'picture-card', 38 | $rules:{required:true,type:'array',message:'请选择附件'} 39 | }"> 40 | <i class="fa fa-upload"></i>选择文件 41 | </ms-upload> 42 | </ms-form-item> 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 | <ms-form-item :widget="{label:'标题'}"> 9 | <ms-input :widget="{col:'title',$rules:{required:true}}"></ms-input> 10 | </ms-form-item> 11 | <ms-form-item :widget="{label:'内容'}"> 12 | <ms-textarea :widget="{col:'content',$rules:{required:true}}"></ms-textarea> 13 | </ms-form-item> 14 | <button type="button" class="btn btn-primary btn-sm" :click="@save">保存</button> 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 | <ms-form-item :widget="{label:'标题:'}"> 60 | <ms-input :widget="{col:'title'}"></ms-input> 61 | </ms-form-item> 62 | <ms-form-item :widget="{label:'内容:'}"> 63 | <ms-input :widget="{col:'content'}"></ms-input> 64 | </ms-form-item> 65 | <button type="button" class="btn btn-primary btn-sm" :click="@save">搜索</button> 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 | <ms-form-item> 9 | <ms-select :widget="{col:'comic'}"> 10 | <ms-select-option :widget="{value:'onepiece'}">海贼王</ms-select-option> 11 | <ms-select-option :widget="{value:'conna'}">名侦探柯南</ms-select-option> 12 | <ms-select-option :widget="{value:'titan'}">进击的巨人</ms-select-option> 13 | <ms-select-option :widget="{value:'disabled', disabled:true}">禁用</ms-select-option> 14 | <ms-select-option :widget="{value:'onepunchman'}">一拳超人</ms-select-option> 15 | </ms-select> 16 | </ms-form-item> 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 |
21 | 22 | 23 | 24 | 25 | {{@currentYear}} 26 | 27 | 28 | 29 | 30 |
31 | 42 |
43 | 44 | 45 | 46 | {{@startOfCentury + '-' + (@startOfCentury + 99)}} 47 | 48 | 49 | 50 |
51 |
52 | 53 | {{@currentMonth}} 54 | {{@currentDay}} 55 | {{@currentYear}} 56 | 57 |
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 | } --------------------------------------------------------------------------------