├── src ├── schema │ ├── cache.js │ ├── messages.js │ ├── format.js │ └── store.js ├── icon │ ├── iconfont.eot │ ├── iconfont.ttf │ ├── iconfont.woff │ ├── iconfont.woff2 │ ├── iconfont.css │ └── index.html ├── collapse-transition.css ├── locales │ ├── index.js │ ├── en.js │ └── zh.js ├── basic │ ├── dropdown-divider.vue │ ├── nav-menu.vue │ ├── panel.vue │ ├── dropdown-item.vue │ ├── vbox.vue │ ├── progress-bar.vue │ ├── sticky.vue │ ├── dialog.vue │ ├── button-group.vue │ ├── nav-menu-item.vue │ ├── alert.vue │ └── dropdown.vue ├── nav │ ├── breadcrumb.vue │ ├── accordion.vue │ ├── breadcrumb-item.vue │ └── tab.vue ├── form │ ├── radio.vue │ ├── select.vue │ ├── checkbox.vue │ ├── form.vue │ ├── index.js │ ├── radio-group.vue │ ├── select-option.vue │ ├── draggable.js │ └── fields │ │ ├── label.vue │ │ ├── check.vue │ │ ├── radio.vue │ │ ├── check-group.vue │ │ ├── field.vue │ │ └── text.vue ├── service │ ├── dropdown.js │ ├── loading-mask.js │ ├── tooltip.js │ ├── notification.js │ ├── loading-mask.vue │ ├── msgbox.js │ ├── notification.vue │ ├── msgbox.vue │ └── tooltip.vue ├── collapse-transition.js ├── data │ └── tree.vue ├── popover.js ├── index.js ├── common.css └── util.js ├── .gitignore ├── docs ├── schema │ ├── first-form.png │ └── README.MD ├── sticky.md ├── progress-bar.md ├── tabs.md ├── form.md ├── tooltip.md ├── slider.md ├── breadcrumb.md ├── alert.md ├── panel.md ├── dialog.md ├── notification.md ├── accordion.md ├── navmenu.md ├── pagination.md ├── loading-mask.md ├── dropdown.md ├── popup-mixin.md ├── button.md ├── msgbox.md ├── form-field.md ├── grid.md └── tree.md ├── examples ├── json │ ├── mapping.json │ └── mapping2.json ├── empty.vue ├── form │ ├── upload.vue │ ├── tags.vue │ ├── schema.js │ ├── editor.vue │ ├── field.vue │ ├── validation.vue │ └── mapping.vue ├── markdown.js ├── index.html ├── nav │ ├── breadcrumb.vue │ ├── accordion.vue │ └── tabs.vue ├── basic │ ├── slider.vue │ ├── alert.vue │ ├── progress-bar.vue │ ├── sticky.vue │ ├── dropdown.vue │ ├── dialog.vue │ ├── nav-menu.vue │ └── button.vue ├── service │ ├── tooltip.vue │ ├── loading-mask.vue │ ├── msgbox.vue │ └── notification.vue ├── data │ ├── pagination.vue │ ├── custom-grid.vue │ ├── custom-grid-test.vue │ └── tree.vue ├── util.js ├── markdown.css ├── index.js └── app.vue ├── .babelrc ├── .npmignore ├── test ├── index.js ├── index.html └── validator.js ├── Makefile ├── icons ├── arrow-down.svg ├── arrow-right.svg ├── info.svg ├── config.json ├── warning.svg ├── arrow-left.svg ├── confirm-circle.svg ├── first.svg ├── last.svg ├── error.svg ├── arrow-up.svg ├── close.svg ├── success.svg ├── date-trigger.svg └── close-circle.svg ├── README.md ├── webpack.config.js └── package.json /src/schema/cache.js: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | coverage 5 | .idea 6 | .jshintrc 7 | /lib/ -------------------------------------------------------------------------------- /src/icon/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElemeFE/vue-desktop/HEAD/src/icon/iconfont.eot -------------------------------------------------------------------------------- /src/icon/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElemeFE/vue-desktop/HEAD/src/icon/iconfont.ttf -------------------------------------------------------------------------------- /src/icon/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElemeFE/vue-desktop/HEAD/src/icon/iconfont.woff -------------------------------------------------------------------------------- /src/icon/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElemeFE/vue-desktop/HEAD/src/icon/iconfont.woff2 -------------------------------------------------------------------------------- /docs/schema/first-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElemeFE/vue-desktop/HEAD/docs/schema/first-form.png -------------------------------------------------------------------------------- /examples/json/mapping.json: -------------------------------------------------------------------------------- 1 | { 2 | "Test1": 1, 3 | "Test2": 2, 4 | "Test3": 3, 5 | "Test4": 4 6 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-1"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } -------------------------------------------------------------------------------- /examples/empty.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | coverage 5 | .idea 6 | .jshintrc 7 | /src/ 8 | /examples/ 9 | /docs/ 10 | /icons/ 11 | /test/ -------------------------------------------------------------------------------- /src/collapse-transition.css: -------------------------------------------------------------------------------- 1 | .collapse-transition { 2 | transition: 0.3s height ease-in-out, 0.3s padding-top ease-in-out, 0.3s padding-bottom ease-in-out; 3 | } -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | chai.should(); 3 | chai.use(require('sinon-chai')); 4 | 5 | require('./schema'); 6 | require('./validator'); 7 | -------------------------------------------------------------------------------- /src/locales/index.js: -------------------------------------------------------------------------------- 1 | import { default as en } from './en'; 2 | import { default as zh } from './zh'; 3 | 4 | export default { 5 | en: en, 6 | zh: zh 7 | }; 8 | -------------------------------------------------------------------------------- /examples/form/upload.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /examples/markdown.js: -------------------------------------------------------------------------------- 1 | var Vue = require('vue'); 2 | 3 | var marked = require('marked'); 4 | 5 | Vue.elementDirective('markdown', { 6 | bind() { 7 | this.el.className = 'markdown'; 8 | this.el.innerHTML = marked(this.el.innerHTML); 9 | } 10 | }); 11 | 12 | export default {}; 13 | -------------------------------------------------------------------------------- /docs/sticky.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Sticky 用于创建顶部固定组件。 4 | 5 | # Usage 6 | 7 | 使用 d-sticky 标签即可创建 Sticky 组件: 8 | 9 | ```HTML 10 | 固定在顶部 11 | ``` 12 | 13 | # Properties 14 | 15 | Sticky 目前可以使用的属性如下: 16 | 17 | | Property | Description | 18 | | ---- | ---- | 19 | | top | 组件固定时与视口顶部的距离, 单位为px, 默认值为0。 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue Webpack Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/basic/dropdown-divider.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | -------------------------------------------------------------------------------- /examples/json/mapping2.json: -------------------------------------------------------------------------------- 1 | { 2 | "1": { 3 | "11": 11, 4 | "12": 12, 5 | "13": 13 6 | }, 7 | "2": { 8 | "21": 21, 9 | "22": 22, 10 | "23": 23 11 | }, 12 | "3": { 13 | "31": 31, 14 | "32": 32, 15 | "33": 33 16 | }, 17 | "4": { 18 | "41": 41, 19 | "42": 42, 20 | "43": 43 21 | } 22 | } -------------------------------------------------------------------------------- /src/nav/breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | @if [ ! -f "$$(which istanbul)" ]; then npm install --registry=https://registry.npm.taobao.org -g browserify watchify mocha istanbul; fi 3 | @npm install --registry=https://registry.npm.taobao.org 4 | 5 | dev: install 6 | @npm run dev 7 | 8 | test: install 9 | @npm run test 10 | 11 | coverage: install 12 | @npm run coverage 13 | -------------------------------------------------------------------------------- /src/nav/accordion.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /examples/nav/breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /examples/basic/slider.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /src/schema/messages.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'default': '字段{label}校验失败', 3 | required: '{label}为必填项', 4 | pattern: '{label}的格式不正确', 5 | whitespace: '{label}不能为空', 6 | 'enum': '{label}必须为以下值中的一个: {list}', 7 | length: { 8 | 'min': '{label}至少有{min}个字符', 9 | 'max': '{label}最多有{max}个字符', 10 | 'range': '{label}的长度应该大于等于{min}并且小于等于{max}' 11 | }, 12 | range: { 13 | 'min': '{label}应该大于{min}', 14 | 'max': '{label}应该小于{max}', 15 | 'range': '{label}应该介于{min}和{max}之间' 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /examples/service/tooltip.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /docs/progress-bar.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Progress-bar 组件用于创建进度条。 4 | 5 | # Usage 6 | 7 | 使用 d-progress-bar 标签即可创建 Progress-bar 组件: 8 | 9 | ```HTML 10 | 11 | ``` 12 | 13 | # Properties 14 | 15 | Slider 目前可以使用的属性如下: 16 | 17 | | Property | Description | 18 | | ---- | ---- | 19 | | percent | 进度条的百分值, 默认值为0。 | 20 | | type | 进度条的颜色主题, 可设置为"success", "info", "warning", "error", 默认值为 "info"。 | 21 | | showPercent | 是否在进度条的右侧显示百分值, Boolean 类型,默认值为 true。 | 22 | | barHeight | 进度条的高度, 单位为px, 默认值为15。 | -------------------------------------------------------------------------------- /docs/tabs.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | tabs 是一个常见的标签页组件,使用方式如下: 4 | 5 | ```Vue 6 | 7 | 8 | 9 | ``` 10 | 11 | ## Tabs 12 | 13 | 通过 d-tabs 在 .vue 中定义一个 Tabs 组件,Tabs 目前没有提供属性支持,后续会陆续添加。 14 | 15 | ## Tab 16 | 17 | 通过 d-tab 在 Tabs 中添加一个 Tab 组件,Tab 目前可以使用的属性如下: 18 | 19 | | Property | Description | 20 | | ---- | ---- | 21 | | title | Tab 上显示的标题。 | 22 | | icon | Tab 上显示的 icon 使用的 className。 | 23 | | disabled | Tab 是否处于 disabled 状态,Boolean 类型,默认值为 false。 | 24 | | closable | Tab 是否可以关闭,Boolean 类型,默认值为 false。| -------------------------------------------------------------------------------- /examples/nav/accordion.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | -------------------------------------------------------------------------------- /docs/form.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Form 可以定义列数,并为 FormField 定义一些公用属性。 4 | 5 | # Usage 6 | 7 | 使用 d-form 即可创建一个 Form,例子如下: 8 | 9 | ```HTML 10 | 11 | ... 12 | 13 | ``` 14 | 15 | # Properties 16 | 17 | Form 可以使用的属性如下 18 | 19 | | Property | Description | 20 | | ---- | ---- | 21 | | cols | Form 显示的列数,数值类型,默认值为1。 | 22 | | schema | Form 中的 FormField 使用的 Schema。 | 23 | | model | Form 中的 FormField 使用的 model。 | 24 | | labelWidth | Form 中的 FormField 的 labelWidth 的默认值。| 25 | | labelSuffix | Form 中的 FormField 的 labelSuffix 的默认值。| 26 | | editorWidth | Form 中的 FormField 的 editorWidth 的默认值。| -------------------------------------------------------------------------------- /examples/basic/alert.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | -------------------------------------------------------------------------------- /examples/basic/progress-bar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | -------------------------------------------------------------------------------- /icons/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 16 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/basic/sticky.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 26 | 27 | -------------------------------------------------------------------------------- /examples/form/tags.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /docs/tooltip.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Tooltip 是 vue-desktop 提供的一个directive,并不是一个组件 不能单独实例化。 4 | 5 | 6 | # Usage 7 | 在需要显示 tooltip 的组件上添加 v-tooltip 这个属性,即可激活这个功能。在 v-tooltip 之外,你还可以定义 tooltip-placement、tooltip-content、tooltip-delay、tooltip-trigger 等属性。 8 | 9 | ```HTML 10 | Right Tooltip 11 | ``` 12 | 13 | # Properties 14 | 请在使用的时候为属性添加上 tooltip- 前缀。 15 | 16 | | Property | Description | 17 | | ---- | ---- | 18 | | placement | Tooltip 显示的位置,默认值为 bottom。 | 19 | | content | Tooltip 中显示的内容。 | 20 | | delay | Tooltip 打开的延时,默认无延时。 | 21 | | trigger | Tooltip 触发的方式,默认为 mouseenter,可选值 mouseenter、click、focus。 | 22 | -------------------------------------------------------------------------------- /examples/data/pagination.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /docs/slider.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Slider 组件用于创建滑动条。 4 | 5 | # Usage 6 | 7 | 使用 d-slider 标签即可创建 Slider 组件: 8 | 9 | ```HTML 10 | 11 | ``` 12 | 13 | # Properties 14 | 15 | Slider 目前可以使用的属性如下: 16 | 17 | | Property | Description | 18 | | ---- | ---- | 19 | | min | 滑动条的最小值, 默认值为0。 | 20 | | max | 滑动条的最大值, 默认值为100。 | 21 | | step | 滑动条的步长, 默认值为1。 | 22 | | defaultValue | 滑动条的初始值, 默认等于 min 属性的值。 | 23 | | showInput | 是否在滑动条右侧显示一个输入框, Boolean 类型,默认值为 false。设置为 true 时, 可通过该输入框控制滑动条的值。 | 24 | 25 | 当滑动条的值发生变化时, 会触发 @change 事件, 调用相应的回调函数, 该回调函数的参数为滑动条的值。绑定事件的方法如下: 26 | 27 | ```HTML 28 | 29 | ``` -------------------------------------------------------------------------------- /icons/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/form/radio.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/service/dropdown.js: -------------------------------------------------------------------------------- 1 | var dropdowns = []; 2 | 3 | document.addEventListener('click', function(event) { 4 | dropdowns.forEach(function(dropdown) { 5 | var target = event.target; 6 | if (!dropdown || !dropdown.$el) return; 7 | if (target === dropdown.$el || dropdown.$el.contains(target)) { 8 | return; 9 | } 10 | dropdown.onDocumentClick && dropdown.onDocumentClick(event); 11 | }); 12 | }); 13 | 14 | export default { 15 | open(instance) { 16 | if (instance) { 17 | dropdowns.push(instance); 18 | } 19 | }, 20 | 21 | close(instance) { 22 | var index = dropdowns.indexOf(instance); 23 | if (index !== -1) { 24 | dropdowns.splice(instance, 1); 25 | } 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /icons/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/breadcrumb.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Breadcrumb 组件用于创建面包屑导航。 4 | 5 | # Usage 6 | 7 | 使用 d-breadcrumb 和 d-breadcrumb-item 标签即可创建 Breadcrumb 组件: 8 | 9 | ```HTML 10 | 11 | 首页 12 | Tabs 13 | Accordion 14 | Breadcrumb 15 | 16 | ``` 17 | 18 | # Properties 19 | 20 | d-breadcrumb-item 目前可以使用的属性如下: 21 | 22 | | Property | Description | 23 | | ---- | ---- | 24 | | link | 链接地址, 若不赋值则该项不可点击。 | 25 | 26 | Breadcrumb 组件会自动判断当前页面的 URL 是否与某个 d-breadcrumb-item 的 link 属性相同, 若相同则会为该 d-breadcrumb-item 添加一些样式, 用来提示用户当前所在的位置。 -------------------------------------------------------------------------------- /examples/service/loading-mask.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | -------------------------------------------------------------------------------- /src/basic/nav-menu.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /docs/alert.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Alert 用于向用户显示警告信息。 4 | 5 | # Usage 6 | 7 | 使用 d-alert 标签即可创建一个 Alert 组件: 8 | 9 | ```HTML 10 | 11 | 消息提示的示例 12 | 13 | ``` 14 | 15 | # Properties 16 | 17 | Alert 目前可以使用的属性如下: 18 | 19 | | Property | Description | 20 | | ---- | ---- | 21 | | title | Alert 上显示的标题, 默认值为"提示"。 | 22 | | type | Alert 的类型, 共有"success", "info", "warning", "error"四种, 默认值为"info"。 | 23 | | closable | Alert 是否可以关闭,Boolean 类型,默认值为 false。 | 24 | | closeText | Alert 关闭按钮的文本(若 Alert 可关闭),不为该属性赋值则显示×。 | 25 | 26 | Alert 组件在关闭后会触发 close 事件, 绑定事件的方法如下: 27 | 28 | ```HTML 29 | 30 | 绑定事件的示例 31 | 32 | ``` -------------------------------------------------------------------------------- /src/schema/format.js: -------------------------------------------------------------------------------- 1 | var RE_ARGS = /\{([0-9a-zA-Z]+)\}/g; 2 | 3 | export default function(string, ...args) { 4 | if (!string) return null; 5 | if (arguments.length === 2 && typeof arguments[1] === 'object') { 6 | args = arguments[1]; 7 | } 8 | 9 | if (!args || !args.hasOwnProperty) { 10 | args = {}; 11 | } 12 | 13 | return string.replace(RE_ARGS, function replaceArg(match, i, index) { 14 | var result; 15 | 16 | if (string[index - 1] === '{' && string[index + match.length] === '}') { 17 | return i; 18 | } else { 19 | result = args.hasOwnProperty(i) ? args[i] : null; 20 | if (result === null || result === undefined) { 21 | return ''; 22 | } 23 | 24 | return result; 25 | } 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /docs/panel.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Panel 是一个面板组件,可以显示内容区的标题,折叠展开,并可以在标题栏添加其他组件。 4 | 5 | # Usage 6 | 7 | 使用 d-panel 即可以创建一个 Panel 组件: 8 | 9 | ```HTML 10 | 11 | ... 12 | 13 | ``` 14 | 15 | 如果要在标题右侧添加一些按钮,则可以使用 Vue 的 slot 功能: 16 | ```HTML 17 | 18 | 19 | 20 | 测试 21 | 测试 22 | 测试 23 | 24 | 25 | 测试 26 | 27 | ... 28 | 29 | ``` 30 | 31 | # Properties 32 | 33 | Panel 目前只有 title 一个属性,其他属性会陆续添加。 34 | 35 | | Property | Description | 36 | | ---- | ---- | 37 | | title | Panel 显示的标题。 | 38 | | expaned | Panel 是否展开,Boolean 类型,默认值为 true。 | -------------------------------------------------------------------------------- /examples/service/msgbox.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | -------------------------------------------------------------------------------- /docs/dialog.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Dialog 是一个对话框组件,在 .vue 文件通过 d-dialog 标签来定义。 4 | 5 | ```HTML 6 | 7 | …… 8 | 9 | ``` 10 | 11 | # Usage 12 | 13 | Dialog 目前拥有的属性如下: 14 | 15 | | Property | Description | 16 | | ---- | ---- | 17 | | title | Dialog 显示的标题。 | 18 | | width | Dialog 的宽度,数值类型。 | 19 | | visible | Dialog 是否可见,Boolean 类型,默认值为 false。一般情况下,请使用 :visible.sync 来和 vm 中的某一个属性做双向绑定。 | 20 | 21 | ## dialog footer 22 | 23 | 如果要在 dialog 的底部定义 Button,请这么定义: 24 | 25 | ```HTML 26 | 27 | …… 28 |
29 | OK 30 | Cancel 31 |
32 |
33 | ``` -------------------------------------------------------------------------------- /docs/notification.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | Notification 用来弹出通知消息。 3 | 4 | # 用法 5 | 6 | ## 引用 7 | 8 | ```JavaScript 9 | import { Notification } from 'vue-desktop' 10 | ``` 11 | 12 | ## 用法 13 | 14 | 使用方法如下: 15 | 16 | Notification({ 17 | title: '通知', 18 | message: '通知信息', 19 | type: 'error', 20 | duration: 3, 21 | callback: function(instance) { 22 | console.log(instance.id + ' is closed.'); 23 | } 24 | }); 25 | 26 | # 参数列表 27 | 目前 Notification 支持如下参数: 28 | 29 | - title: Notification 显示的标题。 30 | - message: Notification 显示的内容。 31 | - type: Notification 显示的图标的类型,可选值:success、info、warning、error,默认值为 info。 32 | - duration: Notification 显示的时长,单位为秒,默认值为 5。设置为 0 则会一直显示,直到用户手动关闭。 33 | - callback: Notification 关闭后的回调函数,参数 instance 为对应于该 Notification 的 Vue 实例对象,它具有上述四个属性,instance.id 为唯一性标识。 -------------------------------------------------------------------------------- /icons/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": { 3 | "default": [ 4 | "arrow-down.svg", 5 | "arrow-left.svg", 6 | "arrow-right.svg", 7 | "arrow-up.svg", 8 | "error.svg", 9 | "info.svg", 10 | "success.svg", 11 | "warning.svg", 12 | "close-circle.svg", 13 | "close.svg", 14 | "confirm-circle.svg", 15 | "date-trigger.svg", 16 | "last.svg", 17 | "first.svg" 18 | ], 19 | "exclude": [], 20 | "new": [] 21 | }, 22 | "useless": [ 23 | "arrow-down.svg", 24 | "arrow-left.svg", 25 | "arrow-right.svg", 26 | "arrow-up.svg", 27 | "close-circle.svg", 28 | "close.svg", 29 | "confirm-circle.svg", 30 | "date-trigger.svg", 31 | "error.svg", 32 | "info.svg", 33 | "success.svg", 34 | "warning.svg" 35 | ] 36 | } -------------------------------------------------------------------------------- /icons/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/form/select.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | 17 | -------------------------------------------------------------------------------- /src/locales/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | validator: { 3 | required: 'Field {label} is required.', 4 | pattern: 'Field {label}\'s format is not correct.', 5 | length: 'Field {label}\'s length is not correct.' 6 | }, 7 | datepicker: { 8 | today: 'Today', 9 | clear: 'Clear', 10 | confirm: 'OK', 11 | weeks: { 12 | sun: 'Sun', 13 | mon: 'Mon', 14 | tue: 'Tue', 15 | wed: 'Wed', 16 | thu: 'Thu', 17 | fri: 'Fri', 18 | sat: 'Sat' 19 | }, 20 | months: { 21 | jan: 'January', 22 | feb: 'February', 23 | mar: 'March', 24 | apr: 'April', 25 | may: 'May', 26 | jun: 'June', 27 | jul: 'July', 28 | aug: 'August', 29 | sep: 'September', 30 | oct: 'October', 31 | nov: 'November', 32 | dec: 'December' 33 | } 34 | }, 35 | alert: { 36 | title: 'Tip' 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /examples/basic/dropdown.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /examples/form/schema.js: -------------------------------------------------------------------------------- 1 | import { Schema } from '../../src/index.js'; 2 | 3 | export default new Schema({ 4 | nickname: { 5 | label: 'NickName', 6 | required: true 7 | }, 8 | 9 | password: { 10 | label: 'Password', 11 | required: true 12 | }, 13 | 14 | birthday: { 15 | label: 'Birthday', 16 | type: 'date', 17 | default() { 18 | return new Date(); 19 | } 20 | }, 21 | 22 | sex: { 23 | label: 'Sex', 24 | default: 'female', 25 | trueValue: 'male', 26 | falseValue: 'female' 27 | }, 28 | 29 | comment: { 30 | label: 'Comment' 31 | }, 32 | 33 | count: { 34 | label: 'Number', 35 | type: 'integer' 36 | }, 37 | 38 | simpleMapping: { 39 | label: 'Mapping', 40 | mapping: { 41 | 'Simple-0': 0, 42 | 'Simple-1': 1, 43 | 'Simple-2': 2, 44 | 'Simple-3': 3, 45 | 'Simple-4': 4, 46 | 'Simple-5': 5 47 | } 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /icons/arrow-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /icons/confirm-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /icons/first.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shape 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /icons/last.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shape 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/service/loading-mask.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | var LoadingMask = Vue.extend(require('./loading-mask.vue')); 3 | 4 | var instance; 5 | var service = { 6 | open(text) { 7 | if (!instance) { 8 | instance = new LoadingMask({ 9 | el: document.createElement('div') 10 | }); 11 | } 12 | instance.text = text; 13 | Vue.nextTick(() => { 14 | instance.open(); 15 | }); 16 | }, 17 | 18 | close() { 19 | if (instance) { 20 | Vue.nextTick(() => { 21 | instance.close(); 22 | }); 23 | } 24 | } 25 | }; 26 | 27 | Vue.directive('action', { 28 | bind() { 29 | var el = this.el; 30 | el.addEventListener('click', () => { 31 | var result = this.vm.$get(this.expression); 32 | if (result && result.then) { 33 | service.open(); 34 | result.catch(() => void 0).then(() => { 35 | service.close(); 36 | }); 37 | } 38 | }, false); 39 | } 40 | }); 41 | 42 | export default service; 43 | -------------------------------------------------------------------------------- /icons/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/util.js: -------------------------------------------------------------------------------- 1 | const matchHtmlRegExp = /["'&<>]/; 2 | const ESCAPE_CHAR_MAP = { 3 | 34: '"', 4 | 38: '&', 5 | 39: ''', 6 | 60: '<', 7 | 62: '>' 8 | }; 9 | 10 | var escapeHTML = function (string) { 11 | string = '' + string; 12 | var match = matchHtmlRegExp.exec(string); 13 | 14 | if (!match) { 15 | return string; 16 | } 17 | 18 | var html = ''; 19 | var lastIndex = 0; 20 | var escape, index; 21 | 22 | for (index = match.index; index < string.length; index++) { 23 | var charCode = string.charCodeAt(index); 24 | if (ESCAPE_CHAR_MAP[charCode]) { 25 | escape = ESCAPE_CHAR_MAP[charCode]; 26 | } else { 27 | continue; 28 | } 29 | 30 | if (lastIndex !== index) { 31 | html += string.substring(lastIndex, index); 32 | } 33 | 34 | lastIndex = index + 1; 35 | html += escape; 36 | } 37 | 38 | return lastIndex !== index ? html + string.substring(lastIndex, index) : html; 39 | }; 40 | 41 | export default { 42 | escapeHTML: escapeHTML 43 | }; -------------------------------------------------------------------------------- /icons/arrow-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/schema/store.js: -------------------------------------------------------------------------------- 1 | import { default as cache } from './cache'; 2 | import { default as Schema } from './index'; 3 | import { default as validators } from './validators'; 4 | 5 | export default { 6 | getSchema(name) { 7 | return cache[name]; 8 | }, 9 | 10 | defineSchema(name, config) { 11 | if (typeof name !== 'string') { 12 | throw new Error('name is required when define a schema.'); 13 | } 14 | 15 | if (typeof config !== 'object') { 16 | throw new Error('config should be an object.'); 17 | } 18 | 19 | var result = new Schema(config); 20 | 21 | cache[name] = result; 22 | 23 | return result; 24 | }, 25 | 26 | removeSchema(name) { 27 | cache[name] = null; 28 | delete cache[name]; 29 | }, 30 | 31 | defineValidator(name, fn) { 32 | if (typeof name !== 'string' || typeof fn !== 'function') return; 33 | 34 | validators[name] = fn; 35 | }, 36 | 37 | getValidator(name) { 38 | if (typeof name !== 'string') return null; 39 | 40 | return validators[name]; 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /docs/accordion.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Accordion 组件用于创建手风琴导航。 4 | 5 | # Usage 6 | 7 | 使用 d-accordion 和 d-accordion-panel 标签即可创建 Accordion 组件: 8 | 9 | ```HTML 10 | 11 | 12 | 这是标题一的内容 13 | 14 | 15 | 这是标题二的内容
这是标题二的内容 16 |
17 | 18 | 这是标题三的内容
这是标题三的内容
这是标题三的内容 19 |
20 | 21 | 这是标题四的内容 22 | 23 |
24 | ``` 25 | 26 | # Properties 27 | 28 | d-accordion 目前可以使用的属性如下: 29 | 30 | | Property | Description | 31 | | ---- | ---- | 32 | | type | 手风琴的颜色主题, 可设置为"default" 或 "transparent", 默认值为 "default"。当设置为 "transparent" 时, 组件背景色为透明, 用户可为其添加自定义的样式。 | 33 | 34 | d-accordion-panel 目前可以使用的属性如下: 35 | 36 | | Property | Description | 37 | | ---- | ---- | 38 | | title | 面板的标题文本 | 39 | | active | 添加 active 属性可让面板处于激活状态。 | 40 | | disabled | 添加 disabled 属性可让面板处于不可用状态。 | 41 | -------------------------------------------------------------------------------- /src/form/checkbox.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | 19 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Vue Desktop 2 | 3 | A UI library for building admin panel website. 4 | 5 | ## Install 6 | 7 | ```Bash 8 | npm install vue vue-i18n vue-desktop 9 | ``` 10 | 11 | ## Usage 12 | 13 | ### All components 14 | 15 | Import all components provided by vue-desktop: 16 | 17 | ```JavaScript 18 | require('vue-desktop') 19 | ``` 20 | 21 | Or 22 | 23 | ```JavaScript 24 | import components from 'vue-desktop' 25 | ``` 26 | 27 | ### Import one component 28 | 29 | Replace src to lib in source code. 30 | 31 | ```JavaScript 32 | export default { 33 | components: { 34 | GridColumn: require('vue-desktop/lib/data/grid-column.vue'), 35 | Grid: require('vue-desktop/lib/data/grid.vue') 36 | } 37 | }; 38 | ``` 39 | 40 | ```HTML 41 | 42 | 43 | 44 | 45 | 46 | ``` 47 | 48 | ## Dev 49 | 50 | ```Bash 51 | make dev 52 | ``` 53 | 54 | ## Examples 55 | 56 | After npm run dev, visit http://localhost:8088/examples. 57 | 58 | ## License 59 | MIT. -------------------------------------------------------------------------------- /src/form/form.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | -------------------------------------------------------------------------------- /examples/basic/dialog.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | -------------------------------------------------------------------------------- /src/locales/zh.js: -------------------------------------------------------------------------------- 1 | export default { 2 | validator: { 3 | default: '字段{label}校验失败', 4 | required: '{label}为必填项', 5 | pattern: '{label}的格式不正确', 6 | whitespace: '{label}不能为空', 7 | enum: '{label}必须为以下值中的一个: {list}', 8 | length: { 9 | min: '{label}至少有{min}个字符', 10 | max: '{label}最多有{max}个字符', 11 | range: '{label}的长度应该大于等于{min}并且小鱼等于{max}' 12 | }, 13 | range: { 14 | min: '{label}应该大于{min}', 15 | max: '{label}应该小于{max}', 16 | range: '{label}应该介于{min}和{max}之间' 17 | } 18 | }, 19 | datepicker: { 20 | today: '今天', 21 | clear: '清空', 22 | confirm: '确定', 23 | weeks: { 24 | sun: '日', 25 | mon: '一', 26 | tue: '二', 27 | wed: '三', 28 | thu: '四', 29 | fri: '五', 30 | sat: '六' 31 | }, 32 | months: { 33 | jan: '一月', 34 | feb: '二月', 35 | mar: '三月', 36 | apr: '四月', 37 | may: '五月', 38 | jun: '六月', 39 | jul: '七月', 40 | aug: '八月', 41 | sep: '九月', 42 | oct: '十月', 43 | nov: '十一月', 44 | dec: '十二月' 45 | } 46 | }, 47 | alert: { 48 | title: '提示' 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /src/service/tooltip.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | var Tooltip = Vue.extend(require('./tooltip.vue')); 3 | 4 | Vue.directive('tooltip', { 5 | params: ['tooltip-content', 'tooltip-placement', 'tooltip-trigger', 'tooltip-delay'], 6 | paramWatchers: { 7 | tooltipContent(val) { 8 | if (this.instance) { 9 | this.instance.content = val; 10 | } 11 | } 12 | }, 13 | bind() { 14 | var el = this.el; 15 | var placement = this.params.tooltipPlacement; 16 | var content = this.params.tooltipContent; 17 | var trigger = this.params.tooltipTrigger || 'mouseenter'; 18 | var delay = parseInt(this.params.tooltipDelay, 10); 19 | 20 | if (trigger === 'mouseenter' && isNaN(delay)) { 21 | delay = 300; 22 | } 23 | 24 | if (!placement) placement = 'bottom'; 25 | 26 | this.instance = new Tooltip({ 27 | el: document.createElement('div') 28 | }); 29 | 30 | this.instance.placement = placement; 31 | this.instance.content = content; 32 | this.instance.trigger = trigger; 33 | this.instance.target = el; 34 | this.instance.openDelay = delay; 35 | this.instance.bindTarget(); 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /docs/navmenu.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | NavMenu 是一个导航菜单,可以支持多级菜单。 4 | 5 | # Usage 6 | 7 | 使用 d-nav-menu 即可创建一个 NavMenu 组件,在 NavMenu 里面使用 d-nav-menu-item 即可定义菜单项。 8 | 9 | ```HTML 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ``` 23 | 24 | # Properties 25 | 26 | NavMenu 的属性如下: 27 | | Property | Description | 28 | | ---- | ---- | 29 | | exclusive | 是否排他展开,Boolean 类型,默认值为 true。 这个属性只对第一级的 NavMenuItem 起作用,如果是二级、三级,则需要指定子 NavMenu 的 exclusive 的属性。 | 30 | 31 | 32 | NavMenuItem 的属性如下: 33 | 34 | | Property | Description | 35 | | ---- | ---- | 36 | | text | 显示的文本。 | 37 | | path | Item 对应的路径,类似使用 v-link 中的 path。 | 38 | | exact | 是否路径完全匹配的时候才高亮 NavMenuItem,Boolean 类型,默认值为 false。 | 39 | | expanded | 是否展开,Boolean 类型,默认值为 false。 | 40 | | exclusive | 是否排他展开,Boolean 类型,默认值为 false。 | 41 | 42 | -------------------------------------------------------------------------------- /icons/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/form/index.js: -------------------------------------------------------------------------------- 1 | import Form from './form.vue'; 2 | import Radio from './radio.vue'; 3 | import TextEditor from './text-editor.vue'; 4 | import Checkbox from './checkbox.vue'; 5 | import Tags from './tags.vue'; 6 | 7 | import RadioGroup from './radio-group.vue'; 8 | import DatePicker from './date-picker.vue'; 9 | import TimePicker from './time-picker.vue'; 10 | import Select from './select.vue'; 11 | import Option from './select-option.vue'; 12 | 13 | import Field from './fields/field.vue'; 14 | import TextField from './fields/text.vue'; 15 | import LabelField from './fields/label.vue'; 16 | import CheckboxField from './fields/check.vue'; 17 | import SelectField from './fields/select.vue'; 18 | import RadiogroupField from './fields/radio.vue'; 19 | import CheckgroupField from './fields/check-group.vue'; 20 | 21 | import Upload from './upload.vue'; 22 | 23 | export default { 24 | Form, 25 | 26 | DatePicker, 27 | TimePicker, 28 | 29 | Select, 30 | Option, 31 | Tags, 32 | TextEditor, 33 | Checkbox, 34 | Radio, 35 | RadioGroup, 36 | 37 | Field, 38 | TextField, 39 | LabelField, 40 | CheckboxField, 41 | SelectField, 42 | RadiogroupField, 43 | CheckgroupField, 44 | 45 | Upload 46 | }; 47 | -------------------------------------------------------------------------------- /icons/success.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/pagination.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Pagination 是一个分页组件,可以实现常见的分页功能。 4 | 5 | # Usage 6 | 7 | 使用 d-pagination 标签即可创建一个 Pagination 组件: 8 | 9 | ```HTML 10 | 11 | ``` 12 | 13 | # Properties 14 | 15 | Pagination 目前可用的属性如下: 16 | 17 | | Property | Description | 18 | | ---- | ---- | 19 | | pageSize | 每一页数据的长度,默认值为10。 | 20 | | itemCount | 数据的总数目,默认值为0。 | 21 | | currentPage | 当前是第几页,没有默认值。| 22 | | layout | 分页组件中内容的排列,默认值为: first, prev, manual, next, last, slot, ->, info。详细设置见下面的表格。 | 23 | | pageSizeList | 在显示 List 的情况下,所有可切换的 pageSize 的大小。数组类型,默认值为 [ 10, 20, 30, 40, 50, 100 ]。| 24 | 25 | layout 的所有可选项: 26 | 27 | | Property | Description | 28 | | ---- | ---- | 29 | | first, last | 第一个和最后一个的按钮。| 30 | | prev, next | 上一页和最后一页的按钮。| 31 | | manual | 显示 第 a 页, 共 b 页的信息,用户可以修改 a 的值。| 32 | | list | 显示 pageSizeList,就是用户可以修改的 pageSize 的列表。| 33 | | slot | Vue 中的 slot 的位置。| 34 | | -> | 左右分割的标识,-> 左侧的内容会显示在左侧,-> 右侧的内容会显示在右侧。| 35 | | info | 显示共有多少数据,现在显示的数据的范围。| 36 | | pager | 互联网风格的分页显示。| 37 | 38 | Pagination 在用户点击页码切换 currentPage 的时候,会触发 current-change 事件,可以这么绑定该事件: 39 | 40 | ```HTML 41 | 42 | ``` 43 | -------------------------------------------------------------------------------- /src/basic/panel.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 35 | 36 | -------------------------------------------------------------------------------- /docs/loading-mask.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | LoadingMask 是 vue-desktop 提供的一个 Service,用来在用户提交数据的时候,页面上显示一个半透明的层,阻止用户的进一步操作。 4 | 5 | # Usage 6 | 7 | LoadingMask 有两种使用方式: 8 | 1. 使用 API 手动调用。 9 | 2. 使用 Directive。 10 | 11 | ## 手动调用 12 | 13 | LoadingMask 只提供了两个方法:open、close。open 用来打开 LoadingMask,close 用来关闭 LoadingMask。 14 | 15 | ```JavaScript 16 | import { LoadingMask } from 'vue-desktop' 17 | LoadingMask.open(); 18 | setTimeout(function() { 19 | LoadingMask.close(); 20 | }, 1000); 21 | ``` 22 | 23 | ## 使用 Directive 24 | 25 | 使用 Directive 需要用户在自己的方法中返回一个 Promise: 26 | 1. 在 Promise 返回之后,打开 LoadingMask。 27 | 1. 在 Promise resolve 或者 reject 之后,关闭 LoadingMask。 28 | 29 | 假设目前有这么一个 Button,需要在 doAction 的时候显示一个 LoadingMask,在 doAction 中的行为执行结束的时候,隐藏 LoadingMask。HTML 中是这么定义的: 30 | 31 | ```HTML 32 | Do Something need stop user action 33 | ``` 34 | 35 | 那么如果要想系统智能的显示隐藏 LoadingMask,修改这段 HTML 为如下内容: 36 | ```HTML 37 | Show Loading Mask By Directive 38 | ``` 39 | 40 | doAction 的代码如下: 41 | ```JavaScript 42 | var doAction = function() { 43 | return new Promise(function(resolve) { 44 | setTimeout(function() { 45 | resolve(1); 46 | }, 1000); 47 | }); 48 | } 49 | ``` 50 | 51 | ## 修改显示文案 52 | LoadingMask.text = '加载中'; 53 | -------------------------------------------------------------------------------- /examples/service/notification.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /src/form/radio-group.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /docs/dropdown.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Dropdown 组件用于创建下拉菜单。 4 | 5 | # Usage 6 | 7 | 使用 d-dropdown-button, d-dropdown-item, d-dropdown-divider 标签即可创建 Dropdown 组件, d-dropdown-button 创建下拉按钮, d-dropdown-item 创建菜单项, d-dropdown-divider 创建菜单项之间的分割线: 8 | 9 | ```HTML 10 | 11 | 第一个菜单项 12 | 第二个菜单项 13 | 第三个菜单项(不可用) 14 | 15 | 第四个菜单项 16 | 17 | ``` 18 | 19 | # Properties 20 | 21 | d-dropdown-button 目前可以使用的属性如下: 22 | 23 | | Property | Description | 24 | | ---- | ---- | 25 | | title | 显示在下拉按钮上的文字。 | 26 | | trigger | 下拉菜单的触发方式, 设置为 "click" 时通过点击触发, 设置为 "mouseenter" 时通过鼠标移入触发。触发默认值为 "click"。 | 27 | | position | 下拉菜单相对于下拉按钮的的位置, 可设置为 "top", "bottom", "left" 或 "right", 默认值为 "bottom"。 | 28 | 29 | d-dropdown-item 目前可以使用的属性如下: 30 | 31 | | Property | Description | 32 | | ---- | ---- | 33 | | disabled | 添加 disabled 属性即可让菜单项处于不可选状态。 | 34 | | name | 用户选择某个菜单项时, 传向回调函数的值。 | 35 | 36 | 当用户选择某个菜单项时, 会触发 @select 事件, 调用相应的回调函数, 该回调函数的参数即为被选中菜单项的 name 值。绑定事件的方法如下: 37 | 38 | ```HTML 39 | 40 | 第一个菜单项 41 | 第二个菜单项 42 | 43 | ``` -------------------------------------------------------------------------------- /src/nav/breadcrumb-item.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 45 | 46 | -------------------------------------------------------------------------------- /src/basic/dropdown-item.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 40 | 41 | -------------------------------------------------------------------------------- /src/icon/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'eleme'; 3 | src: url('iconfont.eot'); 4 | src: url('iconfont.eot#iefix') format('embedded-opentype'), 5 | url('iconfont.woff') format('woff'), 6 | url('iconfont.ttf') format('truetype'), 7 | url('iconfont.svg') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | [class^="d-icon-"]:before, [class*=" d-icon-"]:before { 12 | font-family: "eleme"; 13 | font-style: normal; 14 | font-weight: normal; 15 | speak: none; 16 | display: inline-block; 17 | font-variant: normal; 18 | text-transform: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | .d-icon-arrow-down:before { content: '\E901'; } /* '' */ 23 | .d-icon-arrow-left:before { content: '\E902'; } /* '' */ 24 | .d-icon-arrow-right:before { content: '\E903'; } /* '' */ 25 | .d-icon-arrow-up:before { content: '\E904'; } /* '' */ 26 | .d-icon-error:before { content: '\E905'; } /* '' */ 27 | .d-icon-info:before { content: '\E906'; } /* '' */ 28 | .d-icon-success:before { content: '\E907'; } /* '' */ 29 | .d-icon-warning:before { content: '\E908'; } /* '' */ 30 | .d-icon-close-circle:before { content: '\E909'; } /* '' */ 31 | .d-icon-close:before { content: '\E90A'; } /* '' */ 32 | .d-icon-confirm-circle:before { content: '\E90B'; } /* '' */ 33 | .d-icon-date-trigger:before { content: '\E90C'; } /* '' */ 34 | .d-icon-last:before { content: '\E90D'; } /* '' */ 35 | .d-icon-first:before { content: '\E90E'; } /* '' */ -------------------------------------------------------------------------------- /src/nav/tab.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 50 | 51 | 79 | -------------------------------------------------------------------------------- /src/form/select-option.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | -------------------------------------------------------------------------------- /src/service/notification.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | var NotificationConstructor = Vue.extend(require('./notification.vue')); 3 | 4 | var instance; 5 | var instances = []; 6 | var seed = 1; 7 | 8 | var Notification = function(options) { 9 | options = options || {}; 10 | var userOnClose = options.callback; 11 | var id = 'notification_' + seed++; 12 | 13 | options.callback = function() { 14 | Notification.close(id, userOnClose); 15 | }; 16 | 17 | instance = new NotificationConstructor({ 18 | data: options 19 | }); 20 | instance.id = id; 21 | instance.vm = instance.$mount(); 22 | instance.vm.$appendTo('body'); 23 | instance.dom = instance.vm.$el; 24 | 25 | var topDist = 0; 26 | for (var i = 0, len = instances.length; i < len; i++) { 27 | topDist += instances[i].$el.offsetHeight + 10; 28 | } 29 | topDist += 10; 30 | instance.top = topDist; 31 | instances.push(instance); 32 | }; 33 | 34 | Notification.close = function(id, userOnClose) { 35 | for (var i = 0, len = instances.length; i < len; i++) { 36 | if (id === instances[i].id) { 37 | if (typeof userOnClose === 'function') { 38 | userOnClose(instances[i]); 39 | } 40 | var index = i; 41 | var removedHeight = instances[i].dom.offsetHeight; 42 | instances.splice(i, 1); 43 | break; 44 | } 45 | } 46 | 47 | if (len > 1) { 48 | for (i = index; i < len - 1 ; i++) { 49 | instances[i].dom.style.top = parseInt(instances[i].dom.style.top, 10) - removedHeight - 10 + 'px'; 50 | } 51 | } 52 | }; 53 | 54 | export default Notification; 55 | -------------------------------------------------------------------------------- /icons/date-trigger.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/form/draggable.js: -------------------------------------------------------------------------------- 1 | import { on, off } from 'wind-dom'; 2 | 3 | var isDragging = false; 4 | 5 | var isIE8 = Number(document.documentMode) < 9; 6 | 7 | var fixEvent = function(event) { 8 | var scrollTop = Math.max(window.scrollY || 0, document.documentElement.scrollTop || 0); 9 | var scrollLeft = Math.max(window.scrollX || 0, document.documentElement.scrollLeft || 0); 10 | 11 | event.target = event.srcElement; 12 | event.pageX = scrollLeft + event.clientX; 13 | event.pageY = scrollTop + event.clientY; 14 | }; 15 | 16 | export default function(element, options) { 17 | var moveFn = function(event) { 18 | if (isIE8) { 19 | fixEvent(event); 20 | } 21 | if (options.drag) { 22 | options.drag(event); 23 | } 24 | }; 25 | var upFn = function(event) { 26 | if (isIE8) { 27 | fixEvent(event); 28 | } 29 | off(document, 'mousemove', moveFn); 30 | off(document, 'mouseup', upFn); 31 | document.onselectstart = null; 32 | document.ondragstart = null; 33 | 34 | isDragging = false; 35 | 36 | if (options.end) { 37 | options.end(event); 38 | } 39 | }; 40 | on(element, 'mousedown', function(event) { 41 | if (isIE8) { 42 | fixEvent(event); 43 | } 44 | if (isDragging) return; 45 | document.onselectstart = function() { return false; }; 46 | document.ondragstart = function() { return false; }; 47 | 48 | on(document, 'mousemove', moveFn); 49 | on(document, 'mouseup', upFn); 50 | isDragging = true; 51 | 52 | if (options.start) { 53 | options.start(event); 54 | } 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /examples/form/editor.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | -------------------------------------------------------------------------------- /docs/popup-mixin.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | Popup是一个Mixin, 为任何对象添加两个方法: open 和 close. 3 | 4 | # Usage 5 | 6 | Popup可以在open的时候指定自己显示的一些参数, 也可以通过实现popupOptions属性来返回参数的默认值。 7 | 8 | ```JavaScript 9 | import { Popup } from 'vue-desktop' 10 | 11 | export default { 12 | mixins: [ Popup ], 13 | 14 | computed: { 15 | popupOptions() { 16 | return { 17 | target: 'center', 18 | animation: false, 19 | modal: true, 20 | modalClass: 'loading-mask-modal', 21 | closeDelay: 300, 22 | updatePositionOnResize: true 23 | }; 24 | } 25 | }, 26 | 27 | props: { 28 | text: {} 29 | } 30 | } 31 | ``` 32 | 33 | 通过 popupOptions 拿到参数和 open 传入参数是可以共用的, open 传入的参数会覆盖 popupOptions 返回的参数。 34 | 35 | 可以使用的属性如下: 36 | 37 | - openDelay: 显示 Popup 的延时,默认值为 0。 38 | - closeDelay: 隐藏 Popup 的延时,默认值为 0。 39 | - target: 默认值为 null,可以为 HTMLElement、Event、Array。 40 | - placement: 只有当 target 为 HTMLElement 的时候才起作用,Popup 相对于 target 摆放的位置,可选值有:left、right、top、bottom、innerLeft、innerRight、center,默认值为'top'。 41 | - alignment: 只有当 target 为 HTMLElement 的时候才起作用,Popup 相对于 target 布局的位置,可选值有:start、center、end,默认值为 'center'。 42 | - adjustLeft: Popup 在定位时位置在水平方向的偏移值,默认值为0。 43 | - adjustTop: Popup 在定位时位置在垂直方向的偏移值,默认值为0。 44 | - modal: 是否显示模态层,默认值为 false。 45 | - modalClass:模态层使用的 className。 46 | - zIndex: 在 modal 为 false 的时候该属性才起作用,该属性为 dom 的 style.zIndex的值,默认值为 null,即不设置 dom 的 zIndex。 47 | - closeOnPressEscape: 是否在按了 Esc 之后关闭 Popup,在 modal 为 true 的时候该属性才起作用,默认值为 false。 48 | - closeOnClickModal: 是否在点击了 Modal 层之后关闭 Popup,在 modal 为true的时候该属性才起作用,默认值为 false。 49 | - updatePositionOnResize:是否在 window resize 之后重新进行定位,默认值为 false。 -------------------------------------------------------------------------------- /src/basic/vbox.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /docs/button.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Button 组件用于创建各种按钮, Button-group 可创建按钮组。 4 | 5 | # Usage 6 | 7 | 使用 d-button 标签即可创建一个 Button 组件: 8 | 9 | ```HTML 10 | Primary Button 11 | ``` 12 | 13 | 使用 d-button-group 标签来创建一个按钮组, 需在其中嵌套若干个由 d-button 标签创建的按钮: 14 | 15 | ```HTML 16 | 17 | 18 | 19 | 20 | 21 | ``` 22 | 23 | # Properties 24 | 25 | Button 目前可以使用的属性如下: 26 | 27 | | Property | Description | 28 | | ---- | ---- | 29 | | type | Button 的颜色主题, 可设置为 "primary", "success", "info", "warning", "danger", 若不设置则为默认主题。 | 30 | | size | Button 的尺寸, 可设置为 "large" 或 "small", 若不设置则尺寸为中。 | 31 | | disabled | 添加 disabled 属性即可让按钮处于不可用状态,同时按钮样式也会改变。 | 32 | | icon | 显示在按钮文字前的图标, 值为 Icon 组件的类名。 | 33 | 34 | Button-group 目前可以使用的属性如下: 35 | 36 | | Property | Description | 37 | | ---- | ---- | 38 | | size | Button-group 的尺寸, 可设置为 "large" 或 "small", 若不设置则尺寸为中。无需再为 Button-group 中的 Button 组件设置尺寸。 | 39 | | selection-mode | Button-group 中可被选中的 Button 是否唯一, 设置为 'single' 时只能选中一个,设置为 'multiple' 时可选择多个, 默认值为 'none', 即按钮不可被选中。 | 40 | 41 | 嵌套于 Button-group 组件中的 Button 组件还可设置如下两个属性: 42 | 43 | | Property | Description | 44 | | ---- | ---- | 45 | | selected | 添加 selected 属性即可让 Button 组件处于默认选中状态。 | 46 | | value | Button-group 组件中被选中的 Button 发生变化时, 传向回调函数的值。 | 47 | 48 | 关于最后一个 value 属性: 49 | 当用户通过点击按钮, 使 Button-group 组件中被选中的 Button 发生变化时, 会触发 @select 事件, 调用相应的回调函数, 该回调函数的参数即为目前被选中的各 Button 组件的 value 值所组成的数组。绑定事件的方法如下: 50 | 51 | ```HTML 52 | 53 | 全部商家 54 | 甜品饮品 55 | 小吃零食 56 | 鲜花蛋糕 57 | 果蔬生鲜 58 | 59 | ``` -------------------------------------------------------------------------------- /examples/form/field.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | -------------------------------------------------------------------------------- /src/collapse-transition.js: -------------------------------------------------------------------------------- 1 | require('./collapse-transition.css'); 2 | 3 | export default { 4 | beforeEnter(el) { 5 | el.dataset.oldPaddingTop = el.style.paddingTop; 6 | el.dataset.oldPaddingBottom = el.style.paddingBottom; 7 | el.style.height = '0'; 8 | el.style.paddingTop = 0; 9 | el.style.paddingBottom = 0; 10 | }, 11 | 12 | enter(el) { 13 | el.dataset.oldOverflow = el.style.overflow; 14 | 15 | el.style.display = 'block'; 16 | if (el.scrollHeight !== 0) { 17 | el.style.height = el.scrollHeight + 'px'; 18 | el.style.paddingTop = el.dataset.oldPaddingTop; 19 | el.style.paddingBottom = el.dataset.oldPaddingBottom; 20 | } else { 21 | el.style.height = ''; 22 | el.style.paddingTop = el.dataset.oldPaddingTop; 23 | el.style.paddingBottom = el.dataset.oldPaddingBottom; 24 | } 25 | 26 | el.style.overflow = 'hidden'; 27 | }, 28 | 29 | afterEnter(el) { 30 | el.style.display = ''; 31 | el.style.height = ''; 32 | el.style.overflow = el.dataset.oldOverflow; 33 | }, 34 | 35 | beforeLeave(el) { 36 | el.dataset.oldPaddingTop = el.style.paddingTop; 37 | el.dataset.oldPaddingBottom = el.style.paddingBottom; 38 | el.dataset.oldOverflow = el.style.overflow; 39 | 40 | el.style.display = 'block'; 41 | if (el.scrollHeight !== 0) { 42 | el.style.height = el.scrollHeight + 'px'; 43 | } 44 | el.style.overflow = 'hidden'; 45 | }, 46 | 47 | leave(el) { 48 | if (el.scrollHeight !== 0) { 49 | setTimeout(() => { 50 | el.style.height = 0; 51 | el.style.paddingTop = 0; 52 | el.style.paddingBottom = 0; 53 | }); 54 | } 55 | }, 56 | 57 | afterLeave(el) { 58 | el.style.display = el.style.height = ''; 59 | el.style.overflow = el.dataset.oldOverflow; 60 | el.style.paddingTop = el.dataset.oldPaddingTop; 61 | el.style.paddingBottom = el.dataset.oldPaddingBottom; 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /examples/form/validation.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | -------------------------------------------------------------------------------- /docs/msgbox.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | MessageBox 是一个类似 SweetAlert 的组件,用来替代浏览器的 alert 和 confirm。 3 | 4 | # 用法 5 | 6 | ## 引用 7 | 8 | ```JavaScript 9 | import { MessageBox } from 'vue-desktop' 10 | ``` 11 | 12 | ## 不需要callback 13 | 14 | 在不需要callback的情况下,只需要这么使用即可: 15 | 16 | MessageBox("Good job!", "You clicked the button!", "success"); 17 | 18 | 第一个参数为 title,第二个参数为 message,第三个参数为 type。 19 | 20 | ## 需要callback 21 | 22 | 在需要 callback 的情况下,需要这么使用: 23 | 24 | MessageBox({ 25 | title: 'I'm a title', 26 | message: 'I'm a message', 27 | type: 'success', 28 | showCancelButton: true 29 | }, function(action) { 30 | console.log('callback:', action); 31 | MessageBox('你点击了: ' + action); 32 | }); 33 | 34 | 如果 callback 返回一个 false,则可以阻止 MessageBox 的关闭,你可以使用 MessageBox.close() 来关闭所有的 MessageBox。 35 | 36 | ## 需要自定义按钮 37 | 如果默认 Button 的样式无法满足你的需求,可以使用自定义按钮来完成你需要的功能。 38 | 39 | 自定义按钮使用buttons属性来配置,该属性为数组,每个button可以定义这么两个属性: 40 | 41 | - action: 触发的action,会在callback里面传入。 42 | - content: button的HTML。 43 | 44 | 举例说明: 45 | 46 | MessageBox({ 47 | title: 'Title', 48 | message: 'Message', 49 | showConfirmButton: false, 50 | buttons: [{ 51 | action: 'test', 52 | content: '' 53 | }] 54 | }, function(action) { 55 | if (action === 'test') { 56 | MessageBox('自定义Button测试', '你点击了:' + action); 57 | } 58 | }); 59 | 60 | # 参数列表 61 | 目前MessageBox支持如下参数: 62 | 63 | - title: MessageBox显示的标题。 64 | - message: MessageBox显示的内容。 65 | - type: MessageBox显示的图标的类型,可选值:success、warning、error,默认值为空。 66 | - showConfirmButton: 是否显示确定按钮,默认值为true。 67 | - showCancelButton: 是否显示取消按钮,默认值为false。 68 | - confirmButtonText: 确定按钮显示的文本,默认值为『确定』。 69 | - confirmButtonDisabled: 确定按钮是否不可点击,默认值为false。 70 | - cancelButtonText: 取消按钮显示的文本,默认值为『取消』。 71 | - confirmButtonClass: 确定按钮的className,默认值为空。注:msgbox-confirm这个class是一定存在的。 72 | - cancelButtonClass: 取消按钮的className,默认值为空。注:msgbox-cancel这个class是一定存在的。 73 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | const isProduction = process.env.NODE_ENV === 'production'; 4 | 5 | module.exports = { 6 | entry: { 7 | example: './examples/index.js', 8 | test: './test/index.js' 9 | }, 10 | output: { 11 | path: './dist', 12 | publicPath: '/dist/', 13 | filename: '[name].js' 14 | }, 15 | devServer: { 16 | stats: { 17 | children: false, 18 | chunks: false 19 | } 20 | }, 21 | stats: { 22 | children: false, 23 | chunks: false 24 | }, 25 | vue: { 26 | loaders: { 27 | js: 'babel!eslint', 28 | css: ExtractTextPlugin.extract('style', 'css') 29 | } 30 | }, 31 | babel: { 32 | presets: ['es2015'] 33 | //,plugins: ['transform-runtime'] 34 | }, 35 | module: { 36 | preLoaders: [ 37 | { test: /\.js$/, exclude: /node_modules/, loader: 'eslint-loader' } 38 | ], 39 | loaders: [ 40 | { test: /\.js$/, exclude: /node_modules\/(?!vue-desktop)/, loader: 'babel' }, 41 | { test: /\.css$/, loader: ExtractTextPlugin.extract('style', 'css') }, 42 | { test: /\.html$/, loader: 'html' }, 43 | { test: /\.vue$/, loader: 'vue' }, 44 | { test: /\.(ttf|svg|woff2|woff|eot)$/, loader: 'url?limit=20000&name=[path][name].[hash:6].[ext]' } 45 | ] 46 | } 47 | }; 48 | 49 | if (isProduction) { 50 | module.exports.plugins = [ 51 | new ExtractTextPlugin('[name].[contenthash:6].css'), 52 | new webpack.DefinePlugin({ 53 | 'process.env': { 54 | NODE_ENV: '"production"' 55 | } 56 | }), 57 | new webpack.optimize.UglifyJsPlugin({ 58 | compress: { 59 | warnings: false 60 | } 61 | }), 62 | new webpack.optimize.OccurenceOrderPlugin() 63 | ]; 64 | } else { 65 | module.exports.plugins = [ 66 | new ExtractTextPlugin('[name].css') 67 | ]; 68 | module.exports.devtool = '#source-map'; 69 | } 70 | -------------------------------------------------------------------------------- /src/service/loading-mask.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 62 | 63 | -------------------------------------------------------------------------------- /src/data/tree.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 86 | -------------------------------------------------------------------------------- /src/basic/progress-bar.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 57 | 58 | -------------------------------------------------------------------------------- /src/basic/sticky.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | -------------------------------------------------------------------------------- /src/form/fields/label.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 29 | 30 | -------------------------------------------------------------------------------- /examples/nav/tabs.vue: -------------------------------------------------------------------------------- 1 | 95 | 96 | -------------------------------------------------------------------------------- /examples/data/custom-grid.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 56 | 57 | -------------------------------------------------------------------------------- /src/icon/index.html: -------------------------------------------------------------------------------- 1 |

eleme 34 | font demo

d-icon-arrow-down

0xE901

d-icon-arrow-left

0xE902

d-icon-arrow-right

0xE903

d-icon-arrow-up

0xE904

d-icon-error

0xE905

d-icon-info

0xE906

d-icon-success

0xE907

d-icon-warning

0xE908

d-icon-close-circle

0xE909

d-icon-close

0xE90A

d-icon-confirm-circle

0xE90B

d-icon-date-trigger

0xE90C

d-icon-last

0xE90D

d-icon-first

0xE90E

-------------------------------------------------------------------------------- /examples/basic/nav-menu.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 57 | 58 | -------------------------------------------------------------------------------- /src/form/fields/check.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | 23 | -------------------------------------------------------------------------------- /docs/form-field.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | FormField 是表单中的表单项,有多种类型,目前支持的类型有: 4 | 5 | - field: 内部没有任何编辑器,使用 d-field 标签创建。 6 | - text:内部使用 TextEditor 作为自己的编辑器,使用 d-text-field 创建。 7 | - select:内部使用 Select 作为自己的编辑器,使用 d-select-field 创建。 8 | - checkbox:内部使用 CheckBox 作为自己的编辑器,使用 d-checkbox-field 创建。 9 | - radiogroup: 内部使用 RadioGroup 作为自己的编辑器,使用 d-radiogroup-field 创建。 10 | 11 | # Usage 12 | 13 | 一般情况下,一组 FormField 会有一组公用的属性,这些属性可以在 Form 组件上定义,在 FormField 没有定义该属性的时候,会使用 Form 上的属性作为默认值。 14 | 15 | 一个简单的例子如下: 16 | 17 | ```HTML 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ``` 26 | 27 | # Properties 28 | 29 | FormField 通用的属性如下: 30 | 31 | | Property | Description | 32 | | ---- | ---- | 33 | | schema | FormField 使用的 Schema。如果没有定义该属性,则默认值从 Form 中取得。 | 34 | | model | FormField 使用的 model,这个 model 可以是一个普通对象,也可以是 Schema 生成的一个 model。 | 35 | | property | FormField 中的编辑器绑定的 model 中的 property。 | 36 | | label | FormField 显示的 label 信息,如果定义了 Schema,没有定义 label 属性,则会从 Schema 中取得。 | 37 | | labelSuffix | FormField 中的 label 的后缀。如果没有定义该属性,则默认值从 Form 中取得。| 38 | | labelWidth | FormField 中的 label 显示的宽度,数值类型。如果没有定义该属性,则默认值从 Form 中取得。| 39 | | required | 是否在 label 的左侧显示一个必填的指示星号,如果定义了 schema 和 property 属性,并且该 property 的 required 属性为 true,则默认会显示必填项的星号。当然,你也可以定义 required 属性为 false 来关闭这个星号的显示。| 40 | | hideLabel | 是否隐藏 FormField 的 label 信息,Boolean 类型,默认值为 false。| 41 | 42 | TextField 可以使用的输入如下: 43 | 44 | | Property | Description | 45 | | ---- | ---- | 46 | | type | TextField 的类型,可选值:text、textarea、password。| 47 | | editorHeight | 文本框的高度。| 48 | | placeholder | 文本框使用的 placeholder。 | 49 | | editorWidth | 文本框的宽度,可以使用数值和百分比。| 50 | 51 | SelectField 可以使用的属性如下: 52 | 53 | | Property | Description | 54 | | ---- | ---- | 55 | | multiSelect | 是否可以多选,Boolean 类型,默认值为 false。 | 56 | | emptyRecord | 是否显示一个空的记录在最上方,Boolean 类型,默认值为 false。 | 57 | | editorWidth | Select的宽度,可以使用数值和百分比。| 58 | 59 | CheckBoxField 可以使用的属性如下: 60 | 61 | | Property | Description | 62 | | ---- | ---- | 63 | | trueValue | CheckBox 勾选的时候写到 model 中的值。 | 64 | | falseValue | CheckBox 不勾选的时候写到 model 中的值。 | 65 | -------------------------------------------------------------------------------- /src/basic/dialog.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 58 | 102 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-desktop", 3 | "version": "0.2.31", 4 | "description": "A UI library for building admin panel website.", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "prepublish": "mkdir -p lib & cp -r src/* lib & node_modules/babel-cli/bin/babel.js lib --out-dir lib", 8 | "dev": "webpack-dev-server --inline --hot --port 8088", 9 | "watch": "webpack --progress --hide-modules --watch", 10 | "build": "NODE_ENV=production webpack --progress --hide-modules", 11 | "test": "mocha -r chai -r sinon test/index.js", 12 | "coverage": "istanbul cover _mocha -- -r chai -r sinon -R spec test/index.js & open ./coverage/lcov-report/index.html" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git@github.com:ElemeFE/vue-desktop.git" 17 | }, 18 | "files": [ 19 | "LICENSE", 20 | "README.md", 21 | "lib" 22 | ], 23 | "keywords": [ 24 | "vue", 25 | "vue-component" 26 | ], 27 | "dependencies": { 28 | "fecha": "^1.0.0", 29 | "vue-popup": "0.0.10", 30 | "wind-dom": "0.0.3" 31 | }, 32 | "peerDependencies": { 33 | "vue": "^1.0.0", 34 | "vue-i18n": "^2.2.0" 35 | }, 36 | "devDependencies": { 37 | "babel-cli": "^6.3.17", 38 | "babel-core": "^6.7.0", 39 | "babel-loader": "^6.2.4", 40 | "babel-plugin-transform-runtime": "^6.1.18", 41 | "babel-preset-es2015": "^6.1.18", 42 | "babel-preset-stage-1": "^6.5.0", 43 | "babel-runtime": "^6.0.0", 44 | "chai": "^3.3.0", 45 | "chai-as-promised": "^5.1.0", 46 | "css-loader": "^0.21.0", 47 | "eslint": "^2.7.0", 48 | "eslint-config-standard": "^5.1.0", 49 | "eslint-loader": "^1.3.0", 50 | "eslint-plugin-html": "^1.4.0", 51 | "eslint-plugin-promise": "^1.1.0", 52 | "eslint-plugin-standard": "^1.3.2", 53 | "extract-text-webpack-plugin": "^0.9.1", 54 | "file-loader": "^0.8.4", 55 | "highlight.js": "^8.9.1", 56 | "html-loader": "^0.3.0", 57 | "jade": "^1.11.0", 58 | "marked": "^0.3.5", 59 | "mocha": "^2.3.3", 60 | "sinon": "^1.17.1", 61 | "sinon-chai": "^2.8.0", 62 | "style-loader": "^0.13.0", 63 | "stylus-loader": "^1.4.0", 64 | "template-html-loader": "0.0.3", 65 | "url-loader": "^0.5.7", 66 | "vue-hot-reload-api": "1.2.0", 67 | "vue-html-loader": "^1.0.0", 68 | "vue-loader": "^8.1.0", 69 | "vue-router": "0.7.4", 70 | "webpack": "^1.12.2", 71 | "webpack-dev-server": "^1.12.0" 72 | }, 73 | "author": "long.zhang@ele.me", 74 | "license": "MIT" 75 | } 76 | -------------------------------------------------------------------------------- /src/popover.js: -------------------------------------------------------------------------------- 1 | var bindEvent = require('wind-dom').on; 2 | var unbindEvent = require('wind-dom').off; 3 | 4 | export default { 5 | props: { 6 | target: {}, 7 | trigger: { 8 | type: String, 9 | default: 'mouseenter' 10 | } 11 | }, 12 | 13 | beforeDestroy() { 14 | this.unbindTarget(); 15 | }, 16 | 17 | methods: { 18 | bindTarget: function() { 19 | var popover = this; 20 | var target = this.target; 21 | if (!target) return; 22 | 23 | var trigger = this.trigger; 24 | 25 | if (trigger === 'click') { 26 | var toggle = () => { 27 | if (popover.visible) { 28 | popover.close(); 29 | } else { 30 | popover.open({ 31 | target: target, 32 | placement: this.placement 33 | }); 34 | } 35 | }; 36 | popover.toggleListener = toggle; 37 | 38 | bindEvent(target, 'click', toggle); 39 | } else { 40 | var open = () => { 41 | popover.open({ 42 | target: target, 43 | placement: this.placement 44 | }); 45 | }; 46 | var close = () => { 47 | popover.close(); 48 | }; 49 | popover.openListener = open; 50 | popover.closeListener = close; 51 | 52 | if (trigger === 'mouseenter') { 53 | bindEvent(target, 'mouseenter', open); 54 | bindEvent(target, 'mouseleave', close); 55 | } else if (trigger === 'focus') { 56 | bindEvent(target, 'focus', open); 57 | bindEvent(target, 'blur', close); 58 | } 59 | } 60 | }, 61 | unbindTarget: function() { 62 | var popover = this; 63 | var target = popover.get('target'); 64 | if (!target) return; 65 | 66 | var trigger = popover.get('trigger'); 67 | 68 | if (trigger === 'click') { 69 | var toggle = popover.toggleListener; 70 | if (toggle) { 71 | bindEvent(target, 'click', toggle); 72 | } 73 | } else { 74 | var open = popover.openListener; 75 | var close = popover.closeListener; 76 | if (!open) return; 77 | 78 | if (trigger === 'mouseenter') { 79 | unbindEvent(target, 'mouseenter', open); 80 | unbindEvent(target, 'mouseleave', close); 81 | } else if (trigger === 'focus') { 82 | unbindEvent(target, 'focus', open); 83 | unbindEvent(target, 'blur', close); 84 | } 85 | } 86 | } 87 | } 88 | }; 89 | -------------------------------------------------------------------------------- /test/validator.js: -------------------------------------------------------------------------------- 1 | import { default as SchemaStore } from '../src/schema/store'; 2 | 3 | describe('Validator Unit test', function() { 4 | it('type: required', function() { 5 | var validator = SchemaStore.getValidator('required'); 6 | 7 | validator('').should.be.false; 8 | validator(null).should.be.false; 9 | validator(undefined).should.be.false; 10 | 11 | validator(0).should.be.true; 12 | validator(true).should.be.true; 13 | validator(false).should.be.true; 14 | validator(' ').should.be.true; 15 | }); 16 | 17 | it('type: length', function() { 18 | var validator = SchemaStore.getValidator('length'); 19 | 20 | validator('').should.be.true; 21 | validator(null).should.be.true; 22 | validator(undefined).should.be.true; 23 | 24 | validator('', { min: 1 }).should.be.false; 25 | validator(null, { min: 1 }).should.be.false; 26 | validator(undefined, { min: 1 }).should.be.false; 27 | }); 28 | 29 | it('type: range', function() { 30 | var validator = SchemaStore.getValidator('range'); 31 | 32 | validator(0).should.be.true; 33 | validator(null).should.be.true; 34 | validator(undefined).should.be.true; 35 | validator(null, { min: 1 }).should.be.true; 36 | validator(undefined, { min: 1 }).should.be.true; 37 | 38 | validator(0, { min: 1 }).should.be.false; 39 | validator('').should.be.false; 40 | }); 41 | 42 | it('type: pattern', function() { 43 | var validator = SchemaStore.getValidator('pattern'); 44 | 45 | (function() { 46 | validator(''); 47 | }).should.throw; 48 | 49 | validator('', { pattern: /i/ }).should.be.false; 50 | validator('i', { pattern: /i/ }).should.be.true; 51 | }); 52 | 53 | it('type: enum', function() { 54 | var validator = SchemaStore.getValidator('enum'); 55 | 56 | validator(null, { enum: ['a', 'b', 'c'] }).should.be.true; 57 | validator(undefined, { enum: ['a', 'b', 'c'] }).should.be.true; 58 | 59 | validator('', { enum: ['a', 'b', 'c'] }).should.be.false; 60 | validator('a', { enum: ['a', 'b', 'c'] }).should.be.true; 61 | }); 62 | 63 | it('type: custom', function() { 64 | var validator = SchemaStore.getValidator('custom'); 65 | 66 | (function() { 67 | validator(''); 68 | }).should.throw; 69 | 70 | validator('', { validate: function() { return true; } }).should.be.true; 71 | validator('', { validate: function() { return false; } }).should.be.false; 72 | validator('', { validate: function() {} }).should.be.false; 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /src/form/fields/radio.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 25 | 26 | -------------------------------------------------------------------------------- /examples/markdown.css: -------------------------------------------------------------------------------- 1 | .markdown { 2 | font-family: "Avenir Next", Helvetica, Arial, sans-serif; 3 | padding: 1em; 4 | margin: auto; 5 | max-width: 48em; 6 | color: #5c6b77; 7 | display: block; 8 | } 9 | 10 | .markdown h1, .markdown h2, .markdown h3, .markdown h4, .markdown h5, .markdown h6 { 11 | font-weight: bold; 12 | } 13 | 14 | .markdown h1 { 15 | color: #000000; 16 | font-size: 28pt; 17 | } 18 | 19 | .markdown h2 { 20 | border-bottom: 1px solid #CCCCCC; 21 | color: #000000; 22 | font-size: 24px; 23 | } 24 | 25 | .markdown h3 { 26 | font-size: 18px; 27 | } 28 | 29 | .markdown h4 { 30 | font-size: 16px; 31 | } 32 | 33 | .markdown h5 { 34 | font-size: 14px; 35 | } 36 | 37 | .markdown h6 { 38 | color: #777777; 39 | background-color: inherit; 40 | font-size: 14px; 41 | } 42 | 43 | .markdown hr { 44 | height: 0.2em; 45 | border: 0; 46 | color: #CCCCCC; 47 | background-color: #CCCCCC; 48 | } 49 | 50 | .markdown p, .markdown blockquote, .markdown ul, .markdown ol, .markdown dl, .markdown li, .markdown table, .markdown pre { 51 | margin: 8px 0; 52 | } 53 | 54 | .markdown a, .markdown a:visited { 55 | color: #4183C4; 56 | background-color: inherit; 57 | text-decoration: none; 58 | } 59 | 60 | .markdown #message { 61 | border-radius: 6px; 62 | border: 1px solid #ccc; 63 | display: block; 64 | width: 100%; 65 | height: 60px; 66 | margin: 6px 0px; 67 | } 68 | 69 | .markdown button, .markdown #ws { 70 | font-size: 10pt; 71 | padding: 4px 6px; 72 | border-radius: 5px; 73 | border: 1px solid #bbb; 74 | background-color: #eee; 75 | } 76 | 77 | .markdown code, .markdown pre, .markdown #ws, .markdown #message { 78 | font-family: Monaco; 79 | font-size: 10pt; 80 | border-radius: 3px; 81 | background-color: #F8F8F8; 82 | color: inherit; 83 | } 84 | 85 | .markdown code { 86 | border: 1px solid #EAEAEA; 87 | margin: 0 2px; 88 | padding: 0 5px; 89 | } 90 | 91 | .markdown pre { 92 | border: 1px solid #CCCCCC; 93 | overflow: auto; 94 | padding: 4px 8px; 95 | } 96 | 97 | .markdown pre > code { 98 | border: 0; 99 | margin: 0; 100 | padding: 0; 101 | } 102 | 103 | .markdown table { 104 | border: 1px solid #e9e9e9; 105 | border-collapse: collapse; 106 | background-color: #fff; 107 | } 108 | 109 | .markdown table td, 110 | .markdown table th { 111 | border: 1px solid #e9e9e9; 112 | padding: 8px; 113 | } 114 | 115 | .markdown table th { 116 | background-color: #59677e; 117 | color: #fff; 118 | } 119 | -------------------------------------------------------------------------------- /src/service/msgbox.js: -------------------------------------------------------------------------------- 1 | var CONFIRM_TEXT = '确定'; 2 | var CANCEL_TEXT = '取消'; 3 | 4 | var defaults = { 5 | title: '', 6 | message: '', 7 | type: '', 8 | showConfirmButton: true, 9 | showCancelButton: false, 10 | confirmButtonText: CONFIRM_TEXT, 11 | cancelButtonText: CANCEL_TEXT, 12 | confirmButtonClass: '', 13 | cancelButtonClass: '' 14 | }; 15 | 16 | import Vue from 'vue'; 17 | import { merge } from '../util'; 18 | 19 | var MessageBoxConstructor = Vue.extend(require('./msgbox.vue')); 20 | 21 | var currentMsg, instance; 22 | var msgQueue = []; 23 | 24 | var initInstance = function() { 25 | instance = new MessageBoxConstructor({ 26 | el: document.createElement('div') 27 | }); 28 | 29 | instance.callback = function(action) { 30 | var result; 31 | if (currentMsg) { 32 | var callback = currentMsg.callback; 33 | if (typeof callback === 'function') { 34 | result = callback(action); 35 | } 36 | } 37 | if (result !== false) { 38 | showNextMsg(); 39 | } else { 40 | return false; 41 | } 42 | }; 43 | }; 44 | 45 | var showNextMsg = function() { 46 | if (!instance) { 47 | initInstance(); 48 | } 49 | 50 | if (!instance.visible || instance.closeTimer) { 51 | if (msgQueue.length > 0) { 52 | currentMsg = msgQueue.shift(); 53 | 54 | var oldVisible = instance.visible; 55 | instance.visible = false; 56 | 57 | var options = currentMsg.options; 58 | for (var prop in options) { 59 | if (options.hasOwnProperty(prop)) { 60 | instance[prop] = options[prop]; 61 | } 62 | } 63 | 64 | instance.visible = oldVisible; 65 | 66 | instance.open(); 67 | } 68 | } 69 | }; 70 | 71 | var MessageBox = function(options, callback) { 72 | if (typeof options === 'string') { 73 | options = { 74 | title: options 75 | }; 76 | if (arguments[1]) { 77 | options.message = arguments[1]; 78 | } 79 | if (arguments[2]) { 80 | options.type = arguments[2]; 81 | } 82 | } else if (options.callback && !callback) { 83 | callback = options.callback; 84 | } 85 | 86 | msgQueue.push({ 87 | options: merge({}, defaults, MessageBox.defaults || {}, options), 88 | callback: callback 89 | }); 90 | 91 | showNextMsg(); 92 | }; 93 | 94 | MessageBox.setDefaults = function(defaults) { 95 | MessageBox.defaults = defaults; 96 | }; 97 | 98 | MessageBox.close = function() { 99 | instance.close(); 100 | msgQueue = []; 101 | currentMsg = null; 102 | }; 103 | 104 | export default MessageBox; 105 | -------------------------------------------------------------------------------- /examples/data/custom-grid-test.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /src/form/fields/check-group.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /src/form/fields/field.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 88 | 89 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | var Vue = require('vue'); 2 | Vue.config.debug = true; 3 | var VueRouter = require('vue-router'); 4 | Vue.use(VueRouter); 5 | 6 | require('./markdown.css'); 7 | require('./markdown'); 8 | require('highlight.js/styles/agate.css'); 9 | 10 | require('../src/index'); 11 | 12 | var router = new VueRouter(); 13 | 14 | // basic 15 | router.map({ 16 | '/dialog': { 17 | component: require('./basic/dialog.vue') 18 | }, 19 | '/button': { 20 | component: require('./basic/button.vue') 21 | }, 22 | '/progress-bar': { 23 | component: require('./basic/progress-bar.vue') 24 | }, 25 | '/alert': { 26 | component: require('./basic/alert.vue') 27 | }, 28 | '/slider': { 29 | component: require('./basic/slider.vue') 30 | }, 31 | '/sticky': { 32 | component: require('./basic/sticky.vue') 33 | }, 34 | '/dropdown': { 35 | component: require('./basic/dropdown.vue') 36 | }, 37 | '/navmenu': { 38 | component: require('./basic/nav-menu.vue') 39 | } 40 | }); 41 | 42 | // form 43 | router.map({ 44 | '/validation': { 45 | component: require('./form/validation.vue') 46 | }, 47 | '/mapping': { 48 | component: require('./form/mapping.vue') 49 | }, 50 | '/field': { 51 | component: require('./form/field.vue') 52 | }, 53 | '/editor': { 54 | component: require('./form/editor.vue') 55 | }, 56 | '/tags': { 57 | component: require('./form/tags.vue') 58 | }, 59 | '/upload': { 60 | component: require('./form/upload.vue') 61 | } 62 | }); 63 | 64 | // nav 65 | router.map({ 66 | '/tabs': { 67 | component: require('./nav/tabs.vue') 68 | }, 69 | '/accordion': { 70 | component: require('./nav/accordion.vue') 71 | }, 72 | '/breadcrumb': { 73 | component: require('./nav/breadcrumb.vue') 74 | } 75 | }); 76 | 77 | // data 78 | router.map({ 79 | '/grid': { 80 | component: require('./data/grid.vue') 81 | }, 82 | '/tree': { 83 | component: require('./data/tree.vue') 84 | }, 85 | '/crud': { 86 | component: require('./data/crud.vue') 87 | }, 88 | '/custom-grid-test': { 89 | component: require('./data/custom-grid-test.vue') 90 | }, 91 | '/pagination': { 92 | component: require('./data/pagination.vue') 93 | } 94 | }); 95 | 96 | // service 97 | router.map({ 98 | '/msgbox': { 99 | component: require('./service/msgbox.vue') 100 | }, 101 | '/notification': { 102 | component: require('./service/notification.vue') 103 | }, 104 | '/tooltip': { 105 | component: require('./service/tooltip.vue') 106 | }, 107 | '/loading-mask': { 108 | component: require('./service/loading-mask.vue') 109 | } 110 | }); 111 | 112 | router.start(Vue.extend({ 113 | components: { 114 | app: require('./app.vue') 115 | } 116 | }), 'body'); 117 | -------------------------------------------------------------------------------- /docs/schema/README.MD: -------------------------------------------------------------------------------- 1 | # Schema 使用说明 2 | 3 | ## 介绍 4 | vue-desktop 项目中 Field 和 Grid 的使用依赖于 Schema,Schema 是 vue-desktop 区别于其他 UI 项目的特点,在使用 vue-desktop 之前,需要先了解 Schema 的概念和用途。 5 | 6 | ## 简单示例 7 | Schema 用来描述表单里面的一个数据显示使用的 label、数据类型、校验规则等。 8 | 9 | 先来看一个简单的例子,我们有一个表单是这个样子: 10 | 11 | ![First Form](first-form.png) 12 | 13 | Schema 里面定义的是数据的结构,比如我们这个表单需要这么几个属性: 14 | 15 | - userName: 字符串,必填项。 16 | - email: 字符串,必填项。 17 | - password: 字符串,必填项,长度介于6-20。 18 | - confirmPassword: 字符串,必填项,长度介于6-20,需要与 password 一致。 19 | - comment:字符串,可以不填。 20 | 21 | 那么我们的 Schema 需要这么定义: 22 | 23 | ``` 24 | var userSchema = new Schema({ 25 | userName: { 26 | label: ‘用户名’, 27 | whitespace: false, 28 | required: true 29 | }, 30 | 31 | email: { 32 | label: ‘Email’, 33 | required: true 34 | }, 35 | 36 | password: { 37 | label: ‘密码’, 38 | required: true, 39 | minLength: 6, 40 | maxLength: 20 41 | }, 42 | 43 | confirmPassword: { 44 | label: ‘确认密码’, 45 | required: true, 46 | minLength: 6, 47 | maxLength: 20, 48 | rules: { 49 | type: ‘custom’, 50 | message: ‘两次输入的密码不一致’, 51 | validate() { 52 | return this.password === this.confirmPassword; 53 | } 54 | } 55 | }, 56 | 57 | comment: { 58 | label: ‘备注’ 59 | } 60 | }); 61 | ``` 62 | 63 | 可以看到,我们在这个 Schema 里面定义了字段的如下信息: 64 | - label:页面上看到的字段信息。 65 | - 校验规则:required、minLength、maxLength等。 66 | 67 | ## 使用说明 68 | 69 | ### 属性列表 70 | 71 | | 属性 | 说明 | 72 | |------|------| 73 | | type | 数据类型,可选值:string、boolean、number、float、integer、date、datetime。 | 74 | | label | 在页面上显示的文本信息,比如 Field 的 label 或者 Grid 的表头信息。 | 75 | | default | 该字段的默认值。| 76 | | format | 该字段显示为文本的时候使用的格式,目前仅数据类型为 date、datetime 的字段支持该属性。// TODO format 说明 | 77 | | required | 是否必填项目,默认值为 false。| 78 | | whitespace | 是否允许内容为带空格的字符串,默认值为 false。| 79 | | min | 该字段值的最小值,只对 number、integer、float 类型的字段起作用。| 80 | | max | 该字段值的最大值,只对 number、integer、float 类型的字段起作用。| 81 | | minLength | 该字段值的长度的最小值,只对 string 类型的字段起作用。| 82 | | minLength | 该字段值的长度的最大值,只对 string 类型的字段起作用。| 83 | | pattern | 该字段需要匹配的正则表达式。 | 84 | | enum | 该字段值的取值列表,需要定义为数组。比如只能取值 A、B、C,则定义为[ 'A', 'B', 'C' ] | 85 | | messages | 通过属性定义的校验规则都有默认的校验信息,如果不想使用默认的校验信息,可以使用 messages 属性来覆写。比如想覆写 pattern 类型的校验信息,则 messages 定义为 { pattern: '没有通过校验' }| 86 | | rules | 如果通过属性定义的校验规则无法满足需求,可以使用 rules 属性来增加自定义规则。rules 可以是一个数组,也可以是一个对象。rules 里面可以定义两个属性,一个是 message(校验失败后的信息),一个是 validate 方法(校验方法)。| 87 | 88 | ### 方法列表 89 | 90 | | 方法 | 说明 | 91 | | ------ | ------ | 92 | | create | 创建一个空的对象,该对象有 schema 中定义的所有字段,如果 schema 有默认值定义,则空对象该字段的值为默认值。| 93 | | validate(object, options, callback) | 校验 object 是否符合 schema 中定义的规则。 | 94 | | validateProperty(object, property) | 校验 object 的某个 property 是否符合规则。 | 95 | | save(object) | 保存 object 当前的值,可以通过 reset 方法来恢复保存的值。 | 96 | | reset(object) | 恢复保存的 object 的值,如果没有通过 save 方法保存过,则恢复为一个新建的对象。| 97 | | getPropertyDescriptor(property) | 取得某个 property 的定义。 | 98 | -------------------------------------------------------------------------------- /icons/close-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/basic/button-group.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 44 | 45 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | require('./icon/iconfont.css'); 2 | require('./common.css'); 3 | 4 | import Alert from './basic/alert.vue'; 5 | import Panel from './basic/panel.vue'; 6 | import Dialog from './basic/dialog.vue'; 7 | import Button from './basic/button.vue'; 8 | import ButtonGroup from './basic/button-group.vue'; 9 | import ProgressBar from './basic/progress-bar.vue'; 10 | import Slider from './basic/slider.vue'; 11 | import Sticky from './basic/sticky.vue'; 12 | import DropdownButton from './basic/dropdown.vue'; 13 | import DropdownItem from './basic/dropdown-item.vue'; 14 | import DropdownDivider from './basic/dropdown-divider.vue'; 15 | import Vbox from './basic/vbox.vue'; 16 | import NavMenu from './basic/nav-menu.vue'; 17 | import NavMenuItem from './basic/nav-menu-item.vue'; 18 | 19 | import Accordion from './nav/accordion.vue'; 20 | import AccordionPanel from './nav/accordion-panel.vue'; 21 | import Tab from './nav/tab.vue'; 22 | import Tabs from './nav/tabs.vue'; 23 | import Breadcrumb from './nav/breadcrumb.vue'; 24 | import BreadcrumbItem from './nav/breadcrumb-item.vue'; 25 | 26 | import Grid from './data/grid.vue'; 27 | import GridColumn from './data/grid-column.vue'; 28 | import Tree from './data/tree.vue'; 29 | 30 | import Pagination from './data/pagination.vue'; 31 | 32 | export var Components = { 33 | Alert, 34 | Panel, 35 | Dialog, 36 | Button, 37 | ButtonGroup, 38 | ProgressBar, 39 | Slider, 40 | Sticky, 41 | 42 | DropdownButton, 43 | DropdownItem, 44 | DropdownDivider, 45 | 46 | Vbox, 47 | 48 | NavMenu, 49 | NavMenuItem, 50 | 51 | Accordion, 52 | AccordionPanel, 53 | 54 | Tab, 55 | Tabs, 56 | 57 | Breadcrumb, 58 | BreadcrumbItem, 59 | 60 | Grid, 61 | GridColumn, 62 | Tree, 63 | Pagination 64 | }; 65 | 66 | import FormComponents from './form/index'; 67 | import { merge } from './util'; 68 | 69 | merge(Components, FormComponents); 70 | 71 | var initComponents = (Vue, prefix, components) => { 72 | if (arguments.length < 3) { // eslint-disable-line no-undef 73 | components = prefix; 74 | prefix = 'D'; 75 | } else { 76 | if (!prefix) { 77 | prefix = 'D'; 78 | } 79 | } 80 | 81 | if (!components) components = Object.keys(Components); 82 | 83 | if (components instanceof Array) { 84 | for (var i = 0, j = components.length; i < j; i++) { 85 | var key = components[i]; 86 | Vue.component(prefix + key, Components[key]); 87 | } 88 | } 89 | }; 90 | 91 | var Vue = require('vue'); 92 | var i18n = require('vue-i18n'); 93 | 94 | import { default as locales } from './locales/index'; 95 | 96 | Vue.use(i18n, { 97 | lang: 'zh', 98 | locales: locales 99 | }); 100 | 101 | initComponents(Vue); 102 | 103 | require('./service/tooltip'); 104 | 105 | export { default as MessageBox } from './service/msgbox'; 106 | 107 | export { default as Notification } from './service/notification'; 108 | 109 | export { default as Schema } from './schema/index'; 110 | 111 | export { default as LoadingMask } from './service/loading-mask'; 112 | -------------------------------------------------------------------------------- /src/form/fields/text.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 26 | 27 | -------------------------------------------------------------------------------- /docs/grid.md: -------------------------------------------------------------------------------- 1 | 2 | # Overview 3 | 4 | Grid 是一个数据表格组件,用来展示一组数据。 5 | 6 | # Usage 7 | 8 | 使用 d-grid 标签即可创建一个 Grid 组件。 9 | 10 | ```HTML 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 测试1 21 | 22 | 23 | ``` 24 | 25 | 其中 gridData 和 gridSchema 的定义如下: 26 | 27 | ```JavaScript 28 | import { Schema } from 'vue-desktop' 29 | 30 | var gridSchema = new Schema({ 31 | prop1: { 32 | label: '测试1', 33 | required: true 34 | }, 35 | prop2: { 36 | label: '测试2', 37 | required: true 38 | }, 39 | prop3: { 40 | label: '测试3', 41 | type: 'date' 42 | }, 43 | prop4: { 44 | label: '测试4' 45 | }, 46 | prop5: { 47 | label: '测试5', 48 | mapping: { 49 | 'Male': true, 50 | 'Female': false 51 | } 52 | } 53 | }); 54 | ``` 55 | 56 | ```JavaScript 57 | var gridData = [ 58 | {prop1: '11', prop2: '12', prop3: new Date(), prop4: '14', prop5: true}, 59 | {prop1: '21', prop2: '22', prop3: new Date(), prop4: '24', prop5: false}, 60 | {prop1: '31', prop2: '32', prop3: new Date(), prop4: '34', prop5: false}, 61 | {prop1: '41', prop2: '42', prop3: new Date(), prop4: '44', prop5: true}, 62 | {prop1: '51', prop2: '52', prop3: new Date(), prop4: '54', prop5: false} 63 | ]; 64 | ``` 65 | 66 | # Grid Properties 67 | 68 | Grid 目前可用的属性如下: 69 | 70 | | Property | Description | 71 | | ---- | ---- | 72 | | data | Grid 显示的数据。 | 73 | | schema | Grid 使用的 Schema。 | 74 | | height | Grid 的高度,默认高度为空,即自动高度。 | 75 | | fit | Grid 的列的宽度是否自撑开,Boolean 类型,默认为 false。| 76 | | selectionMode | selectionMode的可选值 single,multiple,none; 默认值 single。| 77 | | selection | 多选模式下返回数组,单选模式下返回选中的元素。 | 78 | | fixedColumnCount | 锁定列的数量,默认值为0。| 79 | 80 | # Grid 的事件 81 | 82 | Grid 目前支持的事件如下: 83 | 84 | | Property | Description | Params | 85 | | ---- | ---- | ---- | 86 | | selection-change | 当 Grid 的选择修改的时候会触发该事件。 | selected | 87 | | cell-mouse-enter | 当 Grid 的单元格 hover 进入的时候会触发该事件。 | row, column, cell, event | 88 | | cell-mouse-leave | 当 Grid 的单元格 hover 退出的时候会触发该事件。 | row, column, cell, event | 89 | | cell-click | 当 Grid 的某个单元格被点击的时候会触发该事件。| row, column, cell, event | 90 | 91 | # Grid Column Properties 92 | 93 | Grid Column 目前可用的属性如下: 94 | 95 | | Property | Description | 96 | | ---- | ---- | 97 | | label | Grid Column 显示的标题。如果不设置此属性,并且 Grid 定义了 Schema,并且 Grid Column 定义了property,则从 Schema 中获得 label。 | 98 | | property | Grid Column 要显示的字段名。 | 99 | | width | Grid Column 的宽度。 | 100 | | sortable | Grid Column 是否可以排序,在设置了 property 的情况下,该属性的默认值为 true,反之为 false。 | 101 | | type | Grid Column 的类型,默认为空,可选值:selection、index。如果设置了 selection 则显示多选按钮,如果设置了 index,则显示该行的索引(从1开始计算)。 | 102 | | formatter | Grid Column 的 formatter,Function 类型,可以用来格式化内容,在 formatter 执行的时候,会传入 row 和 column。 | 103 | 104 | ## Grid Column中的内容 105 | 106 | Grid Column 中可以定制该列显示的内容,在 Grid Column 中,可以使用 row 属性访问到每一行的数据。 107 | 108 | 比如想为上面例子中的 prop2 列的显示内容之后添加一个前缀,比如是『¥』。 109 | 110 | 那么,可以这么定义该列: 111 | ```HTML 112 | ¥{{row.prop2}} 113 | ``` 114 | -------------------------------------------------------------------------------- /examples/basic/button.vue: -------------------------------------------------------------------------------- 1 | 95 | 96 | 101 | 102 | -------------------------------------------------------------------------------- /src/basic/nav-menu-item.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 83 | 84 | -------------------------------------------------------------------------------- /src/service/notification.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 83 | 84 | -------------------------------------------------------------------------------- /docs/tree.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Tree 是用来展现树形结构的组件,节点支持多选,节点可以显示图标。 4 | 5 | # Usage 6 | 7 | Tree 的使用可以依赖于 levelConfig,也可以不依赖于 levelConfig。 8 | - 在简单的场景下,可以不使用 levelConfig,但是要求 data 属性的格式为指定的格式。 9 | - 假设每一个层级的数据都不一样,有点层级可能还是在需要显示的时候才去服务器加载数据。在这种情况下,需要使用 levelConfig 来定义每一个层级的数据。 10 | 11 | ## 不使用 levelConfig 12 | 13 | 在不使用 levelConfig 的情况下,要求节点使用的文本使用 label 属性来定义,子节点使用 children 属性来定义,一个简单的例子如下: 14 | 15 | ```JavaScript 16 | export default { 17 | data() { 18 | return { 19 | data: [{ 20 | label: 'bb', 21 | children: [{ 22 | label: 'b1' 23 | }] 24 | }, { 25 | label: 'cc', 26 | children: [{ 27 | label: 'cc1' 28 | }, { 29 | label: 'cc2' 30 | }] 31 | }] 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | ```HTML 38 | 39 | ``` 40 | 41 | ## 使用 levelConfig 42 | 43 | 对 Tree 的每一个层级的节点来讲,每一个节点需要从 JavaScript 中的一个对象映射过来。对于数据来讲,需要关注的点有这么几个:label 用的属性是哪一个,children 用的属性是哪一个,以及下级节点与数据的映射关系。 44 | 45 | 举例来讲,我们有一个两级的树,第一级叫 regions,第二级叫 zones: 46 | - 第一级的数据使用的 label 使用 name 属性。在 label 显示为 label或name 的情况下,无需指定 labelProperty。 47 | - 第二级是一个懒加载的,例子里面我们使用一个虚拟的加载,使用 setTimeout 做一个简单的模拟,在500ms后有50%的概率返回两条数据。 48 | 49 | 那么,levelConfig 这么定义: 50 | 51 | ```JavaScript 52 | var count = 1; 53 | var levelConfig = { 54 | childrenProperty: 'zones', 55 | 56 | children: { 57 | lazy: true, 58 | load: function (node, callback) { 59 | var hasChild = Math.random() > 0.5; 60 | setTimeout(function () { 61 | var data; 62 | if (hasChild) { 63 | data = [{ 64 | name: 'zone' + count++ 65 | }, { 66 | name: 'zone' + count++ 67 | }]; 68 | } else { 69 | data = []; 70 | } 71 | 72 | node.children = data; 73 | if (callback) { 74 | callback(); 75 | } 76 | }, 500); 77 | } 78 | } 79 | }; 80 | ``` 81 | 82 | 数据为: 83 | ```JavaScript 84 | var regions = [ 85 | { 86 | 'name': 'region1' 87 | }, 88 | { 89 | 'name': 'region2' 90 | } 91 | ]; 92 | ``` 93 | 94 | HTML 为: 95 | 96 | ```HTML 97 | 98 | ``` 99 | 100 | # Properties 101 | 102 | Tree 目前可以使用的属性如下: 103 | 104 | | Property | Description | 105 | | ---- | ---- | 106 | | data | 数组类型。 | 107 | | levelConfig | 树的层级定义,具体使用说明请参考下面的表格。 | 108 | | lazyRender | 是否懒渲染子节点,默认值为 true。| 109 | 110 | levelConfig可以使用的属性如下: 111 | 112 | | Property | Description | 113 | | ---- | ---- | 114 | | labelProperty | label 使用的属性是哪一个,如果需要使用 name 或者 label 作为节点的文本,则无需定义该属性。 | 115 | | childrenProperty | children 使用的属性是哪一个,如果为 children,则无需定义该属性。 | 116 | | checkedProperty | Node 的 checkbox 选中使用的属性是哪一个,没有默认值,如果不定义,则表示不需要同步选中的节点。 | 117 | | lazy | 节点是否懒加载,默认为 false。 | 118 | | load | 如果 lazy 属性为 true,在需要展现该节点的时候,会使用 load 方法来加载数据。 | 119 | | recursive | 是否一个递归 level,默认值为 false。如果是一个递归 level,则 children 的 levelConfig 和自身相同。| 120 | | children | 下级节点的配置,同样是一个 levelConfig 。 | 121 | 122 | Tree 目前可以使用的方法如下: 123 | 124 | - getCheckedNodes(leafNodeOnly): 取得目前所有选中的节点,leafNodeOnly 用来表明是否只获取叶子节点,默认值为 false。 125 | 126 | ## lazy: true 的 levelConfig 127 | 128 | 对于 lazy: true 的 levelConfig,需要同时提供一个 load 方法,该方法是一个回调,需要这么做: 129 | 130 | ```JavaScript 131 | load: function (node, callback) { 132 | var hasChild = Math.random() > 0.5; 133 | setTimeout(function () { 134 | var data; 135 | if (hasChild) { 136 | data = [{ 137 | name: 'zone' + count++ 138 | }, { 139 | name: 'zone' + count++ 140 | }]; 141 | } else { 142 | data = []; 143 | } 144 | 145 | node.children = data; 146 | if (callback) { 147 | callback(); 148 | } 149 | }, 500); 150 | } 151 | ``` 152 | 153 | 需要注意两点: 154 | 155 | 1. 需要注意的是 callback 需要检查并执行。 156 | 2. 设置 node.children 用来设置 node 的子节点。 157 | -------------------------------------------------------------------------------- /src/basic/alert.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 119 | 120 | -------------------------------------------------------------------------------- /src/service/msgbox.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 99 | 100 | 151 | -------------------------------------------------------------------------------- /src/basic/dropdown.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 48 | 49 | -------------------------------------------------------------------------------- /src/service/tooltip.vue: -------------------------------------------------------------------------------- 1 | 109 | 110 | 118 | 119 | -------------------------------------------------------------------------------- /src/common.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Modified from http://purecss.io/grids/ 3 | */ 4 | 5 | .d-cell { 6 | display: inline-block; 7 | *display: inline; /* IE < 8: fake inline-block */ 8 | zoom: 1; 9 | letter-spacing: normal; 10 | word-spacing: normal; 11 | vertical-align: top; 12 | text-rendering: auto; 13 | } 14 | 15 | .d-cell-1, 16 | .d-cell-1-1, 17 | .d-cell-1-2, 18 | .d-cell-1-3, 19 | .d-cell-2-3, 20 | .d-cell-1-4, 21 | .d-cell-3-4, 22 | .d-cell-1-5, 23 | .d-cell-2-5, 24 | .d-cell-3-5, 25 | .d-cell-4-5, 26 | .d-cell-5-5, 27 | .d-cell-1-6, 28 | .d-cell-5-6, 29 | .d-cell-1-8, 30 | .d-cell-3-8, 31 | .d-cell-5-8, 32 | .d-cell-7-8, 33 | .d-cell-1-12, 34 | .d-cell-5-12, 35 | .d-cell-7-12, 36 | .d-cell-11-12, 37 | .d-cell-1-24, 38 | .d-cell-2-24, 39 | .d-cell-3-24, 40 | .d-cell-4-24, 41 | .d-cell-5-24, 42 | .d-cell-6-24, 43 | .d-cell-7-24, 44 | .d-cell-8-24, 45 | .d-cell-9-24, 46 | .d-cell-10-24, 47 | .d-cell-11-24, 48 | .d-cell-12-24, 49 | .d-cell-13-24, 50 | .d-cell-14-24, 51 | .d-cell-15-24, 52 | .d-cell-16-24, 53 | .d-cell-17-24, 54 | .d-cell-18-24, 55 | .d-cell-19-24, 56 | .d-cell-20-24, 57 | .d-cell-21-24, 58 | .d-cell-22-24, 59 | .d-cell-23-24, 60 | .d-cell-24-24 { 61 | display: inline-block; 62 | *display: inline; 63 | zoom: 1; 64 | letter-spacing: normal; 65 | word-spacing: normal; 66 | vertical-align: top; 67 | text-rendering: auto; 68 | } 69 | 70 | .d-cell-1-24 { 71 | width: 4.1667%; 72 | *width: 4.1357%; 73 | } 74 | 75 | .d-cell-1-12, 76 | .d-cell-2-24 { 77 | width: 8.3333%; 78 | *width: 8.3023%; 79 | } 80 | 81 | .d-cell-1-8, 82 | .d-cell-3-24 { 83 | width: 12.5000%; 84 | *width: 12.4690%; 85 | } 86 | 87 | .d-cell-1-6, 88 | .d-cell-4-24 { 89 | width: 16.6667%; 90 | *width: 16.6357%; 91 | } 92 | 93 | .d-cell-1-5 { 94 | width: 20%; 95 | *width: 19.9690%; 96 | } 97 | 98 | .d-cell-5-24 { 99 | width: 20.8333%; 100 | *width: 20.8023%; 101 | } 102 | 103 | .d-cell-1-4, 104 | .d-cell-6-24 { 105 | width: 25%; 106 | *width: 24.9690%; 107 | } 108 | 109 | .d-cell-7-24 { 110 | width: 29.1667%; 111 | *width: 29.1357%; 112 | } 113 | 114 | .d-cell-1-3, 115 | .d-cell-8-24 { 116 | width: 33.3333%; 117 | *width: 33.3023%; 118 | } 119 | 120 | .d-cell-3-8, 121 | .d-cell-9-24 { 122 | width: 37.5000%; 123 | *width: 37.4690%; 124 | } 125 | 126 | .d-cell-2-5 { 127 | width: 40%; 128 | *width: 39.9690%; 129 | } 130 | 131 | .d-cell-5-12, 132 | .d-cell-10-24 { 133 | width: 41.6667%; 134 | *width: 41.6357%; 135 | } 136 | 137 | .d-cell-11-24 { 138 | width: 45.8333%; 139 | *width: 45.8023%; 140 | } 141 | 142 | .d-cell-1-2, 143 | .d-cell-12-24 { 144 | width: 50%; 145 | *width: 49.9690%; 146 | } 147 | 148 | .d-cell-13-24 { 149 | width: 54.1667%; 150 | *width: 54.1357%; 151 | } 152 | 153 | .d-cell-7-12, 154 | .d-cell-14-24 { 155 | width: 58.3333%; 156 | *width: 58.3023%; 157 | } 158 | 159 | .d-cell-3-5 { 160 | width: 60%; 161 | *width: 59.9690%; 162 | } 163 | 164 | .d-cell-5-8, 165 | .d-cell-15-24 { 166 | width: 62.5000%; 167 | *width: 62.4690%; 168 | } 169 | 170 | .d-cell-2-3, 171 | .d-cell-16-24 { 172 | width: 66.6667%; 173 | *width: 66.6357%; 174 | } 175 | 176 | .d-cell-17-24 { 177 | width: 70.8333%; 178 | *width: 70.8023%; 179 | } 180 | 181 | .d-cell-3-4, 182 | .d-cell-18-24 { 183 | width: 75%; 184 | *width: 74.9690%; 185 | } 186 | 187 | .d-cell-19-24 { 188 | width: 79.1667%; 189 | *width: 79.1357%; 190 | } 191 | 192 | .d-cell-4-5 { 193 | width: 80%; 194 | *width: 79.9690%; 195 | } 196 | 197 | .d-cell-5-6, 198 | .d-cell-20-24 { 199 | width: 83.3333%; 200 | *width: 83.3023%; 201 | } 202 | 203 | .d-cell-7-8, 204 | .d-cell-21-24 { 205 | width: 87.5000%; 206 | *width: 87.4690%; 207 | } 208 | 209 | .d-cell-11-12, 210 | .d-cell-22-24 { 211 | width: 91.6667%; 212 | *width: 91.6357%; 213 | } 214 | 215 | .d-cell-23-24 { 216 | width: 95.8333%; 217 | *width: 95.8023%; 218 | } 219 | 220 | .d-cell-1, 221 | .d-cell-1-1, 222 | .d-cell-5-5, 223 | .d-cell-24-24 { 224 | width: 100%; 225 | } -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | const dateUtil = require('fecha'); 2 | 3 | export function merge(target) { 4 | for (var i = 1, j = arguments.length; i < j; i++) { 5 | var source = arguments[i]; 6 | for (var prop in source) { 7 | if (source.hasOwnProperty(prop)) { 8 | var value = source[prop]; 9 | if (value !== undefined) { 10 | target[prop] = value; 11 | } 12 | } 13 | } 14 | } 15 | 16 | return target; 17 | } 18 | 19 | export function formatDate(date, format) { 20 | if (!(date instanceof Date)) return ''; 21 | return dateUtil.format(date, format || 'YYYY-MM-DD'); 22 | } 23 | 24 | export function parseDate(string, format) { 25 | return dateUtil.parse(string, format || 'YYYY-MM-DD'); 26 | } 27 | 28 | export function debounce(fn, delay) { 29 | var timer; 30 | 31 | return function() { 32 | var context = this; 33 | var args = arguments; 34 | if (timer) { 35 | clearTimeout(timer); 36 | timer = null; 37 | } 38 | timer = setTimeout(function() { 39 | fn.apply(context, args); 40 | timer = null; 41 | }, delay); 42 | }; 43 | } 44 | 45 | export function throttle(fn, delay) { 46 | var now, lastExec, timer, context, args; 47 | 48 | var execute = function() { 49 | fn.apply(context, args); 50 | lastExec = now; 51 | }; 52 | 53 | return function() { 54 | context = this; 55 | args = arguments; 56 | 57 | now = Date.now(); 58 | 59 | if (timer) { 60 | clearTimeout(timer); 61 | timer = null; 62 | } 63 | 64 | if (!lastExec) { 65 | execute(); 66 | } else { 67 | var diff = delay - (now - lastExec); 68 | if (diff < 0) { 69 | execute(); 70 | } else { 71 | timer = setTimeout(function() { 72 | execute(); 73 | }, diff); 74 | } 75 | } 76 | }; 77 | } 78 | 79 | export function getNestedPath(object, nestedProp) { 80 | let propertyArr = nestedProp.split('.'); 81 | let property = propertyArr.pop(); 82 | return getPath(object, propertyArr.join('.')).fields[property]; 83 | } 84 | 85 | export function getPath(object, prop) { 86 | prop = prop || ''; 87 | var paths = prop.split('.'); 88 | var current = object; 89 | var result = null; 90 | for (var i = 0, j = paths.length; i < j; i++) { 91 | var path = paths[i]; 92 | if (!current) break; 93 | 94 | if (i === j - 1) { 95 | result = current[path]; 96 | break; 97 | } 98 | current = current[path]; 99 | } 100 | return result; 101 | } 102 | 103 | export function setPath(object, prop, value) { 104 | if (prop === undefined || prop === null) return; 105 | 106 | if (typeof prop === 'object') { 107 | var target = prop; 108 | for (prop in target) { 109 | if (target.hasOwnProperty(prop)) { 110 | setPath(object, prop, target[prop]); 111 | } 112 | } 113 | } else { 114 | prop = prop || ''; 115 | var paths = prop.split('.'); 116 | var current = object; 117 | for (var i = 0, j = paths.length; i < j; i++) { 118 | var path = paths[i]; 119 | if (!current) break; 120 | if (i === j - 1) { 121 | current[path] = value; 122 | break; 123 | } 124 | current = current[path]; 125 | } 126 | } 127 | } 128 | 129 | var scrollbarWidth; 130 | 131 | export function getScrollbarWidth() { 132 | if (scrollbarWidth !== undefined) return scrollbarWidth; 133 | 134 | var outer = document.createElement('div'); 135 | outer.style.visibility = 'hidden'; 136 | outer.style.width = '100px'; 137 | outer.style.position = 'absolute'; 138 | outer.style.top = '-9999px'; 139 | document.body.appendChild(outer); 140 | 141 | var widthNoScroll = outer.offsetWidth; 142 | outer.style.overflow = 'scroll'; 143 | 144 | var inner = document.createElement('div'); 145 | inner.style.width = '100%'; 146 | outer.appendChild(inner); 147 | 148 | var widthWithScroll = inner.offsetWidth; 149 | outer.parentNode.removeChild(outer); 150 | 151 | return widthNoScroll - widthWithScroll; 152 | } 153 | -------------------------------------------------------------------------------- /examples/form/mapping.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | -------------------------------------------------------------------------------- /examples/app.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 59 | 60 | 156 | -------------------------------------------------------------------------------- /examples/data/tree.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 25 | 26 | --------------------------------------------------------------------------------