├── docs ├── intro.md ├── uploader.md ├── icon.md ├── theme.md ├── sticky.md ├── spinner.md ├── switch.md ├── tag.md ├── use.md ├── utils.md ├── slide.md ├── toast.md ├── popup.md ├── actionsheet.md ├── table.md ├── tabs.md ├── radio.md ├── button.md ├── notice-bar.md ├── layout.md ├── tabbar.md ├── checkbox.md ├── modal.md ├── field.md └── nav-bar.md ├── packages ├── theme │ ├── src │ │ ├── icon.scss │ │ ├── slide-item.scss │ │ ├── common │ │ │ ├── _ellipsis.scss │ │ │ ├── _1px.scss │ │ │ ├── _var.scss │ │ │ └── _transition.scss │ │ ├── spinner.scss │ │ ├── sticky.scss │ │ ├── uploader.scss │ │ ├── tab-pane.scss │ │ ├── col.scss │ │ ├── tabbar.scss │ │ ├── table.scss │ │ ├── cell-group.scss │ │ ├── row.scss │ │ ├── slide.scss │ │ ├── base.scss │ │ ├── index.scss │ │ ├── field.scss │ │ ├── checkbox.scss │ │ ├── tabbar-item.scss │ │ ├── actionsheet.scss │ │ ├── popup.scss │ │ ├── switch.scss │ │ ├── toast.scss │ │ ├── radio.scss │ │ ├── tag.scss │ │ ├── cell.scss │ │ ├── modal.scss │ │ ├── notice-bar.scss │ │ ├── nav-bar.scss │ │ ├── button.scss │ │ └── tabs.scss │ └── gulpfile.js ├── mixins │ ├── index.js │ ├── find-parent.js │ └── popup.js ├── cell │ ├── index.js │ └── src │ │ └── index.vue ├── col │ ├── index.js │ └── src │ │ └── index.vue ├── icon │ ├── index.js │ ├── src │ │ └── index.vue │ └── demo │ │ └── index.vue ├── row │ ├── index.js │ ├── src │ │ └── index.vue │ └── demo │ │ └── index.vue ├── tabs │ ├── index.js │ ├── src │ │ └── title.vue │ └── demo │ │ └── index.vue ├── tag │ ├── index.js │ ├── src │ │ └── index.vue │ └── demo │ │ └── index.vue ├── button │ ├── index.js │ ├── demo │ │ └── index.vue │ └── src │ │ └── index.vue ├── checkbox │ ├── index.js │ ├── demo │ │ └── index.vue │ └── src │ │ └── index.vue ├── field │ ├── index.js │ ├── src │ │ └── index.vue │ └── demo │ │ └── index.vue ├── nav-bar │ ├── index.js │ ├── src │ │ └── index.vue │ └── demo │ │ └── index.vue ├── popup │ ├── index.js │ ├── src │ │ └── index.vue │ └── demo │ │ └── index.vue ├── radio │ ├── index.js │ ├── src │ │ └── index.vue │ └── demo │ │ └── index.vue ├── slide │ ├── index.js │ └── demo │ │ └── index.vue ├── spinner │ ├── index.js │ └── demo │ │ └── index.vue ├── sticky │ ├── index.js │ ├── demo │ │ └── index.vue │ └── src │ │ └── index.vue ├── switch │ ├── index.js │ ├── demo │ │ └── index.vue │ └── src │ │ └── index.vue ├── tab-pane │ ├── index.js │ └── src │ │ └── index.vue ├── tabbar │ ├── index.js │ ├── src │ │ └── index.vue │ └── demo │ │ └── index.vue ├── table │ ├── index.js │ ├── src │ │ └── index.vue │ └── demo │ │ └── index.vue ├── uploader │ ├── index.js │ ├── demo │ │ └── index.vue │ └── src │ │ └── index.vue ├── actionsheet │ ├── index.js │ ├── demo │ │ └── index.vue │ └── src │ │ └── index.vue ├── cell-group │ ├── index.js │ └── src │ │ └── index.vue ├── checkbox-group │ ├── index.js │ └── src │ │ └── index.vue ├── notice-bar │ ├── index.js │ └── demo │ │ └── index.vue ├── radio-group │ ├── index.js │ └── src │ │ └── index.vue ├── slide-item │ ├── index.js │ └── src │ │ └── index.vue ├── tabbar-item │ ├── index.js │ └── src │ │ └── index.vue ├── utils │ ├── index.js │ ├── event.js │ └── shared.js ├── toast │ ├── demo │ │ └── index.vue │ ├── index.js │ └── src │ │ └── index.vue ├── overlay │ ├── overlay.vue │ └── index.js └── modal │ ├── demo │ └── index.vue │ ├── index.js │ └── src │ └── index.vue ├── .eslintignore ├── .gitignore ├── postcss.config.js ├── examples ├── assets │ ├── images │ │ ├── logo1.png │ │ ├── logo2.png │ │ └── device-frame.png │ └── styles │ │ ├── global.scss │ │ └── docs.scss ├── components │ ├── doc-container.vue │ ├── doc-main.vue │ ├── demo-block.vue │ ├── doc-simulator.vue │ ├── demo-header.vue │ ├── doc-nav.vue │ ├── doc-header.vue │ ├── demo-nav.vue │ └── doc-brand.vue ├── utils │ ├── index.js │ └── iframe-router.js ├── index.html ├── router.js ├── pages │ └── demo-home.vue ├── app_docs.vue ├── index_docs.js ├── index_mobile.js └── app_mobile.vue ├── .editorconfig ├── .travis.yml ├── .babelrc ├── .eslintrc ├── LICENSE ├── README.md ├── src └── index.js └── package.json /docs/intro.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/theme/src/icon.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/uploader.md: -------------------------------------------------------------------------------- 1 | ## Uploader 文件上传 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | examples 3 | node_modules 4 | packages/**/demo 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | examples/dist 3 | lib 4 | *.log* 5 | .DS_Store 6 | .idea 7 | .vscode 8 | -------------------------------------------------------------------------------- /packages/theme/src/slide-item.scss: -------------------------------------------------------------------------------- 1 | .i-slide-item { 2 | float: left; 3 | height: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {}, 4 | cssnano: {} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/assets/images/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaojundebug/unique-ui/HEAD/examples/assets/images/logo1.png -------------------------------------------------------------------------------- /examples/assets/images/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaojundebug/unique-ui/HEAD/examples/assets/images/logo2.png -------------------------------------------------------------------------------- /examples/assets/images/device-frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaojundebug/unique-ui/HEAD/examples/assets/images/device-frame.png -------------------------------------------------------------------------------- /packages/mixins/index.js: -------------------------------------------------------------------------------- 1 | import findParent from './find-parent' 2 | import popup from './popup' 3 | 4 | export { findParent, popup } 5 | -------------------------------------------------------------------------------- /packages/theme/src/common/_ellipsis.scss: -------------------------------------------------------------------------------- 1 | .ellipsis { 2 | overflow: hidden; 3 | white-space: nowrap; 4 | text-overflow: ellipsis; 5 | } 6 | -------------------------------------------------------------------------------- /packages/cell/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/col/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/icon/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/row/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/tabs/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/tag/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/button/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/checkbox/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/field/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/nav-bar/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/popup/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/radio/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/slide/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/spinner/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/sticky/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/switch/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/tab-pane/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/tabbar/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/table/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/uploader/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/actionsheet/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/cell-group/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/checkbox-group/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/notice-bar/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/radio-group/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/slide-item/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /packages/tabbar-item/index.js: -------------------------------------------------------------------------------- 1 | import Component from './src' 2 | 3 | Component.install = function(Vue) { 4 | Vue.component(Component.name, Component) 5 | } 6 | 7 | export default Component 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = crlf 7 | charset = utf-8 8 | trim_trailing_whitespace = false 9 | insert_final_newline = false 10 | -------------------------------------------------------------------------------- /packages/cell-group/src/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /packages/theme/src/spinner.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | 3 | .i-spinner { 4 | display: inline-block; 5 | svg { 6 | stroke: $primary-color; 7 | fill: $primary-color; 8 | vertical-align: middle; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/components/doc-container.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /packages/theme/src/sticky.scss: -------------------------------------------------------------------------------- 1 | .i-sticky { 2 | &__inner { 3 | position: relative; 4 | width: 100%; 5 | } 6 | &--native { 7 | position: sticky; 8 | } 9 | &--simulate { 10 | .i-sticky__inner { 11 | position: fixed; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/theme/src/uploader.scss: -------------------------------------------------------------------------------- 1 | .i-uploader { 2 | position: relative; 3 | display: inline-block; 4 | &__input { 5 | position: absolute; 6 | top: 0; 7 | right: 0; 8 | bottom: 0; 9 | left: 0; 10 | opacity: 0; 11 | width: 100%; 12 | height: 100%; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/theme/src/tab-pane.scss: -------------------------------------------------------------------------------- 1 | .i-tab { 2 | &__panel { 3 | width: 100%; 4 | padding: 20px; 5 | box-sizing: border-box; 6 | } 7 | &--animated { 8 | .i-tab__content { 9 | display: flex; 10 | } 11 | .i-tab__panel { 12 | flex-shrink: 0; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10 4 | install: 5 | - yarn 6 | script: 7 | - yarn build:doc 8 | cache: yarn 9 | deploy: 10 | provider: pages 11 | local_dir: examples/dist 12 | skip_cleanup: true 13 | keep_history: true 14 | github_token: $GITHUB_TOKEN 15 | on: 16 | branch: master -------------------------------------------------------------------------------- /packages/theme/src/col.scss: -------------------------------------------------------------------------------- 1 | .i-col { 2 | flex-shrink: 0; 3 | width: 100%; 4 | box-sizing: border-box; 5 | } 6 | 7 | $i: 1; 8 | 9 | @while $i <= 24 { 10 | .i-col--#{$i} { 11 | width: $i * 100% / 24; 12 | } 13 | .i-col--offset-#{$i} { 14 | margin-left: $i * 100% / 24; 15 | } 16 | 17 | $i: $i + 1; 18 | } 19 | -------------------------------------------------------------------------------- /packages/radio-group/src/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "loose": true, 5 | "modules": false 6 | } 7 | ], 8 | ["@vue/babel-preset-jsx", { 9 | "functional": false 10 | } 11 | ] 12 | ], 13 | "plugins": [ 14 | "@babel/plugin-transform-runtime", 15 | "@babel/plugin-syntax-dynamic-import", 16 | "@babel/plugin-transform-object-assign" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/theme/src/tabbar.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | 3 | .i-tabbar { 4 | height: $tab-bar-height; 5 | &__inner { 6 | position: relative; 7 | display: flex; 8 | height: $tab-bar-height; 9 | background-color: #fff; 10 | } 11 | &--fixed { 12 | .i-tabbar__inner { 13 | position: fixed; 14 | left: 0; 15 | bottom: 0; 16 | width: 100%; 17 | z-index: 1; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["plugin:vue-libs/recommended", "plugin:vue/strongly-recommended"], 4 | "rules": { 5 | "space-before-function-paren": ["error", "never"], 6 | "vue/html-self-closing": 0, 7 | "vue/require-default-prop": 0, 8 | "vue/require-v-for-key": 0, 9 | "vue/max-attributes-per-line": [ 10 | 2, 11 | { 12 | "singleline": 1 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/theme/src/table.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | 3 | .i-table { 4 | overflow: auto; 5 | font-size: 0; 6 | background-color: #fff; 7 | &__tr { 8 | display: inline-flex; 9 | min-width: 100%; 10 | } 11 | &__th { 12 | font-weight: bold; 13 | } 14 | &__th, 15 | &__td { 16 | flex-grow: 1; 17 | display: inline-block; 18 | padding: 15px; 19 | box-sizing: border-box; 20 | font-size: 14px; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/components/doc-main.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | -------------------------------------------------------------------------------- /packages/mixins/find-parent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * find parent component by name 3 | */ 4 | 5 | export default { 6 | data() { 7 | return { 8 | parent: null 9 | } 10 | }, 11 | 12 | methods: { 13 | findParent(name) { 14 | let parent = this.$parent 15 | while (parent) { 16 | if (parent.$options.name === name) { 17 | this.parent = parent 18 | break 19 | } 20 | parent = parent.$parent 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/checkbox-group/src/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 29 | -------------------------------------------------------------------------------- /packages/theme/src/cell-group.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | 3 | .i-cell-group { 4 | background-color: #fff; 5 | &:not(:last-child) { 6 | margin-bottom: 10px; 7 | } 8 | .i-cell { 9 | &:not(:first-child)::after { 10 | content: ''; 11 | position: absolute; 12 | pointer-events: none; 13 | box-sizing: border-box; 14 | left: 0.3rem; 15 | right: 0; 16 | top: 0; 17 | transform: scaleY(0.5); 18 | transform-origin: 0 0; 19 | border-bottom: 1px solid $border-color; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/utils/index.js: -------------------------------------------------------------------------------- 1 | // 测试css兼容性 2 | function featureTest(property, value, noPrefixes) { 3 | var prop = property + ':' 4 | 5 | var el = document.createElement('test') 6 | 7 | var mStyle = el.style 8 | 9 | if (!noPrefixes) { 10 | mStyle.cssText = 11 | prop + ['-webkit-', '-moz-', '-ms-', '-o-', ''].join(value + ';' + prop) + value + ';' 12 | } else { 13 | mStyle.cssText = prop + value 14 | } 15 | return mStyle[property].indexOf(value) !== -1 16 | } 17 | 18 | export { featureTest } 19 | export * from './event' 20 | export * from './shared' 21 | -------------------------------------------------------------------------------- /packages/theme/src/row.scss: -------------------------------------------------------------------------------- 1 | .i-row { 2 | display: flex; 3 | justify-content: flex-start; 4 | align-items: flex-start; 5 | flex-wrap: wrap; 6 | &--justify-center { 7 | justify-content: center; 8 | } 9 | &--justify-end { 10 | justify-content: flex-end; 11 | } 12 | &--justify-space-around { 13 | justify-content: space-around; 14 | } 15 | &--justify-space-between { 16 | justify-content: space-between; 17 | } 18 | &--align-center { 19 | align-items: center; 20 | } 21 | &--align-bottom { 22 | align-items: flex-end; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/row/src/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 29 | 30 | -------------------------------------------------------------------------------- /packages/toast/demo/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 22 | -------------------------------------------------------------------------------- /packages/theme/src/slide.scss: -------------------------------------------------------------------------------- 1 | .i-slide { 2 | position: relative; 3 | overflow: hidden; 4 | &__track { 5 | height: 100%; 6 | user-select: none; 7 | } 8 | &__indicators { 9 | position: absolute; 10 | left: 50%; 11 | transform: translateX(-50%); 12 | bottom: 10px; 13 | display: flex; 14 | margin: 0; 15 | padding: 0; 16 | list-style: none; 17 | } 18 | &__indicator { 19 | height: 6px; 20 | width: 6px; 21 | border-radius: 50%; 22 | margin: 0 2px; 23 | background-color: rgba(0, 0, 0, 0.3); 24 | &.active { 25 | background-color: orange; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/theme/src/base.scss: -------------------------------------------------------------------------------- 1 | @import './common/normalize'; 2 | @import './common/ellipsis'; 3 | @import './common/transition'; 4 | @import './common/var'; 5 | @import './common/1px'; 6 | 7 | body { 8 | font-family: 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Hiragino Sans GB', 'Heiti SC', 9 | 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif; 10 | margin: 0; 11 | font-size: 14px; 12 | color: $dark-color; 13 | -webkit-tap-highlight-color: transparent; 14 | } 15 | 16 | button, 17 | input, 18 | textarea { 19 | outline: none; 20 | } 21 | 22 | ul { 23 | list-style-type: none; 24 | margin: 0; 25 | padding: 0; 26 | } 27 | -------------------------------------------------------------------------------- /packages/icon/src/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 36 | 37 | -------------------------------------------------------------------------------- /packages/utils/event.js: -------------------------------------------------------------------------------- 1 | // Test via a getter in the options object to see 2 | // if the passive property is accessed 3 | var supportsPassive = false 4 | try { 5 | var opts = Object.defineProperty({}, 'passive', { 6 | get: function() { 7 | supportsPassive = true 8 | } 9 | }) 10 | window.addEventListener('test', null, opts) 11 | } catch (e) {} 12 | 13 | function on(elem, event, handler, passive = false) { 14 | elem.addEventListener(event, handler, supportsPassive ? { passive } : false) 15 | } 16 | 17 | function off(elem, event, handler) { 18 | elem.removeEventListener(event, handler) 19 | } 20 | 21 | export { 22 | on, 23 | off 24 | } 25 | -------------------------------------------------------------------------------- /examples/utils/index.js: -------------------------------------------------------------------------------- 1 | function iframeReady(iframe, callback) { 2 | const doc = iframe.contentWindow.document 3 | const interval = () => { 4 | if (iframe.contentWindow.changePath) { 5 | callback() 6 | } else { 7 | setTimeout(() => { 8 | interval() 9 | }, 50) 10 | } 11 | } 12 | 13 | if (doc.readyState === 'complete') { 14 | interval() 15 | } else { 16 | iframe.onload = interval 17 | } 18 | } 19 | 20 | const ua = navigator.userAgent.toLowerCase() 21 | const isMobile = /ios|iphone|ipod|ipad|android/.test(ua) 22 | const randNum = (a = 0, b) => a + Math.round(Math.random() * (b - a)) 23 | 24 | export { isMobile, iframeReady, randNum } 25 | -------------------------------------------------------------------------------- /packages/icon/demo/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /packages/theme/src/index.scss: -------------------------------------------------------------------------------- 1 | @import './base.scss'; 2 | 3 | @import './actionsheet'; 4 | @import './button'; 5 | @import './cell-group'; 6 | @import './cell'; 7 | @import './checkbox'; 8 | @import './col'; 9 | @import './field'; 10 | @import './modal'; 11 | @import './nav-bar'; 12 | @import './notice-bar'; 13 | @import './popup'; 14 | @import './radio'; 15 | @import './row'; 16 | @import './slide-item'; 17 | @import './slide'; 18 | @import './spinner'; 19 | @import './sticky'; 20 | @import './switch'; 21 | @import './tab-pane'; 22 | @import './tabbar-item'; 23 | @import './tabbar'; 24 | @import './table'; 25 | @import './tabs'; 26 | @import './tag'; 27 | @import './toast'; 28 | @import './uploader'; 29 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | unique-ui 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /packages/theme/src/field.scss: -------------------------------------------------------------------------------- 1 | @import './cell.scss'; 2 | 3 | .i-field { 4 | .i-cell__hd { 5 | width: 90px; 6 | } 7 | &__body { 8 | width: 100%; 9 | height: 100%; 10 | display: flex; 11 | align-items: center; 12 | } 13 | &__control { 14 | width: 100%; 15 | height: 100%; 16 | border: none; 17 | padding: 0; 18 | box-sizing: border-box; 19 | background-color: transparent; 20 | resize: none; 21 | &:disabled { 22 | opacity: 1; 23 | color: #999; 24 | -webkit-text-fill-color: #999; 25 | } 26 | &::placeholder { 27 | color: #bbb; 28 | } 29 | } 30 | &__clear { 31 | font-size: 20px; 32 | color: #aaa; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/utils/iframe-router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 同步父窗口和 iframe 的 vue-router 状态 3 | */ 4 | 5 | import { iframeReady, isMobile } from './' 6 | 7 | window.syncPath = function() { 8 | const router = window.vueRouter 9 | const isInIframe = window !== window.top 10 | const currPath = router.history.current.path 11 | 12 | if (!isInIframe && !isMobile) { 13 | const iframe = document.querySelector('iframe') 14 | if (iframe) { 15 | iframeReady(iframe, () => { 16 | iframe.contentWindow.changePath(currPath) 17 | }) 18 | } 19 | } else if (isInIframe) { 20 | window.top.changePath(currPath) 21 | } 22 | } 23 | 24 | window.changePath = function(path = '') { 25 | window.vueRouter.replace(path) 26 | } 27 | -------------------------------------------------------------------------------- /packages/theme/src/common/_1px.scss: -------------------------------------------------------------------------------- 1 | @import './var'; 2 | 3 | [class*='i-1px'] { 4 | position: relative; 5 | 6 | &::after { 7 | content: ' '; 8 | position: absolute; 9 | top: -50%; 10 | left: -50%; 11 | right: -50%; 12 | bottom: -50%; 13 | transform: scale(0.5); 14 | border: 0 solid $border-color; 15 | box-sizing: border-box; 16 | pointer-events: none; 17 | } 18 | } 19 | 20 | .i-1px { 21 | &::after { 22 | border-width: 1px; 23 | } 24 | &--t::after { 25 | border-top-width: 1px; 26 | } 27 | &--r::after { 28 | border-right-width: 1px; 29 | } 30 | &--b::after { 31 | border-bottom-width: 1px; 32 | } 33 | &--l::after { 34 | border-left-width: 1px; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/theme/gulpfile.js: -------------------------------------------------------------------------------- 1 | const { series, src, dest } = require('gulp') 2 | const sass = require('gulp-sass') 3 | const autoprefixer = require('gulp-autoprefixer') 4 | const cssmin = require('gulp-cssmin') 5 | const path = require('path') 6 | 7 | const inputPath = path.resolve(__dirname, './src') 8 | const outputPath = path.resolve(__dirname, '../../lib/theme') 9 | 10 | function compile() { 11 | return src(inputPath + '/*.scss') 12 | .pipe(sass.sync()) 13 | .pipe(autoprefixer()) 14 | .pipe(cssmin()) 15 | .pipe(dest(outputPath)) 16 | } 17 | 18 | function copyfont() { 19 | return src(inputPath + '/fonts/**') 20 | .pipe(cssmin()) 21 | .pipe(dest(outputPath + '/fonts')) 22 | } 23 | 24 | exports.build = series(compile, copyfont) 25 | -------------------------------------------------------------------------------- /packages/theme/src/checkbox.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | 3 | .i-checkbox { 4 | display: inline-flex; 5 | align-items: center; 6 | user-select: none; 7 | margin-right: 10px; 8 | &__icon { 9 | border: 1px solid #ddd; 10 | border-radius: 50%; 11 | width: 17px; 12 | height: 17px; 13 | text-align: center; 14 | line-height: 18px; 15 | font-size: 14px; 16 | color: transparent; 17 | transition: 0.2s; 18 | } 19 | &__label { 20 | font-size: 14px; 21 | margin-left: 5px; 22 | } 23 | &--checked { 24 | .i-checkbox__icon { 25 | background-color: $primary-color; 26 | border-color: $primary-color; 27 | color: #fff; 28 | } 29 | } 30 | &--disabled { 31 | opacity: 0.5; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/icon.md: -------------------------------------------------------------------------------- 1 | ## Icon 图标 2 | 3 | > 图标资源来自于[Font Awesome 4.7.0](https://fontawesome.com/v4.7.0/),兼容官方写法,如果你只是想简单使用某个图标,可以使用这里提供的`Icon`组件,也许能省下一些你写类名的时间 4 | 5 | #### 简单用法 6 | 7 | ```html 8 | 9 | ``` 10 | 11 | #### 自定义大小 12 | 13 | ```html 14 | 15 | 16 | 17 | ``` 18 | 19 | #### 自定义颜色 20 | 21 | ```html 22 | 23 | 24 | ``` 25 | 26 | #### Props 27 | 28 | | 参数 | 说明 | 类型 | 默认值 | 29 | |------|------|------|------| 30 | | name | 图标名称 | `String` | `''` | 31 | | size | 图标大小 | `String` | `'inherit'` | 32 | | color | 图标颜色 | `String` | `'inherit'` | 33 | -------------------------------------------------------------------------------- /examples/components/demo-block.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 21 | 22 | 38 | -------------------------------------------------------------------------------- /packages/slide-item/src/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 39 | 40 | -------------------------------------------------------------------------------- /packages/theme/src/tabbar-item.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | 3 | .i-tabbar-item { 4 | flex: 1; 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | justify-content: center; 9 | color: #a9b1b9; 10 | &__icon { 11 | position: relative; 12 | margin-bottom: 5px; 13 | font-size: 16px; 14 | } 15 | &__icon--dot::after { 16 | content: ''; 17 | position: absolute; 18 | width: 8px; 19 | height: 8px; 20 | border-radius: 50%; 21 | background-color: #f44; 22 | top: 0; 23 | right: 0; 24 | transform: translate(50%, -50%); 25 | } 26 | &__text { 27 | font-size: 12px; 28 | } 29 | &--active { 30 | color: $primary-color; 31 | } 32 | img { 33 | height: 20px; 34 | margin-bottom: 5px; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/router.js: -------------------------------------------------------------------------------- 1 | import navConfig from './nav.config' 2 | import './utils/iframe-router' 3 | 4 | const registerRoutes = isDemo => { 5 | const routes = [] 6 | 7 | if (isDemo) { 8 | routes.push({ 9 | path: '/', 10 | component: () => import('./pages/demo-home'), 11 | meta: { hideNavBar: true } 12 | }) 13 | } else { 14 | routes.push({ 15 | path: '/', 16 | component: () => import('../README') 17 | }) 18 | } 19 | 20 | navConfig.forEach(group => { 21 | group.list.forEach(nav => { 22 | routes.push({ 23 | name: nav.name, 24 | path: nav.routePath, 25 | component: isDemo ? nav.demo : nav.doc, 26 | meta: { hideNavBar: nav.hideNavBar } 27 | }) 28 | }) 29 | }) 30 | 31 | return routes 32 | } 33 | 34 | export default registerRoutes 35 | -------------------------------------------------------------------------------- /packages/sticky/demo/index.vue: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /packages/theme/src/actionsheet.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | @import './popup.scss'; 3 | 4 | .i-actionsheet { 5 | background-color: #f8f8f8; 6 | &__header, 7 | &__body, 8 | &__footer { 9 | background-color: #fff; 10 | } 11 | &__header { 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | flex-direction: column; 16 | min-height: 50px; 17 | } 18 | &__action { 19 | &--disabled { 20 | opacity: 0.5; 21 | } 22 | } 23 | &__footer { 24 | margin-top: 10px; 25 | } 26 | &__action, 27 | &__footer { 28 | height: $action-height; 29 | line-height: $action-height; 30 | text-align: center; 31 | font-size: 16px; 32 | &:active:not(.i-actionsheet__action--disabled) { 33 | background-color: $border-color; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/theme.md: -------------------------------------------------------------------------------- 1 | ## 更换主题 2 | 3 | 1、安装命令行主题构建工具 4 | 5 | ```bash 6 | npm i unique-theme-builder -g 7 | ``` 8 | 9 | 2、修改主题变量 _只能修改,不要删除某个变量_ 10 | 11 | 主题变量文件地址`node_modules/unique-ui/packages/theme/src/common/_var.scss` 12 | 13 | 3、构建主题 14 | 15 | ```bash 16 | # 必须在你工程根目录下输入 17 | utb # 等同于 utb -o .theme 18 | ``` 19 | 20 | 你可以通过`-o`参数指定打包目录,默认 .theme 21 | 22 | 4、引入主题 23 | 24 | 如果是搭配 [babel-plugin-component](https://github.com/ElementUI/babel-plugin-component) 一起使用,只需要修改 .babelrc 的配置,指定 styleLibraryName 路径为自定义主题相对于 .babelrc 的路径,注意要加`~` 25 | 26 | ```javascript 27 | { 28 | // ... 29 | "plugins": [ 30 | // ... 31 | [ 32 | "component", 33 | { 34 | "libraryName": "unique-ui", 35 | "styleLibraryName": "~.theme" 36 | } 37 | ] 38 | ] 39 | } 40 | ``` 41 | 42 | 如果没用这个插件,那么需要你手动引入构建后的主题文件,操作与以前一样,只是路径是你打包后的文件地址 43 | -------------------------------------------------------------------------------- /packages/col/src/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 38 | 39 | -------------------------------------------------------------------------------- /examples/assets/styles/global.scss: -------------------------------------------------------------------------------- 1 | ::-webkit-scrollbar-track { 2 | background-color: transparent; 3 | } 4 | ::-webkit-scrollbar { 5 | width: 6px; 6 | height: 6px; 7 | } 8 | ::-webkit-scrollbar-thumb { 9 | background-color: rgba(165, 181, 194, 0.5); 10 | border-radius: 3px; 11 | } 12 | ::-webkit-scrollbar-thumb:hover { 13 | background-color: rgba(165, 181, 194, 0.85); 14 | } 15 | 16 | html, 17 | body { 18 | height: 100%; 19 | } 20 | body { 21 | margin: 0; 22 | font-family: 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Hiragino Sans GB', 'Heiti SC', 23 | 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif; 24 | color: #34495e; 25 | font-size: 14px; 26 | } 27 | * { 28 | box-sizing: border-box; 29 | } 30 | a { 31 | text-decoration: none; 32 | } 33 | ul { 34 | list-style-type: none; 35 | margin: 0; 36 | padding: 0; 37 | } 38 | -------------------------------------------------------------------------------- /packages/mixins/popup.js: -------------------------------------------------------------------------------- 1 | import Overlay from '../overlay' 2 | 3 | export default { 4 | props: { 5 | show: Boolean, 6 | position: { 7 | type: String, 8 | default: 'center' 9 | }, 10 | overlay: { 11 | type: Boolean, 12 | default: true 13 | }, 14 | overlayOpacity: { 15 | type: Number, 16 | default: 0.5 17 | }, 18 | overlayColor: { 19 | type: String, 20 | default: '#000' 21 | }, 22 | transitionName: String 23 | }, 24 | mounted() { 25 | if (this.show && this.overlay) { 26 | Overlay.open(this) 27 | } 28 | }, 29 | beforeDestroy() { 30 | Overlay.close(this) 31 | }, 32 | watch: { 33 | show(val) { 34 | if (val && this.overlay) { 35 | Overlay.open(this) 36 | } else { 37 | Overlay.close(this) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/tag/src/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 40 | 41 | -------------------------------------------------------------------------------- /packages/tabbar/src/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 45 | 46 | -------------------------------------------------------------------------------- /packages/theme/src/popup.scss: -------------------------------------------------------------------------------- 1 | .i-popup { 2 | backface-visibility: hidden; 3 | overflow-y: auto; 4 | -webkit-overflow-scrolling: touch; 5 | &--center { 6 | position: fixed; 7 | left: 50%; 8 | top: 50%; 9 | width: 85%; 10 | transform: translate3d(-50%, -50%, 0); 11 | } 12 | &--top { 13 | position: fixed; 14 | top: 0; 15 | left: 0; 16 | right: 0; 17 | max-height: 85%; 18 | transform: translate3d(0, 0, 0); 19 | } 20 | &--right { 21 | position: fixed; 22 | top: 0; 23 | bottom: 0; 24 | right: 0; 25 | transform: translate3d(0, 0, 0); 26 | } 27 | &--bottom { 28 | position: fixed; 29 | bottom: 0; 30 | left: 0; 31 | right: 0; 32 | max-height: 85%; 33 | transform: translate3d(0, 0, 0); 34 | } 35 | &--left { 36 | position: fixed; 37 | top: 0; 38 | bottom: 0; 39 | left: 0; 40 | transform: translate3d(0, 0, 0); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /docs/sticky.md: -------------------------------------------------------------------------------- 1 | ## Sticky 粘性定位 2 | 3 | 常用来实现吸顶效果,如果浏览器支持`sticky`定位,则使用原生的,否则会模拟实现,但可能与原生有些许差别 4 | 5 | ```html 6 |

*

7 | 8 | 9 | top: 60px 10 | 11 | 12 |

*

13 | 14 | 15 | 5m6blY.jpg 16 | 17 | 18 |

*

19 | 20 | 21 | top: 300px 22 | 23 | 24 |

*

25 | 26 | 27 | top: 350px 28 | 29 | 30 |

*

31 | ``` 32 | 33 | 34 | #### Props 35 | 36 | | 参数 | 说明 | 类型 | 默认值 | 37 | |------|------|------|------| 38 | | top | 距浏览器顶部的距离(单位为px) | `String | Number` | `0` | 39 | | z-index | 原生 z-index 属性 | `String | Number` | `1` | 40 | -------------------------------------------------------------------------------- /examples/components/doc-simulator.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 38 | -------------------------------------------------------------------------------- /examples/pages/demo-home.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 25 | 26 | 44 | -------------------------------------------------------------------------------- /packages/popup/src/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 49 | 50 | -------------------------------------------------------------------------------- /examples/components/demo-header.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | 23 | 47 | 48 | -------------------------------------------------------------------------------- /packages/uploader/demo/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 25 | 26 | 50 | 51 | -------------------------------------------------------------------------------- /packages/overlay/overlay.vue: -------------------------------------------------------------------------------- 1 | 12 | 55 | -------------------------------------------------------------------------------- /packages/switch/demo/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 xiaojun1994 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/theme/src/switch.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | @import './spinner.scss'; 3 | 4 | $width: $switch-width - 2px; 5 | $height: $switch-height - 2px; 6 | 7 | .i-switch { 8 | position: relative; 9 | display: inline-block; 10 | width: $width; 11 | height: $height; 12 | border: 1px solid rgba(0, 0, 0, 0.1); 13 | border-radius: ($height + 2px) / 2; 14 | box-sizing: content-box; 15 | background-color: #fff; 16 | transition: background-color 0.3s; 17 | &__node { 18 | position: absolute; 19 | width: $height; 20 | height: $height; 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | border-radius: 100%; 25 | top: 0; 26 | left: 0; 27 | box-shadow: 28 | 0 1px 1px 0 rgba(0, 0, 0, 0.05), 29 | 0 2px 2px 0 rgba(0, 0, 0, 0.1), 30 | 0 3px 3px 0 rgba(0, 0, 0, 0.05); 31 | background-color: #fff; 32 | transition: transform 0.3s; 33 | } 34 | &--on { 35 | background-color: $primary-color; 36 | .i-switch__node { 37 | transform: translateX($width - $height); 38 | } 39 | } 40 | &--disabled { 41 | opacity: 0.5; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/app_docs.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 41 | 42 | 49 | -------------------------------------------------------------------------------- /packages/theme/src/common/_var.scss: -------------------------------------------------------------------------------- 1 | /* Color 2 | -------------------------- */ 3 | $primary-color: #4a4d5a; 4 | $info-color: #409eff; 5 | $warning-color: #fdb470; 6 | $success-color: #36be52; 7 | $danger-color: #f86f74; 8 | $dark-color: #34495e; 9 | $light-color: #fff; 10 | 11 | /* Border 12 | -------------------------- */ 13 | $border-radius: 2px; 14 | $border-color: #eee; 15 | 16 | /* NavBar 17 | -------------------------- */ 18 | $nav-bar-height: 50px; 19 | 20 | /* TabBar 21 | -------------------------- */ 22 | $tab-bar-height: 50px; 23 | 24 | /* Button 25 | -------------------------- */ 26 | $button-height-mini: 25px; 27 | $button-height-small: 35px; 28 | $button-height-normal: 45px; 29 | $button-height-large: 50px; 30 | 31 | /* Tag 32 | -------------------------- */ 33 | $tag-height: 24px; 34 | 35 | /* Switch 36 | -------------------------- */ 37 | $switch-width: 50px; 38 | $switch-height: 30px; 39 | 40 | /* Tab 41 | -------------------------- */ 42 | $tab-height_line: 44px; 43 | $tab-height_card: 30px; 44 | 45 | /* NoticeBar 46 | -------------------------- */ 47 | 48 | /* Actionsheet 49 | -------------------------- */ 50 | $action-height: 45px; 51 | -------------------------------------------------------------------------------- /packages/theme/src/toast.scss: -------------------------------------------------------------------------------- 1 | @import './popup.scss'; 2 | 3 | .i-popup.i-toast { 4 | background-color: rgba(50, 50, 51, 0.88); 5 | color: #fff; 6 | z-index: 9999; 7 | overflow: hidden; 8 | &__text { 9 | font-size: 14px; 10 | line-height: 2; 11 | } 12 | &__icon { 13 | i { 14 | font-size: 40px; 15 | } 16 | } 17 | &--normal { 18 | top: auto; 19 | bottom: 75px; 20 | width: fit-content; 21 | max-width: 80%; 22 | padding: 10px 30px; 23 | border-radius: 100px; 24 | } 25 | &--loading, 26 | &--success, 27 | &--fail { 28 | display: flex; 29 | flex-direction: column; 30 | align-items: center; 31 | justify-content: center; 32 | width: 100px; 33 | height: 100px; 34 | padding: 10px; 35 | border-radius: 5px; 36 | text-align: center; 37 | .toast__icon { 38 | width: 50px; 39 | height: 50px; 40 | display: flex; 41 | align-items: center; 42 | justify-content: center; 43 | i { 44 | font-size: 40px; 45 | } 46 | } 47 | .toast__text { 48 | width: 100%; 49 | margin-top: 5px; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/theme/src/radio.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | 3 | // transition-duration 4 | $duration: 0.3s; 5 | 6 | .i-radio { 7 | display: inline-flex; 8 | align-items: center; 9 | user-select: none; 10 | margin-right: 10px; 11 | &__icon { 12 | position: relative; 13 | height: 16px; 14 | width: 16px; 15 | border: 1px solid #ddd; 16 | border-radius: 50%; 17 | transition: border-color $duration; 18 | color: $primary-color; 19 | &::after { 20 | content: ' '; 21 | position: absolute; 22 | width: 10px; 23 | height: 10px; 24 | top: 3px; 25 | left: 3px; 26 | border-radius: 50%; 27 | background-color: currentColor; 28 | transform: scale(0.5); 29 | opacity: 0; 30 | transition: transform $duration, opacity $duration; 31 | } 32 | } 33 | &__label { 34 | font-size: 14px; 35 | margin-left: 5px; 36 | } 37 | &--checked { 38 | .i-radio__icon { 39 | border-color: currentColor; 40 | &::after { 41 | transform: scale(1); 42 | opacity: 1; 43 | } 44 | } 45 | } 46 | &--disabled { 47 | opacity: 0.5; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /docs/spinner.md: -------------------------------------------------------------------------------- 1 | ## Spinner 加载动画 2 | 3 | #### 简单用法 4 | 5 | ```html 6 | 7 | 8 | 9 | 10 | 11 | ``` 12 | 13 | #### 设置大小 14 | 15 | ```html 16 | 17 | 18 | 19 | 20 | 21 | ``` 22 | 23 | #### 自定义颜色 24 | 25 | ```html 26 | 27 | 28 | 29 | 30 | 31 | ``` 32 | 33 | #### Props 34 | 35 | | 参数 | 说明 | 类型 | 默认值 | 36 | |------|------|------|------| 37 | | type | 类型,可选值为`ios` `dots` `ripple` `lines` `crescent` | `String` | `ios` | 38 | | size | 大小 | `String` | `30px` | 39 | | color | 颜色 | `String` | 主题色 | 40 | -------------------------------------------------------------------------------- /docs/switch.md: -------------------------------------------------------------------------------- 1 | ## Switch 开关 2 | 3 | #### 简单用法 4 | 5 | ```html 6 | 7 | ``` 8 | 9 | #### 禁用状态 10 | 11 | ```html 12 | 13 | ``` 14 | 15 | #### 加载状态 16 | 17 | 设置`loading`属性开启加载状态,此时不可点击 18 | 19 | ```html 20 | 21 | ``` 22 | 23 | #### 自定义颜色 24 | 25 | ```html 26 | 27 | ``` 28 | 29 | #### 搭配 Cell 使用 30 | 31 | ```html 32 | 33 | 34 | 35 | 36 | 37 | ``` 38 | 39 | #### Props 40 | 41 | | 参数 | 说明 | 类型 | 默认值 | 42 | |------|------|------|------| 43 | | value | 为`true`代表打开,为`false`代表关闭 | `Boolean` | `false` | 44 | | disabled | 是否为禁用状态 | `Boolean` | `false` | 45 | | loading | 是否为加载状态 | `Boolean` | `false` | 46 | | active-color | 打开时的背景色 | `String` | 主题色 | 47 | | inactive-color | 关闭时的背景色 | `String` | `#fff` | 48 | 49 | #### Events 50 | 51 | | 事件名 | 说明 | 参数 | 52 | |------|------|------| 53 | | change | 切换时触发 | 状态 | -------------------------------------------------------------------------------- /packages/theme/src/tag.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | 3 | .i-tag { 4 | display: inline-flex; 5 | align-items: center; 6 | justify-content: center; 7 | height: $tag-height; 8 | padding: 0 10px; 9 | border-radius: $border-radius; 10 | background-color: $primary-color; 11 | color: $light-color; 12 | font-size: 12px; 13 | &::after { 14 | border-radius: $border-radius * 2; 15 | border-color: currentColor; 16 | } 17 | &--info { 18 | background-color: $info-color; 19 | } 20 | &--warning { 21 | background-color: $warning-color; 22 | } 23 | &--success { 24 | background-color: $success-color; 25 | } 26 | &--danger { 27 | background-color: $danger-color; 28 | } 29 | &--round { 30 | border-radius: $tag-height / 2; 31 | } 32 | &--plain { 33 | background-color: transparent; 34 | color: $primary-color; 35 | &.i-tag--info { 36 | color: $info-color; 37 | } 38 | &.i-tag--warning { 39 | color: $warning-color; 40 | } 41 | &.i-tag--success { 42 | color: $success-color; 43 | } 44 | &.i-tag--danger { 45 | color: $danger-color; 46 | } 47 | &.i-tag--round::after { 48 | border-radius: $tag-height; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/index_docs.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './app_docs' 3 | import VueRouter from 'vue-router' 4 | import routes from './router' 5 | import 'highlight.js/styles/solarized-light.css' 6 | import './assets/styles/global.scss' 7 | import './assets/styles/docs.scss' 8 | import { isMobile } from './utils' 9 | import VueProgressBar from 'vue-progressbar' 10 | 11 | Vue.use(VueRouter) 12 | Vue.use(VueProgressBar, { 13 | color: "#ff7e4a", 14 | transition: { 15 | speed: '0.2s', 16 | opacity: '0.6s', 17 | termination: 300 18 | } 19 | }) 20 | 21 | const router = new VueRouter({ 22 | mode: 'hash', 23 | routes: routes(false) 24 | }) 25 | 26 | router.beforeEach((to, from, next) => { 27 | Vue.prototype.$Progress.start() 28 | if (isMobile) { 29 | location.replace('mobile.html' + location.hash) 30 | } 31 | next() 32 | }) 33 | 34 | router.afterEach((to, from) => { 35 | Vue.prototype.$Progress.finish() 36 | window.document.querySelector('.doc-container').scrollTo(0, 0) 37 | Vue.nextTick(window.syncPath) 38 | }) 39 | 40 | window.vueRouter = router 41 | 42 | Vue.config.productionTip = false 43 | 44 | new Vue({ 45 | el: '#app', 46 | router, 47 | components: { App }, 48 | template: '' 49 | }) 50 | -------------------------------------------------------------------------------- /packages/tab-pane/src/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 58 | 59 | -------------------------------------------------------------------------------- /packages/theme/src/cell.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | 3 | .i-cell { 4 | position: relative; 5 | display: flex; 6 | min-height: 30px; 7 | padding: 10px 15px; 8 | background-color: #fff; 9 | &__icon { 10 | margin-right: 5px; 11 | .fa { 12 | font-size: 16px; 13 | } 14 | img { 15 | max-height: 32px; 16 | } 17 | } 18 | 19 | &__hd { 20 | display: flex; 21 | flex-direction: column; 22 | justify-content: center; 23 | margin-right: 8px; 24 | } 25 | 26 | &__bd { 27 | flex: 1; 28 | display: flex; 29 | align-items: center; 30 | justify-content: flex-end; 31 | } 32 | 33 | &__ft { 34 | margin-left: 8px; 35 | img { 36 | max-height: 32px; 37 | } 38 | } 39 | 40 | &__icon, 41 | &__ft { 42 | display: flex; 43 | align-items: center; 44 | justify-content: center; 45 | } 46 | 47 | &__title { 48 | line-height: 28px; 49 | } 50 | &__desc { 51 | font-size: 12px; 52 | } 53 | 54 | &__desc, 55 | &__value, 56 | &__arrow { 57 | color: #838993; 58 | } 59 | 60 | &__arrow.fa { 61 | font-size: 20px; 62 | } 63 | 64 | &--clickable { 65 | &:active { 66 | background-color: $border-color; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/theme/src/modal.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | @import './popup.scss'; 3 | 4 | .i-modal { 5 | background-color: #fff; 6 | border-radius: 5px; 7 | &__title { 8 | padding-top: 30px; 9 | text-align: center; 10 | color: #1d273c; 11 | font-size: 15px; 12 | font-weight: bold; 13 | } 14 | &__body { 15 | padding: 20px 25px 25px 25px; 16 | color: #535d72; 17 | font-size: 16px; 18 | text-align: center; 19 | } 20 | &__footer { 21 | } 22 | &__btn-group { 23 | display: flex; 24 | align-items: center; 25 | justify-content: center; 26 | .button:not(:first-child) { 27 | margin-left: 12px; 28 | } 29 | } 30 | &__confirm-btn, 31 | &__cancel-btn { 32 | width: 100%; 33 | height: 50px; 34 | line-height: 50px; 35 | text-align: center; 36 | font-size: 15px; 37 | &:active { 38 | background-color: rgba(0, 0, 0, 0.05); 39 | } 40 | } 41 | &__confirm-btn { 42 | color: $info-color; 43 | } 44 | &__alert-btn { 45 | width: 100%; 46 | height: 50px; 47 | line-height: 50px; 48 | text-align: center; 49 | font-size: 15px; 50 | color: $primary-color; 51 | &:active { 52 | background-color: rgba(0, 0, 0, 0.05); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/spinner/demo/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/switch/src/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 58 | 59 | -------------------------------------------------------------------------------- /packages/tabs/src/title.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /packages/theme/src/notice-bar.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | 3 | .i-notice-bar { 4 | &__inner { 5 | position: relative; 6 | display: flex; 7 | align-items: center; 8 | padding: 10px 15px; 9 | font-size: 14px; 10 | background: #ffebc4; 11 | color: #df9952; 12 | } 13 | 14 | &__left { 15 | display: flex; 16 | align-items: center; 17 | margin-right: 2px; 18 | } 19 | 20 | &__main { 21 | flex: 1; 22 | position: relative; 23 | display: flex; 24 | align-items: center; 25 | overflow: hidden; 26 | } 27 | &__content { 28 | line-height: 20px; 29 | white-space: nowrap; 30 | &.ellipsis { 31 | max-width: 100%; 32 | } 33 | } 34 | 35 | &__right { 36 | display: flex; 37 | align-items: center; 38 | margin-left: 2px; 39 | } 40 | &__right-icon { 41 | font-size: 20px; 42 | } 43 | 44 | &--play { 45 | animation: notice-bar-slideInRight linear both; 46 | } 47 | 48 | &--loop { 49 | animation: notice-bar-slideInRight--loop linear infinite both; 50 | } 51 | } 52 | 53 | @keyframes notice-bar-slideInRight { 54 | to { 55 | transform: translate3d(-100%, 0, 0); 56 | } 57 | } 58 | 59 | @keyframes notice-bar-slideInRight--loop { 60 | to { 61 | transform: translate3d(-100%, 0, 0); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /docs/tag.md: -------------------------------------------------------------------------------- 1 | ## Tag 标记 2 | 3 | #### 简单用法 4 | 5 | ```html 6 | 标签 7 | 标签 8 | 标签 9 | 标签 10 | 标签 11 | ``` 12 | 13 | #### 圆角样式 14 | 15 | ```html 16 | 标签 17 | 标签 18 | 标签 19 | 标签 20 | 标签 21 | ``` 22 | 23 | #### 空心样式 24 | 25 | ```html 26 | 标签 27 | 标签 28 | 标签 29 | 标签 30 | 标签 31 | ``` 32 | 33 | #### 自定义颜色 34 | 35 | ```html 36 | 标签 37 | 标签 38 | 标签 39 | 标签 40 | ``` 41 | 42 | #### Props 43 | 44 | | 参数 | 说明 | 类型 | 默认值 | 45 | |------|------|------|------| 46 | | type | 类型,可选值为`primary` `info` `warning` `success` `danger` | `String` | `primary` | 47 | | color | 自定义标签颜色 | `String` | `''` | 48 | | plain | 是否为空心样式 | `Boolean` | `false` | 49 | | round | 是否为圆角样式 | `Boolean` | `false` | 50 | -------------------------------------------------------------------------------- /docs/use.md: -------------------------------------------------------------------------------- 1 | ## 快速上手 2 | 3 | 字体图标文件未集成到`unique-ui`中,请在`index.html`中引入以下样式 4 | 5 | ```html 6 | 7 | 8 | ``` 9 | 10 | #### 完整引入 11 | 12 | ```javascript 13 | import 'unique-ui/lib/theme/index.css' // 引入所有样式 14 | import uniqueUI from 'unique-ui' 15 | Vue.use(uniqueUI) 16 | ``` 17 | 18 | #### 手动引入 19 | 20 | ```javascript 21 | import 'unique-ui/lib/theme/base.css' // 引入基本样式 22 | import 'unique-ui/lib/theme/button.css' // 引入组件样式 23 | import { Button } from 'unique-ui' 24 | Vue.use(Button) 25 | ``` 26 | 27 | #### 按需引入(推荐) 28 | 29 | _可以帮你自动引入这个组件的样式_ 30 | 31 | 首先安装 [babel-plugin-component](https://github.com/ElementUI/babel-plugin-component) 库 32 | 33 | ```bash 34 | npm install babel-plugin-component -D 35 | ``` 36 | 37 | 然后修改`.babelrc`文件 38 | 39 | ```javascript 40 | { 41 | // ... 42 | "plugins": [ 43 | // ... 44 | [ 45 | "component", 46 | { 47 | "libraryName": "unique-ui", 48 | "styleLibraryName": "theme" 49 | } 50 | ] 51 | ] 52 | } 53 | ``` 54 | 55 | 接下来,如果你只希望引入部分组件,比如`Button`和`Modal`,那么需要在`main.js`中写入以下内容: 56 | 57 | ```javascript 58 | import { Button, Modal } from 'unique-ui' 59 | Vue.use(Button) 60 | .use(Modal) 61 | ``` 62 | -------------------------------------------------------------------------------- /packages/tag/demo/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 40 | -------------------------------------------------------------------------------- /packages/slide/demo/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 44 | -------------------------------------------------------------------------------- /packages/table/src/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 60 | -------------------------------------------------------------------------------- /packages/theme/src/nav-bar.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | 3 | .i-nav-bar { 4 | height: $nav-bar-height; 5 | &__inner { 6 | position: relative; 7 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); 8 | height: $nav-bar-height; 9 | background-color: #fff; 10 | color: $primary-color; 11 | } 12 | &__left { 13 | height: 100%; 14 | position: absolute; 15 | top: 0; 16 | left: 10px; 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | &:active { 21 | opacity: 0.5; 22 | } 23 | .i-nav-bar__arrow { 24 | font-size: 25px; 25 | margin-top: -1px; 26 | } 27 | .i-nav-bar__text { 28 | font-size: 14px; 29 | margin-left: -6px; 30 | } 31 | } 32 | &__title { 33 | max-width: 50%; 34 | height: $nav-bar-height; 35 | line-height: $nav-bar-height; 36 | text-align: center; 37 | margin: 0 auto; 38 | font-size: 16px; 39 | color: $dark-color; 40 | } 41 | &__right { 42 | height: 100%; 43 | position: absolute; 44 | top: 0; 45 | right: 10px; 46 | display: flex; 47 | align-items: center; 48 | justify-content: center; 49 | &:active { 50 | opacity: 0.5; 51 | } 52 | } 53 | &--fixed { 54 | .i-nav-bar__inner { 55 | position: fixed; 56 | z-index: 99; 57 | top: 0; 58 | left: 0; 59 | width: 100%; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/tabbar-item/src/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 65 | 66 | -------------------------------------------------------------------------------- /packages/nav-bar/src/index.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 63 | 64 | -------------------------------------------------------------------------------- /packages/utils/shared.js: -------------------------------------------------------------------------------- 1 | // 日期格式化 2 | export function dateFormat(date, fmt = 'yyyy-MM-dd hh:mm:ss') { 3 | var o = { 4 | 'M+': date.getMonth() + 1, // 月份 5 | 'd+': date.getDate(), // 日 6 | 'h+': date.getHours(), // 时 7 | 'm+': date.getMinutes(), // 分 8 | 's+': date.getSeconds(), // 秒 9 | 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度 10 | S: date.getMilliseconds() // 毫秒 11 | } 12 | if (/(y+)/.test(fmt)) { 13 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) 14 | } 15 | for (var k in o) { 16 | if (new RegExp('(' + k + ')').test(fmt)) { 17 | fmt = fmt.replace( 18 | RegExp.$1, 19 | RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length) 20 | ) 21 | } 22 | } 23 | return fmt 24 | } 25 | // 数字前补 0 26 | export function prefixInteger(num, n = 2) { 27 | return (Array(n).join(0) + num).slice(-n) 28 | } 29 | 30 | // 防抖 31 | export function debounce(fn, delay) { 32 | let timer = null 33 | return function(...args) { 34 | clearTimeout(timer) 35 | timer = setTimeout(() => { 36 | fn.call(this, ...args) 37 | }, delay) 38 | } 39 | } 40 | 41 | // 节流 42 | export function throttle(fn, delay, cb) { 43 | let time = null 44 | return function(...args) { 45 | const curTime = new Date() 46 | if (time && curTime - time < delay) { 47 | return typeof cb === 'function' && cb.call(this) 48 | } 49 | time = curTime 50 | fn.call(this, ...args) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | logo 3 |

4 | 5 |

A mobile component library for Vue2.x

6 | 7 |

8 | travis  9 | npm  10 | stars  11 | license  12 | contributors 13 |

14 | 15 | > :warning: 由于我工作中很久未再使用 vue 技术栈,且 vue3 也已发布,该仓库没有动力继续维护。 16 | 17 | _这个 UI 库是我练手所用,质量可想而知,所以不要用在正式环境哦~_ 18 | 19 | _其中大量代码参考(抄袭)了 [vant](https://github.com/youzan/vant) ,少量代码参考了 [element-ui](https://github.com/ElemeFE/element)、[vux](https://github.com/airyland/vux) 等其它库_ 20 | 21 | ### 👀 预览 22 | 23 | https://xiaojundebug.github.io/unique-ui/ 24 | 25 | ### 🌵 安装 26 | 27 | ```bash 28 | npm i unique-ui 29 | ``` 30 | 31 | ### 🚀 快速开始 32 | 33 | ```javascript 34 | import Vue from 'vue' 35 | import Unique from 'unique-ui' 36 | 37 | Vue.use(Unique) 38 | ``` 39 | 40 | ### 💩 多年后对该项目的一些感想与反思 41 | 42 | - commit message 不规范,有些 message 看起来很呆 43 | - 主题功能薄弱,需要依靠手动构建文件 44 | - icon 用的 `Font Awesom`,使用方式比较简陋 45 | - 不该暴露一些公共方法,这对于一个 ui 库来说“管的太宽了”,况且写的还不咋样 46 | - 没有单元测试 47 | - 😀 还有。。。。现在感觉这个组件库的名字比较一般 48 | 49 | ... 50 | -------------------------------------------------------------------------------- /packages/table/demo/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 64 | -------------------------------------------------------------------------------- /packages/actionsheet/demo/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 42 | 43 | 50 | -------------------------------------------------------------------------------- /examples/components/doc-nav.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 27 | 28 | 67 | 68 | -------------------------------------------------------------------------------- /packages/radio/src/index.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 73 | 74 | -------------------------------------------------------------------------------- /packages/toast/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Component from './src' 3 | 4 | const Toast = Vue.extend(Component) 5 | let vm = null 6 | let timer = null 7 | 8 | function clear() { 9 | try { 10 | document.body.removeChild(vm.$el) 11 | vm.$destroy() 12 | vm = null 13 | } catch (error) {} 14 | } 15 | 16 | function handler(msg, opts = {}) { 17 | if (vm) clear() 18 | if (timer) window.clearTimeout(timer) 19 | 20 | vm = new Toast() 21 | document.body.appendChild(vm.$mount().$el) 22 | 23 | const { type, duration = 2500, overlay } = opts 24 | 25 | vm.text = msg + '' // 设置弹窗内容 26 | vm.type = type // 设置弹窗类型 27 | vm.overlay = overlay 28 | 29 | vm.show = true 30 | 31 | vm.$on('toggle', function(val) { 32 | this.show = val 33 | }) 34 | 35 | if (duration) { 36 | timer = setTimeout(() => { 37 | vm.show = false 38 | }, duration) 39 | } 40 | } 41 | 42 | Component.toast = (msg, opts = {}) => handler(msg, { type: 'normal', ...opts, overlay: false }) 43 | Component.loading = (msg, opts = {}) => handler(msg, { duration: 0, ...opts, type: 'loading' }) 44 | Component.success = (msg, opts = {}) => handler(msg, { ...opts, type: 'success' }) 45 | Component.fail = (msg, opts = {}) => handler(msg, { ...opts, type: 'fail' }) 46 | 47 | Component.install = function(Vue) { 48 | Vue.prototype.$toast = this.toast 49 | Vue.prototype.$toast.loading = this.loading 50 | Vue.prototype.$toast.success = this.success 51 | Vue.prototype.$toast.fail = this.fail 52 | 53 | Vue.prototype.$toast.close = clear 54 | 55 | Vue.component(Component.name, Component) 56 | } 57 | 58 | export default Component 59 | -------------------------------------------------------------------------------- /docs/utils.md: -------------------------------------------------------------------------------- 1 | ## 实用代码 2 | 3 | `unique-ui`中集成了一些常用工具代码 4 | 5 | #### 1px 方案 6 | 7 | 基于伪类 + transform 实现,提供了以下几个内置类名 8 | 9 | - 上边`i-1px--t` 10 | - 右边`i-1px--r` 11 | - 下边`i-1px--b` 12 | - 左边`i-1px--l` 13 | - 全部`i-1px` 14 | 15 | #### 单行省略 16 | 17 | 提供了内置类名`ellipsis` 18 | 19 | #### 日期格式化 20 | 21 | > dateFormat(date[, fmt = 'yyyy-MM-dd hh:mm:ss']) 22 | > 23 | > - date\: Date 实例 24 | > - fmt\: 格式,默认为 'yyyy-MM-dd hh:mm:ss' 25 | 26 | ```javascript 27 | import { utils: { dateFormat } } from 'unique' 28 | // 常规用法 29 | dateFormat(new Date(), 'yyyy-MM-dd hh:mm:ss') 30 | // 只保留年月日部分 31 | dateFormat(new Date(), 'yyyy-MM-dd') 32 | // 只保留时间部分 33 | dateFormat(new Date(), 'hh:mm:ss') 34 | // 随意组合 35 | dateFormat(new Date(), 'yyyy-MM-dd 00:00:00') 36 | ``` 37 | 38 | #### 数字前补 0 39 | 40 | > prefixInteger(num[, n = 2]) 41 | > 42 | > - num\: 被修饰数字 43 | > - n\: 修饰成多少位,默认 2 位 44 | 45 | ```javascript 46 | import { utils: { prefixInteger } } from 'unique' 47 | prefixInteger(9, 2) // 09 48 | prefixInteger(15, 3) // 015 49 | ``` 50 | 51 | #### 防抖 52 | 53 | > debounce(fn, delay) 54 | > 55 | > - fn\: 被装饰方法 56 | > - delay\: 间隔多少毫秒 57 | 58 | ```javascript 59 | import { utils: { debounce } } from 'unique' 60 | const search = debounce(function() { 61 | // ... 62 | }, 300) 63 | ``` 64 | 65 | #### 节流 66 | 67 | > throttle(fn, delay[, cb]) 68 | > 69 | > - fn\: 被装饰方法 70 | > - delay\: 间隔多少毫秒 71 | > - cb\: 节流发挥作用时触发 72 | 73 | ```javascript 74 | import { utils: { throttle } } from 'unique' 75 | const query = throttle( 76 | function() { 77 | // ... 78 | }, 79 | 1000, 80 | function() { 81 | console.log('请求次数过于频繁') 82 | } 83 | ) 84 | ``` 85 | -------------------------------------------------------------------------------- /packages/modal/demo/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 47 | 48 | 64 | -------------------------------------------------------------------------------- /docs/slide.md: -------------------------------------------------------------------------------- 1 | ## Slide 轮播图 2 | 3 | ```scss 4 | // 示例样式 5 | .i-slide-item { 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | font-size: 38px; 10 | color: #fff; 11 | &:nth-child(odd) { 12 | background-color: #787e8f; 13 | } 14 | &:nth-child(even) { 15 | background-color: #9398a6; 16 | } 17 | } 18 | ``` 19 | 20 | #### 简单用法 21 | 22 | ```html 23 | 24 | 1 25 | 2 26 | 3 27 | 4 28 | 29 | ``` 30 | 31 | #### 自定义指示器 32 | 33 | ```html 34 | 35 | 1 36 | 2 37 | 3 38 | 4 39 |
40 | {{ props.active + 1 }} / 4 41 |
42 |
43 | ``` 44 | 45 | #### Slide Props 46 | 47 | | 参数 | 说明 | 类型 | 默认值 | 48 | |------|------|------|------| 49 | | loop | 是否可以无缝循环轮播 | `Boolean` | `true` | 50 | | autoplay | 自动轮播毫秒数,`0`代表不自动轮播 | `Number` | `3000` | 51 | | duration | 动画时长,单位毫秒 | `Number` | `500` | 52 | | initial-slide | 初始位置索引值 | `Number` | `0` | 53 | 54 | #### Slide Slots 55 | 56 | | 名称 | 说明 | slot-scope | 57 | |------|------|------| 58 | | indicator | 自定义指示器 | active: 当前索引 | 59 | 60 | #### Slide Methods 61 | 62 | | 方法名 | 参数 | 返回值 | 介绍 | 63 | |------|------|------|------| 64 | | slideTo | 索引 | - | 切换到指定位置 | 65 | 66 | #### Slide Events 67 | 68 | | 事件名 | 说明 | 参数 | 69 | |------|------|------| 70 | | change | 滑动完成后触发 | 当前索引 | 71 | -------------------------------------------------------------------------------- /packages/theme/src/common/_transition.scss: -------------------------------------------------------------------------------- 1 | /* 淡入 2 | -------------------------- */ 3 | .fadeIn-enter-active, 4 | .fadeIn-leave-active { 5 | transition: opacity 0.3s !important; 6 | } 7 | .fadeIn-enter, 8 | .fadeIn-leave-to { 9 | opacity: 0 !important; 10 | } 11 | 12 | /* 从上面滑动到下面 13 | -------------------------- */ 14 | .slideInDown-enter-active, 15 | .slideInDown-leave-active { 16 | transition: transform 0.35s !important; 17 | } 18 | .slideInDown-enter, 19 | .slideInDown-leave-to { 20 | transform: translate3d(0, -100%, 0) !important; 21 | } 22 | 23 | /* 从右面滑动到左面 24 | -------------------------- */ 25 | .slideInRight-enter-active, 26 | .slideInRight-leave-active { 27 | transition: transform 0.35s !important; 28 | } 29 | .slideInRight-enter, 30 | .slideInRight-leave-to { 31 | transform: translate3d(100%, 0, 0) !important; 32 | } 33 | 34 | /* 从左面滑动到右面 35 | -------------------------- */ 36 | .slideInLeft-enter-active, 37 | .slideInLeft-leave-active { 38 | transition: transform 0.35s !important; 39 | } 40 | .slideInLeft-enter, 41 | .slideInLeft-leave-to { 42 | transform: translate3d(-100%, 0, 0) !important; 43 | } 44 | 45 | /* 从下面滑动到上面 46 | -------------------------- */ 47 | .slideInUp-enter-active, 48 | .slideInUp-leave-active { 49 | transition: transform 0.35s !important; 50 | } 51 | .slideInUp-enter, 52 | .slideInUp-leave-to { 53 | transform: translate3d(0, 100%, 0) !important; 54 | } 55 | 56 | /* 从中间淡入放大 57 | -------------------------- */ 58 | .zoomIn-enter-active, 59 | .zoomIn-leave-active { 60 | transition: opacity 0.3s, transform 0.3s !important; 61 | } 62 | .zoomIn-enter { 63 | opacity: 0; 64 | transform: translate3d(-50%, -50%, 0) scale(0.7) !important; 65 | } 66 | .zoomIn-leave-to { 67 | opacity: 0; 68 | transform: translate3d(-50%, -50%, 0) scale(0.9) !important; 69 | } 70 | -------------------------------------------------------------------------------- /docs/toast.md: -------------------------------------------------------------------------------- 1 | ## Toast 消息提示 2 | 3 | #### 简单使用 4 | 5 | _第一个参数代表消息内容,第二个参数传入一个 options 配置对象,仅支持单例模式_ 6 | 7 | ```javascript 8 | import { Toast } from 'unique-ui' 9 | 10 | /* 普通提示 */ 11 | Toast.toast('hello~') 12 | 13 | /* 加载提示 */ 14 | Toast.toast('Loading...', { type: 'loading', duration: 3000 }) 15 | Toast.loading('Loading...', { duration: 3000 }) 16 | 17 | /* 成功提示 */ 18 | Toast.toast('成功', { type: 'success' }) 19 | Toast.success('成功') 20 | 21 | /* 失败提示 */ 22 | Toast.toast('失败', { type: 'fail' }) 23 | Toast.fail('失败') 24 | ``` 25 | 26 | #### 全局使用 27 | 28 | > 推荐用`Vue.use`方式来使用本组件,以后便可以在每个Vue实例上直接通过`this.$toast`方式使用 29 | 30 | ```javascript 31 | import { Toast } from 'unique-ui' 32 | Vue.use(Toast) 33 | 34 | /* 普通提示 */ 35 | this.$toast('hello~') 36 | 37 | /* 加载提示 */ 38 | this.$toast('Loading...', { type: 'loading', duration: 3000 }) 39 | this.$toast.loading('Loading...', { duration: 3000 }) 40 | 41 | /* 成功提示 */ 42 | this.$toast('成功', { type: 'success' }) 43 | this.$toast.success('成功') 44 | 45 | /* 失败提示 */ 46 | this.$toast('失败', { type: 'fail' }) 47 | this.$toast.fail('失败') 48 | ``` 49 | 50 | #### Options(第二个参数) 51 | 52 | | 参数 | 说明 | 类型 | 默认值 | 53 | |------|------|------|------| 54 | | type | 类型,可选值为`normal` `loading` `success` `fail` | `String` | `'normal'` | 55 | | duration | 多久自动消失,`0`代表不消失,单位毫秒 | `Number` | 当`type`为`loading`时默认是`0`,其它为`2500` | 56 | | overlay | 是否显示一个透明度为`0`的蒙版,防止用户点击其它地方,只在`type`不为`normal`时生效 | `Boolean` | `true` | 57 | 58 | #### Props 59 | 60 | | 参数 | 说明 | 类型 | 默认值 | 61 | |------|------|------|------| 62 | | show | 是否显示 | `Boolean` | `false` | 63 | | type | 同 Options 中一致 | `String` | `normal` | 64 | | text | 文字内容 | `String` | `''` | 65 | | overlay | 是否显示一个透明度为`0`的蒙版,不同于 Options 中,这里所有`type`下都会生效 | `Boolean` | `true` | 66 | 67 | 68 | #### Slots 69 | 70 | | 参数 | 说明 | 71 | |------|------| 72 | | icon | 图标 | 73 | | text | 文字 | 74 | -------------------------------------------------------------------------------- /packages/modal/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Component from './src' 3 | 4 | const Modal = Vue.extend(Component) 5 | let vm = null 6 | 7 | function clear() { 8 | try { 9 | document.body.removeChild(vm.$el) 10 | vm.$destroy() 11 | vm = null 12 | } catch (error) {} 13 | } 14 | 15 | function handler(msg, opts = {}) { 16 | return new Promise((resolve, reject) => { 17 | if (vm) clear() 18 | 19 | vm = new Modal() 20 | document.body.appendChild(vm.$mount().$el) 21 | 22 | const { 23 | type, 24 | title, 25 | confirmText, 26 | cancelText, 27 | closeOnClickOverlay, 28 | autoClose = true, 29 | transitionName 30 | } = opts 31 | 32 | title && (vm.title = title) 33 | vm.text = msg 34 | vm.type = type 35 | vm.closeOnClickOverlay = closeOnClickOverlay 36 | vm.transitionName = transitionName 37 | 38 | confirmText && (vm.confirmText = confirmText) 39 | cancelText && (vm.cancelText = cancelText) 40 | 41 | vm.show = true 42 | 43 | if (autoClose) { 44 | vm.$on('toggle', function(val) { 45 | this.show = val 46 | }) 47 | } 48 | vm.$on('cancel', _ => reject('cancel')) 49 | vm.$on('confirm', _ => 50 | resolve(() => { 51 | vm.show = false 52 | }) 53 | ) 54 | }) 55 | } 56 | 57 | Component.modal = (msg, opts = {}) => handler(msg, { type: 'alert', ...opts }) 58 | Component.alert = (msg, opts = {}) => handler(msg, { ...opts, type: 'alert' }) 59 | Component.confirm = (msg, opts = {}) => handler(msg, { ...opts, type: 'confirm' }) 60 | 61 | Component.install = function(Vue) { 62 | Vue.prototype.$modal = this.modal 63 | Vue.prototype.$modal.alert = this.alert 64 | Vue.prototype.$modal.confirm = this.confirm 65 | 66 | Vue.component(Component.name, Component) 67 | } 68 | 69 | export default Component 70 | -------------------------------------------------------------------------------- /packages/tabs/demo/index.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 55 | -------------------------------------------------------------------------------- /examples/components/doc-header.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 24 | 25 | 50 | -------------------------------------------------------------------------------- /packages/toast/src/index.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 86 | 87 | -------------------------------------------------------------------------------- /docs/popup.md: -------------------------------------------------------------------------------- 1 | ## Popup 弹出层 2 | 3 | > `Popup` 组件在整个组件库中是一个十分重要的基本组件,很多弹出类组件都依赖于它 4 | 5 | #### 位置 6 | 7 | 通过`position`属性来设置弹层出现位置 8 | 9 | ```html 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ``` 26 | 27 | #### 自定义样式 28 | 29 | ```html 30 | 31 | 32 | 33 | ``` 34 | 35 | ```css 36 | .fade-enter-active, 37 | .fade-leave-active { 38 | transition: opacity 0.3s, transform 0.3s; 39 | } 40 | .fade-enter, 41 | .fade-leave-to { 42 | opacity: 0; 43 | transform: translate3d(-50%, -50%, 0); 44 | } 45 | ``` 46 | 47 | #### Props 48 | 49 | | 参数 | 说明 | 类型 | 默认值 | 50 | |------|------|------|------| 51 | | show | 是否显示 | `Boolean` | `false` | 52 | | position | 位置,可选值为`center` `top` `right` `bottom` `left` | `String` | `center` | 53 | | overlay | 是否显示蒙版 | `Boolean` | `true` | 54 | | overlay-opacity | 蒙版透明度 | `Number` | `0.5` | 55 | | overlay-color | 蒙版颜色 | `String` | `#000` | 56 | | transition-name | 同 vue 中`transition`的`name`属性 | `String` | 取决于`position` | 57 | 58 | #### Events 59 | 60 | | 事件名 | 说明 | 参数 | 61 | |------|------|------| 62 | | click-overlay | 点击蒙版时触发 | - | 63 | -------------------------------------------------------------------------------- /packages/cell/src/index.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 83 | 84 | -------------------------------------------------------------------------------- /docs/actionsheet.md: -------------------------------------------------------------------------------- 1 | ## Actionsheet 上拉菜单 2 | 3 | #### 简单用法 4 | 5 | 需要传入一个`actions`数组,数组的每一项是一个对象,对象属性见文档下方表格。 6 | 7 | ```html 8 | 9 | ``` 10 | 11 | ```javascript 12 | export default { 13 | data() { 14 | return { 15 | show: false, 16 | actions: [{ name: '唱' }, { name: '跳' }, { name: 'rap' }, { name: '篮球', disabled: true }], 17 | } 18 | }, 19 | methods: { 20 | onSelect(data) { 21 | this.$toast(data.name) 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | #### 显示取消按钮 28 | 29 | 传入`cancel-text`以显示底部按钮 30 | 31 | 32 | ```html 33 | 34 | ``` 35 | 36 | #### 自定义样式 37 | 38 | ```html 39 | 40 | 44 | 45 | ``` 46 | 47 | ```javascript 48 | export default { 49 | data() { 50 | return { 51 | show: false, 52 | actions: [{ name: '删除', className: 'danger' }], 53 | } 54 | } 55 | } 56 | ``` 57 | 58 | ```scss 59 | .danger { 60 | color: #f86f74; 61 | } 62 | ``` 63 | 64 | #### Props 65 | 66 | | 参数 | 说明 | 类型 | 默认值 | 67 | |------|------|------|------| 68 | | show | 是否显示 | `Boolean` | `false` | 69 | | actions | 选项,请参考下方详情 | `Array` | `[]` | 70 | | cancel-text | 取消按钮文字 | `String` | `''` | 71 | | close-on-click-overlay | 点击蒙版时是否自动关闭 | `Boolean` | `false` | 72 | 73 | #### Actions 74 | 75 | | 参数 | 说明 | 76 | |------|------| 77 | | name | 选项内容 | 78 | | disabled | 禁用该项 | 79 | | className | 设置额外class | 80 | 81 | #### Slots 82 | 83 | | 名称 | 说明 | 84 | |------|------| 85 | | header | 头部区域 | 86 | | footer | 尾部区域 | 87 | 88 | #### Events 89 | 90 | | 事件名 | 说明 | 参数 | 91 | |------|------|------| 92 | | select | 选中时触发 | 该项对应数据 | 93 | | cancel | 取消时触发 | - | -------------------------------------------------------------------------------- /packages/overlay/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Component from './overlay' // 引入 overlay 组件 3 | const O = Vue.extend(Component) 4 | let overlayVM = null 5 | 6 | let zIndex = 1000 7 | const getZIndex = function() { 8 | return zIndex++ 9 | } 10 | 11 | const Overlay = { 12 | instances: [], 13 | // 打开遮罩层 14 | open(instance) { 15 | if (!instance || this.instances.indexOf(instance) !== -1) return 16 | if (!overlayVM) { 17 | // 当没有遮盖层时,新建遮盖层 18 | this.createOverlay() 19 | } 20 | // 禁止页面滚动 21 | this.bodyOverflow = document.body.style.overflow 22 | document.body.style.overflow = 'hidden' 23 | // 设置内容的z-index 24 | instance.$el.style.zIndex = getZIndex() 25 | overlayVM.color = instance.overlayColor 26 | overlayVM.opacity = instance.overlayOpacity 27 | overlayVM.show = true 28 | 29 | this.instances.push(instance) 30 | }, 31 | // 关闭遮罩层 32 | close(instance) { 33 | const index = this.instances.indexOf(instance) 34 | if (index === -1) return 35 | 36 | Vue.nextTick(() => { 37 | this.instances.splice(index, 1) 38 | 39 | // 当页面上没有弹出层了就关闭遮盖层 40 | if (this.instances.length === 0) { 41 | this.closeOverlay() 42 | } 43 | }) 44 | }, 45 | createOverlay() { 46 | overlayVM = new O() 47 | overlayVM.onClick = this.handleOverlayClick.bind(this) 48 | document.body.appendChild(overlayVM.$mount().$el) 49 | // 设置遮罩层z-index 50 | overlayVM.$el.style.zIndex = getZIndex() 51 | }, 52 | // 关闭遮罩层 53 | closeOverlay() { 54 | if (!overlayVM) return 55 | document.body.style.overflow = this.bodyOverflow 56 | overlayVM.show = false 57 | }, 58 | // 处理遮罩层点击事件 59 | handleOverlayClick() { 60 | if (this.instances.length === 0) return 61 | const instance = this.instances[this.instances.length - 1] 62 | const { handleOverlayClick } = instance 63 | if (handleOverlayClick && typeof handleOverlayClick === 'function') { 64 | handleOverlayClick() 65 | } 66 | } 67 | } 68 | 69 | export default Overlay 70 | -------------------------------------------------------------------------------- /packages/actionsheet/src/index.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 85 | 86 | -------------------------------------------------------------------------------- /docs/table.md: -------------------------------------------------------------------------------- 1 | ## Table 表格 2 | 3 | 一个简单的表格组件,使用`ul`模拟而成 4 | 5 | ```html 6 | 7 | ``` 8 | 9 | #### 用法 10 | 11 | ```javascript 12 | export default { 13 | data() { 14 | return { 15 | tableColumns: [ 16 | { 17 | title: 'Name', 18 | key: 'name', 19 | width: 120, 20 | }, 21 | { 22 | title: 'Age', 23 | key: 'age', 24 | align: 'right', 25 | width: 70, 26 | render(val) { 27 | if (val >= 18) { 28 | return "" + val + '' 29 | } else { 30 | return "" + val + '' 31 | } 32 | } 33 | }, 34 | { 35 | title: 'Address', 36 | key: 'address' 37 | } 38 | ], 39 | tableData: [ 40 | { 41 | name: 'John Brown', 42 | age: 14, 43 | address: 'New York No. 1 Lake Park' 44 | }, 45 | { 46 | name: 'Jim Green', 47 | age: 24, 48 | address: 'London No. 1 Lake Park' 49 | }, 50 | { 51 | name: 'Joe Black', 52 | age: 30, 53 | address: 'Sydney No. 1 Lake Park' 54 | }, 55 | { 56 | name: 'Jon Snow', 57 | age: 17, 58 | address: 'Ottawa No. 2 Lake Park' 59 | } 60 | ] 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | #### Props 67 | 68 | | 参数 | 说明 | 类型 | 默认值 | 69 | |------|------|------|------| 70 | | columns | 列配置,详情见下个表格 | `Array` | `[]` | 71 | | data | 数据 | `Array` | `[]` | 72 | 73 | #### columns 74 | 75 | | 参数 | 说明 | 类型 | 默认值 | 76 | |------|------|------|------| 77 | | title | 列名 | `String` | `''` | 78 | | key | 字段名 | `String` | `''` | 79 | | width | 列宽度,单位 px | `Number` | `100` | 80 | | align | 内容对齐方式,同 css 中 text-align | `String` | `left` | 81 | | render | 自定义渲染,参数为原始值 | `Function` | `''` | 82 | 83 | #### Events 84 | 85 | | 事件名 | 说明 | 参数 | 86 | |------|------|------| 87 | | click-row | 点击某行时触发 | 当前行原始数据 | -------------------------------------------------------------------------------- /packages/theme/src/button.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | @import './spinner.scss'; 3 | 4 | .i-button { 5 | position: relative; 6 | display: inline-flex; 7 | align-items: center; 8 | justify-content: center; 9 | border: 1px solid currentColor; 10 | border-radius: $border-radius; 11 | background-color: currentColor; 12 | vertical-align: middle; 13 | 14 | &::before { 15 | content: ' '; 16 | position: absolute; 17 | top: 50%; 18 | left: 50%; 19 | opacity: 0; 20 | width: 100%; 21 | height: 100%; 22 | border: inherit; 23 | border-color: #000; 24 | background-color: #000; 25 | border-radius: inherit; 26 | transform: translate(-50%, -50%); 27 | } 28 | 29 | &:active::before { 30 | opacity: 0.05; 31 | } 32 | 33 | &:disabled, &--loading { opacity: 0.5; &:active::before { opacity: 0; } } 34 | 35 | &__text { color: $light-color; } 36 | 37 | .i-spinner { margin-right: 3px; } 38 | 39 | &--plain, &--dashed { background-color: #fff; .i-button__text { color: currentColor; } } 40 | 41 | &--dashed { border-style: dashed; } 42 | 43 | &--round { border-radius: 10em; } 44 | 45 | &--circle { 46 | border-radius: 50%; 47 | overflow: hidden; 48 | &.i-button--normal { width: $button-height-normal; } 49 | &.i-button--large { width: $button-height-large; } 50 | &.i-button--small { width: $button-height-small; } 51 | &.i-button--mini { width: $button-height-mini; } 52 | } 53 | 54 | &--default { color: $border-color; .i-button__text { color: $dark-color; } } 55 | 56 | &--normal { height: $button-height-normal; padding: 0 17px; font-size: 14px; } 57 | 58 | &--primary { color: $success-color; } 59 | 60 | &--info { color: $info-color; } 61 | 62 | &--danger { color: $danger-color; } 63 | 64 | &--warning { color: $warning-color; } 65 | 66 | &--large { width: 100%; height: $button-height-large; padding: 0 15px; font-size: 15px; } 67 | 68 | &--small { height: $button-height-small; padding: 0 px; font-size: 13px; } 69 | 70 | &--mini { height: $button-height-mini; padding: 0 10px; font-size: 12px; } 71 | } 72 | -------------------------------------------------------------------------------- /packages/button/demo/index.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 56 | 57 | -------------------------------------------------------------------------------- /packages/checkbox/demo/index.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 61 | -------------------------------------------------------------------------------- /packages/uploader/src/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 83 | -------------------------------------------------------------------------------- /examples/index_mobile.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './app_mobile' 3 | import VueRouter from 'vue-router' 4 | import routes from './router' 5 | import 'packages/theme/src/index.scss' 6 | import uniqueUI from 'src' 7 | import DemoBlock from './components/demo-block.vue' 8 | import VueProgressBar from 'vue-progressbar' 9 | import { isMobile } from './utils' 10 | 11 | Vue.component(DemoBlock.name, DemoBlock) 12 | 13 | Vue.use(VueRouter) 14 | Vue.use(uniqueUI) 15 | Vue.use(VueProgressBar, { 16 | color: '#8d8f91', 17 | transition: { 18 | speed: '0.2s', 19 | opacity: '0.6s', 20 | termination: 300 21 | } 22 | }) 23 | 24 | const router = new VueRouter({ 25 | mode: 'hash', 26 | routes: routes(true) 27 | }) 28 | 29 | router.beforeEach((to, from, next) => { 30 | Vue.prototype.$Progress.start() 31 | next() 32 | }) 33 | 34 | router.afterEach((to, from) => { 35 | Vue.prototype.$Progress.finish() 36 | if (!router.currentRoute.redirectedFrom) { 37 | window.scrollTo(0, 0) 38 | Vue.nextTick(window.syncPath) 39 | } 40 | }) 41 | 42 | window.vueRouter = router 43 | 44 | Vue.config.productionTip = false 45 | 46 | new Vue({ 47 | el: '#app', 48 | router, 49 | components: { App }, 50 | template: '' 51 | }) 52 | 53 | if (!isMobile) { 54 | import('./utils/touch-simulator') 55 | 56 | // const touchPointEl = document.querySelector('.touch-point') 57 | // const { style } = touchPointEl 58 | 59 | // function correctPosition(ev) { 60 | // const { clientX, clientY } = ev 61 | // style.top = clientY - touchPointEl.offsetWidth / 2 + 'px' 62 | // style.left = clientX - touchPointEl.offsetWidth / 2 + 'px' 63 | // } 64 | 65 | // function onMousedown(ev) { 66 | // style.opacity = '0.3' 67 | // correctPosition(ev) 68 | // window.addEventListener('mousemove', onMousemove, true) 69 | // } 70 | 71 | // function onMousemove(ev) { 72 | // style.opacity = '0.3' 73 | // correctPosition(ev) 74 | // } 75 | 76 | // function onMouseup(ev) { 77 | // style.opacity = '0' 78 | // window.removeEventListener('mousemove', onMousemove, true) 79 | // } 80 | 81 | // window.addEventListener('mousedown', onMousedown, true) 82 | // window.addEventListener('mouseup', onMouseup, true) 83 | } 84 | -------------------------------------------------------------------------------- /packages/button/src/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 98 | 99 | -------------------------------------------------------------------------------- /packages/sticky/src/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 93 | 94 | -------------------------------------------------------------------------------- /packages/notice-bar/demo/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 29 | 30 | 37 | 38 | -------------------------------------------------------------------------------- /packages/theme/src/tabs.scss: -------------------------------------------------------------------------------- 1 | @import './common/var'; 2 | 3 | .i-tab { 4 | position: relative; 5 | overflow: hidden; 6 | &--line { 7 | padding-top: $tab-height-line; 8 | background-color: #fff; 9 | .i-tab__wrap { 10 | height: $tab-height_line; 11 | } 12 | .i-tab__nav { 13 | box-sizing: content-box; 14 | height: 100%; 15 | padding-bottom: 15px; 16 | } 17 | .i-tab__title { 18 | height: 100%; 19 | color: #999; 20 | & > span { 21 | line-height: $tab-height_line; 22 | } 23 | &--active { 24 | color: #333; 25 | } 26 | } 27 | } 28 | &--card { 29 | padding-top: $tab-height-card; 30 | .i-tab__wrap { 31 | height: $tab-height_card; 32 | overflow: hidden; 33 | } 34 | .i-tab__nav { 35 | box-sizing: border-box; 36 | height: 100%; 37 | margin: 0 15px; 38 | border: 1px solid $info-color; 39 | border-radius: 2px; 40 | background-color: #fff; 41 | } 42 | .i-tab__title { 43 | height: 100%; 44 | color: $info-color; 45 | & > span { 46 | line-height: $tab-height_card - 2px; 47 | } 48 | &--active { 49 | background-color: $info-color; 50 | color: #fff; 51 | } 52 | &:not(:last-child) { 53 | border-right: 1px solid $info-color; 54 | } 55 | } 56 | } 57 | 58 | &__wrap { 59 | position: absolute; 60 | top: 0; 61 | right: 0; 62 | left: 0; 63 | z-index: 1; 64 | overflow: hidden; 65 | } 66 | &__nav { 67 | position: relative; 68 | display: flex; 69 | user-select: none; 70 | } 71 | &__title { 72 | flex: 1; 73 | position: relative; 74 | box-sizing: border-box; 75 | padding: 0 5px; 76 | font-size: 14px; 77 | text-align: center; 78 | overflow: hidden; 79 | & > span { 80 | display: block; 81 | } 82 | } 83 | &__track { 84 | position: absolute; 85 | bottom: 15px; 86 | left: 0; 87 | z-index: 1; 88 | height: 3px; 89 | background-color: $info-color; 90 | } 91 | &--scrollable { 92 | .i-tab__nav { 93 | overflow-x: auto; 94 | -webkit-overflow-scrolling: touch; 95 | } 96 | .i-tab__title { 97 | flex: 0 0 22%; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /docs/tabs.md: -------------------------------------------------------------------------------- 1 | ## Tabs 标签页 2 | 3 | > 此 demo 通过手机浏览最佳 4 | 5 | #### 简单用法 6 | 7 | ```html 8 | 9 | Vue 10 | React 11 | Angular 12 | 13 | ``` 14 | 15 | #### 样式风格 16 | 17 | ```html 18 | 19 | Vue 20 | React 21 | Angular 22 | 23 | ``` 24 | 25 | #### 横向滚动 26 | 27 | 超过 4 个后会开启横向滚动 28 | 29 | ```html 30 | 31 | 洗护 32 | 生鲜 33 | 男装 34 | 女装 35 | 手机 36 | 零食 37 | 果饮 38 | 39 | ``` 40 | 41 | #### 自定义标签 42 | 43 | ```html 44 | 45 | 46 |  label1 47 | label1 48 | 49 | 50 |  label2 51 | label2 52 | 53 | 54 | ``` 55 | 56 | #### Tabs Props 57 | 58 | | 参数 | 说明 | 类型 | 默认值 | 59 | |------|------|------|------| 60 | | value | 当前选中项索引 | `Number` | `0` | 61 | | type | 整体样式风格,可选值为`line` `card`| `String` | `line` | 62 | | color | 当`type`为`line`时是指示条的颜色,
当`type`为`card`时是背景色和未激活文字颜色 | `String` | `''` | 63 | | active-color | 激活文字颜色 | `String` | `''` | 64 | | inactive-color | 未激活文字颜色 | `String` | `''` | 65 | | animated | 是否开启内容区切换动画 | `Boolean` | `false` | 66 | | duration | 设置该组件全局动画时长,单位秒 | `Number | String` | `0.3` | 67 | | lazy | 是否切换到当前 tab 以后才渲染 | `Boolean` | `true` | 68 | 69 | #### TabPane Props 70 | 71 | | 参数 | 说明 | 类型 | 默认值 | 72 | |------|------|------|------| 73 | | label | tab 标题 | `String` | `''` | 74 | 75 | #### TabPane Slots 76 | 77 | | 名称 | 说明 | 78 | |------|------| 79 | | label | 标题区域内容 | 80 | 81 | #### Tabs Events 82 | 83 | | 事件名 | 说明 | 参数 | 84 | |------|------|------| 85 | | change | 选中项改变时触发 | 索引 | -------------------------------------------------------------------------------- /examples/assets/styles/docs.scss: -------------------------------------------------------------------------------- 1 | .doc-container h1, 2 | .doc-container h2, 3 | .doc-container h3, 4 | .doc-container h4, 5 | .doc-container h5, 6 | .doc-container h6 { 7 | line-height: 1.5; 8 | font-weight: 400; 9 | color: #333; 10 | } 11 | 12 | .doc-container h1 { 13 | font-size: 40px; 14 | } 15 | 16 | .doc-container h2 { 17 | font-size: 30px; 18 | } 19 | 20 | .doc-container h3 { 21 | font-size: 22px; 22 | } 23 | 24 | .doc-container h4 { 25 | font-size: 18px; 26 | } 27 | 28 | .doc-container p { 29 | margin: 15px 0; 30 | font-size: 14px; 31 | line-height: 26px; 32 | color: #34495e; 33 | } 34 | 35 | .doc-container li > code, 36 | .doc-container p > code, 37 | .doc-container table code { 38 | display: inline; 39 | margin: 2px; 40 | padding: 2px 7px; 41 | border-radius: 2px; 42 | color: #e96900; 43 | background-color: #f1f4f8; 44 | } 45 | 46 | .doc-container pre.hljs { 47 | background: #f6f8fa; 48 | padding: 1.3em; 49 | line-height: 1.5em; 50 | } 51 | 52 | .doc-container pre.hljs .hljs-comment { 53 | font-style: italic; 54 | font-size: 13px; 55 | } 56 | 57 | .doc-container code { 58 | font-family: Menlo, Monaco, Consolas, 'Courier New', 'PingFang SC'; 59 | } 60 | 61 | .doc-container table { 62 | width: 100%; 63 | font-size: 13px; 64 | line-height: 1.5; 65 | margin-bottom: 45px; 66 | background-color: #fff; 67 | border-collapse: collapse; 68 | color: #34495e; 69 | } 70 | 71 | .doc-container table th { 72 | padding: 10px; 73 | text-align: left; 74 | font-weight: 400; 75 | background-color: #f1f4f8; 76 | } 77 | .doc-container table td { 78 | padding: 10px; 79 | border-bottom: 1px solid #f1f4f8; 80 | line-height: 1.8; 81 | } 82 | 83 | .doc-container a { 84 | color: #008df6; 85 | &:hover { 86 | text-decoration: underline; 87 | } 88 | } 89 | 90 | .doc-container blockquote { 91 | border-left: 4px solid #dcdcdc; 92 | margin: 20px 0; 93 | padding: 0 15px; 94 | p { 95 | margin: 0; 96 | } 97 | } 98 | 99 | .doc-container ul { 100 | display: block; 101 | list-style-type: disc; 102 | margin-block-start: 1em; 103 | margin-block-end: 1em; 104 | margin-inline-start: 0px; 105 | margin-inline-end: 0px; 106 | padding-inline-start: 40px; 107 | } 108 | 109 | .doc-container ul > li { 110 | list-style: circle; 111 | line-height: 32px; 112 | } 113 | -------------------------------------------------------------------------------- /docs/radio.md: -------------------------------------------------------------------------------- 1 | ## Radio 单选框 2 | 3 | #### 简单用法 4 | 5 | ```html 6 | 7 | a 8 | b 9 | c 10 | 11 | ``` 12 | 13 | #### 自定义颜色 14 | 15 | ```html 16 | 17 | a 18 | b 19 | c 20 | 21 | ``` 22 | 23 | #### 禁用 24 | 25 | ```html 26 | 27 | a 28 | b 29 | c 30 | 31 | ``` 32 | 33 | #### 自定义图标 34 | 35 | ```html 36 | 37 | 38 | a 39 | 40 | 41 | b 42 | 43 | 44 | c 45 | 46 | 47 | ``` 48 | 49 | #### 搭配 Cell 使用 50 | 51 | ```html 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ``` 63 | 64 | #### RadioGroup Props 65 | 66 | | 参数 | 说明 | 类型 | 默认值 | 67 | |------|------|------|------| 68 | | value | 被选中项的`value`值 | `Boolean | Number | String` | - | 69 | | disabled | 是否禁用所有单选框 | `Boolean` | `false` | 70 | 71 | #### Radio Props 72 | 73 | | 参数 | 说明 | 类型 | 默认值 | 74 | |------|------|------|------| 75 | | value | 该单选框被选中后的值,可以认为是一个唯一标识 | `Boolean | Number | String` | - | 76 | | disabled | 是否禁用该单选框 | `Boolean` | `false` | 77 | | color | 选中状态图标背景色 | `String` | 主题色 | 78 | 79 | #### Radio Slots 80 | 81 | | 名称 | 说明 | slot-scope | 82 | |------|------|------| 83 | | icon | 自定义图标 | checked: 该单选框是否被选中 | 84 | 85 | #### RadioGroup Events 86 | 87 | | 事件名 | 说明 | 参数 | 88 | |------|------|------| 89 | | change | 选中项改变时触发 | 被选中项的`value`值 | 90 | -------------------------------------------------------------------------------- /packages/tabbar/demo/index.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | -------------------------------------------------------------------------------- /packages/popup/demo/index.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 55 | 56 | 79 | -------------------------------------------------------------------------------- /docs/button.md: -------------------------------------------------------------------------------- 1 | ## Button 按钮 2 | 3 | #### 按钮类型 4 | 5 | 支持`default` `primary` `info` `danger` `warning`五种类型,默认为`default` 6 | 7 | ```html 8 | 默认按钮 9 | 主要按钮 10 | 信息按钮 11 | 危险按钮 12 | 警告按钮 13 | ``` 14 | 15 | #### 空心按钮 16 | 17 | ```html 18 | 空心按钮 19 | 空心按钮 20 | ``` 21 | 22 | #### 虚线按钮 23 | 24 | ```html 25 | 虚线按钮 26 | 虚线按钮 27 | ``` 28 | 29 | #### 圆形按钮 30 | 31 | ```html 32 | 33 | 34 | ``` 35 | 36 | #### 圆角按钮 37 | 38 | ```html 39 | 圆角按钮 40 | 圆角按钮 41 | ``` 42 | 43 | #### 按钮状态 44 | 45 | ```html 46 | 禁用状态 47 | 加载状态 48 | ``` 49 | 50 | #### 自定义颜色 51 | 52 | ```html 53 | 自定义颜色 54 | 自定义颜色 55 | 56 | ``` 57 | 58 | #### 按钮尺寸 59 | 60 | ```html 61 | 大号按钮 62 | 正常按钮 63 | 小型按钮 64 | 迷你按钮 65 | ``` 66 | 67 | #### Props 68 | 69 | | 参数 | 说明 | 类型 | 默认值 | 70 | |------|------|------|------| 71 | | type | 类型,可选值为 `default` `primary`
`info` `danger` `warning` | `String` | `default` | 72 | | plain | 是否为空心按钮 | `Boolean` | `false` | 73 | | dashed | 是否为虚线按钮 | `Boolean` | `false` | 74 | | circle | 是否为圆形按钮 | `Boolean` | `false` | 75 | | round | 是否为圆角按钮 | `Boolean` | `false` | 76 | | disabled | 是否禁用 | `Boolean` | `false` | 77 | | loading | 是否为 loading 状态 | `Boolean` | `false` | 78 | | size | 尺寸,可选值为 `normal` `large`
`small` `mini` | `String` | `normal` | 79 | | color | 按钮颜色 | `String` | `''` | 80 | | text-color | 按钮字体颜色 | `String` | 1、未设置`color`情况下为`''`
2、否则为`color`或`#fff`
_取决于`plain` `dashed`_ | 81 | 82 | #### Events 83 | 84 | | 事件名 | 说明 | 参数 | 85 | |------|------|------| 86 | | click | 点击按钮且按钮状态不为加载或禁用时触发 | 原生事件对象 | 87 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Actionsheet from 'packages/actionsheet' 2 | import Button from 'packages/button' 3 | import Cell from 'packages/cell' 4 | import CellGroup from 'packages/cell-group' 5 | import Checkbox from 'packages/checkbox' 6 | import CheckboxGroup from 'packages/checkbox-group' 7 | import Col from 'packages/col' 8 | import Field from 'packages/field' 9 | import Icon from 'packages/icon' 10 | import Modal from 'packages/modal' 11 | import NavBar from 'packages/nav-bar' 12 | import NoticeBar from 'packages/notice-bar' 13 | import Popup from 'packages/popup' 14 | import Radio from 'packages/radio' 15 | import RadioGroup from 'packages/radio-group' 16 | import Row from 'packages/row' 17 | import Slide from 'packages/slide' 18 | import SlideItem from 'packages/slide-item' 19 | import Spinner from 'packages/spinner' 20 | import Sticky from 'packages/sticky' 21 | import Switch from 'packages/switch' 22 | import Tabbar from 'packages/tabbar' 23 | import TabbarItem from 'packages/tabbar-item' 24 | import Table from 'packages/table' 25 | import TabPane from 'packages/tab-pane' 26 | import Tabs from 'packages/tabs' 27 | import Tag from 'packages/tag' 28 | import Toast from 'packages/toast' 29 | import Uploader from 'packages/uploader' 30 | 31 | const components = [ 32 | Actionsheet, 33 | Button, 34 | Cell, 35 | CellGroup, 36 | Checkbox, 37 | CheckboxGroup, 38 | Col, 39 | Field, 40 | Icon, 41 | Modal, 42 | NavBar, 43 | NoticeBar, 44 | Popup, 45 | Radio, 46 | RadioGroup, 47 | Row, 48 | Slide, 49 | SlideItem, 50 | Spinner, 51 | Sticky, 52 | Switch, 53 | Tabbar, 54 | TabbarItem, 55 | Table, 56 | TabPane, 57 | Tabs, 58 | Tag, 59 | Toast, 60 | Uploader 61 | ] 62 | 63 | export { 64 | Actionsheet, 65 | Button, 66 | Cell, 67 | CellGroup, 68 | Checkbox, 69 | CheckboxGroup, 70 | Col, 71 | Field, 72 | Icon, 73 | Modal, 74 | NavBar, 75 | NoticeBar, 76 | Popup, 77 | Radio, 78 | RadioGroup, 79 | Row, 80 | Slide, 81 | SlideItem, 82 | Spinner, 83 | Sticky, 84 | Switch, 85 | Tabbar, 86 | TabbarItem, 87 | Table, 88 | TabPane, 89 | Tabs, 90 | Tag, 91 | Toast, 92 | Uploader 93 | } 94 | 95 | const install = function(Vue) { 96 | components.forEach(component => { 97 | Vue.use(component) 98 | }) 99 | } 100 | 101 | if (typeof window !== 'undefined' && window.Vue) { 102 | install(window.Vue) 103 | } 104 | 105 | export default { 106 | install 107 | } 108 | -------------------------------------------------------------------------------- /packages/checkbox/src/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 101 | 102 | -------------------------------------------------------------------------------- /packages/radio/demo/index.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 83 | 84 | -------------------------------------------------------------------------------- /examples/components/demo-nav.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 43 | 44 | 102 | -------------------------------------------------------------------------------- /examples/components/doc-brand.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 23 | 24 | 95 | 96 | -------------------------------------------------------------------------------- /docs/notice-bar.md: -------------------------------------------------------------------------------- 1 | ## NoticeBar 通告栏 2 | 3 | ```javascript 4 | export default { 5 | data() { 6 | return { 7 | msg1: '您订阅的双11抽奖活动已开始,共3次抽奖机会,仅限今日。', 8 | msg2: '网络不给力,请检查网络设置。' 9 | } 10 | } 11 | } 12 | ``` 13 | 14 | #### 多种模式 15 | 16 | ```html 17 | 18 | 19 | 20 | ``` 21 | 22 | #### 禁止滚动 23 | 24 | 通过`scrollable`属性设置消息过长是否自动滚动,默认`true`,允许滚动 25 | 26 | ```html 27 | 28 | ``` 29 | 30 | #### 高级用法 31 | 32 | ```html 33 | 34 | left-icon 35 | 36 | ``` 37 | 38 | #### Props 39 | 40 | | 参数 | 说明 | 类型 | 默认值 | 41 | |------|------|------|------| 42 | | text | 文本消息| `String` | `''` | 43 | | mode | 模式 | `String`,可另选为`closeable` `link` | `''` | 44 | | scrollable | 超出一行自动滚动 | `Boolean` | `true` | 45 | | speed | 滚动速率 (px/s) | `Number` | `50` | 46 | | delay | 动画延迟时间 (s) | `Number` | `1` | 47 | | background-color | 背景色 | `String` | `#ffebc4` | 48 | | color | 文字颜色 | `String` | `#df9952` | 49 | 50 | #### Slots 51 | 52 | | 名称 | 说明 | 53 | |------|------| 54 | | left | 自定义左侧区域内容 | 55 | | right | 自定义右侧区域内容 | 56 | 57 | #### Events 58 | 59 | | 事件名 | 说明 | 参数 | 60 | |------|------|------| 61 | | click | 点击时触发 | - | 62 | -------------------------------------------------------------------------------- /examples/app_mobile.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 65 | 66 | 112 | -------------------------------------------------------------------------------- /packages/row/demo/index.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 87 | -------------------------------------------------------------------------------- /packages/modal/src/index.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 116 | -------------------------------------------------------------------------------- /docs/layout.md: -------------------------------------------------------------------------------- 1 | ## Layout 布局 2 | 3 | 基于`flex`实现,提供`i-row`和`i-col`两个组件进行布局 4 | 5 | #### 简单用法 6 | 7 | 提供了`24`列栅格,可以在`col`上添加`span`属性设置该列所占格数,还可以通过`offset`属性设置偏移格数 8 | 9 | ```html 10 | 11 | span: 8 12 | span: 8 13 | span: 8 14 | 15 | 16 | span: 5 17 | span: 15, offset: 4 18 | 19 | ``` 20 | 21 | #### 在列元素之间增加间距 22 | 23 | 通过`gutter`设置列间距,默认为`0` 24 | 25 | ```html 26 | 27 | span: 8 28 | span: 8 29 | span: 8 30 | 31 | ``` 32 | 33 | #### 主轴对齐方式 34 | 35 | ```html 36 | 37 | 38 | span: 6 39 | span: 6 40 | span: 6 41 | 42 | 43 | 44 | span: 6 45 | span: 6 46 | span: 6 47 | 48 | 49 | 50 | span: 6 51 | span: 6 52 | span: 6 53 | 54 | 55 | 56 | span: 6 57 | span: 6 58 | span: 6 59 | 60 | 61 | 62 | span: 6 63 | span: 6 64 | span: 6 65 | 66 | ``` 67 | 68 | #### 交叉轴对齐方式 69 | 70 | ```html 71 | 72 | 73 | span: 6 74 | span: 6 75 | span:
6
76 |
77 | 78 | 79 | span:
6
80 | span: 6 81 | span: 6 82 |
83 | 84 | 85 | span:
6
86 | span: 6 87 | span
:
6
88 |
89 | ``` 90 | 91 | #### Row Props 92 | 93 | | 参数 | 说明 | 类型 | 默认值 | 94 | |------|------|------|------| 95 | | gutter | 列元素之间的间距(单位为px) | `String | Number` | - | 96 | | justify | 主轴对齐方式,可选值为`start` `center` `end`
`space-around` `space-between` | `String` | `start` | 97 | | align | 交叉轴对齐方式,可选值为 `top` `center` `bottom` | `String` | `top` | 98 | 99 | #### Col Props 100 | 101 | | 参数 | 说明 | 类型 | 默认值 | 102 | |------|------|------|------| 103 | | span | 列元素所占格数 | `String | Number` | `24` | 104 | | offset | 列元素偏移格数 | `String | Number` | - | 105 | -------------------------------------------------------------------------------- /docs/tabbar.md: -------------------------------------------------------------------------------- 1 | ## Tabbar 标签栏 2 | 3 | #### 简单用法 4 | 5 | ```html 6 | 7 | 8 | 9 | 10 | 11 | ``` 12 | 13 | ```javascript 14 | export default { 15 | data() { 16 | return { 17 | active: '/shopping-cart' 18 | } 19 | } 20 | } 21 | ``` 22 | 23 | #### 自定义颜色 24 | 25 | ```html 26 | 27 | 28 | 29 | 30 | 31 | ``` 32 | 33 | ```javascript 34 | export default { 35 | data() { 36 | return { 37 | active: '/star' 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | #### 高级用法 44 | 45 | 通过插槽定制内容 46 | 47 | ```html 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ``` 60 | 61 | ```javascript 62 | export default { 63 | data() { 64 | return { 65 | active: '/share' 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | #### Tabbar Props 72 | 73 | | 参数 | 说明 | 类型 | 默认值 | 74 | |------|------|------|------| 75 | | value | 选中某项,传入值为该项`value` | `String` | `''` | 76 | | inactive-color | 未激活文字的颜色 | `String` | `#a9b1b9` | 77 | | active-color | 已激活文字的颜色 | `String` | 主题色 | 78 | | fixed | 是否固定在底部 | `Boolean` | `true` | 79 | 80 | #### TabbarItem Props 81 | 82 | | 参数 | 说明 | 类型 | 默认值 | 83 | |------|------|------|------| 84 | | label | 标签文字 | `String` | `''` | 85 | | value | 唯一标识 | `String` | `''` | 86 | | icon | 图标 | `String` | `''` | 87 | | dot | 是否显示图标右上角小红点 | `Boolean` | `''` | 88 | 89 | #### TabbarItem Slots 90 | 91 | | 名称 | 说明 | slot-scope | 92 | |------|------|------| 93 | | icon | 自定义图标 | active: 是否为选中标签 | 94 | 95 | #### Tabbar Events 96 | 97 | | 事件名 | 说明 | 参数 | 98 | |------|------|------| 99 | | change | 切换标签时触发 | 当前选中标签的value | -------------------------------------------------------------------------------- /docs/checkbox.md: -------------------------------------------------------------------------------- 1 | ## Checkbox 复选框 2 | 3 | #### 简单用法 4 | 5 | ```html 6 | {{ value }} 7 | ``` 8 | 9 | #### 自定义颜色 10 | 11 | ```html 12 | {{ value }} 13 | ``` 14 | 15 | #### 自定义状态值 16 | 17 | ```html 18 | value: {{ value }} 19 | ``` 20 | 21 | #### 禁用 22 | 23 | ```html 24 | disabled 25 | ``` 26 | 27 | #### 自定义图标 28 | 29 | ```html 30 | 31 | 35 | 36 | ``` 37 | 38 | #### Checkbox 组 39 | 40 | 需要与`CheckboxGroup`搭配使用,选中值是一个数组,数组中的项是`Checkbox`上`name`属性设置的值,可以通过设置`max`属性来限制最大选中数量 41 | 42 | ```html 43 | 44 | a 45 | b 46 | c 47 | d 48 | 49 | ``` 50 | 51 | #### 搭配 Cell 使用 52 | 53 | `Checkbox`提供了一个`toggle`方法用来切换选中状态,你可以搭配`Cell`组件一起使用 54 | 55 | ```html 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | ``` 65 | 66 | #### CheckboxGroup Props 67 | 68 | | 参数 | 说明 | 类型 | 默认值 | 69 | |------|------|------|------| 70 | | value | 所有选中项`name`值集合 | `Array` | `[]` | 71 | | disabled | 是否禁用所有复选框 | `Boolean` | `false` | 72 | | max | 限制最大选中数量,`0`代表不限制 | `Number` | `0` | 73 | 74 | #### Checkbox Props 75 | 76 | | 参数 | 说明 | 类型 | 默认值 | 77 | |------|------|------|------| 78 | | name | 标识符,与`CheckboxGroup`组件搭配使用 | `String | Number` | `''` | 79 | | value | 状态值 | `Boolean | Number | String` | - | 80 | | true-value | 设置选中状态的值 | `Boolean | Number | String` | `true` | 81 | | false-value | 设置未选中状态的值 | `Boolean | Number | String` | `false` | 82 | | disabled | 是否禁用该复选框 | `Boolean` | `false` | 83 | | color | 选中状态图标背景色 | `String` | 主题色 | 84 | 85 | #### Checkbox Slots 86 | 87 | | 名称 | 说明 | slot-scope | 88 | |------|------|------| 89 | | icon | 自定义图标 | value: 状态值 | 90 | 91 | #### Checkbox Methods 92 | 93 | | 方法名 | 参数 | 返回值 | 介绍 | 94 | |------|------|------|------| 95 | | toggle | - | - | 切换选中状态 | 96 | 97 | #### CheckboxGroup Events 98 | 99 | | 事件名 | 说明 | 参数 | 100 | |------|------|------| 101 | | change | 选中项改变时触发 | 所有选中项`name`值集合 | 102 | 103 | #### Checkbox Events 104 | 105 | | 事件名 | 说明 | 参数 | 106 | |------|------|------| 107 | | change | 切换时触发 | 状态值 | -------------------------------------------------------------------------------- /docs/modal.md: -------------------------------------------------------------------------------- 1 | ## Modal 弹窗 2 | 3 | #### 使用方法 4 | 5 | _第一个参数代表消息内容,第二个参数传入一个 options 配置对象,仅支持单例模式_ 6 | 7 | ```javascript 8 | import { Modal } from 'unique-ui' 9 | 10 | /* Alert */ 11 | Modal.modal('Are you ok?', { type: 'alert', title: '👽' }) 12 | Modal.alert('Are you ok?', { title: '👽' }) 13 | 14 | /* Confirm */ 15 | Modal.modal('大郎,起来喝药了', { type: 'confirm', title: '提示' }) 16 | Modal.confirm('大郎,起来喝药了', { title: '提示' }) 17 | ``` 18 | 19 | #### 异步关闭 20 | 21 | ```javascript 22 | // 要想使用异步关闭,autoClose一定要传false 23 | Modal.alert('异步关闭', { title: '提示', autoClose: false }).then(close => { 24 | setTimeout(() => { 25 | close() 26 | }, 1000) 27 | }) 28 | ``` 29 | 30 | ### 全局使用 31 | 32 | > 推荐用`Vue.use`方式来使用本组件,以后便可以在每个Vue实例上直接通过`this.$modal`方式使用 33 | 34 | ```javascript 35 | import { Modal } from 'unique-ui' 36 | Vue.use(Modal) 37 | 38 | /* Alert */ 39 | this.$modal('Are you ok?', { type: 'alert', title: '👽' }) 40 | this.$modal.alert('Are you ok?', { title: '👽' }) 41 | 42 | /* Confirm */ 43 | this.$modal('大郎,起来喝药了', { type: 'confirm', title: '提示' }) 44 | this.$modal.confirm('大郎,起来喝药了', { title: '提示' }) 45 | ``` 46 | 47 | #### 组件调用 48 | 49 | 通过组件使用以深度定制样式 50 | 51 | ```html 52 | 53 | avatar 54 | 55 | ``` 56 | 57 | ```scss 58 | .modal-enter-active, 59 | .modal-leave-active { 60 | transition: opacity 0.45s, transform 0.45s; 61 | } 62 | .modal-enter { 63 | opacity: 0; 64 | transform: translate3d(-50%, -50%, 0) scale(2); 65 | } 66 | .modal-leave-to { 67 | opacity: 0; 68 | transform: translate3d(-50%, -50%, 0) scale(1.5); 69 | } 70 | ``` 71 | 72 | #### Options(第二个参数) 73 | 74 | | 参数 | 说明 | 类型 | 默认值 | 75 | |------|------|------|------| 76 | | show | 是否显示 | `Boolean` | `false` | 77 | | type | 类型,可选值为`alert` `confirm` | `String` | `'alert'` | 78 | | title | 标题 | `String` | `''` | 79 | | confirmText | 确定按钮文字 | `String` | `确认` | 80 | | cancelText | 取消按钮文字 | `String` | `取消` | 81 | | closeOnClickOverlay | 点击蒙版时是否关闭弹窗 | `Boolean` | `false` | 82 | | autoClose | 点击按钮时自动关闭,要想实现异步关闭功能,请设为`false` | `Boolean` | `true` | 83 | | transitionName | 同`Popup组件`中一致 | `String` | `''` | 84 | 85 | #### Props 86 | 87 | | 参数 | 说明 | 类型 | 默认值 | 88 | |------|------|------|------| 89 | | type | 同 Options 中一致 | `String` | `normal` | 90 | | title | 同 Options 中一致 | `String` | `''` | 91 | | text | 文字内容 | `String` | `''` | 92 | | overlay | 是否显示蒙版 | `Boolean` | `true` | 93 | | confirm-text | 同 Options 中一致 | `String` | `确认` | 94 | | cancel-text | 同 Options 中一致 | `String` | `取消` | 95 | | close-on-click-overlay | 同 Options 中一致 | `Boolean` | `false` | 96 | | transition-name | 同`Popup组件`中一致 | `String` | `''` | 97 | 98 | 99 | #### Slots 100 | 101 | | 参数 | 说明 | 102 | |------|------| 103 | | default | 弹窗主内容区域 | 104 | | title | 标题区域 | 105 | | footer | 底部区域 | 106 | 107 | #### Events 108 | 109 | | 事件名 | 说明 | 参数 | 110 | |------|------|------| 111 | | confirm | 点击确认按钮时触发 | - | 112 | | cancel | 取消时触发 | - | 113 | 114 | -------------------------------------------------------------------------------- /packages/field/src/index.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 130 | 131 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unique-ui", 3 | "version": "0.10.1", 4 | "description": "A mobile component library for Vue2.x", 5 | "main": "lib/index.js", 6 | "license": "MIT", 7 | "homepage": "https://imzxj.github.io/unique-ui/", 8 | "repository": "git@github.com:imzxj/unique-ui.git", 9 | "author": "imzxj <362896731@qq.com>", 10 | "scripts": { 11 | "dev": "webpack-dev-server --hot --inline --progress --config build/webpack.dev.conf.js", 12 | "build:entry": "node build/build-entry.js", 13 | "build:components": "webpack --color --progress --config build/webpack.prod.conf.js", 14 | "build:theme": "gulp build --gulpfile packages/theme/gulpfile.js", 15 | "build:lib": "rm -rf lib && npm run build:entry && npm run build:components && npm run build:theme", 16 | "build:doc": "webpack --color --progress --config build/webpack.doc.conf.js", 17 | "release": "sh build/release.sh", 18 | "lint": "eslint --ext .js,.vue src packages", 19 | "lint:fix": "eslint --fix --ext .js,.vue src packages" 20 | }, 21 | "files": [ 22 | "lib", 23 | "packages", 24 | "src" 25 | ], 26 | "dependencies": { 27 | "@babel/runtime": "^7.4.5", 28 | "font-awesome": "^4.7.0", 29 | "normalize.css": "^8.0.1", 30 | "qrcode": "^1.3.3", 31 | "raf": "^3.4.1" 32 | }, 33 | "devDependencies": { 34 | "@babel/core": "^7.4.5", 35 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 36 | "@babel/plugin-transform-object-assign": "^7.2.0", 37 | "@babel/plugin-transform-runtime": "^7.4.4", 38 | "@babel/polyfill": "^7.4.4", 39 | "@babel/preset-env": "^7.4.5", 40 | "@vue/babel-preset-jsx": "^1.0.0", 41 | "autoprefixer": "^9.3.1", 42 | "babel-loader": "^8.0.6", 43 | "clean-webpack-plugin": "^1.0.0", 44 | "cross-env": "^5.2.0", 45 | "css-loader": "^2.1.1", 46 | "cssnano": "^4.1.10", 47 | "cz-conventional-changelog": "^2.1.0", 48 | "eslint": "^5.16.0", 49 | "eslint-loader": "^2.1.2", 50 | "eslint-plugin-vue": "^5.2.2", 51 | "eslint-plugin-vue-libs": "^3.0.0", 52 | "friendly-errors-webpack-plugin": "^1.7.0", 53 | "fs-extra": "^8.1.0", 54 | "gulp": "^4.0.2", 55 | "gulp-autoprefixer": "^6.1.0", 56 | "gulp-cssmin": "^0.2.0", 57 | "gulp-sass": "4.x", 58 | "highlight.js": "^9.13.1", 59 | "html-webpack-plugin": "^3.2.0", 60 | "markdown-to-vue-loader": "^1.0.1", 61 | "node-sass": "^4.11.0", 62 | "postcss": "^7.0.16", 63 | "postcss-loader": "^3.0.0", 64 | "progress-bar-webpack-plugin": "^1.12.1", 65 | "sass-loader": "^7.1.0", 66 | "style-loader": "^0.23.1", 67 | "url-loader": "^1.1.2", 68 | "vue": "^2.6.10", 69 | "vue-loader": "^15.7.0", 70 | "vue-progressbar": "^0.7.5", 71 | "vue-router": "^3.0.6", 72 | "vue-template-compiler": "^2.6.10", 73 | "webpack": "^4.32.2", 74 | "webpack-cli": "^3.3.2", 75 | "webpack-dev-server": "^3.4.1", 76 | "webpack-node-externals": "^1.7.2" 77 | }, 78 | "peerDependencies": { 79 | "vue": ">= 2.5.0" 80 | }, 81 | "browserslist": [ 82 | "Android >= 4.0", 83 | "iOS >= 7" 84 | ], 85 | "config": { 86 | "commitizen": { 87 | "path": "./node_modules/cz-conventional-changelog" 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /packages/nav-bar/demo/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /docs/field.md: -------------------------------------------------------------------------------- 1 | ## Field 输入框 2 | 3 | #### 简单用法 4 | 5 | ```html 6 | 7 | 8 | 9 | ``` 10 | 11 | #### 原生属性 12 | 13 | 本组件支持所有原生属性 14 | 15 | ```html 16 | 17 | 18 | 19 | 20 | ``` 21 | 22 | #### 多行输入 23 | 24 | 将`type`设置为`textarea`可以将默认的`input`组件更改为`textarea`组件,支持`rows`等原生属性, 25 | 26 | ```html 27 | 28 | 35 | 36 | ``` 37 | 38 | #### 自定义内容 39 | 40 | ```html 41 | 42 | 43 | left-icon 44 | 发送验证码 45 | 46 | 47 | ``` 48 | 49 | #### Props 50 | 51 | | 参数 | 说明 | 类型 | 默认值 | 52 | |------|------|------|------| 53 | | value | 输入框内容 | `String` | `''` | 54 | | type | 输入框类型 | `String` | `'input'` | 55 | | icon | 左侧图标 | `String` | `''` | 56 | | label | 左侧标签文字 | `String` | `''` | 57 | | clearable | 是否显示清空按钮 | `Boolean` | `false` | 58 | | readonly | 是否只读 | `Boolean` | `false` | 59 | 60 | #### Slots 61 | 62 | | 参数 | 说明 | 63 | |------|------| 64 | | icon | 左侧图标 | 65 | | right-icon | 右侧图标 | 66 | 67 | #### Events 68 | 69 | 本组件支持所有原生事件 -------------------------------------------------------------------------------- /packages/field/demo/index.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 54 | 55 | 59 | -------------------------------------------------------------------------------- /docs/nav-bar.md: -------------------------------------------------------------------------------- 1 | ## NavBar 导航栏 2 | 3 | #### 简单用法 4 | 5 | ```html 6 | 7 | ``` 8 | 9 | #### 高级用法 10 | 11 | 通过插槽定制内容 12 | 13 | ```html 14 | 15 | 16 | 标题 17 | right-icon 18 | 19 | ``` 20 | 21 | #### Props 22 | 23 | | 参数 | 说明 | 类型 | 默认值 | 24 | |------|------|------|------| 25 | | title | 标题 | `String` | `''` | 26 | | left-arrow | 是否显示左侧箭头 | `Boolean` | `false` | 27 | | left-text | 左侧文案 | `String` | `''` | 28 | | fixed | 是否固定在顶部 | `Boolean` | `false` | 29 | 30 | #### Slots 31 | 32 | | 名称 | 说明 | 33 | |------|------| 34 | | title | 标题区域内容 | 35 | | left | 左侧区域内容 | 36 | | right | 右侧区域内容 | 37 | 38 | #### Events 39 | 40 | | 事件名 | 说明 | 参数 | 41 | |------|------|------| 42 | | click-left | 点击左侧按钮时触发 | 原生事件对象 | 43 | | click-right | 点击右侧按钮时触发 | 原生事件对象 | --------------------------------------------------------------------------------